From eb4e95637fc089a0b0acc99ef7663e3f8c107d80 Mon Sep 17 00:00:00 2001 From: Apress Date: Fri, 7 Oct 2016 04:07:03 +0100 Subject: [PATCH] First commit --- 2766.pdf | Bin 0 -> 131847 bytes 2767.pdf | Bin 0 -> 219817 bytes 9781590596135.jpg | Bin 0 -> 8897 bytes Chapter01/listing1-1.php | 46 ++ Chapter01/listing1-2.php | 10 + Chapter01/listing1-3.php | 15 + Chapter01/listing1_4.java | 17 + Chapter03/listing3-1.php | 43 ++ Chapter03/listing3-2.php | 41 ++ Chapter03/listing3-3.php | 23 + Chapter03/listing3-4.php | 41 ++ Chapter03/listing3-5.php | 43 ++ Chapter04/listing4-1.php | 38 + Chapter04/listing4-2.php | 46 ++ Chapter04/listing4-3.php | 52 ++ Chapter04/listing4-4.php | 50 ++ Chapter04/listing4-5.php | 67 ++ Chapter04/listing4-6.php | 54 ++ Chapter04/listing4-7.php | 73 ++ Chapter04/listing4-9.php | 60 ++ Chapter05/listing5-1.php | 40 + Chapter05/listing5-2.php | 84 +++ Chapter05/listing5-3.php | 86 +++ Chapter05/listing5-4.php | 59 ++ Chapter05/listing5-5.php | 59 ++ Chapter05/listing5-6.php | 60 ++ Chapter05/listing5-7.php | 26 + Chapter05/listing5-8.php | 114 +++ Chapter05/listing5-9.php | 114 +++ Chapter06/listing6-1.php | 41 ++ Chapter06/listing6-2.php | 61 ++ Chapter06/listing6-3.php | 68 ++ Chapter06/listing6-4.php | 68 ++ Chapter06/listing6-5.php | 71 ++ Chapter06/listing6-6.php | 39 + Chapter06/listing6-7.php | 50 ++ Chapter06/listing6-8.php | 59 ++ Chapter07/listing7-1.php | 38 + Chapter07/listing7-8.php | 34 + Chapter07/listing7-9.php | 22 + Chapter08/listing8-1.php | 56 ++ Chapter08/listing8-2.php | 52 ++ Chapter08/listing8-4.php | 30 + Chapter08/listing8-5.php | 61 ++ Chapter08/listing8-6.php | 47 ++ Chapter08/listing8-7.php | 34 + Chapter09/listing9-1.php | 36 + Chapter09/listing9-10.php | 98 +++ Chapter09/listing9-11.php | 111 +++ Chapter09/listing9-12.php | 42 ++ Chapter09/listing9-2.php | 59 ++ Chapter09/listing9-3.php | 57 ++ Chapter09/listing9-4.php | 30 + Chapter09/listing9-5.php | 75 ++ Chapter09/listing9-6.php | 97 +++ Chapter09/listing9-7.php | 114 +++ Chapter09/listing9-8.php | 75 ++ Chapter09/listing9-9.php | 89 +++ Chapter10/listing10-2.php | 43 ++ Chapter10/listing10-3.php | 34 + Chapter11/listing11-1.php | 19 + Chapter11/listing11-10.php | 35 + Chapter11/listing11-11.php | 59 ++ Chapter11/listing11-12.php | 47 ++ Chapter11/listing11-13.php | 18 + Chapter11/listing11-14.php | 27 + Chapter11/listing11-2.php | 43 ++ Chapter11/listing11-3.php | 43 ++ Chapter11/listing11-4.php | 27 + Chapter12/listing12-1.php | 19 + Chapter12/listing12-4.php | 22 + Chapter14/listing14-1.php | 29 + Chapter14/listing14-5.php | 63 ++ Chapter14/listing14-6.php | 63 ++ Chapter14/listing14-7.php | 20 + Chapter14/listing14-8.php | 58 ++ Chapter16/listing16-5.php | 42 ++ Chapter17/uninstall.php | 76 ++ Crisscott/AboutDialog.php | 32 + Crisscott/Category.php | 118 +++ Crisscott/Contributor.php | 330 +++++++++ Crisscott/DB.php | 81 +++ Crisscott/DB/Result.php | 69 ++ Crisscott/Inventory.php | 230 ++++++ Crisscott/Iterator.php | 38 + Crisscott/MainNotebook.php | 84 +++ Crisscott/MainWindow.php | 214 ++++++ Crisscott/Product.php | 317 ++++++++ Crisscott/SOAPClient.php | 14 + Crisscott/SplashScreen.php | 144 ++++ Crisscott/Tools/CategorySummary.php | 140 ++++ Crisscott/Tools/ContributorEdit.php | 557 ++++++++++++++ Crisscott/Tools/Menu.php | 156 ++++ Crisscott/Tools/NewsArticle.php | 201 ++++++ Crisscott/Tools/NewsFeed.php | 199 +++++ Crisscott/Tools/ProductEdit.php | 721 +++++++++++++++++++ Crisscott/Tools/ProductSummary.php | 255 +++++++ Crisscott/Tools/ProductTree.php | 140 ++++ Crisscott/Tools/ProgressDialog.php | 89 +++ Crisscott/Tools/StatusBar.php | 38 + Crisscott/Tools/Toolbar.php | 130 ++++ Crisscott/run.php | 14 + CrisscottChapter09/Category.php | 118 +++ CrisscottChapter09/Contributor.php | 330 +++++++++ CrisscottChapter09/DB.php | 81 +++ CrisscottChapter09/Inventory.php | 113 +++ CrisscottChapter09/Iterator.php | 38 + CrisscottChapter09/MainNotebook.php | 84 +++ CrisscottChapter09/MainWindow.php | 91 +++ CrisscottChapter09/Product.php | 308 ++++++++ CrisscottChapter09/SplashScreen.php | 105 +++ CrisscottChapter09/Tools/CategorySummary.php | 140 ++++ CrisscottChapter09/Tools/ContributorEdit.php | 557 ++++++++++++++ CrisscottChapter09/Tools/NewsArticle.php | 201 ++++++ CrisscottChapter09/Tools/NewsFeed.php | 196 +++++ CrisscottChapter09/Tools/ProductEdit.php | 613 ++++++++++++++++ CrisscottChapter09/Tools/ProductSummary.php | 196 +++++ CrisscottChapter09/Tools/ProductTree.php | 137 ++++ CrisscottChapter09/Tools/StatusBar.php | 38 + CrisscottChapter09/db/Result.php | 69 ++ CrisscottChapter09/run.php | 14 + CrisscottChapter11/Category.php | 118 +++ CrisscottChapter11/Contributor.php | 330 +++++++++ CrisscottChapter11/DB.php | 81 +++ CrisscottChapter11/Inventory.php | 113 +++ CrisscottChapter11/Iterator.php | 38 + CrisscottChapter11/MainNotebook.php | 84 +++ CrisscottChapter11/MainWindow.php | 122 ++++ CrisscottChapter11/Product.php | 308 ++++++++ CrisscottChapter11/SplashScreen.php | 105 +++ CrisscottChapter11/Tools/CategorySummary.php | 140 ++++ CrisscottChapter11/Tools/ContributorEdit.php | 557 ++++++++++++++ CrisscottChapter11/Tools/Menu.php | 129 ++++ CrisscottChapter11/Tools/NewsArticle.php | 201 ++++++ CrisscottChapter11/Tools/NewsFeed.php | 199 +++++ CrisscottChapter11/Tools/ProductEdit.php | 613 ++++++++++++++++ CrisscottChapter11/Tools/ProductSummary.php | 196 +++++ CrisscottChapter11/Tools/ProductTree.php | 140 ++++ CrisscottChapter11/Tools/StatusBar.php | 38 + CrisscottChapter11/Tools/Toolbar.php | 81 +++ CrisscottChapter11/db/Result.php | 69 ++ CrisscottChapter11/run.php | 14 + CrisscottChapter12/Category.php | 118 +++ CrisscottChapter12/Contributor.php | 330 +++++++++ CrisscottChapter12/DB.php | 81 +++ CrisscottChapter12/Inventory.php | 113 +++ CrisscottChapter12/Iterator.php | 38 + CrisscottChapter12/MainNotebook.php | 84 +++ CrisscottChapter12/MainWindow.php | 122 ++++ CrisscottChapter12/Product.php | 317 ++++++++ CrisscottChapter12/SplashScreen.php | 105 +++ CrisscottChapter12/Tools/CategorySummary.php | 140 ++++ CrisscottChapter12/Tools/ContributorEdit.php | 557 ++++++++++++++ CrisscottChapter12/Tools/Menu.php | 152 ++++ CrisscottChapter12/Tools/NewsArticle.php | 201 ++++++ CrisscottChapter12/Tools/NewsFeed.php | 199 +++++ CrisscottChapter12/Tools/ProductEdit.php | 664 +++++++++++++++++ CrisscottChapter12/Tools/ProductSummary.php | 253 +++++++ CrisscottChapter12/Tools/ProductTree.php | 140 ++++ CrisscottChapter12/Tools/StatusBar.php | 38 + CrisscottChapter12/Tools/Toolbar.php | 81 +++ CrisscottChapter12/db/Result.php | 69 ++ CrisscottChapter12/run.php | 14 + LICENSE.txt | 27 + README.md | 15 + README.txt | 1 + contributing.md | 14 + 167 files changed, 19151 insertions(+) create mode 100644 2766.pdf create mode 100644 2767.pdf create mode 100644 9781590596135.jpg create mode 100644 Chapter01/listing1-1.php create mode 100644 Chapter01/listing1-2.php create mode 100644 Chapter01/listing1-3.php create mode 100644 Chapter01/listing1_4.java create mode 100644 Chapter03/listing3-1.php create mode 100644 Chapter03/listing3-2.php create mode 100644 Chapter03/listing3-3.php create mode 100644 Chapter03/listing3-4.php create mode 100644 Chapter03/listing3-5.php create mode 100644 Chapter04/listing4-1.php create mode 100644 Chapter04/listing4-2.php create mode 100644 Chapter04/listing4-3.php create mode 100644 Chapter04/listing4-4.php create mode 100644 Chapter04/listing4-5.php create mode 100644 Chapter04/listing4-6.php create mode 100644 Chapter04/listing4-7.php create mode 100644 Chapter04/listing4-9.php create mode 100644 Chapter05/listing5-1.php create mode 100644 Chapter05/listing5-2.php create mode 100644 Chapter05/listing5-3.php create mode 100644 Chapter05/listing5-4.php create mode 100644 Chapter05/listing5-5.php create mode 100644 Chapter05/listing5-6.php create mode 100644 Chapter05/listing5-7.php create mode 100644 Chapter05/listing5-8.php create mode 100644 Chapter05/listing5-9.php create mode 100644 Chapter06/listing6-1.php create mode 100644 Chapter06/listing6-2.php create mode 100644 Chapter06/listing6-3.php create mode 100644 Chapter06/listing6-4.php create mode 100644 Chapter06/listing6-5.php create mode 100644 Chapter06/listing6-6.php create mode 100644 Chapter06/listing6-7.php create mode 100644 Chapter06/listing6-8.php create mode 100644 Chapter07/listing7-1.php create mode 100644 Chapter07/listing7-8.php create mode 100644 Chapter07/listing7-9.php create mode 100644 Chapter08/listing8-1.php create mode 100644 Chapter08/listing8-2.php create mode 100644 Chapter08/listing8-4.php create mode 100644 Chapter08/listing8-5.php create mode 100644 Chapter08/listing8-6.php create mode 100644 Chapter08/listing8-7.php create mode 100644 Chapter09/listing9-1.php create mode 100644 Chapter09/listing9-10.php create mode 100644 Chapter09/listing9-11.php create mode 100644 Chapter09/listing9-12.php create mode 100644 Chapter09/listing9-2.php create mode 100644 Chapter09/listing9-3.php create mode 100644 Chapter09/listing9-4.php create mode 100644 Chapter09/listing9-5.php create mode 100644 Chapter09/listing9-6.php create mode 100644 Chapter09/listing9-7.php create mode 100644 Chapter09/listing9-8.php create mode 100644 Chapter09/listing9-9.php create mode 100644 Chapter10/listing10-2.php create mode 100644 Chapter10/listing10-3.php create mode 100644 Chapter11/listing11-1.php create mode 100644 Chapter11/listing11-10.php create mode 100644 Chapter11/listing11-11.php create mode 100644 Chapter11/listing11-12.php create mode 100644 Chapter11/listing11-13.php create mode 100644 Chapter11/listing11-14.php create mode 100644 Chapter11/listing11-2.php create mode 100644 Chapter11/listing11-3.php create mode 100644 Chapter11/listing11-4.php create mode 100644 Chapter12/listing12-1.php create mode 100644 Chapter12/listing12-4.php create mode 100644 Chapter14/listing14-1.php create mode 100644 Chapter14/listing14-5.php create mode 100644 Chapter14/listing14-6.php create mode 100644 Chapter14/listing14-7.php create mode 100644 Chapter14/listing14-8.php create mode 100644 Chapter16/listing16-5.php create mode 100644 Chapter17/uninstall.php create mode 100644 Crisscott/AboutDialog.php create mode 100644 Crisscott/Category.php create mode 100644 Crisscott/Contributor.php create mode 100644 Crisscott/DB.php create mode 100644 Crisscott/DB/Result.php create mode 100644 Crisscott/Inventory.php create mode 100644 Crisscott/Iterator.php create mode 100644 Crisscott/MainNotebook.php create mode 100644 Crisscott/MainWindow.php create mode 100644 Crisscott/Product.php create mode 100644 Crisscott/SOAPClient.php create mode 100644 Crisscott/SplashScreen.php create mode 100644 Crisscott/Tools/CategorySummary.php create mode 100644 Crisscott/Tools/ContributorEdit.php create mode 100644 Crisscott/Tools/Menu.php create mode 100644 Crisscott/Tools/NewsArticle.php create mode 100644 Crisscott/Tools/NewsFeed.php create mode 100644 Crisscott/Tools/ProductEdit.php create mode 100644 Crisscott/Tools/ProductSummary.php create mode 100644 Crisscott/Tools/ProductTree.php create mode 100644 Crisscott/Tools/ProgressDialog.php create mode 100644 Crisscott/Tools/StatusBar.php create mode 100644 Crisscott/Tools/Toolbar.php create mode 100644 Crisscott/run.php create mode 100644 CrisscottChapter09/Category.php create mode 100644 CrisscottChapter09/Contributor.php create mode 100644 CrisscottChapter09/DB.php create mode 100644 CrisscottChapter09/Inventory.php create mode 100644 CrisscottChapter09/Iterator.php create mode 100644 CrisscottChapter09/MainNotebook.php create mode 100644 CrisscottChapter09/MainWindow.php create mode 100644 CrisscottChapter09/Product.php create mode 100644 CrisscottChapter09/SplashScreen.php create mode 100644 CrisscottChapter09/Tools/CategorySummary.php create mode 100644 CrisscottChapter09/Tools/ContributorEdit.php create mode 100644 CrisscottChapter09/Tools/NewsArticle.php create mode 100644 CrisscottChapter09/Tools/NewsFeed.php create mode 100644 CrisscottChapter09/Tools/ProductEdit.php create mode 100644 CrisscottChapter09/Tools/ProductSummary.php create mode 100644 CrisscottChapter09/Tools/ProductTree.php create mode 100644 CrisscottChapter09/Tools/StatusBar.php create mode 100644 CrisscottChapter09/db/Result.php create mode 100644 CrisscottChapter09/run.php create mode 100644 CrisscottChapter11/Category.php create mode 100644 CrisscottChapter11/Contributor.php create mode 100644 CrisscottChapter11/DB.php create mode 100644 CrisscottChapter11/Inventory.php create mode 100644 CrisscottChapter11/Iterator.php create mode 100644 CrisscottChapter11/MainNotebook.php create mode 100644 CrisscottChapter11/MainWindow.php create mode 100644 CrisscottChapter11/Product.php create mode 100644 CrisscottChapter11/SplashScreen.php create mode 100644 CrisscottChapter11/Tools/CategorySummary.php create mode 100644 CrisscottChapter11/Tools/ContributorEdit.php create mode 100644 CrisscottChapter11/Tools/Menu.php create mode 100644 CrisscottChapter11/Tools/NewsArticle.php create mode 100644 CrisscottChapter11/Tools/NewsFeed.php create mode 100644 CrisscottChapter11/Tools/ProductEdit.php create mode 100644 CrisscottChapter11/Tools/ProductSummary.php create mode 100644 CrisscottChapter11/Tools/ProductTree.php create mode 100644 CrisscottChapter11/Tools/StatusBar.php create mode 100644 CrisscottChapter11/Tools/Toolbar.php create mode 100644 CrisscottChapter11/db/Result.php create mode 100644 CrisscottChapter11/run.php create mode 100644 CrisscottChapter12/Category.php create mode 100644 CrisscottChapter12/Contributor.php create mode 100644 CrisscottChapter12/DB.php create mode 100644 CrisscottChapter12/Inventory.php create mode 100644 CrisscottChapter12/Iterator.php create mode 100644 CrisscottChapter12/MainNotebook.php create mode 100644 CrisscottChapter12/MainWindow.php create mode 100644 CrisscottChapter12/Product.php create mode 100644 CrisscottChapter12/SplashScreen.php create mode 100644 CrisscottChapter12/Tools/CategorySummary.php create mode 100644 CrisscottChapter12/Tools/ContributorEdit.php create mode 100644 CrisscottChapter12/Tools/Menu.php create mode 100644 CrisscottChapter12/Tools/NewsArticle.php create mode 100644 CrisscottChapter12/Tools/NewsFeed.php create mode 100644 CrisscottChapter12/Tools/ProductEdit.php create mode 100644 CrisscottChapter12/Tools/ProductSummary.php create mode 100644 CrisscottChapter12/Tools/ProductTree.php create mode 100644 CrisscottChapter12/Tools/StatusBar.php create mode 100644 CrisscottChapter12/Tools/Toolbar.php create mode 100644 CrisscottChapter12/db/Result.php create mode 100644 CrisscottChapter12/run.php create mode 100644 LICENSE.txt create mode 100644 README.md create mode 100644 README.txt create mode 100644 contributing.md diff --git a/2766.pdf b/2766.pdf new file mode 100644 index 0000000000000000000000000000000000000000..9d1ebb612ac4d3cd481f4d2fd22b1aa7d73d4a65 GIT binary patch literal 131847 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 literal 0 HcmV?d00001 diff --git a/2767.pdf b/2767.pdf new file mode 100644 index 0000000000000000000000000000000000000000..b5dbba4e3d6320ac64e3cb9c20b5ce8c5bf3d5cc GIT binary patch literal 219817 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

<9|!U7IV(Ph)0Prf|DCs2q{}Hb1M})=)f&rZ|ti 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
D;01g!hmkNaG1lRxo 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 M7s&f1c?vW2A5UpgNdN!< literal 0 HcmV?d00001 diff --git a/Chapter01/listing1-1.php b/Chapter01/listing1-1.php new file mode 100644 index 0000000..4441aa5 --- /dev/null +++ b/Chapter01/listing1-1.php @@ -0,0 +1,46 @@ +name . ' ' . $this->suffix . ".\n"; + } + + public function giveBirth() + { + echo "It's a boy!\n"; + return new Ralph_Jr(); + } +} + +class Ralph_Jr extends Ralph { + + protected $suffix = 'Jr'; + + public function giveBirth() + { + throw new Exception('Ralph Jr. can\'t have kids!'); + } +} + +$senior = new Ralph(); +$junior = $senior->giveBirth(); + +try { + $junior->giveBirth(); +} catch (Exception $e) { + echo $e->getMessage() . "\n"; +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/Chapter01/listing1-2.php b/Chapter01/listing1-2.php new file mode 100644 index 0000000..9878f26 --- /dev/null +++ b/Chapter01/listing1-2.php @@ -0,0 +1,10 @@ +connect_object('destroy', array('Gtk', 'main_quit')); + +$dateTime = new GtkLabel(date('Y-m-d H:i:s')); + +$window->add($dateTime); +$window->show_all(); +Gtk::main(); +?> \ No newline at end of file diff --git a/Chapter01/listing1-3.php b/Chapter01/listing1-3.php new file mode 100644 index 0000000..ba4326f --- /dev/null +++ b/Chapter01/listing1-3.php @@ -0,0 +1,15 @@ +connect_object('destroy', array('Gtk', 'main_quit')); +$window->show(); +Gtk::main(); +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/Chapter01/listing1_4.java b/Chapter01/listing1_4.java new file mode 100644 index 0000000..a47f855 --- /dev/null +++ b/Chapter01/listing1_4.java @@ -0,0 +1,17 @@ +import javax.swing.*; + +public class listing1_4 { + + public static void createAndShowGUI() { + JFrame frame = new JFrame(); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + frame.setVisible(true); + } + + public static void main(String[] args) { + javax.swing.SwingUtilities.invokeLater( + createAndShowGUI(); + ); + createAndShowGUI(); + } +} diff --git a/Chapter03/listing3-1.php b/Chapter03/listing3-1.php new file mode 100644 index 0000000..d2cf3e2 --- /dev/null +++ b/Chapter03/listing3-1.php @@ -0,0 +1,43 @@ +flags; + } + + public function set_flags($flags) + { + $this->flags = $this->flags | $flags; + } + + public function sink() + { + if (--$this->refCounter < 1) { + $this->destroy(); + } + } + + public function unset_flags($flags) + { + $this->flags = $this->flags & ~$flags; + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/Chapter03/listing3-2.php b/Chapter03/listing3-2.php new file mode 100644 index 0000000..62341a7 --- /dev/null +++ b/Chapter03/listing3-2.php @@ -0,0 +1,41 @@ +window); +var_dump($widget->flags()); + +$widget->realize(); + +// Now that the widget is realized, we can grab +// the window property. +var_dump($widget->window); +var_dump($widget->flags()); + +$widget->show(); + +// Showing and hiding a widget changes the value +// of its flags. +var_dump($widget->flags()); + +$widget->hide(); + +var_dump($widget->flags()); + +$widget->unrealize(); + +// Now that the widget is realized, we can grab +// the window property. +var_dump($widget->window); +var_dump($widget->flags()); +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/Chapter03/listing3-3.php b/Chapter03/listing3-3.php new file mode 100644 index 0000000..a554d08 --- /dev/null +++ b/Chapter03/listing3-3.php @@ -0,0 +1,23 @@ +add($button); +$frame->add($button); + +/* +Spits out this message: +(listing-3.php:1870): Gtk-WARNING **: Attempting to add a widget with type GtkButton to a container of type GtkFrame, but the widget is already inside a container of type GtkWindow, the GTK+ FAQ at http://www.gtk.org/faq/ explains how to reparent a widget. +*/ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/Chapter03/listing3-4.php b/Chapter03/listing3-4.php new file mode 100644 index 0000000..1dbb517 --- /dev/null +++ b/Chapter03/listing3-4.php @@ -0,0 +1,41 @@ +get_parent(); + + echo 'The ' . get_class($widget) . ' has '; + if (isset($parent)) { + echo 'a ' . get_class($parent); + } else { + echo 'no'; + } + echo " parent.\n"; +} + +// Start with three widgets. +$window = new GtkWindow(); +$frame = new GtkFrame('I am a frame'); +$button = new GtkButton("I'm a button"); + +testForParent($button); + +$button->set_parent($frame); +testForParent($button); + +// What if we want the button to be added directly to +// the window? +$button->unparent(); +$button->set_parent($window); +testForParent($button); +$button->unparent(); +testForParent($button); +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/Chapter03/listing3-5.php b/Chapter03/listing3-5.php new file mode 100644 index 0000000..1c1b1b6 --- /dev/null +++ b/Chapter03/listing3-5.php @@ -0,0 +1,43 @@ +get_parent(); + + echo 'The ' . get_class($widget) . ' has '; + if (isset($parent)) { + echo 'a ' . get_class($parent); + } else { + echo 'no'; + } + echo " parent.\n"; +} + +// Start with three widgets. +$window = new GtkWindow(); +$frame = new GtkFrame('I am a frame'); +$button = new GtkButton("I'm a button"); + +testForParent($button); + +$frame->add($button); +testForParent($button); + +// What if we want the button to be added directly to +// the window? +$frame->remove($button); +$window->add($button); +testForParent($button); + +// No switch it back to the frame. +$button->reparent($frame); +testForParent($button); +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/Chapter04/listing4-1.php b/Chapter04/listing4-1.php new file mode 100644 index 0000000..3581afe --- /dev/null +++ b/Chapter04/listing4-1.php @@ -0,0 +1,38 @@ +get_parent(); + + echo 'The ' . get_class($widget) . ' has '; + if (isset($parent)) { + echo 'a ' . get_class($parent); + } else { + echo 'no'; + } + echo " parent.\n"; +} + +// Start with three widgets. +$window = new GtkWindow(); +$frame = new GtkFrame('I am a frame'); +$button = new GtkButton("I'm a button"); + +// Connect the event to our test function +$button->connect('parent-set', 'setParentFunction'); + +// Now set some parents. +$button->set_parent($window); +$button->unparent(); +$frame->add($button); + +$button->reparent($window); +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/Chapter04/listing4-2.php b/Chapter04/listing4-2.php new file mode 100644 index 0000000..226bbd8 --- /dev/null +++ b/Chapter04/listing4-2.php @@ -0,0 +1,46 @@ +connect('parent-set', array($this, 'printParentSet')); + } + + public function printParentSet($widget) + { + $parent = $widget->get_parent(); + + echo 'The ' . get_class($widget) . ' has '; + if (isset($parent)) { + echo 'a ' . get_class($parent); + } else { + echo 'no'; + } + echo " parent.\n"; + } +} + +// Start with three widgets. +$window = new GtkWindow(); +$frame = new GtkFrame('I am a frame'); +$button = new ExtendedButton("I'm a button"); + +// Now set some parents. +$button->set_parent($window); +$button->unparent(); +$frame->add($button); + +$frame->connect('parent-set', array('ExtendedButton', 'printParentSet')); +$window->add($frame); +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/Chapter04/listing4-3.php b/Chapter04/listing4-3.php new file mode 100644 index 0000000..69c276d --- /dev/null +++ b/Chapter04/listing4-3.php @@ -0,0 +1,52 @@ +label1); + } + + public function changeLabel($button1, $button2) + { + // Change the label of the button that was pressed. + $button1->child->set_text($this->label2); + // Change the label of the other button. + $button2->child->set_text($this->label1); + } +} + +// Start with four widgets. +$window = new GtkWindow(); +$buttonBox = new GtkHButtonBox(); +$buttonA = new ExtendedButton(); +$buttonB = new ExtendedButton(); + +// Create a signal handler for buttonA's clicked signal. +// Pass buttonB as the second argument for changeLabel. +$buttonA->connect('clicked', array($buttonA, 'changeLabel'), $buttonB); + +// Create a signal handler for buttonB's clicked signal. +// Pass buttonA as the second argument for changeLabel. +$buttonB->connect('clicked', array($buttonB, 'changeLabel'), $buttonA); + +// Set up the window to close cleanly. +$window->connect_simple('destroy', array('Gtk', 'main_quit')); + +// Add the button box to the window. +$window->add($buttonBox); + +// Add the buttons to the button box. +$buttonBox->add($buttonA); +$buttonBox->add($buttonB); + +// Show the window and all of it children and grandchildren. +$window->show_all(); +// Start the main loop. +Gtk::main(); +?> diff --git a/Chapter04/listing4-4.php b/Chapter04/listing4-4.php new file mode 100644 index 0000000..b9fa3f0 --- /dev/null +++ b/Chapter04/listing4-4.php @@ -0,0 +1,50 @@ +counter . "\n"; + } + + public function doubleCounter() + { + $this->counter *= 2; + echo $this->counter . "\n"; + } + + public function checkLimit($child) + { + if ($this->counter > 1) { + echo "Whoa! Too many children.\n"; + $this->remove($child); + $this->counter--; + } + return; + } +} + +$container = new ExtendedContainer(); +$button = new GtkButton(); + +$container->connect_object('add', array($container, 'incCounter')); +$container->connect_object_after('add', array($container, 'checkLimit')); + +$container->add($button); +$container->add(new GtkLabel('label')); +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/Chapter04/listing4-5.php b/Chapter04/listing4-5.php new file mode 100644 index 0000000..27946bb --- /dev/null +++ b/Chapter04/listing4-5.php @@ -0,0 +1,67 @@ +window = new GtkWindow(); + // Create a button with the label 'Save'. + $this->button = new GtkButton('Save'); + + // Add the button to the window. + $this->window->add($this->button); + + // Set up the window to close cleanly after the other signal + // handlers have been called. + $this->window->connect_simple_after('destroy', array('Gtk', 'main_quit')); + + // Create a signal handler to check if the user has saved their + // work before allowing the application to be closed. + $this->window->connect_simple('delete-event', array($this, 'checkSaved')); + + // Create a signal handler that calls the saveFile method when the + // user clicks the Save button. + $this->button->connect_simple('clicked', array($this, 'saveFile')); + } + + public function start() + { + // Show the window and its contents. + $this->window->show_all(); + + // Start the main loop. + Gtk::main(); + } + + public function saveFile() + { + // For now, just set the saved flag to true. + $this->saved = true; + } + + public function checkSaved() + { + // Check the value of the saved flag. + if (!$this->saved) { + // Echo a message saying the file wasn't saved. + echo "File not saved.\n"; + // Return true to prevent the app from closing. + return true; + } + } +} + +// Create a new editor. +$editor = new Editor(); + +// Start the application and catch any exceptions. +try { + $editor->start(); +} catch (Exception $e) { + echo $e->getMessage() . "\n"; +} +?> diff --git a/Chapter04/listing4-6.php b/Chapter04/listing4-6.php new file mode 100644 index 0000000..7788467 --- /dev/null +++ b/Chapter04/listing4-6.php @@ -0,0 +1,54 @@ +mouseOverLabel = new GtkLabel($mouseOverText); + $this->mouseOutLabel = new GtkLabel($mouseOutText); + + // Call the parent constructor. + parent::__construct(); + + // Add the mouse out label to start. + $this->add($this->mouseOutLabel); + + // Connect the mouse over and out events. + $this->connect_object('enter-notify-event', array($this, 'switchLabels')); + $this->connect_object('leave-notify-event', array($this, 'switchLabels')); + } + + public function switchLabels() + { + if ($this->child === $this->mouseOverLabel) { + $this->remove($this->mouseOverLabel); + $this->add($this->mouseOutLabel); + $this->show_all(); + } else { + $this->remove($this->mouseOutLabel); + $this->add($this->mouseOverLabel); + $this->show_all(); + } + } +} + +// Create a window and add our new class to it. +$window = new GtkWindow(); +$window->add(new ChangingLabel('Whoo Hoo!', 'Move the mouse here.')); + +$window->connect_object('destroy', array('Gtk', 'main_quit')); +$window->show_all(); +Gtk::main(); +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/Chapter04/listing4-7.php b/Chapter04/listing4-7.php new file mode 100644 index 0000000..ac11325 --- /dev/null +++ b/Chapter04/listing4-7.php @@ -0,0 +1,73 @@ +handlerId = $this->connect_simple('clicked', + array($this, 'turnOff'), + $otherButton + ); + } + + public function turnOff($otherButton) + { + // Turn this button off. + $this->block($this->handlerId); + + // Change the text to process and add the number of times the + // button has been pressed. + $this->child->set_text('Processing (' . ++$this->counter . ')'); + + // Turn the other button on. + $otherButton->unblock($otherButton->handlerId); + + // Change the text to press me and add the number of times the + // button has been pressed. + $otherButton->child->set_text('Press Me (' . + $otherButton->counter . ')' + ); + } +} + +// Create a new Window. +$window = new GtkWindow(); + +// Create two new OnOff buttons. +$button1 = new OnOff('Press Me'); +$button2 = new OnOff('Press Me'); + +// Create a new box to hold the buttons. +$box = new GtkHBox(); + +// Set up the connections. +$button1->setUp($button2); +$button2->setUp($button1); + +// Add both buttons to the box. +$box->add($button1); +$box->add($button2); + +// Add the box to the window. +$window->add($box); + +// Show the window and its contents. +$window->show_all(); + +// Set up the window to close cleanly. +$window->connect_simple('destroy', array('Gtk', 'main_quit')); + +// Start the main loop. +Gtk::main(); +?> \ No newline at end of file diff --git a/Chapter04/listing4-9.php b/Chapter04/listing4-9.php new file mode 100644 index 0000000..e90d1a5 --- /dev/null +++ b/Chapter04/listing4-9.php @@ -0,0 +1,60 @@ +add_events(Gdk::KEY_PRESS_MASK); + + // Create a signal handler for the key-press-event signal. + $this->connect_simple('key-press-event', array($this, 'echoText')); + + return true; + } + + public function echoText() + { + // Echo the current text of the entry. + echo $this->get_text() . "\r\n"; + } +} + +// Build some widgets +$window = new GtkWindow(); +$vBox = new GtkVBox(); +$label = new GtkLabel('Type something in the entry field'); +$entry = new EchoEntry(); + +// Pack them all together. +$window->add($vBox); +$vBox->add($label); +$vBox->add($entry); + +// Set up the window to close cleanly. +$window->connect_simple('destroy', array('Gtk', 'main_quit')); + +// Show the window and its contents. +$window->show_all(); + +// Add the key-press-event signal to the signals that the EchoEntry listens +// for. +$entry->addKeyPress(); + +// Start the main loop. +Gtk::main(); +?> diff --git a/Chapter05/listing5-1.php b/Chapter05/listing5-1.php new file mode 100644 index 0000000..3f1569c --- /dev/null +++ b/Chapter05/listing5-1.php @@ -0,0 +1,40 @@ +set_decorated(!$window->get_decorated()); + + // Update the button. + if ($window->get_decorated()) { + $button->child->set_text('Off'); + } else { + $button->child->set_text('On'); + } + + // Hide and show the window. + $window->hide_all(); + $window->show_all(); +} + +$window = new GtkWindow(); +$vBox = new GtkVBox(); +$label = new GtkLabel('Press the button to toggle the borders.'); +$button = new GtkButton('Off'); + +$window->add($vBox); +$vBox->add($label); +$vBox->add($button); + +$button->connect('clicked', 'toggle', $window); + +$window->connect_object('destroy', array('Gtk', 'main_quit')); +$window->show_all(); + +Gtk::main(); +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/Chapter05/listing5-2.php b/Chapter05/listing5-2.php new file mode 100644 index 0000000..c6c0203 --- /dev/null +++ b/Chapter05/listing5-2.php @@ -0,0 +1,84 @@ +set_decorated(false); + + // Set the background color to white. + $style = $this->style->copy(); + $style->bg[Gtk::STATE_NORMAL] = $style->white; + $this->set_style($style); + + // Call a helper method to create the pieces of the splash screen. + $this->_populate(); + + // Set up the application to shutdown cleanly. + $this->connect_object('destroy', array('Gtk', 'main_quit')); + } + + private function _populate() + { + // Create the containers. + $frame = new GtkFrame(); + $hBox = new GtkHBox(); + $vBox = new GtkVBox(); + + // Set the shadow type. + $frame->set_shadow_type(Gtk::SHADOW_ETCHED_OUT); + + // Create title label. + $titleText = 'Crisscott ' . + 'Product Information Management System'; + $title = new GtkLabel($titleText); + // Use markup to make the label blue and bold. + $title->set_use_markup(true); + + // Create an initial status message. + $this->status = new GtkLabel('Initializing Main Window'); + + // Stack the labels vertically. + $vBox->pack_start($title, true, true, 10); + $vBox->pack_start($this->status, true, true, 10); + + // Add a logo image. + $logoImg = GtkImage::new_from_file('Crisscott/images/logo.png'); + + // Put the image and the first box next to each other. + $hBox->pack_start($logoImg, false, false, 10); + $hBox->pack_start($vBox, false, false, 10); + + // Put everything inside a frame. + $frame->add($hBox); + + // Put the frame inside the window. + $this->add($frame); + } + + + public function start() + { + $this->show_all(); + Gtk::main(); + } +} + +$splash = new Crisscott_SplashScreen(); +$splash->start(); +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/Chapter05/listing5-3.php b/Chapter05/listing5-3.php new file mode 100644 index 0000000..bb468e1 --- /dev/null +++ b/Chapter05/listing5-3.php @@ -0,0 +1,86 @@ +set_decorated(false); + + // Set the background color to white. + $style = $this->style->copy(); + $style->bg[Gtk::STATE_NORMAL] = $style->white; + $this->set_style($style); + + // Move the window to the center of the screen. + $this->set_uposition(Gdk::screen_width() / 2, Gdk::screen_height() / 2); + // Call a helper method to create the pieces of the splash screen. + $this->_populate(); + + // Set up the application to shutdown cleanly. + $this->connect_object('destroy', array('Gtk', 'main_quit')); + } + + private function _populate() + { + // Create the containers. + $frame = new GtkFrame(); + $hBox = new GtkHBox(); + $vBox = new GtkVBox(); + + // Set the shadow type. + $frame->set_shadow_type(Gtk::SHADOW_ETCHED_OUT); + + // Create title label. + $titleText = 'Crisscott ' . + 'Product Information Management System'; + $title = new GtkLabel($titleText); + // Use markup to make the label blue and bold. + $title->set_use_markup(true); + + // Create an initial status message. + $this->status = new GtkLabel('Initializing Main Window'); + + // Stack the labels vertically. + $vBox->pack_start($title, true, true, 10); + $vBox->pack_start($this->status, true, true, 10); + + // Add a logo image. + $logoImg = GtkImage::new_from_file('Crisscott/images/logo.png'); + + // Put the image and the first box next to each other. + $hBox->pack_start($logoImg, false, false, 10); + $hBox->pack_start($vBox, false, false, 10); + + // Put everything inside a frame. + $frame->add($hBox); + + // Put the frame inside the window. + $this->add($frame); + } + + + public function start() + { + $this->show_all(); + Gtk::main(); + } +} + +$splash = new Crisscott_SplashScreen(); +$splash->start(); +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/Chapter05/listing5-4.php b/Chapter05/listing5-4.php new file mode 100644 index 0000000..46904b3 --- /dev/null +++ b/Chapter05/listing5-4.php @@ -0,0 +1,59 @@ +set_decorated(false); + + $this->set_size_request(300, 100); + $this->set_uposition(Gdk::screen_width() / 2 - 150, Gdk::screen_height() / 2 - 50); + + $this->_populate(); + + $this->connect_object('destroy', array('Gtk', 'main_quit')); + } + + private function _populate() + { + $vBox = new GtkVBox(); + $logoBox = new GtkHBox(); + $statusBox = new GtkHBox(); + + $logo = new GtkLabel('Crisscott Product Information Management System'); + $this->status = new GtkLabel('Loading...'); + + $vBox->add($logoBox); + $vBox->add($statusBox); + + $logoBox->add($logo); + $statusBox->add($this->status); + + $this->add($vBox); + } + + + public function start() + { + $this->show_all(); + Gtk::main(); + } +} + +$splash = new Crisscott_SplashScreen(); +$splash->start(); +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/Chapter05/listing5-5.php b/Chapter05/listing5-5.php new file mode 100644 index 0000000..b30c221 --- /dev/null +++ b/Chapter05/listing5-5.php @@ -0,0 +1,59 @@ +set_decorated(false); + + $this->set_size_request(300, 100); + $this->set_position(Gtk::WIN_POS_CENTER); + + $this->_populate(); + + $this->connect_object('destroy', array('Gtk', 'main_quit')); + } + + private function _populate() + { + $vBox = new GtkVBox(); + $logoBox = new GtkHBox(); + $statusBox = new GtkHBox(); + + $logo = new GtkLabel('Crisscott Product Information Management System'); + $this->status = new GtkLabel('Loading...'); + + $vBox->add($logoBox); + $vBox->add($statusBox); + + $logoBox->add($logo); + $statusBox->add($this->status); + + $this->add($vBox); + } + + + public function start() + { + $this->show_all(); + Gtk::main(); + } +} + +$splash = new Crisscott_SplashScreen(); +$splash->start(); +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/Chapter05/listing5-6.php b/Chapter05/listing5-6.php new file mode 100644 index 0000000..19c60fe --- /dev/null +++ b/Chapter05/listing5-6.php @@ -0,0 +1,60 @@ +set_decorated(false); + + $this->set_size_request(300, 100); + $this->set_position(Gtk::WIN_POS_CENTER); + + $this->_populate(); + + $this->set_keep_above(true); + + $this->connect_object('destroy', array('Gtk', 'main_quit')); + } + + private function _populate() + { + $vBox = new GtkVBox(); + $logoBox = new GtkHBox(); + $statusBox = new GtkHBox(); + + $logo = new GtkLabel('Crisscott Product Information Management System'); + $this->status = new GtkLabel('Loading...'); + + $vBox->add($logoBox); + $vBox->add($statusBox); + + $logoBox->add($logo); + $statusBox->add($this->status); + + $this->add($vBox); + } + + + public function start() + { + $this->show_all(); + Gtk::main(); + } +} + + +$splash = new Crisscott_SplashScreen(); +$splash->start(); +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/Chapter05/listing5-7.php b/Chapter05/listing5-7.php new file mode 100644 index 0000000..54fb28a --- /dev/null +++ b/Chapter05/listing5-7.php @@ -0,0 +1,26 @@ +set_size_request(500, 300); + $this->set_position(Gtk::WIN_POS_CENTER); + $this->set_title('Criscott PIMS'); + $this->maximize(); + + $this->connect_object('destroy', array('Gtk', 'main_quit')); + } +} + +$main = new Crisscott_MainWindow(); +$main->show_all(); +Gtk::main(); +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/Chapter05/listing5-8.php b/Chapter05/listing5-8.php new file mode 100644 index 0000000..558f39f --- /dev/null +++ b/Chapter05/listing5-8.php @@ -0,0 +1,114 @@ +set_decorated(false); + + $this->set_size_request(300, 100); + $this->set_position(Gtk::WIN_POS_CENTER); + + $this->set_keep_above(true); + + $this->_populate(); + + $this->connect_object_after('show', array($this, 'startMainWindow')); + } + + private function _populate() + { + $vBox = new GtkVBox(); + $logoBox = new GtkHBox(); + $statusBox = new GtkHBox(); + + $logo = new GtkLabel('Crisscott Product Information Management System'); + $this->status = new GtkLabel('Loading...'); + + $vBox->add($logoBox); + $vBox->add($statusBox); + + $logoBox->add($logo); + $statusBox->add($this->status); + + $this->add($vBox); + } + + + public function start() + { + $this->show_all(); + Gtk::main(); + } + + public function startMainWindow() + { + $main = new Crisscott_MainWindow(); + + $this->status->set_text('Connecting to server...'); + while (Gtk::events_pending()) Gtk::main_iteration(); + + if ($main->connectToServer()) { + $this->status->set_text('Connecting to server... OK'); + } + while (Gtk::events_pending()) Gtk::main_iteration(); + sleep(1); + + $this->status->set_text('Connecting to local database...'); + while (Gtk::events_pending()) Gtk::main_iteration(); + + if ($main->connectToLocalDB()) { + $this->status->set_text('Connecting to local database... OK'); + } + while (Gtk::events_pending()) Gtk::main_iteration(); + + $main->show_all(); + while (Gtk::events_pending()) Gtk::main_iteration(); + sleep(1); + + $this->hide(); + } +} + +class Crisscott_MainWindow extends GtkWindow { + + public function __construct() + { + parent::__construct(); + + $this->set_size_request(500, 300); + $this->set_position(Gtk::WIN_POS_CENTER); + $this->set_title('Criscott PIMS'); + $this->maximize(); + + $this->connect_object('destroy', array('gtk', 'main_quit')); + } + + public function connectToServer() + { + sleep(1); + return true; + } + + public function connectToLocalDB() + { + sleep(1); + return true; + } +} + +$splash = new Crisscott_SplashScreen(); +$splash->start(); +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/Chapter05/listing5-9.php b/Chapter05/listing5-9.php new file mode 100644 index 0000000..0ae482c --- /dev/null +++ b/Chapter05/listing5-9.php @@ -0,0 +1,114 @@ +set_decorated(false); + + $this->set_size_request(300, 100); + $this->set_position(Gtk::WIN_POS_CENTER); + + $this->_populate(); + + $this->set_keep_above(true); + + $this->connect_object_after('show', array($this, 'startMainWindow')); + } + + private function _populate() + { + $vBox = new GtkVBox(); + $logoBox = new GtkHBox(); + $statusBox = new GtkHBox(); + + $logo = new GtkLabel('Crisscott Product Information Management System'); + $this->status = new GtkLabel('Loading...'); + + $vBox->add($logoBox); + $vBox->add($statusBox); + + $logoBox->add($logo); + $statusBox->add($this->status); + + $this->add($vBox); + } + + + public function start() + { + $this->show_all(); + Gtk::main(); + } + + public function startMainWindow() + { + $main = new Crisscott_MainWindow(); + + $this->status->set_text('Connecting to server...'); + while (Gtk::events_pending()) Gtk::main_iteration(); + + if ($main->connectToServer()) { + $this->status->set_text('Connecting to server... OK'); + } + while (Gtk::events_pending()) Gtk::main_iteration(); + sleep(1); + + $this->status->set_text('Connecting to local database...'); + while (Gtk::events_pending()) Gtk::main_iteration(); + + if ($main->connectToLocalDB()) { + $this->status->set_text('Connecting to local database... OK'); + } + while (Gtk::events_pending()) Gtk::main_iteration(); + + $main->show_all(); + sleep(1); + + $this->set_keep_above(false); + $this->hide(); + } +} + +class Crisscott_MainWindow extends GtkWindow { + + public function __construct() + { + parent::__construct(); + + $this->set_size_request(500, 300); + $this->set_position(Gtk::WIN_POS_CENTER); + $this->set_title('Criscott PIMS'); + $this->maximize(); + + $this->connect_object('destroy', array('gtk', 'main_quit')); + } + + public function connectToServer() + { + sleep(1); + return true; + } + + public function connectToLocalDB() + { + sleep(1); + return true; + } +} + +$splash = new Crisscott_SplashScreen(); +$splash->start(); +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/Chapter06/listing6-1.php b/Chapter06/listing6-1.php new file mode 100644 index 0000000..f4260fc --- /dev/null +++ b/Chapter06/listing6-1.php @@ -0,0 +1,41 @@ +set_size_request(500, 300); + $this->set_position(Gtk::WIN_POS_CENTER); + $this->set_title('Criscott PIMS'); + + $this->_populate(); + + $this->maximize(); + + $this->connect_object('destroy', array('Gtk', 'main_quit')); + } + + private function _populate() + { + $vb1 = new GtkVBox(); + + $vb1->pack_start(new GtkFrame('MENU'), false, false, 0); + $vb1->pack_start(new GtkFrame('TOOLBAR'), false, false, 0); + $vb1->pack_start(new GtkFrame('MAIN'), true, true, 0); + $vb1->pack_start(new GtkFrame('STATUS'), false, false, 0); + + $this->add($vb1); + } +} + +$main = new Crisscott_MainWindow(); +$main->show_all(); +Gtk::main(); +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/Chapter06/listing6-2.php b/Chapter06/listing6-2.php new file mode 100644 index 0000000..6007fa9 --- /dev/null +++ b/Chapter06/listing6-2.php @@ -0,0 +1,61 @@ +set_size_request(500, 300); + $this->set_position(Gtk::WIN_POS_CENTER); + $this->set_title('Criscott PIMS'); + + $this->_populate(); + + $this->maximize(); + + $this->connect_object('destroy', array('Gtk', 'main_quit')); + } + + private function _populate() + { + $vb1 = new GtkVBox(); + $vb2 = new GtkVBox(); + $vb3 = new GtkVBox(); + $hb1 = new GtkHBox(); + $hb2 = new GtkHBox(); + + $vb1->pack_start(new GtkFrame('MENU'), false, false, 0); + $vb1->pack_start(new GtkFrame('TOOLBAR'), false, false, 0); + $vb1->pack_start($hb1); + $vb1->pack_start(new GtkFrame('STATUS'), false, false, 0); + + $hb1->pack_start($vb2, false, false, 0); + $hb1->pack_start($vb3); + + $vb2->pack_start(new GtkFrame('PRODUCT TREE')); + $vb2->pack_start(new GtkFrame('NEWS')); + + $vb2->set_size_request(150, -1); + + $vb3->pack_start($hb2, false, false, 0); + $vb3->pack_start(new GtkFrame('EDITING PRODUCTS')); + + $hb2->pack_start(new GtkFrame('PRODUCT SUMMARY')); + $hb2->pack_start(new GtkFrame('INVENTORY SUMMARY')); + + $hb2->set_size_request(-1, 150); + + $this->add($vb1); + } +} + +$main = new Crisscott_MainWindow(); +$main->show_all(); +Gtk::main(); +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/Chapter06/listing6-3.php b/Chapter06/listing6-3.php new file mode 100644 index 0000000..a4153da --- /dev/null +++ b/Chapter06/listing6-3.php @@ -0,0 +1,68 @@ +set_size_request(500, 300); + $this->set_position(Gtk::WIN_POS_CENTER); + $this->set_title('Criscott PIMS'); + + $this->_populate(); + + $this->maximize(); + + $this->connect_object('destroy', array('Gtk', 'main_quit')); + } + + private function _populate() + { + $table = new GtkTable(5, 3); + + $expandFill = GTK::EXPAND|GTK::FILL; + + $table->attach(new GtkFrame('MENU'), 0, 2, 0, 1, $expandFill, 0, 0, 0); + $table->attach(new GtkFrame('TOOLBAR'), 0, 2, 1, 2, $expandFill, 0, 0, 0); + + $productTree = new GtkFrame('PRODUCT TREE'); + $productTree->set_size_request(150, -1); + + $table->attach($productTree, 0, 1, 2, 3, 0, $expandFill, 0, 0); + + $news = new GtkFrame('NEWS'); + $news->set_size_request(150, -1); + + $table->attach($news, 0, 1, 3, 4, 0, $expandFill, 0, 0); + + $table2 = new GtkTable(2, 2); + + $productSummary = new GtkFrame('PRODUCT SUMMARY'); + $productSummary->set_size_request(-1, 150); + + $table2->attach($productSummary, 0, 1, 0, 1, $expandFill, 0, 1, 1); + + $inventorySummary = new GtkFrame('INVENTORY SUMMARY'); + $inventorySummary->set_size_request(-1, 150); + + $table2->attach($inventorySummary, 1, 2, 0, 1, $expandFill, 0, 1, 1); + $table2->attach(new GtkFrame('EDITING PRODUCTS'), 0, 2, 1, 2, $expandFill, $expandFill, 1, 1); + + $table->attach($table2, 1, 2, 2, 4, $expandFill, $expandFill, 0, 0); + + $table->attach(new GtkFrame('STATUS'), 0, 2, 4, 5, $expandFill, 0, 0, 0); + + $this->add($table); + } +} + +$main = new Crisscott_MainWindow(); +$main->show_all(); +Gtk::main(); +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/Chapter06/listing6-4.php b/Chapter06/listing6-4.php new file mode 100644 index 0000000..de1d71b --- /dev/null +++ b/Chapter06/listing6-4.php @@ -0,0 +1,68 @@ +set_size_request(500, 300); + $this->set_position(Gtk::WIN_POS_CENTER); + $this->set_title('Criscott PIMS'); + + $this->_populate(); + + $this->maximize(); + + $this->connect_object('destroy', array('Gtk', 'main_quit')); + } + + private function _populate() + { + $fixed = new GtkFixed(); + + $menu = new GtkFrame('MENU'); + $menu->set_size_request(GDK::screen_width() - 10, -1); + $fixed->put($menu, 0, 0); + + $toolbar = new GtkFrame('TOOLBAR'); + $toolbar->set_size_request(GDK::screen_width() - 10, -1); + $fixed->put($toolbar, 0, 18); + + $pTree = new GtkFrame('PRODUCT TREE'); + $pTree->set_size_request(150, GDK::screen_height() / 2 - 54); + $fixed->put($pTree, 0, 36); + + $news = new GtkFrame('NEWS'); + $news->set_size_request(150, GDK::screen_height() / 2 - 54); + $fixed->put($news, 0, GDK::screen_height() / 2 - 18); + + $status = new GtkFrame('STATUS'); + $status->set_size_request(GDK::screen_width() - 10, -1); + $fixed->put($status, 0, GDK::screen_height() - 72); + + $pSummary = new GtkFrame('PRODUCT SUMMARY'); + $pSummary->set_size_request(GDK::screen_width() / 2 - 90, 150); + $fixed->put($pSummary, 152, 36); + + $iSummary = new GtkFrame('INVENTORY SUMMARY'); + $iSummary->set_size_request(GDK::screen_width() / 2 - 75, 150); + $fixed->put($iSummary, GDK::screen_width() / 2 - 90 + 154, 36); + + $edit = new GtkFrame('EDIT PRODUCTS'); + $edit->set_size_request(GDK::screen_width() - 150, GDK::screen_height() - 262); + $fixed->put($edit, 152, 190); + + $this->add($fixed); + } +} + +$main = new Crisscott_MainWindow(); +$main->show_all(); +Gtk::main(); +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/Chapter06/listing6-5.php b/Chapter06/listing6-5.php new file mode 100644 index 0000000..35b487d --- /dev/null +++ b/Chapter06/listing6-5.php @@ -0,0 +1,71 @@ +set_size_request(500, 300); + $this->set_position(Gtk::WIN_POS_CENTER); + $this->set_title('Criscott PIMS'); + + $this->_populate(); + + $this->maximize(); + + $this->connect_object('destroy', array('Gtk', 'main_quit')); + } + + private function _populate() + { + $table = new GtkTable(5, 3); + + $expandFill = GTK::EXPAND|GTK::FILL; + + $table->attach(new GtkFrame('MENU'), 0, 2, 0, 1, $expandFill, 0, 0, 0); + $table->attach(new GtkFrame('TOOLBAR'), 0, 2, 1, 2, $expandFill, 0, 0, 0); + + $productTree = new GtkFrame('PRODUCT TREE'); + $productTree->set_size_request(150, -1); + + $table->attach($productTree, 0, 1, 2, 3, 0, $expandFill, 0, 0); + + $news = new GtkFrame('NEWS'); + $news->set_size_request(150, -1); + + $table->attach($news, 0, 1, 3, 4, 0, $expandFill, 0, 0); + + $table2 = new GtkTable(2, 2); + + $productSummary = new GtkFrame('PRODUCT SUMMARY'); + $productSummary->set_size_request(-1, 150); + + $table2->attach($productSummary, 0, 1, 0, 1, $expandFill, 0, 1, 1); + + $inventorySummary = new GtkFrame('INVENTORY SUMMARY'); + $inventorySummary->set_size_request(-1, 150); + + $table2->attach($inventorySummary, 1, 2, 0, 1, $expandFill, 0, 1, 1); + + require_once 'Crisscott/MainNotebook.php'; + $this->mainNotebook = new Crisscott_MainNotebook(); + $table2->attach($this->mainNotebook, 0, 2, 1, 2, $expandFill, $expandFill, 1, 1); + + $table->attach($table2, 1, 2, 2, 4, $expandFill, $expandFill, 0, 0); + + $table->attach(new GtkFrame('STATUS'), 0, 2, 4, 5, $expandFill, 0, 0, 0); + + $this->add($table); + } +} + +$main = new Crisscott_MainWindow(); +$main->show_all(); +Gtk::main(); +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/Chapter06/listing6-6.php b/Chapter06/listing6-6.php new file mode 100644 index 0000000..1e8eaf3 --- /dev/null +++ b/Chapter06/listing6-6.php @@ -0,0 +1,39 @@ +append_page(new GtkVBox(), new GtkLabel($title)); + $this->pages[$title] = $this->get_nth_page($pageNum); + } + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/Chapter06/listing6-7.php b/Chapter06/listing6-7.php new file mode 100644 index 0000000..23daa97 --- /dev/null +++ b/Chapter06/listing6-7.php @@ -0,0 +1,50 @@ +append_page(new GtkVBox(), new GtkLabel($title)); + $page = $this->get_nth_page($pageNum); + $this->pages[$title] = $page; + + $button = new GtkButton('PREVIOUS'); + $button->connect_object('clicked', array($this, 'next_page')); + + $page->pack_start($button, false, false); + + $button = new GtkButton('NEXT'); + $button->connect_object('clicked', array($this, 'next_page')); + + $page->pack_start($button, false, false); + } + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/Chapter06/listing6-8.php b/Chapter06/listing6-8.php new file mode 100644 index 0000000..fe18748 --- /dev/null +++ b/Chapter06/listing6-8.php @@ -0,0 +1,59 @@ +append_page(new GtkVBox(), new GtkLabel($title)); + $page = $this->get_nth_page($pageNum); + $this->pages[$title] = $page; + + $button = new GtkButton('PREVIOUS'); + $button->connect_object('clicked', array($this, 'next_page')); + + $page->pack_start($button, false, false); + + $button = new GtkButton('RANDOM'); + $button->connect_object('clicked', array($this, 'goToRandomPage')); + $page->pack_start($button, false, false); + + $button = new GtkButton('NEXT'); + $button->connect_object('clicked', array($this, 'next_page')); + + $page->pack_start($button, false, false); + } + } + + public function goToRandomPage() + { + $this->set_current_page($this->page_num($this->pages[array_rand($this->pages)])); + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/Chapter07/listing7-1.php b/Chapter07/listing7-1.php new file mode 100644 index 0000000..2afefc0 --- /dev/null +++ b/Chapter07/listing7-1.php @@ -0,0 +1,38 @@ +set_use_markup(!$label->get_use_markup()); +} + +$window = new GtkWindow(); +$window->connect_object('destroy', array('Gtk', 'main_quit')); + +$box = new GtkVBox(); +$window->add($box); + +$label = new GtkLabel('Test Label Test Label Test Label Test Label Test Label TestLabel Test Label Test Label Test Label Test Label Test Label'); +//$label->set_ellipsize(Pango::ELLIPSIZE_END); + +//$label->set_size_request(100, 100); +//$label->set_max_width_chars(10); +//var_dump($label->get_max_width_chars()); +$label->set_justify(GTK::JUSTIFY_FILL); +$label->set_line_wrap(true); + +$button = new GtkButton('Change'); +$button->connect_object('clicked', 'change', $label); + +$box->pack_start($label, false, false); +$box->pack_start($button); + + +$window->show_all(); +Gtk::main(); + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/Chapter07/listing7-8.php b/Chapter07/listing7-8.php new file mode 100644 index 0000000..46f73ea --- /dev/null +++ b/Chapter07/listing7-8.php @@ -0,0 +1,34 @@ +get_value() . "\n"; +} + +$window = new GtkWindow(); +$window->set_size_request(150, 150); + +$hScale = new GtkHScale(new GtkAdjustment(4, 0, 10, 1, 2)); +$hScale->connect('value-changed', 'echoValue'); + +$vScale = new GtkVScale(new GtkAdjustment(4, 0, 10, 1, 2)); +$vScale->connect('value-changed', 'echoValue'); +$vScale->set_value_pos(GTK::POS_LEFT); + +$hBox = new GtkHBox(); +$vBox1 = new GtkVBox(); +$vBox2 = new GtkVBox(); + +$window->add($hBox); +$hBox->pack_start($vBox1); +$hBox->pack_start($vBox2); + +$vBox1->pack_start(new GtkLabel('GtkHScale'), false, false); +$vBox1->pack_start($hScale, false, false); + +$vBox2->pack_start(new GtkLabel('GtkVScale'), false, false); +$vBox2->pack_start($vScale); + +$window->connect_object('destroy', array('Gtk', 'main_quit')); +$window->show_all(); +Gtk::main(); +?> \ No newline at end of file diff --git a/Chapter07/listing7-9.php b/Chapter07/listing7-9.php new file mode 100644 index 0000000..438b1e1 --- /dev/null +++ b/Chapter07/listing7-9.php @@ -0,0 +1,22 @@ +get_value() . "\n"; +} + +$window = new GtkWindow(); +$window->set_size_request(100, 100); + +$spin = new GtkSpinButton(new GtkAdjustment(4, 0, 10, 1, 2), 1, 0); +$spin->connect('changed', 'echoValue'); + +$vBox = new GtkVBox(); + +$window->add($vBox); +$vBox->pack_start(new GtkLabel('GtkSpinButton'), false, false); +$vBox->pack_start($spin, false, false); + +$window->connect_object('destroy', array('Gtk', 'main_quit')); +$window->show_all(); +Gtk::main(); +?> \ No newline at end of file diff --git a/Chapter08/listing8-1.php b/Chapter08/listing8-1.php new file mode 100644 index 0000000..492b57c --- /dev/null +++ b/Chapter08/listing8-1.php @@ -0,0 +1,56 @@ +get_mark('insert')) { + $mark2 = $buffer->get_mark('selection_bound'); + } else { + $mark2 = $buffer->get_mark('insert'); + } + // Get the iter at the other mark. + $iter2 = $buffer->get_iter_at_offset(0); + $buffer->get_iter_at_mark($iter2, $mark2); + + //echo 'Iter1: ' . $iter->get_offset() . "\t"; + //echo 'Iter2: ' . $iter2->get_offset() . "\t"; + + // Print the text between the two iters. + echo 'SELECTION: ' . $buffer->get_text($iter, $iter2) . "\n"; +} + +// Create a GtkTextView. +$text = new GtkTextView(); +// Get the buffer from the view. +$buffer = $text->get_buffer(); + +// Add some text. +$buffer->set_text('Moving a mark is done with either move_mark or move_mark_by_name.'); + +// Connect the printSelected method. +$buffer->connect('mark-set', 'printSelected'); + +// How NOT to move the cursor to the beginning of the text. +echo "Move to start\n"; +$buffer->move_mark_by_name('insert', $buffer->get_start_iter()); +$buffer->move_mark_by_name('selection_bound', $buffer->get_start_iter()); + +// How NOT to select a range of text. +echo "Select range\n"; +$buffer->move_mark_by_name('selection_bound', $buffer->get_iter_at_offset(7)); +$buffer->move_mark_by_name('insert', $buffer->get_iter_at_offset(16)); + +echo "\nBetter way\n"; +// The better way to move the cursor to the beginning of the text. +echo "Move to start\n"; +$buffer->place_cursor($buffer->get_start_iter()); + +// The better way to select a range of text. +echo "Select range\n"; +$buffer->select_range($buffer->get_iter_at_offset(7), $buffer->get_iter_at_offset(16)); +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/Chapter08/listing8-2.php b/Chapter08/listing8-2.php new file mode 100644 index 0000000..4dae2b0 --- /dev/null +++ b/Chapter08/listing8-2.php @@ -0,0 +1,52 @@ +get_buffer(); + +// Add some text. +$buffer->set_text('Moving a mark is done with either move_mark or move_mark_by_name.'); + +// Get the fifth word from the buffer. +$iter = $buffer->get_start_iter(); +$iter->forward_word_ends(5); +$iter2 = $buffer->get_iter_at_offset($iter->get_offset()); +$iter->backward_word_start(); +echo $buffer->get_text($iter, $iter2) . "\n"; + +// Get the second to last word. +$iter = $buffer->get_end_iter(); +$iter->backward_word_starts(2); +$iter2 = $buffer->get_iter_at_offset($iter->get_offset()); +$iter2->forward_word_end(); +echo $buffer->get_text($iter, $iter2) . "\n"; + +// Figure out how many characters are between the third and sixth words. +$iter = $buffer->get_start_iter(); +$iter->forward_word_ends(3); +$endThird = $iter->get_offset(); +$iter->forward_word_ends(3); +echo 'There are ' . ($iter->get_offset() - $endThird) . ' '; +echo "characters between the third and sixth words.\n"; + +// Check to see if the end of the first sentence is the end of the buffer. +$iter = $buffer->get_start_iter(); +$iter->forward_sentence_end(); +if ($iter == $buffer->get_end_iter()) { + echo "The buffer only contains one sentence.\n"; +} else { + echo "The buffer contains more than one sentence.\n"; +} + +// Count the words in the buffer. +$iter = $buffer->get_start_iter(); +$count = 0; +while($iter->forward_word_end()) $count++; +echo 'There are ' . $count . " words in the buffer.\n"; +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/Chapter08/listing8-4.php b/Chapter08/listing8-4.php new file mode 100644 index 0000000..905ea79 --- /dev/null +++ b/Chapter08/listing8-4.php @@ -0,0 +1,30 @@ +get_buffer(); + +// Add some text. +$buffer->set_text('Moving a mark is done with either move_mark or move_mark_by_name.'); + +$tag = $buffer->create_tag(); +//$table = $buffer->get_tag_table(); +$tag = new GtkTextTag(); +$tag->foreground = 'red'; + +$table->add($tag); + +$buffer->apply_tag($tag, $buffer->get_start_iter(), $buffer->get_end_iter()); + +$window = new GtkWindow(); +$window->add($text); +$window->show_all(); +Gtk::main(); + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/Chapter08/listing8-5.php b/Chapter08/listing8-5.php new file mode 100644 index 0000000..31e6bf4 --- /dev/null +++ b/Chapter08/listing8-5.php @@ -0,0 +1,61 @@ +weight == Pango::WEIGHT_BOLD) { + $bold[] = $tag; + } +} + +// Create an array to hold the bold tags. +$bold = array(); + +// Create a GtkTextView. +$text = new GtkTextView(); + +// Get the buffer from the view. +$buffer = $text->get_buffer(); + +// Add some text. +$buffer->set_text('Moving a mark is done with either move_mark or ' . + 'move_mark_by_name.'); + +// Get the buffer's tag table. +$table = $buffer->get_tag_table(); + +// Create a new tag and set some properties. +$tag = new GtkTextTag(); +$tag->set_property('foreground', 'red'); +$tag->set_property('background', 'gray'); + +// Add the tag to the table. +$table->add($tag); + +// Create a new tag and set some properties. +$tag = new GtkTextTag(); +$tag->set_property('weight', Pango::WEIGHT_BOLD); + +// Add the tag to the table. +$table->add($tag); + +// Create a new tag and set some properties. +$tag = new GtkTextTag(); +$tag->set_property('foreground', 'blue'); +$tag->set_property('weight', Pango::WEIGHT_NORMAL); + +// Add the tag to the table. +$table->add($tag); + +// Create a new tag and set some properties. +$tag = new GtkTextTag(); +$tag->set_property('font', 'Arial Bold 10'); + +// Add the tag to the table. +$table->add($tag); + +// Call checkForBold on all tags in the table. +$table->foreach('checkForBold'); + +var_dump($bold); +?> \ No newline at end of file diff --git a/Chapter08/listing8-6.php b/Chapter08/listing8-6.php new file mode 100644 index 0000000..655c42b --- /dev/null +++ b/Chapter08/listing8-6.php @@ -0,0 +1,47 @@ +get_buffer(); + +// Add some text. +$buffer->insert_at_cursor('Moving a mark is done with either ', -1); + +// Create some tags. +$tag = new GtkTextTag(); +$tag->set_property('foreground', 'red'); +$tag2 = new GtkTextTag(); +$tag2->set_property('weight', Pango::WEIGHT_BOLD);; + +// Add them to the tag table. +$table = $buffer->get_tag_table(); +$table->add($tag); +$table->add($tag2); + +// Insert some text as red and bold. +$buffer->insert($buffer->get_end_iter(), 'move_mark or move_mark_by_name.');//, -1, $tag, $tag2); + +$start = $buffer->get_iter_at_offset(strpos($buffer->get_text($buffer->get_start_iter(), $buffer->get_end_iter()),'move_mark')); + +$buffer->apply_tag($tag, $start, $buffer->get_end_iter()); +$buffer->apply_tag($tag2, $start, $buffer->get_end_iter()); + +// Get an iter for the end of the first word. +$firstWord = $buffer->get_start_iter(); +$firstWord->forward_word_end(); + +// Remove the first word. +$buffer->delete($buffer->get_start_iter(), $firstWord); + +$window = new GtkWindow(); +$window->add($text); +$window->show_all(); +Gtk::main(); + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/Chapter08/listing8-7.php b/Chapter08/listing8-7.php new file mode 100644 index 0000000..76a8199 --- /dev/null +++ b/Chapter08/listing8-7.php @@ -0,0 +1,34 @@ +get_buffer(); + +// Set the buffer as the buffer for the second view. +$text2->set_buffer($buffer); + +// Add some text. +$buffer->insert_at_cursor('Moving a mark is done with either move_mark or move_mark_by_name.', -1); + +// Create a window and a box. +$window = new GtkWindow(); +$vBox = new GtkVBox(); + +// Add the text views. +$window->add($vBox); +$vBox->pack_start($text); +$vBox->pack_start($text2); + +// Show the application. +$window->show_all(); +gtk::main(); + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/Chapter09/listing9-1.php b/Chapter09/listing9-1.php new file mode 100644 index 0000000..c0cdd53 --- /dev/null +++ b/Chapter09/listing9-1.php @@ -0,0 +1,36 @@ +append(); +$listStore->set($iter, 0, 'Alabama'); +$iter = $listStore->append(); +$listStore->set($iter, 0, 'Alaska'); +$iter = $listStore->append(); +$listStore->set($iter, 0, 'Arizona'); +$iter = $listStore->append(); +$listStore->set($iter, 0, 'Arkansas'); +$iter = $listStore->append(); +$listStore->set($iter, 0, 'California'); +$iter = $listStore->append(); +$listStore->set($iter, 0, 'Colorodo'); + +$view = new GtkTreeView(); +$view->set_model($listStore); +$column = new GtkTreeViewColumn(); +$column->set_title('Column 1'); +$view->insert_column($column, 0); +$cell_renderer = new GtkCellRendererText(); +$column->pack_start($cell_renderer, true); +$column->set_attributes($cell_renderer, 'text', 0); + +$window = new GtkWindow(); +$window->add($view); +$window->show_all(); +Gtk::main(); + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/Chapter09/listing9-10.php b/Chapter09/listing9-10.php new file mode 100644 index 0000000..f25725e --- /dev/null +++ b/Chapter09/listing9-10.php @@ -0,0 +1,98 @@ +get_value($iter, 1); + // Set the value property of the cell renderer. + $renderer->set_property('value', $inventory / $totalInventory * 100); + + // Check to see if the inventory level is low. + if ($inventory < 10) { + // Make the cell background red. + $renderer->set_property('cell-background', '#F00'); + } else { + $renderer->set_property('cell-background', 'white'); + } +} + +// Create a tree store. +$treeStore = new GtkTreeStore(Gtk::TYPE_STRING, Gtk::TYPE_LONG, Gtk::TYPE_DOUBLE); + +// Add some product data. +$csMerch = $treeStore->append(null, array('Crisscott', null, null)); +$phpGtkMerch = $treeStore->append(null, array('PHP-GTK', null, null)); + +$tShirts = $treeStore->append($csMerch, array('T-Shirts', 10, 19.95)); +$treeStore->append($tShirts, array('Small', 3, 19.95)); +$treeStore->append($tShirts, array('Medium', 5, 19.95)); +$treeStore->append($tShirts, array('Large', 2, 19.95)); + +$pencils = $treeStore->append($csMerch, array('Pencils', 18, .99)); +$treeStore->append($pencils, array('Blue', 9, .99)); +$treeStore->append($pencils, array('White', 9, .99)); + +$treeStore->append($phpGtkMerch, array('PHP-GTK Bumper Stickers', 37, 1.99)); +$treeStore->append($phpGtkMerch, array('Pro PHP-GTK', 23, 44.95)); + +// Create a veiw to show the tree. +$view = new GtkTreeView(); +$view->set_model($treeStore); + +// Create columns for each type of data. +$column = new GtkTreeViewColumn(); +$column->set_title('Product Name'); +$view->insert_column($column, 0); + +// Create a renderer for the column. +$cell_renderer = new GtkCellRendererText(); +$column->pack_start($cell_renderer, true); +$column->set_attributes($cell_renderer, 'text', 0); + +// Make the column resizeable by the user. +$column->set_resizable(true); +$column->set_sort_column_id(0); + +// Create columns for each type of data. +$column2 = new GtkTreeViewColumn(); +$column2->set_title('Inventory'); +$view->insert_column($column2, 1); + +// Create a renderer for the column. +$cell_renderer = new GtkCellRendererProgress(); +$column2->pack_start($cell_renderer, true); + +// Take greater control of how the data is displayed. +$column2->set_cell_data_func($cell_renderer, 'percentageInventory', 88); + +// Allow the user to resize the column +$column2->set_resizable(true); +$column2->set_reorderable(true); + +// Create columns for each type of data. +$column3 = new GtkTreeViewColumn(); +$column3->set_title('Price'); +$view->insert_column($column3, 2); +// Create a renderer for the column. +$cell_renderer = new GtkCellRendererText(); +$column3->pack_start($cell_renderer, true); +$column3->set_attributes($cell_renderer, 'text', 2); + +// Allow the user to resize the column. +$column3->set_resizable(true); +$column3->set_reorderable(true); +$column3->set_sort_column_id(2); + +// Create a window and show everything. +$window = new GtkWindow(); +$window->add($view); +$window->show_all(); +$window->connect_simple('destroy', array('Gtk', 'main_quit')); +Gtk::main(); + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> diff --git a/Chapter09/listing9-11.php b/Chapter09/listing9-11.php new file mode 100644 index 0000000..2365e47 --- /dev/null +++ b/Chapter09/listing9-11.php @@ -0,0 +1,111 @@ +get_selected_rows(); + foreach ($paths as $path) { + $iter = $model->get_iter($path); + $model->set($iter, 3, Pango::WEIGHT_NORMAL); + } +} + +function columnsAutosize($view) +{ + $view->columns_autosize(); +} + +function hideColumn($column) +{ + $column->set_visible(!$column->get_visible()); +} + +// Create a tree store. +$treeStore = new GtkTreeStore(Gtk::TYPE_STRING, Gtk::TYPE_LONG, Gtk::TYPE_DOUBLE, Gtk::TYPE_LONG); + +// Add some product data. +$csMerch = $treeStore->append(null, array('Crisscott', null, null, Pango::WEIGHT_BOLD)); +$phpGtkMerch = $treeStore->append(null, array('PHP-GTK', null, null, Pango::WEIGHT_BOLD)); + +$tShirts = $treeStore->append($csMerch, array('T-Shirts', 10, 19.95, Pango::WEIGHT_BOLD)); +$treeStore->append($tShirts, array('Small', 3, 19.95, Pango::WEIGHT_BOLD)); +$treeStore->append($tShirts, array('Medium', 5, 19.95, Pango::WEIGHT_BOLD)); +$iter = $treeStore->append($tShirts, array('Large', 2, 19.95, Pango::WEIGHT_BOLD)); + +$pencils = $treeStore->append($csMerch, array('Pencils', 18, .99, Pango::WEIGHT_BOLD)); +$treeStore->append($pencils, array('Blue', 9, .99, Pango::WEIGHT_BOLD)); +$treeStore->append($pencils, array('White', 9, .99, Pango::WEIGHT_BOLD)); + +$treeStore->append($phpGtkMerch, array('PHP-GTK Bumper Stickers', 37, 1.99, Pango::WEIGHT_BOLD)); +$treeStore->append($phpGtkMerch, array('Pro PHP-GTK', 23, 44.95, Pango::WEIGHT_BOLD)); + +// Create a veiw to show the tree. +$view = new GtkTreeView($treeStore); +$view->connect('row-expanded', 'columnsAutosize'); + +// Create columns for each type of data. +$column = new GtkTreeViewColumn(); +$column->set_title('Product Name'); +$view->insert_column($column, 0); +// Create a renderer for the column. +$cell_renderer = new GtkCellRendererText(); +$column->pack_start($cell_renderer, true); +$column->add_attribute($cell_renderer, 'text', 0); +$column->add_attribute($cell_renderer, 'weight', 3); + +// Make the column resizeable by the user. +$column->set_resizable(true); +$column->set_sort_column_id(0); + +// Create column2s for each type of data. +$column2 = new GtkTreeViewColumn(); +$column2->set_title('Inventory'); +$view->insert_column($column2, 1); +// Create a renderer for the column2. +$cell_renderer = new GtkCellRendererProgress(); +$column2->pack_start($cell_renderer, true); +$column2->set_attributes($cell_renderer, 'value', 2); + +// Make the column2 resizeable by the user. +$column2->set_resizable(true); +$column2->set_reorderable(true); + +// Make the inventory column2 hide-able. +$column2->set_clickable(true); +$column2->connect('clicked', 'hideColumn'); +$column->set_sort_column_id(0); + +// Create columns for each type of data. +$column3 = new GtkTreeViewColumn(); +$column3->set_title('Price'); +$view->insert_column($column3, 2); +// Create a renderer for the column3. +$cell_renderer = new GtkCellRendererText(); +$column3->pack_start($cell_renderer, true); +$column3->set_attributes($cell_renderer, 'text', 2); + +// Make the column3 resizeable by the user. +$column3->set_resizable(true); +$column3->set_reorderable(true); +$column3->set_sort_column_id(2); + +//$view->expand_all(); +$view->set_cursor(array(0,0,1), $column); +$view->get_selection()->set_mode(Gtk::SELECTION_MULTIPLE); + +$selection = $view->get_selection(); +$selection->connect('changed', 'unbold'); + +// Create a window and show everything. +$window = new GtkWindow(); +$window->add($view); +$window->show_all(); +$window->set_size_request(200, 200); +$window->connect_simple('destroy', array('Gtk', 'main_quit')); +Gtk::main(); + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> diff --git a/Chapter09/listing9-12.php b/Chapter09/listing9-12.php new file mode 100644 index 0000000..6cdcda9 --- /dev/null +++ b/Chapter09/listing9-12.php @@ -0,0 +1,42 @@ +parse(); + +// Create a model to store the items. +$listStore = new GtkListStore(Gtk::TYPE_STRING, Gtk::TYPE_STRING, Gtk::TYPE_STRING, Gtk::TYPE_LONG ); + +// Add a row for each item in the feed. +foreach ($rss->getItems() as $item) { + $rowData = array($item['title'], $item['date'], $item['description'], Pango::WEIGHT_BOLD); + $listStore->append($rowData); +} + +// Create the view responsible for showing the feed. +$view = new GtkTreeView($listStore); +$view->set_headers_visible(false); + +// The view only has one column. +$column = new GtkTreeViewColumn(); +$view->insert_column($column, 0); + +// Create a renderer for the column. +$cell_renderer = new GtkCellRendererText(); +$column->pack_start($cell_renderer, true); +$column->add_attribute($cell_renderer, 'text', 0); +$column->add_attribute($cell_renderer, 'weight', 3); + +// Sort the column by date. +$column->set_sort_column_id(1); + +$window = new GtkWindow(); +$window->connect_simple('destroy', array('Gtk', 'main_quit')); + +// Add the view, show everything and start the loop. +$window->add($view); +$window->show_all(); +Gtk::main(); +?> \ No newline at end of file diff --git a/Chapter09/listing9-2.php b/Chapter09/listing9-2.php new file mode 100644 index 0000000..332f06a --- /dev/null +++ b/Chapter09/listing9-2.php @@ -0,0 +1,59 @@ +append(); +$listStore->set($iter, 0, 'Crisscott T-Shirts', 1, 10, 2, 19.95); +$iter = $listStore->prepend(); +$listStore->set($iter, 0, 'PHP-GTK Bumper Stickers', 1, 37, 2, 1.99); +$iter = $listStore->prepend(); +$listStore->set($iter, 0, 'Pro PHP-GTK', 1, 23, 2, 44.95); +$iter = $listStore->insert(2); +$listStore->set($iter, 0, 'Crisscott Pencils', 2, .99, 1, 18); + +// Create a veiw to show the list. +$view = new GtkTreeView(); +$view->set_model($listStore); + +// Create columns for each type of data. +$column = new GtkTreeViewColumn(); +$column->set_title('Product Name'); +$view->insert_column($column, 0); +// Create a renderer for the column. +$cell_renderer = new GtkCellRendererText(); +$column->pack_start($cell_renderer, true); +$column->set_attributes($cell_renderer, 'text', 0); + +// Create columns for each type of data. +$column = new GtkTreeViewColumn(); +$column->set_title('Inventory'); +$view->insert_column($column, 1); +// Create a renderer for the column. +$cell_renderer = new GtkCellRendererText(); +$column->pack_start($cell_renderer, true); +$column->set_attributes($cell_renderer, 'text', 1); + +// Create columns for each type of data. +$column = new GtkTreeViewColumn(); +$column->set_title('Price'); +$view->insert_column($column, 2); +// Create a renderer for the column. +$cell_renderer = new GtkCellRendererText(); +$column->pack_start($cell_renderer, true); +$column->set_attributes($cell_renderer, 'text', 2); + +// Create a window and show everything. +$window = new GtkWindow(); +$window->add($view); +$window->show_all(); +$window->connect_simple('destroy', array('Gtk', 'main_quit')); +Gtk::main(); + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/Chapter09/listing9-3.php b/Chapter09/listing9-3.php new file mode 100644 index 0000000..2acb3cf --- /dev/null +++ b/Chapter09/listing9-3.php @@ -0,0 +1,57 @@ +append(array('Crisscott T-Shirts', 10, 19.95)); +$listStore->prepend(array('PHP-GTK Bumper Stickers', 37, 1.99)); +$listStore->prepend(array('Pro PHP-GTK', 23, 44.95)); + +$pencils = array('Crisscott Pencils', 18, .99); +$listStore->insert(2, array('Crisscott Pencils', 18, .99));//$pencils); + +// Create a veiw to show the list. +$view = new GtkTreeView(); +$view->set_model($listStore); + +// Create columns for each type of data. +$column = new GtkTreeViewColumn(); +$column->set_title('Product Name'); +$view->insert_column($column, 0); +// Create a renderer for the column. +$cell_renderer = new GtkCellRendererText(); +$column->pack_start($cell_renderer, true); +$column->set_attributes($cell_renderer, 'text', 0); + +// Create columns for each type of data. +$column = new GtkTreeViewColumn(); +$column->set_title('Inventory'); +$view->insert_column($column, 1); +// Create a renderer for the column. +$cell_renderer = new GtkCellRendererText(); +$column->pack_start($cell_renderer, true); +$column->set_attributes($cell_renderer, 'text', 1); + +// Create columns for each type of data. +$column = new GtkTreeViewColumn(); +$column->set_title('Price'); +$view->insert_column($column, 2); +// Create a renderer for the column. +$cell_renderer = new GtkCellRendererText(); +$column->pack_start($cell_renderer, true); +$column->set_attributes($cell_renderer, 'text', 2); + +// Create a window and show everything. +$window = new GtkWindow(); +$window->add($view); +$window->show_all(); +$window->connect_object('destroy', array('Gtk', 'main_quit')); +Gtk::main(); + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> diff --git a/Chapter09/listing9-4.php b/Chapter09/listing9-4.php new file mode 100644 index 0000000..a96f82d --- /dev/null +++ b/Chapter09/listing9-4.php @@ -0,0 +1,30 @@ +get_value($iter, 1) < 15) { + echo 'Reordering ' . $model->get_value($iter, 0) . "\n"; + } + + return false; +} + +// Create a list store. +$listStore = new GtkListStore(Gtk::TYPE_STRING, Gtk::TYPE_LONG, Gtk::TYPE_DOUBLE); + +// Add some product data. +$listStore->append(array('Crisscott T-Shirts', 10, 19.95)); +$listStore->prepend(array('PHP-GTK Bumper Stickers', 37, 1.99)); +$listStore->prepend(array('Pro PHP-GTK', 23, 44.95)); + +$pencils = array('Crisscott Pencils', 18, .99); +$listStore->insert(2, $pencils); + +$listStore->foreach('checkInventory'); + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> diff --git a/Chapter09/listing9-5.php b/Chapter09/listing9-5.php new file mode 100644 index 0000000..b646a9d --- /dev/null +++ b/Chapter09/listing9-5.php @@ -0,0 +1,75 @@ +append(null, array('Crisscott', null, null)); +$phpGtkMerch = $treeStore->append(null, array('PHP-GTK', null, null)); + +// Add a child row to csMerch. +// Again catpure the return value so that children can be added. +$tShirts = $treeStore->append($csMerch, array('T-Shirts', 10, 19.95)); + +// Add three children to tShirts. +$treeStore->append($tShirts, array('Small', 3, 19.95)); +$treeStore->append($tShirts, array('Medium', 5, 19.95)); +$treeStore->append($tShirts, array('Large', 2, 19.95)); + +// Add another child to csMerch. +// Capture the return value so that children can be added. +$pencils = $treeStore->append($csMerch, array(' Pencils', 18, .99)); + +// Add two children to pencils +$treeStore->append($pencils, array('Blue', 9, .99)); +$treeStore->append($pencils, array('White', 9, .99)); + +// Add two children to phpGtkMerch. +$treeStore->prepend($phpGtkMerch, array('PHP-GTK Bumper Stickers', 37, 1.99)); +$treeStore->prepend($phpGtkMerch, array('Pro PHP-GTK', 23, 44.95)); + +// Create a veiw to show the tree. +$view = new GtkTreeView(); +$view->set_model($treeStore); + +// Create columns for each type of data. +$column = new GtkTreeViewColumn(); +$column->set_title('Product Name'); +$view->insert_column($column, 0); +// Create a renderer for the column. +$cell_renderer = new GtkCellRendererText(); +$column->pack_start($cell_renderer, true); +$column->set_attributes($cell_renderer, 'text', 0); + +// Create columns for each type of data. +$column = new GtkTreeViewColumn(); +$column->set_title('Inventory'); +$view->insert_column($column, 1); +// Create a renderer for the column. +$cell_renderer = new GtkCellRendererText(); +$column->pack_start($cell_renderer, true); +$column->set_attributes($cell_renderer, 'text', 1); + +// Create columns for each type of data. +$column = new GtkTreeViewColumn(); +$column->set_title('Price'); +$view->insert_column($column, 2); +// Create a renderer for the column. +$cell_renderer = new GtkCellRendererText(); +$column->pack_start($cell_renderer, true); +$column->set_attributes($cell_renderer, 'text', 2); + +// Create a window and show everything. +$window = new GtkWindow(); +$window->add($view); +$window->show_all(); +$window->connect_object('destroy', array('Gtk', 'main_quit')); +Gtk::main(); + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> diff --git a/Chapter09/listing9-6.php b/Chapter09/listing9-6.php new file mode 100644 index 0000000..890a18e --- /dev/null +++ b/Chapter09/listing9-6.php @@ -0,0 +1,97 @@ +iter_depth($iter); ++$i) { + $dashes.= '--'; + } + + echo $dashes . ' ' . $tree->get_value($iter, 0) . "\n"; + + if ($tree->iter_has_child($iter)) { + $newParent = $iter->copy(); + $tree->iter_nth_child($iter, $newParent, 0); + traverseTree($tree, $iter, $newParent, 0); + } elseif ($childNum < $tree->iter_n_children($parent) - 1) { + if ($tree->iter_nth_child($iter, $parent, $childNum + 1)) { + traverseTree($tree, $iter, $parent, $childNum + 1); + } + } elseif ($tree->iter_next($parent)) { + traverseTree($tree, $parent, $iter, $childNum + 1); + } else { + if ($tree->iter_parent($iter, $parent)) { + $tree->iter_next($iter); + $tree->iter_parent($parent, $iter); + if ($tree->iter_is_valid($iter)) { + traverseTree($tree, $iter, $parent, $childNum); + } + } + } +} + +// Create a tree store. +$treeStore = new GtkTreeStore(Gtk::TYPE_STRING, Gtk::TYPE_LONG, Gtk::TYPE_DOUBLE); + +// Add some product data. +$csMerch = $treeStore->append(null, array('Crisscott', null, null)); +$phpGtkMerch = $treeStore->append(null, array('PHP-GTK', null, null)); + +$tShirts = $treeStore->append($csMerch, array('T-Shirts', 10, 19.95)); +$treeStore->append($tShirts, array('Small', 3, 19.95)); +$treeStore->append($tShirts, array('Medium', 5, 19.95)); +$treeStore->append($tShirts, array('Large', 2, 19.95)); + +$pencils = $treeStore->append($csMerch, array(' Pencils', 18, .99)); +$treeStore->append($pencils, array('Blue', 9, .99)); +$treeStore->append($pencils, array('White', 9, .99)); + +$treeStore->append($phpGtkMerch, array('PHP-GTK Bumper Stickers', 37, 1.99)); +$treeStore->append($phpGtkMerch, array('Pro PHP-GTK', 23, 44.95)); + +traverseTree($treeStore, $treeStore->get_iter_first(), NULL, 0); + +// Create a veiw to show the tree. +$view = new GtkTreeView(); +$view->set_model($treeStore); + +// Create columns for each type of data. +$column = new GtkTreeViewColumn(); +$column->set_title('Product Name'); +$view->insert_column($column, 0); +// Create a renderer for the column. +$cell_renderer = new GtkCellRendererText(); +$column->pack_start($cell_renderer, true); +$column->set_attributes($cell_renderer, 'text', 0); + +// Create columns for each type of data. +$column = new GtkTreeViewColumn(); +$column->set_title('Inventory'); +$view->insert_column($column, 1); +// Create a renderer for the column. +$cell_renderer = new GtkCellRendererText(); +$column->pack_start($cell_renderer, true); +$column->set_attributes($cell_renderer, 'text', 1); + +// Create columns for each type of data. +$column = new GtkTreeViewColumn(); +$column->set_title('Price'); +$view->insert_column($column, 2); +// Create a renderer for the column. +$cell_renderer = new GtkCellRendererText(); +$column->pack_start($cell_renderer, true); +$column->set_attributes($cell_renderer, 'text', 2); + +// Create a window and show everything. +$window = new GtkWindow(); +$window->add($view); +$window->show_all(); +$window->connect_simple('destroy', array('Gtk', 'main_quit')); +Gtk::main(); + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> diff --git a/Chapter09/listing9-7.php b/Chapter09/listing9-7.php new file mode 100644 index 0000000..181bcd9 --- /dev/null +++ b/Chapter09/listing9-7.php @@ -0,0 +1,114 @@ +get_sort_column_id(); +} + +// Create a tree store. +$treeStore = new GtkTreeStore(Gtk::TYPE_STRING, Gtk::TYPE_LONG, Gtk::TYPE_DOUBLE); + +// Add some top level product data. +$csMerch = $treeStore->append(null, array('Crisscott', null, null)); +$phpGtkMerch = $treeStore->append(null, array('PHP-GTK', null, null)); + +$tShirts = $treeStore->append($csMerch, array('T-Shirts', 10, 19.95)); +$treeStore->append($tShirts, array('Small', 3, 19.95)); +$treeStore->append($tShirts, array('Medium', 5, 19.95)); +$treeStore->append($tShirts, array('Large', 2, 19.95)); + +$pencils = $treeStore->append($csMerch, array(' Pencils', 18, .99)); +$treeStore->append($pencils, array('Blue', 9, .99)); +$treeStore->append($pencils, array('White', 9, .99)); + +$treeStore->append($phpGtkMerch, array('PHP-GTK Bumper Stickers', 37, 1.99)); +$treeStore->append($phpGtkMerch, array('Pro PHP-GTK', 23, 44.95)); + +// Create one sortable tree. +$sortable = new GtkTreeModelSort($treeStore); +$sortable->set_sort_column_id(0, Gtk::SORT_DESCENDING); + +// Create the other sortable tree. +$sortable2 = new GtkTreeModelSort($treeStore); +$sortable2->set_sort_column_id(2, Gtk::SORT_ASCENDING); + +// Create a veiw to show the tree. +$view = new GtkTreeView(); +$view->set_model($sortable); + +// Create columns for each type of data. +$column = new GtkTreeViewColumn(); +$column->set_title('Product Name'); +$view->insert_column($column, 0); +// Create a renderer for the column. +$cell_renderer = new GtkCellRendererText(); +$column->pack_start($cell_renderer, true); +$column->set_attributes($cell_renderer, 'text', 0); + +// Create columns for each type of data. +$column = new GtkTreeViewColumn(); +$column->set_title('Inventory'); +$view->insert_column($column, 1); +// Create a renderer for the column. +$cell_renderer = new GtkCellRendererText(); +$column->pack_start($cell_renderer, true); +$column->set_attributes($cell_renderer, 'text', 1); + +// Create columns for each type of data. +$column = new GtkTreeViewColumn(); +$column->set_title('Price'); +$view->insert_column($column, 2); +// Create a renderer for the column. +$cell_renderer = new GtkCellRendererText(); +$column->pack_start($cell_renderer, true); +$column->set_attributes($cell_renderer, 'text', 2); + +// Create a veiw to show the tree. +$view2 = new GtkTreeView(); +$view2->set_model($sortable2); + +// Create columns for each type of data. +$column = new GtkTreeViewColumn(); +$column->set_title('Product Name'); +$view2->insert_column($column, 0); +// Create a renderer for the column. +$cell_renderer = new GtkCellRendererText(); +$column->pack_start($cell_renderer, true); +$column->set_attributes($cell_renderer, 'text', 0); + +// Create columns for each type of data. +$column = new GtkTreeViewColumn(); +$column->set_title('Inventory'); +$view2->insert_column($column, 1); +// Create a renderer for the column. +$cell_renderer = new GtkCellRendererText(); +$column->pack_start($cell_renderer, true); +$column->set_attributes($cell_renderer, 'text', 1); + +// Create columns for each type of data. +$column = new GtkTreeViewColumn(); +$column->set_title('Price'); +$view2->insert_column($column, 2); +// Create a renderer for the column. +$cell_renderer = new GtkCellRendererText(); +$column->pack_start($cell_renderer, true); +$column->set_attributes($cell_renderer, 'text', 2); + +// Create a window and show everything. +$window = new GtkWindow(); +$hBox = new GtkHBox(); + +$window->add($hBox); +$hBox->pack_start($view); +$hBox->pack_start($view2); + +$window->show_all(); +$window->connect_simple('destroy', array('Gtk', 'main_quit')); +Gtk::main(); + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> diff --git a/Chapter09/listing9-8.php b/Chapter09/listing9-8.php new file mode 100644 index 0000000..9c34db5 --- /dev/null +++ b/Chapter09/listing9-8.php @@ -0,0 +1,75 @@ +append(null, array('Crisscott', null, null, true)); +$phpGtkMerch = $treeStore->append(null, array('PHP-GTK', null, null, false)); + +$tShirts = $treeStore->append($csMerch, array('T-Shirts', 10, 19.95, false)); +$treeStore->append($tShirts, array('Small', 3, 19.95, true)); +$treeStore->append($tShirts, array('Medium', 5, 19.95, true)); +$treeStore->append($tShirts, array('Large', 2, 19.95, true)); + +$pencils = $treeStore->append($csMerch, array(' Pencils', 18, .99, true)); +$treeStore->append($pencils, array('Blue', 9, .99, true)); +$treeStore->append($pencils, array('White', 9, .99, true)); + +$treeStore->append($phpGtkMerch, array('PHP-GTK Bumper Stickers', 37, 1.99, true)); +$treeStore->append($phpGtkMerch, array('Pro PHP-GTK', 23, 44.95, true)); + +// Get a filtered model. +$filtered = $treeStore->filter_new(); + +// Only show rows that have column three set to true. +$filtered->set_visible_column(3); + +// Create a veiw to show the tree. +$view = new GtkTreeView(); +$view->set_model($filtered); + +// Create columns for each type of data. +$column = new GtkTreeViewColumn(); +$column->set_title('Product Name'); +$view->insert_column($column, 0); + +// Create a renderer for the column. +$cell_renderer = new GtkCellRendererText(); +$column->pack_start($cell_renderer, true); +$column->set_attributes($cell_renderer, 'text', 0); + +// Create columns for each type of data. +$column = new GtkTreeViewColumn(); +$column->set_title('Inventory'); +$view->insert_column($column, 1); + +// Create a renderer for the column. +$cell_renderer = new GtkCellRendererText(); +$column->pack_start($cell_renderer, true); +$column->set_attributes($cell_renderer, 'text', 1); + +// Create columns for each type of data. +$column = new GtkTreeViewColumn(); +$column->set_title('Price'); +$view->insert_column($column, 2); + +// Create a renderer for the column. +$cell_renderer = new GtkCellRendererText(); +$column->pack_start($cell_renderer, true); +$column->set_attributes($cell_renderer, 'text', 2); + +// Create a window and show everything. +$window = new GtkWindow(); +$window->add($view); +$window->show_all(); +$window->connect_simple('destroy', array('Gtk', 'main_quit')); +Gtk::main(); + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> diff --git a/Chapter09/listing9-9.php b/Chapter09/listing9-9.php new file mode 100644 index 0000000..4765610 --- /dev/null +++ b/Chapter09/listing9-9.php @@ -0,0 +1,89 @@ +set_visible(!$column->get_visible()); +} + +// Create a tree store. +$treeStore = new GtkTreeStore(Gtk::TYPE_STRING, Gtk::TYPE_LONG, Gtk::TYPE_DOUBLE); + +// Add some product data. +$csMerch = $treeStore->append(null, array('Crisscott', null, null)); +$phpGtkMerch = $treeStore->append(null, array('PHP-GTK', null, null)); + +$tShirts = $treeStore->append($csMerch, array('T-Shirts', 10, 19.95)); +$treeStore->append($tShirts, array('Small', 3, 19.95)); +$treeStore->append($tShirts, array('Medium', 5, 19.95)); +$treeStore->append($tShirts, array('Large', 2, 19.95)); + +$pencils = $treeStore->append($csMerch, array('Pencils', 18, .99)); +$treeStore->append($pencils, array('Blue', 9, .99)); +$treeStore->append($pencils, array('White', 9, .99)); + +$treeStore->append($phpGtkMerch, array('PHP-GTK Bumper Stickers', 37, 1.99)); +$treeStore->append($phpGtkMerch, array('Pro PHP-GTK', 23, 44.95)); + +// Create a veiw to show the tree. +$view = new GtkTreeView(); +$view->set_model($treeStore); + +// Create columns for each type of data. +$column = new GtkTreeViewColumn(); +$column->set_title('Product Name'); +$view->insert_column($column, 0); +// Create a renderer for the column. +$cell_renderer = new GtkCellRendererText(); +$column->pack_start($cell_renderer, true); +$column->set_attributes($cell_renderer, 'text', 0); + +// Make the column resizeable by the user. +$column->set_resizable(true); +$column->set_sort_column_id(0); + +// Create column2s for each type of data. +$column2 = new GtkTreeViewColumn(); +$column2->set_title('Inventory'); +$view->insert_column($column2, 1); +// Create a renderer for the column2. +$cell_renderer = new GtkCellRendererText(); +$column2->pack_start($cell_renderer, true); +$column2->set_attributes($cell_renderer, 'text', 1); + +// Make the column2 resizeable by the user. +$column2->set_resizable(true); +$column2->set_reorderable(true); + +// Make the inventory column2 hide-able. +$column2->set_clickable(true); +$column2->connect('clicked', 'hideColumn'); +$column->set_sort_column_id(0); + +// Create columns for each type of data. +$column3 = new GtkTreeViewColumn(); +$column3->set_title('Price'); +$view->insert_column($column3, 2); +// Create a renderer for the column3. +$cell_renderer = new GtkCellRendererText(); +$column3->pack_start($cell_renderer, true); +$column3->set_attributes($cell_renderer, 'text', 2); + +// Make the column3 resizeable by the user. +$column3->set_resizable(true); +$column3->set_reorderable(true); +$column3->set_sort_column_id(2); + +// Create a window and show everything. +$window = new GtkWindow(); +$window->add($view); +$window->show_all(); +$window->connect_simple('destroy', array('Gtk', 'main_quit')); +Gtk::main(); + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> diff --git a/Chapter10/listing10-2.php b/Chapter10/listing10-2.php new file mode 100644 index 0000000..2efb955 --- /dev/null +++ b/Chapter10/listing10-2.php @@ -0,0 +1,43 @@ +connect_simple('destroy', array('Gtk', 'main_quit')); + +// Add a table to the window. +$table = new GtkTable(2, 2); +$window->add($table); + +// Create four scrolled windows. +$sw1 = new GtkScrolledWindow(); +$sw2 = new GtkScrolledWindow(); +$sw3 = new GtkScrolledWindow(); +$sw4 = new GtkScrolledWindow(); + +// Set each window to a different position. +$sw1->set_placement(Gtk::CORNER_TOP_LEFT); +$sw2->set_placement(Gtk::CORNER_TOP_RIGHT); +$sw3->set_placement(Gtk::CORNER_BOTTOM_LEFT); +$sw4->set_placement(Gtk::CORNER_BOTTOM_RIGHT); + +// Create four frames. +$frame1 = new GtkFrame('TOP_LEFT'); +$frame2 = new GtkFrame('TOP_RIGHT'); +$frame3 = new GtkFrame('BOTTOM_LEFT'); +$frame4 = new GtkFrame('BOTTOM_RIGHT'); + +// Add the scrolled windows to the frames. +$frame1->add($sw1); +$frame2->add($sw2); +$frame3->add($sw3); +$frame4->add($sw4); + +// Attach the frames to the table. +$table->attach($frame1, 0, 1, 0, 1); +$table->attach($frame2, 1, 2, 0, 1); +$table->attach($frame3, 0, 1, 1, 2); +$table->attach($frame4, 1, 2, 1, 2); + +// Show everything. +$window->show_all(); +Gtk::main(); +?> \ No newline at end of file diff --git a/Chapter10/listing10-3.php b/Chapter10/listing10-3.php new file mode 100644 index 0000000..b496b26 --- /dev/null +++ b/Chapter10/listing10-3.php @@ -0,0 +1,34 @@ +connect_simple('destroy', array('Gtk', 'main_quit')); + +// Add a table to the window. +$table = new GtkTable(1, 1); + +// Add some stuff to the table that will make it large. +$label = new GtkLabel('This is a rather long label. Hopefully ' . + 'the table will scroll now.'); + +// Attach the label. +$table->attach($label, 0, 1, 0, 1); + +// Create the view port. +$viewPort = new GtkViewPort(); + +// Create the scrolled window. +$sWindow = new GtkScrolledWindow(); + +// Add the table to the view port. +$viewPort->add($table); + +// Add the view port to the scrolled window. +$sWindow->add($viewPort); + +// Add the scrolled window to the main window. +$window->add($sWindow); + +// Show everything. +$window->show_all(); +Gtk::main(); +?> \ No newline at end of file diff --git a/Chapter11/listing11-1.php b/Chapter11/listing11-1.php new file mode 100644 index 0000000..b403cb6 --- /dev/null +++ b/Chapter11/listing11-1.php @@ -0,0 +1,19 @@ +append($help); + +// Create a file menu item. +$file = new GtkMenuItem('File'); +// Prepend it to the menu bar. +$menuBar->prepend($file); + +// Create an edit menu item. +$edit = new GtkMenuItem('Edit'); +// Insert it into the menu bar. +$menuBar->insert($edit, 1); +?> \ No newline at end of file diff --git a/Chapter11/listing11-10.php b/Chapter11/listing11-10.php new file mode 100644 index 0000000..673d31a --- /dev/null +++ b/Chapter11/listing11-10.php @@ -0,0 +1,35 @@ +type == Gdk::BUTTON_PRESS) { + // See if button three was pressed. + if ($event->button == 3) { + // Pop up the menu. + $menu->popup(null, null, null, $event->button, $event->time); + return true; + } + } + return false; +} + +$window = new GtkWindow(); +$window->connect_simple('destroy', array('Gtk', 'main_quit')); + +$contextMenu = new GtkMenu(); +$contextMenu->append(new GtkMenuItem('Copy')); +$contextMenu->append(new GtkMenuItem('Cut')); +$contextMenu->append(new GtkMenuItem('Paste')); + +$contextMenu->show_all(); + +$contextArea = new GtkTextView(); +$contextArea->connect('button-press-event', 'popupContext', $contextMenu); + +$box = new GtkVBox(); +$box->pack_start($contextArea, false, false); +$window->add($box); +$window->set_title('listing 11-9.php'); +$window->show_all(); +Gtk::main(); +?> \ No newline at end of file diff --git a/Chapter11/listing11-11.php b/Chapter11/listing11-11.php new file mode 100644 index 0000000..370df60 --- /dev/null +++ b/Chapter11/listing11-11.php @@ -0,0 +1,59 @@ +connect_simple('destroy', array('Gtk', 'main_quit')); + +// Create a vertical toolbar. +$vToolBar = new GtkToolbar(); +$vToolBar->set_orientation(Gtk::ORIENTATION_VERTICAL); + +// Turn off the overflow. +$vToolBar->set_show_arrow(false); + +// Add a new item. +$new = GtkToolButton::new_from_stock(Gtk::STOCK_NEW); +$vToolBar->add($new); + +// Add an open item. +$open = GtkToolButton::new_from_stock(Gtk::STOCK_OPEN); +$vToolBar->add($open); + +// Add a save item. +$save = GtkToolButton::new_from_stock(Gtk::STOCK_SAVE); +$vToolBar->add($save); + +// Add a copy item. +$copy = GtkToolButton::new_from_stock(Gtk::STOCK_COPY); +$vToolBar->add($copy); + +// Create a horizontal toolbar. +$hToolBar = new GtkToolbar(); + +// Add a new item. +$new = GtkToolButton::new_from_stock(Gtk::STOCK_NEW); +$hToolBar->add($new); + +// An an open item. +$open = GtkToolButton::new_from_stock(Gtk::STOCK_OPEN); +$hToolBar->add($open); + +// Add a save item. +$save = GtkToolButton::new_from_stock(Gtk::STOCK_SAVE); +$hToolBar->add($save); + +// Add a copy item. +$copy = GtkToolButton::new_from_stock(Gtk::STOCK_COPY); +$hToolBar->add($copy); + +// Pack both toolbars into a few boxes. +$vBox = new GtkVBox(); +$hBox = new GtkHBox(); +$vBox->pack_start($hToolBar, false, false); +$hBox->pack_start($vToolBar, false, false); +$vBox->pack_start($hBox, false, false); + +// Show everything. +$window->add($vBox); +$window->set_title('listing 11-10.php'); +$window->show_all(); +Gtk::main(); +?> \ No newline at end of file diff --git a/Chapter11/listing11-12.php b/Chapter11/listing11-12.php new file mode 100644 index 0000000..4804498 --- /dev/null +++ b/Chapter11/listing11-12.php @@ -0,0 +1,47 @@ +connect_simple('destroy', array('Gtk', 'main_quit')); + +// Create a horizontal toolbar. +$hToolBar = new GtkToolbar(); + +// Add a new item. +$new = GtkToolButton::new_from_stock(Gtk::STOCK_NEW); +$hToolBar->add($new); + +// An an open item. +$open = GtkToolButton::new_from_stock(Gtk::STOCK_OPEN); +$hToolBar->add($open); + +// Add a save item. +$save = GtkToolButton::new_from_stock(Gtk::STOCK_SAVE); +$hToolBar->add($save); + +// Add a copy item. +$copy = GtkToolButton::new_from_stock(Gtk::STOCK_COPY); +$hToolBar->add($copy); + +// Add tool tips. +$tooltips = new GtkTooltips(); + +// Create a tool tip for each item. +$new->set_tooltip($tooltips, 'New', 'Creates a new product.'); +$open->set_tooltip($tooltips, 'Open', 'Open an existing inventory file.'); +$save->set_tooltip($tooltips, 'Save', 'Saves the current inventory.'); +$copy->set_tooltip($tooltips, 'Copy', 'Copies a product.'); + +// Make sure the toolbar is set to display tooltips. +$hToolBar->set_tooltips(true); + +$hToolBar->set_style(Gtk::TOOLBAR_ICONS); + +// Pack the toolbar into a box. +$vBox = new GtkVBox(); +$vBox->pack_start($hToolBar, false, false); + +// Show everything. +$window->add($vBox); +$window->set_title('listing11-11.php'); +$window->show_all(); +Gtk::main(); +?> \ No newline at end of file diff --git a/Chapter11/listing11-13.php b/Chapter11/listing11-13.php new file mode 100644 index 0000000..68b6133 --- /dev/null +++ b/Chapter11/listing11-13.php @@ -0,0 +1,18 @@ +connect_simple('destroy', array('Gtk', 'main_quit')); + +// Create a new toolbar. +$toolbar = new GtkToolbar(); + +// Add a stock quit item. +$quit = GtkToolButton::new_from_stock(Gtk::STOCK_QUIT); +$toolbar->add($quit); + +// Create a signal handler. +$quit->connect_simple('clicked', array('gtk', 'main_quit')); + +$window->add($toolbar); +$window->show_all(); +Gtk::main(); +?> \ No newline at end of file diff --git a/Chapter11/listing11-14.php b/Chapter11/listing11-14.php new file mode 100644 index 0000000..f698d08 --- /dev/null +++ b/Chapter11/listing11-14.php @@ -0,0 +1,27 @@ +connect_simple('destroy', array('Gtk', 'main_quit')); + +// Create a new toolbar. +$toolbar = new GtkToolbar(); + +// Create an empty button. +$crisscott = new GtkToggleToolButton(); + +// Add an icon. +$crisscott->set_icon_widget(GtkImage::new_from_file('Crisscott/images/menuItemGrey.png')); + +// Create a special label. +$crisscottLabel = new GtkLabel('Send data too Crisscott'); +$crisscottLabel->set_ellipsize(Pango::ELLIPSIZE_START); + +// Set the label widget. +$crisscott->set_label_widget($crisscottLabel); + +// Add the tool button. +$toolbar->add($crisscott); + +$window->add($toolbar); +$window->show_all(); +Gtk::main(); +?> \ No newline at end of file diff --git a/Chapter11/listing11-2.php b/Chapter11/listing11-2.php new file mode 100644 index 0000000..8e603df --- /dev/null +++ b/Chapter11/listing11-2.php @@ -0,0 +1,43 @@ +set_pack_direction(Gtk::PACK_DIRECTION_RTL); + +// Create a help menu item. +$help = new GtkMenuItem('Help'); +// Append it to the menu bar. +$hMenuBar->append($help); + +// Create a file menu item. +$file = new GtkMenuItem('File'); +// Prepend it to the menu bar. +$hMenuBar->prepend($file); + +// Create an edit menu item. +$edit = new GtkMenuItem('Edit'); +// Insert it into the menu bar. +$hMenuBar->insert($edit, 1); + +// Create a menu bar. +$vMenuBar = new GtkMenuBar(); + +// Set the packing direction. +$vMenuBar->set_pack_direction(Gtk::PACK_DIRECTION_BTT); + +// Create a help menu item. +$help = new GtkMenuItem('Help'); +// Append it to the menu bar. +$vMenuBar->append($help); + +// Create a file menu item. +$file = new GtkMenuItem('File'); +// Prepend it to the menu bar. +$vMenuBar->prepend($file); + +// Create an edit menu item. +$edit = new GtkMenuItem('Edit'); +// Insert it into the menu bar. +$vMenuBar->insert($edit, 1); +?> \ No newline at end of file diff --git a/Chapter11/listing11-3.php b/Chapter11/listing11-3.php new file mode 100644 index 0000000..ee9ee31 --- /dev/null +++ b/Chapter11/listing11-3.php @@ -0,0 +1,43 @@ +append($help); + +// Create a file menu item. +$file = new GtkMenuItem('File'); +// Prepend it to the menu bar. +$menuBar->prepend($file); + +// Create a menu. +$fileMenu = new GtkMenu(); +// Create four menu items to be added to the file menu. +$new = new GtkMenuItem('New'); +$open = new GtkMenuItem('Open'); +$save = new GtkMenuItem('Save'); +$edit = new GtkMenuItem('Edit'); + +// Attach the four items to the menu. +$fileMenu->attach($new, 0, 1, 0, 1); +$fileMenu->attach($open, 1, 2, 0, 1); +$fileMenu->attach($save, 0, 1, 1, 2); +$fileMenu->attach($edit, 1, 2, 1, 2); + +// Add the file menu to the file item. +$file->set_submenu($fileMenu); + +// Create an edit menu item. +$edit = new GtkMenuItem('Edit'); +// Insert it into the menu bar. +$menuBar->insert($edit, 1); + +// Create a window and add the menu. +$window = new GtkWindow(); +$window->connect_simple('destroy', array('Gtk', 'main_quit')); +$window->add($menuBar); +$window->show_all(); +Gtk::main(); +?> \ No newline at end of file diff --git a/Chapter11/listing11-4.php b/Chapter11/listing11-4.php new file mode 100644 index 0000000..daf0d78 --- /dev/null +++ b/Chapter11/listing11-4.php @@ -0,0 +1,27 @@ +add(new GtkLabel('File')); +// Prepend it to the menu bar. +$menuBar->prepend($file); + +// Create an edit menu item. +$edit = new GtkMenuItem('Edit'); +// Insert it into the menu bar. +$menuBar->append($edit); + +// Create a help menu item. +$help = new GtkMenuItem('_Help'); +// Append it to the menu bar. +$menuBar->append($help); + +// Create a window and add the menu. +$window = new GtkWindow(); +$window->connect_simple('destroy', array('Gtk', 'main_quit')); +$window->add($menuBar); +$window->show_all(); +Gtk::main(); +?> \ No newline at end of file diff --git a/Chapter12/listing12-1.php b/Chapter12/listing12-1.php new file mode 100644 index 0000000..6612d46 --- /dev/null +++ b/Chapter12/listing12-1.php @@ -0,0 +1,19 @@ +connect_simple('destroy', array('Gtk', 'main_quit')); + +// Load a pixbuf from a file. +$pb = GdkPixbuf::new_from_file('Crisscott/images/logo.png'); + +// Create the image from the pixbuf. +$image = GtkImage::new_from_pixbuf($pb); + +// Add the image to the window. +$window->add($image); +// Show the image and the window. +$window->show_all(); +// Start the main loop. +Gtk::main(); +?> \ No newline at end of file diff --git a/Chapter12/listing12-4.php b/Chapter12/listing12-4.php new file mode 100644 index 0000000..935674e --- /dev/null +++ b/Chapter12/listing12-4.php @@ -0,0 +1,22 @@ +render_pixmap_and_mask(); + +$image = GtkImage::new_from_file('Crisscott/images/logo.png'); + +$text = new GtkTextView(); +$text->shape_combine_mask($mask, 0, 0); +$text->get_buffer()->set_text('This is a test. There is a whole in the middle of this text view widget.'); + +$text->set_wrap_mode(Gtk::WRAP_WORD); + +$window = new GtkWindow(); +$window->shape_combine_mask($mask, 0, 0); +$window->connect_simple('destroy', array('Gtk', 'main_quit')); +$window->add($text); +$window->show_all(); +Gtk::main(); +?> \ No newline at end of file diff --git a/Chapter14/listing14-1.php b/Chapter14/listing14-1.php new file mode 100644 index 0000000..9af0e00 --- /dev/null +++ b/Chapter14/listing14-1.php @@ -0,0 +1,29 @@ +vbox->pack_start(new GtkLabel('Exit without saving?')); + +?> \ No newline at end of file diff --git a/Chapter14/listing14-5.php b/Chapter14/listing14-5.php new file mode 100644 index 0000000..e1bf64b --- /dev/null +++ b/Chapter14/listing14-5.php @@ -0,0 +1,63 @@ +get_color($color); + + // Create a new tag to modify the text. + $tag = new GtkTextTag(); + // Set the tag color. + $tag->set_property('foreground-gdk', $color); + + // Get the buffer. + $buffer = $text->get_buffer(); + + // Get iters for the start and end of the selection. + $selectionStart = $buffer->get_start_iter(); + $selectionEnd = $buffer->get_start_iter(); + + // Get the iters at the start and end of the selection. + $buffer->get_iter_at_mark($selectionStart, $buffer->get_insert()); + $buffer->get_iter_at_mark($selectionEnd, $buffer->get_selection_bound()); + + // Add the tag to the buffer's tag table. + $buffer->get_tag_table()->add($tag); + + // Apply the tag. + $buffer->apply_tag($tag, $selectionStart, $selectionEnd); +} + +// Create a window and set it to close cleanly. +$window = new GtkWindow(); +$window->connect_simple('destroy', array('Gtk', 'main_quit')); + +// Create a vBox to hold the window's contents. +$vBox = new GtkVBox(); + +// Create an hBox to hold the buttons. +$hBox = new GtkHBox(); + +// Create the color button and pack it into the hBox. +$color = new GtkColorButton(); +$hBox->pack_start($color, false, false); + +// Pack the hBox into the vBox. +$vBox->pack_start($hBox, false, false, 3); + +// Create the text view. +$text = new GtkTextView(); +$text->set_size_request(300, 300); + +// Create a signal handler for the color button. +$color->connect('color-set', 'applyTag', $text); + +// Add the text view to the vBox. +$vBox->pack_start($text); + +// Add the vBox to the window and show everything. +$window->add($vBox); +$window->show_all(); +Gtk::main(); +?> \ No newline at end of file diff --git a/Chapter14/listing14-6.php b/Chapter14/listing14-6.php new file mode 100644 index 0000000..e1bf64b --- /dev/null +++ b/Chapter14/listing14-6.php @@ -0,0 +1,63 @@ +get_color($color); + + // Create a new tag to modify the text. + $tag = new GtkTextTag(); + // Set the tag color. + $tag->set_property('foreground-gdk', $color); + + // Get the buffer. + $buffer = $text->get_buffer(); + + // Get iters for the start and end of the selection. + $selectionStart = $buffer->get_start_iter(); + $selectionEnd = $buffer->get_start_iter(); + + // Get the iters at the start and end of the selection. + $buffer->get_iter_at_mark($selectionStart, $buffer->get_insert()); + $buffer->get_iter_at_mark($selectionEnd, $buffer->get_selection_bound()); + + // Add the tag to the buffer's tag table. + $buffer->get_tag_table()->add($tag); + + // Apply the tag. + $buffer->apply_tag($tag, $selectionStart, $selectionEnd); +} + +// Create a window and set it to close cleanly. +$window = new GtkWindow(); +$window->connect_simple('destroy', array('Gtk', 'main_quit')); + +// Create a vBox to hold the window's contents. +$vBox = new GtkVBox(); + +// Create an hBox to hold the buttons. +$hBox = new GtkHBox(); + +// Create the color button and pack it into the hBox. +$color = new GtkColorButton(); +$hBox->pack_start($color, false, false); + +// Pack the hBox into the vBox. +$vBox->pack_start($hBox, false, false, 3); + +// Create the text view. +$text = new GtkTextView(); +$text->set_size_request(300, 300); + +// Create a signal handler for the color button. +$color->connect('color-set', 'applyTag', $text); + +// Add the text view to the vBox. +$vBox->pack_start($text); + +// Add the vBox to the window and show everything. +$window->add($vBox); +$window->show_all(); +Gtk::main(); +?> \ No newline at end of file diff --git a/Chapter14/listing14-7.php b/Chapter14/listing14-7.php new file mode 100644 index 0000000..10bd9e9 --- /dev/null +++ b/Chapter14/listing14-7.php @@ -0,0 +1,20 @@ +get_font_name()); +} + +$window = new GtkWindow(); +$window->connect_simple('destroy', array('Gtk', 'main_quit')); + +$fButton = new GtkFontButton(); +$fButton->set_use_font(true); +$fButton->set_use_size(true); + +$window->add($fButton); + +$fButton->connect('font-set', 'test'); + +$window->show_all(); +Gtk::main(); +?> \ No newline at end of file diff --git a/Chapter14/listing14-8.php b/Chapter14/listing14-8.php new file mode 100644 index 0000000..2232f6c --- /dev/null +++ b/Chapter14/listing14-8.php @@ -0,0 +1,58 @@ +set_property('font', $fontButton->get_font()); + + // Get the buffer. + $buffer = $text->get_buffer(); + + // Get iters for the start and end of the selection. + $selectionStart = $buffer->get_start_iter(); + $selectionEnd = $buffer->get_start_iter(); + + // Get the iters at the start and end of the selection. + $buffer->get_iter_at_mark($selectionStart, $buffer->get_insert()); + $buffer->get_iter_at_mark($selectionEnd, $buffer->get_selection_bound()); + + // Add the tag to the buffer's tag table. + $buffer->get_tag_table()->add($tag); + + // Apply the tag. + $buffer->apply_tag($tag, $selectionStart, $selectionEnd); +} + +// Create a window and set it to close cleanly. +$window = new GtkWindow(); +$window->connect_simple('destroy', array('Gtk', 'main_quit')); + +// Create a vBox to hold the window's contents. +$vBox = new GtkVBox(); + +// Create an hBox to hold the buttons. +$hBox = new GtkHBox(); + +// Create the color button and pack it into the hBox. +$color = new GtkColorButton(); +$hBox->pack_start($color, false, false); + +// Pack the hBox into the vBox. +$vBox->pack_start($hBox, false, false, 3); + +// Create the text view. +$text = new GtkTextView(); +$text->set_size_request(300, 300); + +// Create a signal handler for the color button. +$color->connect('color-set', 'applyTag', $text); + +// Add the text view to the vBox. +$vBox->pack_start($text); + +// Add the vBox to the window and show everything. +$window->add($vBox); +$window->show_all(); +Gtk::main(); +?> \ No newline at end of file diff --git a/Chapter16/listing16-5.php b/Chapter16/listing16-5.php new file mode 100644 index 0000000..57703d6 --- /dev/null +++ b/Chapter16/listing16-5.php @@ -0,0 +1,42 @@ +connect_simple('destroy', array('Gtk', 'main_quit')); + +// Create a new style object. +$style = new GtkStyle(); + +// Create a pixbuf. +$file = 'Crisscott/images/insensitiveCheckered.png'; +$pixbuf = GdkPixbuf::new_from_file($file); + +// Get a pixmap from the pixbuf. +list($pixmap) = $pixbuf->render_pixmap_and_mask(); + +// Assign the pixmap to the normal bg_pixmap. +$style->bg_pixmap[Gtk::STATE_INSENSITIVE] = $pixmap; + +// Create two buttons. +$button1 = new GtkButton('Active'); +$button2 = new GtkButton('Inactive'); + +// Set the style for both buttons. +$button1->set_style($style); +$button2->set_style($style); + +// Make button two inactive. +$button2->set_sensitive(false); + +// Add a button box to the window. +$buttonBox = new GtkHButtonBox(); +$window->add($buttonBox); + +// Add the buttons to the box. +$buttonBox->pack_start($button1); +$buttonBox->pack_start($button2); + +// Show the window and start the main loop. +$window->show_all(); +Gtk::main(); +?> \ No newline at end of file diff --git a/Chapter17/uninstall.php b/Chapter17/uninstall.php new file mode 100644 index 0000000..1f91f28 --- /dev/null +++ b/Chapter17/uninstall.php @@ -0,0 +1,76 @@ +connect_simple('response', array($this, 'destroy')); + // The static properties must also be unset. + $this->connect_simple('destroy', array($this, 'destroy')); + + // Add an image and a question to the top part of the dialog. + $hBox = new GtkHBox(); + $dialog->vbox->pack_start($hBox); + + // Pack a stock warning image. + $warning = GtkImage::new_from_stock(Gtk::STOCK_DIALOG_WARNING, + Gtk::ICON_SIZE_DIALOG); + $hBox->pack_start($warning, false, false, 5); + + // Add a message + $message = new GtkLabel('Are you sure you want to remove the ' . + 'Crisscott PIMS application?'); + $message->set_line_wrap(); + $hBox->pack_start($message); + } + + public function run() + { + // Show the dialog. + $this->show_all(); + + // Run the dialog and wait for the response. + if (parent::run() === Gtk::RESPONSE_YES) { + // Uninstall the application. + $this->_doUninstall(); + } + } + + private function _doUninstall() + { + // Create a config object. + require_once 'PEAR/Config.php'; + $config = new PEAR_Config(); + + // Create a command object. + require_once 'PEAR/Command.php'; + $uninstall = PEAR_Command::factory('uninstall', $config); + + // Uninstall the application. + $result = $uninstall->doInstall('uninstall', array(), + array('crisscott/Crisscott_PIMS')); + + // Report any errors. + if (PEAR::isError($result)) { + echo $result->getMessage() . "\n"; + } + } +} + +// Create an uninstall instance +$unInst = new UnInstall(); + +// Run the dialog. +$unInst->run(); +?> \ No newline at end of file diff --git a/Crisscott/AboutDialog.php b/Crisscott/AboutDialog.php new file mode 100644 index 0000000..c7e5a95 --- /dev/null +++ b/Crisscott/AboutDialog.php @@ -0,0 +1,32 @@ +init(); + } + + public function init() + { + // Set the logo image. + $this->set_logo(GdkPixbuf::new_from_file('Crisscott/images/logo.png')); + // Set the application name. + $this->set_name('Crisscott PIMS'); + // Set the copyright notice. + $this->set_copyright('2005 Crisscott, Inc.'); + // Set the license. + $this->set_license(file_get_contents('Crisscott/license.txt')); + // Set the URL to the Crisscott website. + $this->set_website('http://www.crisscott.com/'); + // Set the version number. + $this->set_version('1.0.0'); + // Set the description of the application. + $this->set_comments('An application to manage product information ' . + 'for distribution through Crisscott.com'); + } +} +?> \ No newline at end of file diff --git a/Crisscott/Category.php b/Crisscott/Category.php new file mode 100644 index 0000000..35873be --- /dev/null +++ b/Crisscott/Category.php @@ -0,0 +1,118 @@ +categoryId = $categoryId; + } else { + throw new Exception('Cannot instantiate category. Invalid categoryId: ' . $categoryId); + } + + // Get the category name. + require_once 'Crisscott/DB.php'; + $db = Crisscott_DB::singleton(); + + $query = 'SELECT category_name '; + $query.= 'FROM categories '; + $query.= 'WHERE category_id = ' . $this->categoryId; + + $row = $db->query($query)->current(); + $this->name = $row['category_name']; + + // Get all of the products for this category. + $this->_getProducts(); + + // Set the specs for the category. + $this->_setSpecs(); + } + + private function _getProducts() + { + require_once 'Crisscott/DB.php'; + $db = Crisscott_DB::singleton(); + + $query = 'SELECT product_id, product_name, description, '; + $query.= ' product_type, category_id, inventory, available, '; + $query.= ' width, height, depth, weight, '; + $query.= ' price '; + $query.= 'FROM products '; + $query.= ' LEFT JOIN product_price '; + $query.= ' USING (product_id) '; + $query.= 'WHERE category_id = ' . $this->categoryId; + $query.= ' AND (currency = \'USD\' OR currency IS NULL) '; + + $this->products = $db->query($query); + } + + private function _setSpecs() + { + $this->specs = array(); + + if (!$this->products->numRows()) { + return; + } + + $this->specs['Total Products'] = $this->products->numRows(); + + $totalPrice = 0; + foreach ($this->products as $product) { + $totalPrice += $product['price']; + } + $this->specs['Avg. Price (USD)'] = number_format($totalPrice / $this->products->numRows(), 2); + + $totalWeight = 0; + foreach ($this->products as $product) { + $totalWeight += $product['weight']; + } + $this->specs['Avg. Weight (Ounces)'] = number_format($totalWeight / $this->products->numRows(), 2); + } + /** + * Returns a list of category specs. + * + * @static + * @access public + * @return object An iterator of specs. + */ + public static function getCategorySpecs() + { + return array('Total Products', 'Avg. Price (USD)', 'Avg. Weight (Ounces)'); + } + + public function getSpecValueByName($spec) + { + return $this->specs[$spec]; + } + + public function getProducts() + { + return $this->products; + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/Crisscott/Contributor.php b/Crisscott/Contributor.php new file mode 100644 index 0000000..ec9404d --- /dev/null +++ b/Crisscott/Contributor.php @@ -0,0 +1,330 @@ +init($contributorId); + } + } + + /** + * Grabs the values from the database and assigns them to + * the proper member variables. + * + * The contributor data is stored in the database. A singleton + * database instance is used to connect to the database and get + * the contributor values. + * + * @access protected + * @param integer $contributorId + * @return void + */ + protected function init($contributorId) + { + // Check the contributorId. + if (!is_numeric($contributorId)) { + throw new Exception('Invalid contributor id: ' . $contributorId); + } + + // Get a singleton DB instance. + require_once 'Crisscott/DB.php'; + $db = Crisscott_DB::singleton(); + + // Create the query. + $query = 'SELECT first_name, middle_name, last_name, '; + $query.= ' website, email, street1, street2, city, '; + $query.= ' state, country, postal '; + $query.= 'FROM contributor '; + $query.= 'WHERE contributor_id = ' . $contributorId . ' '; + + // Submit the query. + $result = $db->query($query); + + // If the query failed, we wouldn't be here. + $this->contributorId = $contributorId; + $this->firstName = $result['first_name']; + $this->middleName = $result['middle_name']; + $this->lastName = $result['last_name']; + $this->website = $result['website']; + $this->email = $result['email']; + $this->street1 = $result['street1']; + $this->street2 = $result['street2']; + $this->city = $result['city']; + $this->state = $result['state']; + $this->country = $result['country']; + $this->postal = $result['postal']; + } + + /** + * Checks the data to see that it is valid data. + * + * @access public + * @return mixed true if all data is valid or an array of invalid elements. + */ + public function validate() + { + $retArray = array(); + + if ($this->firstName != 'tester') { + $retArray[] = 'firstName'; + } + if ($this->lastName != 'test') { + $retArray[] = 'lastName'; + } + + return $retArray; + } + + /** + * Writes the contributor data to the database. + * + * @access public + * @return void + */ + public function save() + { + return true; + // Get a singleton DB instance. + require_once 'Crisscott/DB.php'; + $db = Crisscott_DB::singleton(); + + // Get the query. + if (isset($this->contributorId)) { + $query = $this->_getUpdateQuery(); + $getNewId = false; + } else { + $query = $this->_getInsertQuery(); + $getNewId = true; + } + + // Submit the query; + $db->query($query); + + // Do we need to get the contributorId? + if ($getNewId) { + $this->_getNewId(); + } + } + + /** + * Creates the query for updating information already + * in the database. + * + * @access private + * @return string + */ + private function _getUpdateQuery() + { + $query = 'UPDATE contributor '; + $query.= 'SET '; + $query.= ' first_name = \'' . $this->firstName . '\', '; + $query.= ' middle_name = \'' . $this->middleName . '\', '; + $query.= ' last_name = \'' . $this->lastName . '\', '; + $query.= ' website = \'' . $this->website . '\', '; + $query.= ' email = \'' . $this->email . '\', '; + $query.= ' street1 = \'' . $this->street1 . '\', '; + $query.= ' street2 = \'' . $this->street2 . '\', '; + $query.= ' city = \'' . $this->city . '\', '; + $query.= ' state = \'' . $this->state . '\', '; + $query.= ' country = \'' . $this->country . '\', '; + $query.= ' postal = \'' . $this->postal . '\' '; + $query.= 'WHERE contributorId = ' . $this->contirbutorId . ' '; + + return $query; + } + + /** + * Creates the query for inserting information into + * the database. + * + * @access private + * @return string + */ + private function _getInsertQuery() + { + $query = 'INSERT INTO contributor '; + $query.= '(first_name, middle_name, last_name, website, email, '; + $query.= ' street1, street2, city, state, country, postal) '; + $query.= 'VALUES ( '; + $query.= '\'' . $this->firstName . '\', '; + $query.= '\'' . $this->middleName . '\', '; + $query.= '\'' . $this->lastName . '\', '; + $query.= '\'' . $this->website . '\', '; + $query.= '\'' . $this->email . '\', '; + $query.= '\'' . $this->street1 . '\', '; + $query.= '\'' . $this->street2 . '\', '; + $query.= '\'' . $this->city . '\', '; + $query.= '\'' . $this->state . '\', '; + $query.= '\'' . $this->country . '\', '; + $query.= '\'' . $this->postal . '\' '; + $query.= ') '; + + return $query; + } + + /** + * Sets the contributor id by looking up the value from + * the database. + * + * @access private + * @return void + */ + private function _getNewId() + { + // Get a singleton DB instance. + require_once 'Crisscott/DB.php'; + $db = Crisscott_DB::singleton(); + + // Create the query. + $query = 'SELECT contributor_id '; + $query.= 'FROM contributor '; + $query.= 'WHERE first_name = \'' . $this->firstName . '\' '; + $query.= ' AND middle_name = \'' . $this->middleName . '\', '; + $query.= ' AND last_name = \'' . $this->lastName . '\' '; + $query.= ' AND website = \'' . $this->website . '\' '; + $query.= ' AND email = \'' . $this->email . '\' '; + $query.= ' AND street1 = \'' . $this->street1 . '\' '; + $query.= ' AND street2 = \'' . $this->street2 . '\' '; + $query.= ' AND city = \'' . $this->city . '\' '; + $query.= ' AND state = \'' . $this->state . '\' '; + $query.= ' AND country = \'' . $this->country . '\' '; + $query.= ' AND postal = \'' . $this->postal . '\' '; + + $result = $db->query($query); + + $this->contributorId = $result['contributor_id']; + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim: et tw=78 + * vi: ts=1 sw=1 + */ +?> \ No newline at end of file diff --git a/Crisscott/DB.php b/Crisscott/DB.php new file mode 100644 index 0000000..39e3eb6 --- /dev/null +++ b/Crisscott/DB.php @@ -0,0 +1,81 @@ +db = DB::connect($dsn); + if (PEAR::isError(self::$instance->db)) { + throw new Exception('Failed to connect to database: ' . self::$instance->db->getMessage() . "\n" . self::$instance->db->getUserInfo()); + } + } + return self::$instance; + } + + public function query($sql) + { + // Execute the query. + $result = $this->db->query($sql); + + // Check for errors. + if (PEAR::isError($result)) { + throw new Exception($result->getMessage()); + } elseif ($result == DB_OK) { + return true; + } else { + require_once 'Crisscott/DB/Result.php'; + return new Crisscott_DB_Result($result); + } + } + + public function prepare($sql) + { + $result = $this->db->prepare($sql); + + // Check for errors. + if (PEAR::isError($result)) { + throw new Exception($result->getMessage()); + } else { + return $result; + } + } + + public function execute($handle, $values) + { + $result = $this->db->execute($handle, $values); + + // Check for errors. + if (PEAR::isError($result)) { + var_dump($result); + throw new Exception($result->getMessage()); + } elseif ($result == DB_OK) { + return true; + } else { + return new Crisscott_DB_Result($result); + } + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/Crisscott/DB/Result.php b/Crisscott/DB/Result.php new file mode 100644 index 0000000..6cf1e72 --- /dev/null +++ b/Crisscott/DB/Result.php @@ -0,0 +1,69 @@ +result = $result; + $this->key = 0; + $this->rowCount = $this->result->numRows(); + } + } + + public function current() + { + return ($this->current = $this->result->fetchRow(DB_FETCHMODE_ASSOC,$this->key)); + } + + public function goToPrev() + { + if (--$this->key >= 0) { + return ($this->current = $this->result->fetchRow(DB_FETCHMODE_ASSOC,$this->key)); + } else { + return false; + } + } + + public function goToNext() + { + if (++$this->key < $this->rowCount) { + return ($this->current = $this->result->fetchRow(DB_FETCHMODE_ASSOC,$this->key)); + } else { + return false; + } + } + + public function valid() + { + return $this->key < $this->rowCount; + } + + public function numRows() + { + return $this->rowCount; + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/Crisscott/Inventory.php b/Crisscott/Inventory.php new file mode 100644 index 0000000..b31d850 --- /dev/null +++ b/Crisscott/Inventory.php @@ -0,0 +1,230 @@ +refreshInventory(); + } + + public function refreshInventory() + { + // Get the categories. + require_once 'Crisscott/DB.php'; + $db = Crisscott_DB::singleton(); + + $query = 'SELECT category_id '; + $query.= 'FROM categories '; + + require_once 'Crisscott/Category.php'; + foreach ($db->query($query) as $row) { + $this->categories[] = new Crisscott_Category($row['category_id']); + } + } + + /** + * Creates or returns a singleton instance of this class. + * + * @static + * @access public + * @return object + */ + public static function singleton() + { + if (!isset(self::$instance)) { + $class = __CLASS__; + self::$instance = new $class; + } + return self::$instance; + } + + /** + * Returns the category with the given name. + * + * @access public + * @param string $category + * @return object + */ + public function getCategoryByName($category) + { + foreach ($this->categories as $cat) { + if ($cat->name == $category) { + return $cat; + } + } + + return null; + } + + /** + * Returns the category with the given id. + * + * @access public + * @param integer $category + * @return object + */ + public function getCategoryById($category) + { + foreach ($this->categories as $cat) { + if ($cat->categoryId == $category) { + return $cat; + } + } + + return null; + } + + /** + * Sends data to the Crisscott server on product at a time. + * + * @static + * @access public + * @return boolean true if there are more products to send. + */ + public static function transmitInventory() + { + // Make sure the singleton instance has been instantiated. + if (!isset(self::$instance)) { + self::singleton(); + } + + // Create a SOAP client. + require_once 'Crisscott/SOAPClient.php'; + $soap = new Crisscott_SOAPClient(); + + // Collect all of the products. + if (empty(self::$products)) { + self::getAllProducts(); + } + + // Create a progress dialog for showing the progress. + require_once 'Crisscott/Tools/ProgressDialog.php'; + $dialog = Crisscott_Tools_ProgressDialog::singleton('Sending Inventory'); + + // Set the transmitting flag. + self::$transmitting = true; + + // Show the progress dialog. + $dialog->show_all(); + + // We need to know the total to know the percentage complete. + $total = count(self::$products); + + // Transmit the current product. + $soap->sendProduct(self::$products[self::$currentProduct]); + + // Update the progress bar. + $percentComplete = (self::$currentProduct + 1) / $total; + $dialog->progress->set_fraction($percentComplete); + + // Display the percentage as a string over the bar. + $percentComplete = round($percentComplete * 100, 0); + $dialog->progress->set_text($percentComplete . '%'); + + // Return true if there are more products to send. + if (self::$products[self::$currentProduct] == end(self::$products)) { + $dialog->destroy(); + + // Set the transmitting flag. + self::$transmitting = false; + + // Clean up the data. + self::$products = null; + self::$currentProduct = 0; + + // Stop the callback from being called again. (timeout only) + return false; + } else { + // Increment the currentProduct. + ++self::$currentProduct; + + return true; + } + } + + public static function getAllProducts() + { + self::$products = array(); + // Loop through categories in the inventory. + foreach (self::$instance->categories as $category) { + // Loop through the products in each category. + foreach ($category->products as $product) { + self::$products[] = $product; + } + } + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/Crisscott/Iterator.php b/Crisscott/Iterator.php new file mode 100644 index 0000000..8993e2e --- /dev/null +++ b/Crisscott/Iterator.php @@ -0,0 +1,38 @@ +key = 0; + } + + public function current() { + return $this->current; + } + + public function key() { + return $this->key; + } + + public function next() { + return $this->goToNext(); + } + + + abstract protected function goToPrev(); + + abstract protected function goToNext(); + + public function valid() { + return isset($this->current); + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/Crisscott/MainNotebook.php b/Crisscott/MainNotebook.php new file mode 100644 index 0000000..a2767a8 --- /dev/null +++ b/Crisscott/MainNotebook.php @@ -0,0 +1,84 @@ +pages = array(); + foreach ($titles as $title) { + $pageNum = $this->append_page(new GtkVBox(), new GtkLabel($title, true)); + $page = $this->get_nth_page($pageNum); + $this->pages[$title] = $page; + } + + //$this->set_show_tabs(false); + + // Add a productediting instance to the notebook. + require_once 'Crisscott/Tools/ProductEdit.php'; + $this->pages['Product Edit']->add(Crisscott_Tools_ProductEdit::singleton()); + + // Add an category summary instance. + require_once 'Crisscott/Tools/CategorySummary.php'; + require_once 'Crisscott/Inventory.php'; + $this->pages['Category Summary']->add(new Crisscott_Tools_CategorySummary(Crisscott_Inventory::singleton())); + + // Add a contributoredit instance. + require_once 'Crisscott/Tools/ContributorEdit.php'; + $this->pages['Contributor Edit']->add(new Crisscott_Tools_ContributorEdit()); + + // Add the news article tool. + require_once 'Crisscott/Tools/NewsArticle.php'; + $news = Crisscott_Tools_NewsArticle::singleton(); + $this->pages['News Story']->add($news); + } + + /** + * Creates or returns a singleton instance of this class. + * + * @static + * @access public + * @return object + */ + public static function singleton() + { + if (!isset(self::$instance)) { + $class = __CLASS__; + self::$instance = new $class; + } + return self::$instance; + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/Crisscott/MainWindow.php b/Crisscott/MainWindow.php new file mode 100644 index 0000000..4f4b1db --- /dev/null +++ b/Crisscott/MainWindow.php @@ -0,0 +1,214 @@ +set_size_request(500, 300); + $this->set_position(Gtk::WIN_POS_CENTER); + $this->set_title('Criscott PIMS'); + + $this->_populate(); + + $this->maximize(); + $this->set_icon_from_file('Crisscott/images/logo.png'); + + $this->connect_simple('destroy', array('gtk', 'main_quit')); + + // Parse the RC file that will change the look and + // feel of the application. + //Gtk::rc_parse(self::RC_PATH); + } + + private function _populate() + { + $table = new GtkTable(5, 3); + + $expandFill = GTK::EXPAND|GTK::FILL; + + require_once 'Crisscott/Tools/Menu.php'; + $table->attach(new Crisscott_Tools_Menu(), 0, 2, 0, 1, $expandFill, 0, 0, 0); + + require_once 'Crisscott/Tools/Toolbar.php'; + $table->attach(new Crisscott_Tools_Toolbar(), 0, 2, 1, 2, $expandFill, 0, 0, 0); + + // Get a singleton instance of the product tree. + require_once 'Crisscott/Tools/ProductTree.php'; + $productTree = Crisscott_Tools_ProductTree::singleton(); + + // Create a scrolled window for the product tree. + $scrolledWindow = new GtkScrolledWindow(); + + // Set the size of the scrolled window. + $scrolledWindow->set_size_request(150, 150); + + // Set the scrollbar policy. + $scrolledWindow->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC); + + // Add the product tree to the scrolled window. + $scrolledWindow->add($productTree); + + // Attach the scrolled window to the tree. + $table->attach($scrolledWindow, 0, 1, 2, 3, 0, $expandFill, 0, 0); + + require_once 'Crisscott/Tools/NewsFeed.php'; + $feed = 'chapter9/news.rss'; + $news = Crisscott_Tools_NewsFeed::singleton(); + $news->setInput($feed); + $news->showList(); + $news->set_size_request(150, -1); + + $table->attach($news, 0, 1, 3, 4, 0, $expandFill, 0, 0); + + $table2 = new GtkTable(2, 2); + + $productSummary = new GtkFrame('PRODUCT SUMMARY'); + $productSummary->set_size_request(-1, 150); + + // Add the product summary tool. + require_once 'Crisscott/Tools/ProductSummary.php'; + $this->productSummary = Crisscott_Tools_ProductSummary::singleton(); + $productSummary->add($this->productSummary); + + $table2->attach($productSummary, 0, 1, 0, 1, $expandFill, 0, 1, 1); + + $inventorySummary = new GtkFrame('INVENTORY SUMMARY'); + $inventorySummary->set_size_request(-1, 150); + + $table2->attach($inventorySummary, 1, 2, 0, 1, $expandFill, 0, 1, 1); + + require_once 'Crisscott/MainNotebook.php'; + $this->mainNotebook = Crisscott_MainNotebook::singleton(); + $table2->attach($this->mainNotebook, 0, 2, 1, 2, $expandFill, $expandFill, 1, 1); + + $table->attach($table2, 1, 2, 2, 4, $expandFill, $expandFill, 0, 0); + + require_once 'Crisscott/Tools/StatusBar.php'; + $table->attach(Crisscott_Tools_StatusBar::singleton(), 0, 2, 4, 5, $expandFill, 0, 0, 0); + + $this->add($table); + } + + public function connectToServer() + { + sleep(1); + } + + public function connectToLocalDB() + { + sleep(1); + } + + static public function quit() + { + // Check to see if the data has been modified + // or sent. If it is modified or not sent, don't + // exit. + if (self::$modified || !self::$sent) { + // Create a dialog to make sure the user wants to quit. + // Set up the options for the dialog + $dialogOptions = 0; + // Make the dialog modal. + $dialogOptions = $dialogOptions | Gtk::DIALOG_MODAL; + // Destroy the dialog if the parent window is destroyed. + $dialogOptions = $dialogOptions | Gtk::DIALOG_DESTROY_WITH_PARENT; + // Don't show a horizontal separator between the two parts. + $dialogOptions = $dialogOptions | Gtk::DIALOG_NO_SEPARATOR; + + // Set up the buttons. + $dialogButtons = array(); + // Add a stock "No" button and make its respnse "No". + $dialogButtons[] = Gtk::STOCK_NO; + $dialogButtons[] = Gtk::RESPONSE_NO; + // Add a stock "Yes" button and make its respnse "Yes". + $dialogButtons[] = Gtk::STOCK_YES; + $dialogButtons[] = Gtk::RESPONSE_YES; + + // Create the dialog. + $dialog = new GtkDialog('Confirm Exit', $window, $dialogOptions);//, $dialogButtons); + + // Add the buttons to the action area. + $noButton = GtkButton::new_from_stock(Gtk::STOCK_NO); + $yesButton = GtkButton::new_from_stock(Gtk::STOCK_YES); + + $dialog->add_action_widget($noButton, Gtk::RESPONSE_NO); + $dialog->add_action_widget($yesButton, Gtk::RESPONSE_YES); + + // Add an image and a question to the top part of the dialog. + $hBox = new GtkHBox(); + $dialog->vbox->pack_start($hBox); + + // Pack a stock warning image. + $warning = GtkImage::new_from_stock(Gtk::STOCK_DIALOG_WARNING, Gtk::ICON_SIZE_DIALOG); + $hBox->pack_start($warning, false, false, 5); + + $message = "The current inventory has not been saved\n"; + $message.= "and transmitted to Crisscott. Are you sure\n"; + $message.= "you would like to quit?\n"; + + $label = new GtkLabel($message); + $label->set_line_wrap(); + + $hBox->pack_start($label); + + // Show the top part of the dialog (The bottom + // will be shown automatically). + $dialog->vbox->show_all(); + + // Run the dialog and check the response. + if ($dialog->run() !== Gtk::RESPONSE_YES) { + $dialog->destroy(); + return false; + } + } + // Exit the application. + gtk::main_quit(); + return true; + } + + public function open() + { + // Create the file selection dialog. + $fileSelection = new GtkFileSelection('Open'); + + // Make sure that only one file is selected. + $fileSelection->set_select_multiple(false); + + // Filter the files for XML files. + $fileSelection->complete('*.xml'); + + // Show the dialog and run it. + $fileSelection->show_all(); + if ($fileSelection->run() == Gtk::RESPONSE_OK) { + // Make sure the file exists. + if (@is_readable($fileSelection->get_filename())) { + // Load the file. + self::loadFile($fileSelection->get_filename()); + } else { + // Pop up a dialog warning... + // ... + // Run this method again. + self::open(); + } + } + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> diff --git a/Crisscott/Product.php b/Crisscott/Product.php new file mode 100644 index 0000000..1d3f7ba --- /dev/null +++ b/Crisscott/Product.php @@ -0,0 +1,317 @@ +init($productId); + } + + protected function init($productId) + { + // Check the product id. + if (!is_numeric($productId)) { + throw new Exception('Cannot initialize product. Invalid productId: ' . $productId); + } else { + $this->productId = $productId; + } + + require_once 'Crisscott/DB.php'; + $db = Crisscott_DB::singleton(); + + $query = 'SELECT product_name, description, product_type, '; + $query.= ' category_id, inventory, available, width, height, '; + $query.= ' depth, weight, image_path, '; + $query.= ' price '; + $query.= 'FROM products '; + $query.= ' LEFT JOIN product_price '; + $query.= ' USING (product_id) '; + $query.= 'WHERE product_id = ' . $this->productId . ' '; + $query.= ' AND (currency = \'USD\' OR currency IS NULL) '; + + $row = $db->query($query)->current(); + if (count($row)) { + $this->name = $row['product_name']; + $this->description = $row['description']; + $this->type = $row['product_type']; + $this->categoryId = $row['category_id']; + $this->inventory = $row['inventory']; + $this->availability = $row['available'] == 't'; + $this->width = $row['width']; + $this->height = $row['height']; + $this->depth = $row['depth']; + $this->weight = $row['weight']; + $this->price = $row['price']; + $this->currency = $row['currency']; + $this->imagePath = $row['image_path']; + } + } + + /** + * Checks to see if the values of the product are valid. + * + * @access public + * @return mixed true or an array of the invalid fields. + */ + public function validate() + { + $invalidFields = array(); + + // Check the easy fields first. + // All of these fields must be numbers. + $numbers = array('price', + 'inventory', + 'width', + 'height', + 'depth', + 'weight' + ); + foreach ($numbers as $numField) { + if (!is_numeric($this->$numField)) { + $invalidFields[] = $numField; + } + } + + // Check the length of the product name. + if (strlen($this->name) > 50 || strlen($this->name) < 1) { + $invalidFields[] = 'name'; + } + + // Check that the availability is a boolean. + if (!is_bool($this->availability)) { + $invalidFields[] = 'availability'; + } + + // Check the product type. + $validTypes = array('Shippable', 'Digital'); + if (!in_array($this->type, $validTypes)) { + $invalidFields[] = 'type'; + } + + // Check the category. + if (empty($this->categoryId) || !is_numeric($this->categoryId)) { + $invalidFields[] = 'category'; + } + + // Check the image path. + if (!empty($this->imagePath) && !@is_readable($this->imagePath)) { + $invalidFields[] = 'image'; + } + + // The description can't really be invalid. + if (count($invalidFields)) { + return $invalidFields; + } else { + return true; + } + } + + public function save() + { + // Save the product information to the database. + if (isset($this->productId) && $this->productId > 0) { + return $this->updateProduct(); + } else { + return $this->saveNewProduct(); + } + } + + protected function updateProduct() + { + require_once 'Crisscott/DB.php'; + $db = Crisscott_DB::singleton(); + + $query = 'UPDATE products '; + $query.= 'SET '; + $query.= ' product_name = ?, '; + $query.= ' description = ?, '; + $query.= ' product_type = ?, '; + $query.= ' category_id = ?, '; + $query.= ' inventory = ?, '; + $query.= ' available = ?, '; + $query.= ' width = ?, '; + $query.= ' height = ?, '; + $query.= ' depth = ?, '; + $query.= ' weight = ?, '; + $query.= ' image_path = ? '; + $query.= 'WHERE product_id = ? '; + + $stmt = $db->prepare($query); + + $prodArray = array( + $this->name, + $this->description, + $this->type, + $this->categoryId, + $this->inventory, + $this->availability, + $this->width, + $this->height, + $this->depth, + $this->weight, + $this->imagePath, + $this->productId + ); + + $db->execute($stmt, $prodArray); + + return true; + } + + protected function saveNewProduct() + { + require_once 'Crisscott/DB.php'; + $db = Crisscott_DB::singleton(); + + $query = 'INSERT INTO products '; + $query.= '(product_name, description, product_type, '; + $query.= ' category_id, inventory, available, width, height, '; + $query.= ' depth, weight, image_path) '; + $query.= 'VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) '; + + $stmt = $db->prepare($query); + + $prodArray = array( + $this->name, + $this->description, + $this->type, + $this->categoryId, + $this->inventory, + $this->availability, + $this->width, + $this->height, + $this->depth, + $this->weight, + $this->imagePath + ); + + + $db->execute($stmt, $prodArray); + + // Get the new product id. + $query = 'SELECT MAX(product_id) '; + $query.= 'FROM products '; + + $this->productId = reset($db->query($query)->current()); + + return true; + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/Crisscott/SOAPClient.php b/Crisscott/SOAPClient.php new file mode 100644 index 0000000..82fbfcb --- /dev/null +++ b/Crisscott/SOAPClient.php @@ -0,0 +1,14 @@ + \ No newline at end of file diff --git a/Crisscott/SplashScreen.php b/Crisscott/SplashScreen.php new file mode 100644 index 0000000..3a0df89 --- /dev/null +++ b/Crisscott/SplashScreen.php @@ -0,0 +1,144 @@ +set_decorated(false); + + // Center the wiindow/ + //$this->set_position(Gtk::WIN_POS_CENTER); + $this->set_uposition(Gdk::screen_width() / 2, Gdk::screen_height() / 2); + /* + // Set the background using a style. + $style = $this->style->copy(); + // Make the background white. + $style->bg[Gtk::STATE_NORMAL] = $style->white; + // Set the style. + $this->set_style($style); + */ + // Set the name for rules in the RC file. + $this->set_name('splash'); + + // Fill the window with the needed pieces. + $this->_populate(); + + // Make the window stay above other windows. + $this->set_keep_above(true); + + // Call a method when the class is shown. + $this->connect_simple_after('show', array($this, 'startMainWindow')); + + // Parse the application's RC file. + require_once 'Crisscott/MainWindow.php'; + Gtk::rc_parse(Crisscott_MainWindow::RC_PATH); + } + + private function _populate() + { + // Create the needed peices. + $frame = new GtkFrame(); + $hBox = new GtkHBox(); + $vBox = new GtkVBox(); + $logoBox = new GtkHBox(); + $statusBox = new GtkHBox(); + + // Set the shadow type for the splash screen. + $frame->set_shadow_type(Gtk::SHADOW_ETCHED_OUT); + + // Create a label for the title. + $title = new GtkLabel('Crisscott Product Information Management System'); + // Mark up the text to change its color to dark blue. + //$title = new GtkLabel('Crisscott Product Information Management System'); + // Tell the label widget that the text contains Pango markup. + //$title->set_use_markup(true); + + // Create a label to display a status message. + $this->status = new GtkLabel('Initializing Main Window'); + + // Pack the logo and status boxes. Allow them to grow and + // expand. Also give them some padding. + $vBox->pack_start($logoBox, true, true, 10); + $vBox->pack_start($statusBox, true, true, 10); + + // Add the elements to the sub boxes. + $logoBox->pack_start($title); + $statusBox->pack_start($this->status); + + // Add a logo image. + $logoImg = GtkImage::new_from_file('Crisscott/images/logo.png'); + + // Finish packing everything. + $hBox->pack_start($logoImg, false, false, 10); + $hBox->pack_start($vBox, false, false, 10); + $frame->add($hBox); + + $this->add($frame); + } + + + public function start() + { + // Show the splash screen. + $this->show_all(); + // Start the main loop. + gtk::main(); + } + + public function startMainWindow() + { + // Update the GUI. + while (gtk::events_pending()) gtk::main_iteration(); + // Give the user enough time to at least see the message. + require_once 'Crisscott/MainWindow.php'; + $main = new Crisscott_MainWindow(); + + $this->status->set_text('Connecting to server...'); + while (gtk::events_pending()) gtk::main_iteration(); + + if ($main->connectToServer()) { + $this->status->set_text('Connecting to server... OK'); + } + while (gtk::events_pending()) gtk::main_iteration(); + + $this->status->set_text('Connecting to local database...'); + while (gtk::events_pending()) gtk::main_iteration(); + + if ($main->connectToLocalDB()) { + $this->status->set_text('Connecting to local database... OK'); + } + while (gtk::events_pending()) gtk::main_iteration(); + + $main->show_all(); + + $this->set_keep_above(false); + $this->hide(); + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/Crisscott/Tools/CategorySummary.php b/Crisscott/Tools/CategorySummary.php new file mode 100644 index 0000000..bf5f7d8 --- /dev/null +++ b/Crisscott/Tools/CategorySummary.php @@ -0,0 +1,140 @@ +attachColHeaders(); + + // If an inventory was passed, add the data to the table. + if (!empty($inventory)) { + $this->summarizeInventory($inventory); + } + } + + /** + * Summarizes all the categories in the inventory. + * + * First clears out the table. Next re-attches the column + * headers. Finally each category is added as its own row. + * + * @access public + * @param object $inventory A Crisscott_Inventory instance. + * @return void + */ + public function summarizeInventory(Crisscott_Inventory $inventory) + { + // Clear out the table. + $this->clear(); + + // Re-attach the headers. + $this->attachColHeaders(); + + // Add a row for each category. + foreach ($inventory->categories as $category) { + $this->summarizeCategory($category); + } + } + + /** + * Attaches column headers to the table. + * + * @access protected + * @return void + */ + protected function attachColHeaders() + { + require_once 'Crisscott/Category.php'; + foreach (Crisscott_Category::getCategorySpecs() as $key => $spec) { + $label = new GtkLabel($spec); + $label->set_angle(90); + $label->set_alignment(.5, 1); + + // Leave the first cell empty. + $this->attach($label, $key + 1, $key + 2, 0, 1, 0, GTK::FILL, 10, 10); + } + + // Increment the last row. + $this->lastRow++; + + } + + /** + * Adds a row of data for the given category. + * + * @access public + * @param object $category A Crisscott_Category instance. + * @return void + */ + public function summarizeCategory(Crisscott_Category $category) + { + // First attach the category name. + $nameLabel = new GtkLabel($category->name); + $nameLabel->set_alignment(0, .5); + $this->attach($nameLabel, 0, 1, $this->lastRow, $this->lastRow + 1, GTK::FILL, 0, 10, 10); + + // Next attach the spec values. + foreach (Crisscott_Category::getCategorySpecs() as $key => $spec) { + $value = $category->getSpecValueByName($spec); + $this->attach(new GtkLabel($value), $key + 1, $key + 2, $this->lastRow, $this->lastRow + 1, 0, 0, 1, 1); + } + + // Increment the last row. + $this->lastRow++; + } + + /** + * Clears all cells of the table. + * + * @access protected + * @return void + */ + protected function clear() + { + foreach ($this->get_children() as $child) { + $this->remove($child); + } + + // Reset the last row. + $this->lastRow = 0; + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim: et tw=78 + * vi: ts=1 sw=1 + */ +?> \ No newline at end of file diff --git a/Crisscott/Tools/ContributorEdit.php b/Crisscott/Tools/ContributorEdit.php new file mode 100644 index 0000000..d6b96a4 --- /dev/null +++ b/Crisscott/Tools/ContributorEdit.php @@ -0,0 +1,557 @@ +'; + const ERROR_MARKUP_CLOSE = ''; + + /** + * The contributor currently being modified. + * + * @access public + * @var object + */ + public $contributor; + + /** + * A label for the contributor's first name. + * + * @access private + * @var object + */ + private $firstNameLabel; + + /** + * A label for the contributor's middle name. + * + * @access private + * @var object + */ + private $middleNameLabel; + + /** + * A label for the contributor's last name. + * + * @access private + * @var object + */ + private $lastNameLabel; + + /** + * A label for the contributor's web address. + * + * @access private + * @var object + */ + private $websiteLabel; + + /** + * A label for the contributor's email address. + * + * @access private + * @var object + */ + private $emailLabel; + + /** + * A label for the contributor's street address. + * + * @access private + * @var object + */ + private $street1Label; + + /** + * A label for the contributor's street address. + * + * @access private + * @var object + */ + private $street2Label; + + /** + * A label for the contributor's city. + * + * @access private + * @var object + */ + private $cityLabel; + + /** + * A label for the contributor's state. + * + * @access private + * @var object + */ + private $stateLabel; + + /** + * A label for the contributor's country. + * + * @access private + * @var object + */ + private $countryLabel; + + /** + * A label for the contributor's postal code. + * + * @access private + * @var object + */ + private $postalLabel; + + /** + * A entry for the contributor's first name. + * + * @access private + * @var object + */ + private $firstNameEntry; + + /** + * A entry for the contributor's middle name. + * + * @access private + * @var object + */ + private $middleNameEntry; + + /** + * A entry for the contributor's last name. + * + * @access private + * @var object + */ + private $lastNameEntry; + + /** + * A entry for the contributor's web address. + * + * @access private + * @var object + */ + private $websiteEntry; + + /** + * A entry for the contributor's email address. + * + * @access private + * @var object + */ + private $emailEntry; + + /** + * A entry for the contributor's street address. + * + * @access private + * @var object + */ + private $street1Entry; + + /** + * A entry for the contributor's street address. + * + * @access private + * @var object + */ + private $street2Entry; + + /** + * A entry for the contributor's city. + * + * @access private + * @var object + */ + private $cityEntry; + + /** + * A entry for the contributor's state. + * + * @access private + * @var object + */ + private $stateEntry; + + /** + * A entry for the contributor's country. + * + * @access private + * @var object + */ + private $countryComboBox; + + /** + * A entry for the contributor's postal code. + * + * @access private + * @var object + */ + private $postalEntry; + + /** + * Constructor. Calls the methods to create the tool and + * sets up the needed callbacks. + * + * @access public + * @param object $contributor A Crisscott_Contributor instance. (Optional) + * @return void + */ + public function __construct($contributor = null) + { + // Call the parent constructor. + parent::__construct(7, 4); + + // Layout the tool. + $this->_layoutTool(); + + // Connect the needed callbacks. + + // Prepopulate the fields if a contributor is given. + if (empty($contributor) || is_a($contributor, 'Crisscott_Contributor')) { + require_once 'Crisscott/Contributor.php'; + $contributor = new Crisscott_Contributor(); + } + $this->populateFields($contributor); + } + + /** + * Laysout the labels, entries and buttons. + * + * This tool consists of several labels with corresponding entries + * and two buttons. One button resets the fields the other submits + * the information to change the contributors values. + * + * @access private + * @return void + */ + private function _layoutTool() + { + // First create the labels that identify the fields. + $this->firstNameLabel = new GtkLabel('First Name'); + $this->middleNameLabel = new GtkLabel('Middle Name'); + $this->lastNameLabel = new GtkLabel('Last Name'); + $this->emailLabel = new GtkLabel('Email Address'); + $this->websiteLabel = new GtkLabel('Website'); + $this->street1Label = new GtkLabel('Street 1'); + $this->street2Label = new GtkLabel('Street 2'); + $this->cityLabel = new GtkLabel('City'); + $this->stateLabel = new GtkLabel('State'); + $this->countryLabel = new GtkLabel('Country'); + $this->postalLabel = new GtkLabel('Postal Code'); + + // Next add the labels to the table. + // The labels will be added in two columns. + // First column. + $this->attach($this->firstNameLabel, 0, 1, 0, 1, GTK::FILL, 0); + $this->attach($this->middleNameLabel, 0, 1, 1, 2, GTK::FILL, 0); + $this->attach($this->lastNameLabel, 0, 1, 2, 3, GTK::FILL, 0); + $this->attach($this->emailLabel, 0, 1, 3, 4, GTK::FILL, 0); + $this->attach($this->websiteLabel, 0, 1, 4, 5, GTK::FILL, 0); + + // Second column. + $this->attach($this->street1Label, 2, 3, 0, 1, GTK::FILL, 0); + $this->attach($this->street2Label, 2, 3, 1, 2, GTK::FILL, 0); + $this->attach($this->cityLabel, 2, 3, 2, 3, GTK::FILL, 0); + $this->attach($this->stateLabel, 2, 3, 3, 4, GTK::FILL, 0); + $this->attach($this->countryLabel, 2, 3, 4, 5, GTK::FILL, 0); + $this->attach($this->postalLabel, 2, 3, 5, 6, GTK::FILL, 0); + + // Right align all of the labels. + $this->firstNameLabel->set_alignment(1, .5); + $this->middleNameLabel->set_alignment(1, .5); + $this->lastNameLabel->set_alignment(1, .5); + $this->emailLabel->set_alignment(1, .5); + $this->websiteLabel->set_alignment(1, .5); + $this->street1Label->set_alignment(1, .5); + $this->street2Label->set_alignment(1, .5); + $this->cityLabel->set_alignment(1, .5); + $this->stateLabel->set_alignment(1, .5); + $this->countryLabel->set_alignment(1, .5); + $this->postalLabel->set_alignment(1, .5); + + // Turn on markup + $this->firstNameLabel->set_use_markup(true); + $this->middleNameLabel->set_use_markup(true); + $this->lastNameLabel->set_use_markup(true); + $this->emailLabel->set_use_markup(true); + $this->websiteLabel->set_use_markup(true); + $this->street1Label->set_use_markup(true); + $this->street2Label->set_use_markup(true); + $this->cityLabel->set_use_markup(true); + $this->stateLabel->set_use_markup(true); + $this->countryLabel->set_use_markup(true); + $this->postalLabel->set_use_markup(true); + + // Next create all of the data collection widgets. + $this->firstNameEntry = new GtkEntry(); + $this->middleNameEntry = new GtkEntry(); + $this->lastNameEntry = new GtkEntry(); + $this->emailEntry = new GtkEntry(); + $this->websiteEntry = new GtkEntry(); + $this->street1Entry = new GtkEntry(); + $this->street2Entry = new GtkEntry(); + $this->cityEntry = new GtkEntry(); + $this->stateEntry = new GtkEntry(); + $this->postalEntry = new GtkEntry(); + + // The country should be a combobox. + $this->countryComboBox = GtkComboBox::new_text(); + $this->countryComboBox->append_text('United States'); + $this->countryComboBox->prepend_text('Canada'); + $this->countryComboBox->insert_text(1, 'United Kingdom'); + $this->countryComboBox->set_active(0); + + // Next add the entrys to the table. + // The entrys will be added in two columns. + // First column. + $this->attach($this->firstNameEntry, 1, 2, 0, 1, 0, 0); + $this->attach($this->middleNameEntry, 1, 2, 1, 2, 0, 0); + $this->attach($this->lastNameEntry, 1, 2, 2, 3, 0, 0); + $this->attach($this->emailEntry, 1, 2, 3, 4, 0, 0); + $this->attach($this->websiteEntry, 1, 2, 4, 5, 0, 0); + + // Second column. + $this->attach($this->street1Entry, 3, 4, 0, 1, 0, 0); + $this->attach($this->street2Entry, 3, 4, 1, 2, 0, 0); + $this->attach($this->cityEntry, 3, 4, 2, 3, 0, 0); + $this->attach($this->stateEntry, 3, 4, 3, 4, 0, 0); + $this->attach($this->countryComboBox, 3, 4, 4, 5, 0, 0); + $this->attach($this->postalEntry, 3, 4, 5, 6, 0, 0); + + // Help the user out with the state by using a GtkEntryCompletion. + $stateCompletion = new GtkEntryCompletion(); + $stateCompletion->set_model(self::createStateList()); + $stateCompletion->set_text_column(0); + $this->stateEntry->set_completion($stateCompletion); + $stateCompletion->set_inline_completion(true); + + // Add the save and clear buttons. + $save = GtkButton::new_from_stock('gtk-save'); + $reset = GtkButton::new_from_stock('gtk-undo'); + $save->connect_simple('clicked', array($this, 'saveContributor')); + $reset->connect_simple('clicked', array($this, 'resetContributor')); + + $this->attach($reset, 0, 1, 6, 7, 0, 0); + $this->attach($save, 3, 4, 6, 7, 0, 0); + } + + /** + * Creates a one column list store that contains the US states + * and Canadian provinces. + * + * This list can be used for combo boxes, tree, or entry + * completions. + * + * @static + * @access public + * @return object A GtkListStore + */ + public static function createStateList() + { + $listStore = new GtkListStore(GTK::TYPE_STRING); + $iter = $listStore->append(); + $listStore->set($iter, 0, 'Alabama'); + $iter = $listStore->append(); + $listStore->set($iter, 0, 'Alaska'); + $iter = $listStore->append(); + $listStore->set($iter, 0, 'Arizona'); + $iter = $listStore->append(); + $listStore->set($iter, 0, 'Arkansas'); + $iter = $listStore->append(); + $listStore->set($iter, 0, 'California'); + $iter = $listStore->append(); + $listStore->set($iter, 0, 'Colorodo'); + + return $listStore; + } + + /* + * + * When a contributor is edited, it is stored in a member variable + * and then its values are used to populate the fields. + * + * @access public + * @param object $contributor A Crisscott_Contributor instance. + * @return void + */ + public function populateFields(Crisscott_Contributor $contributor) + { + // Populate the fields. + $this->firstNameEntry->set_text($contributor->firstName); + $this->middleNameEntry->set_text($contributor->middleName); + $this->lastNameEntry->set_text($contributor->lastName); + $this->emailEntry->set_text($contributor->email); + $this->websiteEntry->set_text($contributor->website); + + $this->street1Entry->set_text($contributor->street1); + $this->street2Entry->set_text($contributor->street2); + $this->cityEntry->set_text($contributor->city); + $this->stateEntry->set_text($contributor->state); + $this->postalEntry->set_text($contributor->postal); + + // Set the active element for the country combo box. + $model = $this->countryComboBox->get_model(); + $iter = $model->get_iter_first(); + for ($iter; $model->iter_is_valid($iter); $model->iter_next($iter)) { + if ($this->contributor->country == $model->get_value($iter, 0)) { + $this->countryComboBox->set_active_iter($iter); + } + } + + // Keep a hold of the contributor. + $this->contributor = $contributor; + } + + /** + * Resets the fields with the original contributor data. + * + * This method basically is an undo for all changes that + * have been made since the last save. It re-grabs the + * values from the contributor and populates them again. + * + * @uses populateFields + * + * @access public + * @return void + */ + public function resetContributor() + { + // Make sure we have a contributor already. + if (!isset($this->contributor)) { + require_once 'Crisscott/Contributor.php'; + $this->contributor = new Crisscott_Contributor(); + $this->contributor->country = 'United States'; + } + + // Reset the fields to the original value. + $this->populateFields($this->contributor); + } + + /** + * Grabs, validates, and saves the contributor information. + * + * First this method collects the data values from the widgets + * and then assigns them to the contributor object. Next the + * values are validated using the contributors validate method. + * If all is ok, the contributor is told to write the data to + * the database. + * + * @access public + * @return boolean true on success. + */ + public function saveContributor() + { + // First grab all of the values. + $this->contributor->firstName = $this->firstNameEntry->get_text(); + $this->contributor->middleName = $this->firstNameEntry->get_text(); + $this->contributor->lastName = $this->lastNameEntry->get_text(); + $this->contributor->website = $this->websiteEntry->get_text(); + $this->contributor->email = $this->emailEntry->get_text(); + $this->contributor->street1 = $this->street1Entry->get_text(); + $this->contributor->street1 = $this->street1Entry->get_text(); + $this->contributor->city = $this->cityEntry->get_text(); + $this->contributor->state = $this->stateEntry->get_text(); + //$this->contributor->country = $this->countryComboBox->get_text(); + $this->contributor->postal = $this->postalEntry->get_text(); + + // Next validate the data. + $valid = $this->contributor->validate(); + + // Create a map of all the values and labels. + $labelMap = array('firstName' => $this->firstNameLabel, + 'middleName' => $this->middleNameLabel, + 'lastName' => $this->lastNameLabel, + 'website' => $this->websiteLabel, + 'email' => $this->emailLabel, + 'street1' => $this->street1Label, + 'street2' => $this->street2Label, + 'city' => $this->cityLabel, + 'state' => $this->stateLabel, + 'country' => $this->countryLabel, + 'postal' => $this->postalLabel + ); + + // Reset all of the labels. + foreach ($labelMap as $label) { + $this->clearError($label); + } + + // If there are invalid values, markup the labels. + if (is_array($valid)) { + foreach ($valid as $labelKey) { + $this->reportError($labelMap[$labelKey]); + } + + // Saving the data was not successful. + return false; + } + + // Try to save the data. + return $this->contributor->save(); + } + + /** + * Marks a label up as red text to indicate an error. + * + * @access public + * @param object $label The GtkLabel to markup. + * @return void + */ + public function reportError(GtkLabel $label) + { + require_once 'Crisscott/Tools/StatusBar.php'; + $status = Crisscott_Tools_StatusBar::singleton(); + var_dump($status->push(rand(), 'Error: ' . $label->get_label())); + + $label->set_label(self::ERROR_MARKUP_OPEN . $label->get_label() . self::ERROR_MARKUP_CLOSE); + } + + /** + * Clears the error markup from the given label. + * + * @access public + * @param $label The GtkLabel to remove markup from. + * @return void + */ + public function clearError(GtkLabel $label) + { + require_once 'Crisscott/Tools/StatusBar.php'; + $status = Crisscott_Tools_StatusBar::singleton(); + $contextId = $status->get_context_id('Error: ' . $label->get_label()); + $status->pop($contextId); + + $text = $label->get_label(); + $text = str_replace(self::ERROR_MARKUP_OPEN, '', $text); + $text = str_replace(self::ERROR_MARKUP_CLOSE, '', $text); + + $label->set_label($text); + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim: et tw=78 + * vi: ts=1 sw=1 + */ +?> \ No newline at end of file diff --git a/Crisscott/Tools/Menu.php b/Crisscott/Tools/Menu.php new file mode 100644 index 0000000..702b3ca --- /dev/null +++ b/Crisscott/Tools/Menu.php @@ -0,0 +1,156 @@ +file = new GtkMenuItem('_File'); + $this->append($this->file); + + $this->edit = new GtkMenuItem('_Edit'); + $this->append($this->edit); + + $this->help = new GtkMenuItem('_Help'); + $this->append($this->help); + + // Create the sub menus. + $this->createSubMenus(); + } + + protected function createSubMenus() + { + // Create the file menu and items. + $fileMenu = new GtkMenu(); + $new = new GtkImageMenuItem(Gtk::STOCK_NEW); + $open = new GtkImageMenuItem(Gtk::STOCK_OPEN); + $send = new GtkImageMenuItem('Send'); + $send->set_image(GtkImage::new_from_file('Crisscott/images/menuItemGrey.png')); + $save = new GtkMenuItem('Save'); + $quit = new GtkMenuItem('Quit'); + + // Add the four items to the file menu. + $fileMenu->append($new); + $fileMenu->append($open); + $fileMenu->append($send); + + // Make the send option send the data. + require_once 'Crisscott/Tools/ProgressDialog.php'; + $send->connect_simple('activate', array('Crisscott_Inventory', 'transmitInventory')); + + // Create a sub menu for the new item. + $newMenu = new GtkMenu(); + $product = new GtkMenuItem('Product'); + $category = new GtkMenuItem('Category'); + $contrib = new GtkMenuItem('Contributor'); + + // Make the new menu detachable. + $newMenu->append(new GtkTearoffMenuItem()); + $newMenu->append($product); + $newMenu->append($category); + $newMenu->append($contrib); + + $new->set_submenu($newMenu); + + // Add a separator. + $fileMenu->append(new GtkSeparatorMenuItem()); + + // Add some check items. + $server = new GtkCheckMenuItem('Connect to Server'); + $database = new GtkCheckMenuItem('Connect to Database'); + + $fileMenu->append($server); + $fileMenu->append($database); + + // Add a separator. + $fileMenu->append(new GtkSeparatorMenuItem()); + + // Add three noise levels. + $quiet = new GtkRadioMenuItem(null, 'Quiet'); + $normal = new GtkRadioMenuItem($quiet, 'Normal'); + $verbose = new GtkRadioMenuItem($quiet, 'Verbose'); + + $fileMenu->append($quiet); + $fileMenu->append($normal); + $fileMenu->append($verbose); + + // Add a separator. + $fileMenu->append(new GtkSeparatorMenuItem()); + + // Finish of the menu. + $fileMenu->append($save); + $fileMenu->append($quit); + + // Connect some signal handlers. + $quit->connect_simple('activate', array('Crisscott_MainWindow', 'quit')); + + $editMenu = new GtkMenu(); + $product = new GtkImageMenuItem('Current Product'); + + $editMenu->append($product); + + // Make the product menu item do something. + require_once 'Crisscott/Tools/ProductSummary.php'; + $summary = Crisscott_Tools_ProductSummary::singleton(); + + $product->connect_simple('activate', array($summary, 'editProduct')); + + // Create the help menu and items. + $helpMenu = new GtkMenu(); + $help = new GtkMenuItem('Help'); + $about = new GtkMenuItem('About'); + + // Add both items to the help menu. + $helpMenu->append($help); + $helpMenu->append($about); + + // Make the two menus submenus for the menu items. + $this->file->set_submenu($fileMenu); + $this->edit->set_submenu($editMenu); + $this->help->set_submenu($helpMenu); + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim: et tw=78 + * vi: ts=1 sw=1 + */ +?> \ No newline at end of file diff --git a/Crisscott/Tools/NewsArticle.php b/Crisscott/Tools/NewsArticle.php new file mode 100644 index 0000000..12d93f7 --- /dev/null +++ b/Crisscott/Tools/NewsArticle.php @@ -0,0 +1,201 @@ +_layout(); + } + + /** + * Lays out the tool. Creates the widgets used by the tool + * and adds them to the container. + * + * @access private + * @return void + */ + private function _layout() + { + // Create a label for the headline. + $this->headline = new GtkLabel(); + + // Create a view for the article. + $this->view = new GtkTextView(); + + // Get the buffer from the view. + $this->buffer = $this->view->get_buffer(); + + // Get a tag for making text bold and dark blue. + $this->tag = new GtkTextTag(); + // Set the tag properties + + // Make the tag part of the buffers tag table. + $tagTable = $this->buffer->get_tag_table(); + $tagTable->add($this->tag); + + // The text in this view should not be editable. + $this->view->set_editable(false); + + // Since the user can't edit the text there is not point in + // letting them see the cursor. + $this->view->set_cursor_visible(false); + + // Pack everything together. + $this->pack_start($this->headline, false, false, 5); + $this->pack_start($this->view); + } + + /** + * Sets the headline and the article text. + * + * @uses setHeadline + * @uses setBody + * + * @access public + * @param string $headline + * @param string $text + * @return void + */ + public function setArticle($headline, $text) + { + // Set the headline. + $this->setHeadline($headline); + + // Set the body. + $this->setBody($text); + } + + /** + * Sets the text of the headline and makes it look like a + * headline. + * + * @access public + * @param string $headline + * @return void + */ + public function setHeadline($headline) + { + // Add some markup to make the headline appear like + // a headline. + $headline = '' . $headline; + $headline.= ''; + + // Set the text of the headline label. + $this->headline->set_text($headline); + + // Make sure the headline is set to use the markup that was added. + $this->headline->set_use_markup(true); + + } + + /** + * Sets the given text as the text of the buffer. + * + * Any time that "Crisscott" is found in the article body, it is + * formatted so that it appears bold and dark blue. This is done + * using tags. + * + * @access public + * @param string $body + * @return void + */ + public function setBody($body) + { + // Do some special formatting of any instances of + // Crisscott found in the article body. + $lastCrisscott = 0; + while ($pos = strpos($body, 'Crisscott', $lastCrisscott)) { + $wordStart = $this->buffer->get_iter_at_offset($pos); + $wordEnd = $this->buffer->get_iter_at_offset($pos); + $wordEnd->forward_word_end(); + + // Apply the tag. + $this->buffer->apply_tag($this->tag, $wordStart, $wordEnd); + + // Update the strpos offset. + $lastCrisscott = $pos; + } + + // Set the article text in the buffer. + $this->buffer->set_text($body); + } + + /** + * Creates or returns a singleton instance of this class. + * + * @static + * @access public + * @return object + */ + public static function singleton() + { + if (!isset(self::$instance)) { + $class = __CLASS__; + self::$instance = new $class; + } + return self::$instance; + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/Crisscott/Tools/NewsFeed.php b/Crisscott/Tools/NewsFeed.php new file mode 100644 index 0000000..db82404 --- /dev/null +++ b/Crisscott/Tools/NewsFeed.php @@ -0,0 +1,199 @@ +rss = new XML_RSS(); + + // Set the input if given. + if (isset($handle)) { + $this->setInput($handle); + } + + // Add the tree column. + $this->addColumn(); + + // Set up the selection to load a selected item. + $selection = $this->get_selection(); + $selection->connect('changed', array($this, 'loadArticle')); + } + + /** + * Sets the input that will be parsed. + * + * @access public + * @param mixed $handle Feed handle. See XML_RSS. + * @return void + */ + public function setInput($handle) + { + $this->rss->setInput($handle); + } + + /** + * Parses the feed and turns it into a list. + * + * @access public + * @return object + */ + public function createList() + { + // Parse the feed. + $this->rss->parse(); + + // Create a list store with four columns. + $listStore = new GtkListStore(Gtk::TYPE_STRING, + Gtk::TYPE_STRING, + Gtk::TYPE_STRING, + Gtk::TYPE_LONG + ); + + // Add a row for each item in the feed. + foreach ($this->rss->getItems() as $item) { + $rowData = array($item['title'], + $item['dc:date'], + $item['description'], + Pango::WEIGHT_BOLD + ); + $listStore->append($rowData); + } + + return $listStore; + } + + /** + * Adds the list to the view to show the feed. + * + * @access public + * @return void + */ + public function showList() + { + // Add the list to the view. + $this->set_model($this->createList()); + } + + /** + * Adds the column to the view and sets the display + * properties. + * + * @access protected + * @return void + */ + protected function addColumn() + { + // Create the column. + $column = new GtkTreeViewColumn(); + $column->set_title('News'); + + // Create a cell renderer. + $cellRenderer = new GtkCellRendererText(); + + // Make the text ellipsize. + $cellRenderer->set_property('ellipsize', Pango::ELLIPSIZE_END); + + // Pack the cell renderer. + $column->pack_start($cellRenderer, true); + $column->add_attribute($cellRenderer, 'text', 0); + $column->add_attribute($cellRenderer, 'weight', 3); + + // Sort the column by date. + $column->set_sort_column_id(1); + + // Add the column to the tree. + $this->append_column($column); + } + + /** + * Loads a selected news item. + * + * @access public + * @param object $selection The selected row of the list. + * @return void + */ + public function loadArticle($selection) + { + // Unbold the selected item. + list($model, $iter) = $selection->get_selected(); + $model->set($iter, 3, Pango::WEIGHT_NORMAL); + + // Get a singleton news article tool. + require_once 'Crisscott/Tools/NewsArticle.php'; + $newsArticle = Crisscott_Tools_NewsArticle::singleton(); + + // Set the article. + $headline = $model->get_value($iter, 0); + $body = $model->get_value($iter, 2); + $newsArticle->setArticle($headline, $body); + + // Bring the news story tab to the front. + require_once 'Crisscott/MainNotebook.php'; + $notebook = Crisscott_MainNotebook::singleton(); + + // Get the page index. + $index = array_search('News Story', array_keys($notebook->pages)); + $notebook->set_current_page($index); + } + + /** + * Creates or returns a singleton instance of this class. + * + * @static + * @access public + * @return object + */ + public static function singleton() + { + if (!isset(self::$instance)) { + $class = __CLASS__; + self::$instance = new $class; + } + return self::$instance; + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/Crisscott/Tools/ProductEdit.php b/Crisscott/Tools/ProductEdit.php new file mode 100644 index 0000000..4b7ab59 --- /dev/null +++ b/Crisscott/Tools/ProductEdit.php @@ -0,0 +1,721 @@ +'; + const ERROR_MARKUP_CLOSE = ''; + + /** + * Singleton instance of this object. + * + * @access public + * @var object + */ + public static $instance; + + /** + * The current/last product being edited. + * + * @access public + * @var object + */ + public $product; + + /** + * A label for the Name entry. + * + * @access public + * @var object + */ + public $nameLabel; + + /** + * A label for the Type entry. + * + * @access public + * @var object + */ + public $typeLabel; + + /** + * A label for the Category entry. + * + * @access public + * @var object + */ + public $categoryLabel; + + /** + * A label for the Price entry. + * + * @access public + * @var object + */ + public $priceLabel; + + /** + * A label for the Description entry. + * + * @access public + * @var object + */ + public $descLabel; + + /** + * A label for the Inventory entry. + * + * @access public + * @var object + */ + public $inventoryLabel; + + /** + * A label for the Availability entry. + * + * @access public + * @var object + */ + public $availLabel; + + /** + * A label for the Width entry. + * + * @access public + * @var object + */ + public $widthLabel; + + /** + * A label for the Height entry. + * + * @access public + * @var object + */ + public $heigthLabel; + + /** + * A label for the Depth entry. + * + * @access public + * @var object + */ + public $depthLabel; + + /** + * A label for the Weight entry. + * + * @access public + * @var object + */ + public $weightLabel; + + /** + * A label for the image. + * + * @access public + * @var object + */ + public $imageLabel; + + /** + * A entry for the Name. + * + * @access public + * @var object + */ + public $nameEntry; + + /** + * A entry for the Type. + * + * @access public + * @var object + */ + public $typeEntry; + + /** + * A entry for the Category. + * + * @access public + * @var object + */ + public $categoryEntry; + + /** + * A entry for the Price. + * + * @access public + * @var object + */ + public $priceEntry; + + /** + * A entry for the Description. + * + * @access public + * @var object + */ + public $descEntry; + + /** + * A entry for the Inventory. + * + * @access public + * @var object + */ + public $inventoryEntry; + + /** + * A entry for the Availability. + * + * @access public + * @var object + */ + public $availEntry; + + /** + * A entry for the Width. + * + * @access public + * @var object + */ + public $widthSpin; + + /** + * A entry for the Height. + * + * @access public + * @var object + */ + public $heightSpin; + + /** + * A entry for the Depth. + * + * @access public + * @var object + */ + public $depthEntry; + + /** + * A entry for the Weight. + * + * @access public + * @var object + */ + public $weightEntry; + + /** + * A container to hold the product image. + * + * @access public + * @var object + */ + public $imageContainer; + + /** + * A entry for the image path. + * + * @access public + * @var object + */ + public $imagePathEntry; + + /** + * Constructor. Sets up the tool. + * + * @access public + * @param object $product An optional product to edit. + * @return void + */ + public function __construct($product = null) + { + // Set up the rows and columns. + parent::__construct(6, 4); + + // Layout the tools. + $this->_layout(); + + // Add product if one was passed in. + if (isset($product)) { + $this->loadProduct($product); + } else { + $this->resetProduct(); + } + } + + private function _layout() + { + // Set up the data entry widgets. + $this->nameEntry = new GtkEntry(); + $this->typeCombo = GtkComboBox::new_text(); + $this->categoryCombo = GtkComboBox::new_text(); + $this->priceSpin = new GtkSpinButton(new GtkAdjustment(1, 1, 1000, .01, 10), .5); + $this->inventorySpin = new GtkSpinButton(new GtkAdjustment(1, 0, 100, 1, 10), .5); + $this->availCombo = GtkComboBox::new_text(); + $this->widthSpin = new GtkSpinButton(new GtkAdjustment(1, 1, 50, .1, 5), .5); + $this->heightSpin = new GtkSpinButton(new GtkAdjustment(1, 1, 50, .1, 5), .5); + $this->depthSpin = new GtkSpinButton(new GtkAdjustment(1, 1, 50, .1, 5), .5); + $this->weightSpin = new GtkSpinButton(new GtkAdjustment(1, 1, 50, .1, 5), .5); + $this->imageContainer = new GtkFrame(); + $this->imagePathEntry = new GtkEntry(); + + // Add two options for the type. + $this->typeCombo->append_text('Digital'); + $this->typeCombo->append_text('Shippable'); + // Make the first category the default. + $this->typeCombo->set_active(0); + + // Add an entry for each category in the inventory. + require_once 'Crisscott/Inventory.php'; + $inventory = Crisscott_Inventory::singleton(); + foreach ($inventory->categories as $cat) { + $this->categoryCombo->append_text($cat->name); + } + // Make the first category the default. + $this->categoryCombo->set_active(0); + + // Add yes/no options for the avialability. + $this->availCombo->append_text('NO'); + $this->availCombo->append_text('YES'); + // Make yes the default. + $this->availCombo->set_active(0); + + // Set the number of decimal places in the spin buttons. + $this->priceSpin->set_digits(2); + $this->inventorySpin->set_digits(0); + $this->widthSpin->set_digits(1); + $this->heightSpin->set_digits(1); + $this->depthSpin->set_digits(1); + $this->weightSpin->set_digits(1); + + // Create the description text view. + $this->descView = new GtkTextView(); + + // We need save and cancel buttons. + $save = GtkButton::new_from_stock('gtk-save'); + $reset = GtkButton::new_from_stock('gtk-undo'); + + // Modify the button's style. + $style = $save->style->copy(); + // Change the normal state. + // Set the background color to dark blue. + $color = new GdkColor(); + $blue = $color->parse('#0A0A6A'); + $style->bg[Gtk::STATE_NORMAL] = $blue; + + // Make the prelight color white. + $style->bg[Gtk::STATE_PRELIGHT] = $style->white; + $save->set_style($style); + + // The label inside the button must be changed too. + $style = new GtkStyle(); + // Change the normal and prelight states. + $style->fg[Gtk::STATE_NORMAL] = $style->white; + $style->fg[Gtk::STATE_PRELIGHT] = $blue; + + // Root through the button's children and grandchildren. + foreach ($save->get_child()->get_children() as $child) { + foreach($child->get_children() as $c) { + // Set the style. + $c->set_style($style); + } + } + + // Connect the buttons to useful methods. + $save->connect_simple('clicked', array($this, 'saveProduct')); + $reset->connect_simple('clicked', array($this, 'resetProduct')); + + // Set up the labels. + $this->nameLabel = new GtkLabel('_Name', true); + $this->typeLabel = new GtkLabel('Type'); + $this->categoryLabel = new GtkLabel('Category'); + $this->priceLabel = new GtkLabel('Price'); + $this->inventoryLabel = new GtkLabel('Inventory'); + $this->availLabel = new GtkLabel('Availability'); + $this->widthLabel = new GtkLabel('Width'); + $this->heightLabel = new GtkLabel('Height'); + $this->depthLabel = new GtkLabel('Depth'); + $this->weightLabel = new GtkLabel('Weight'); + $this->descLabel = new GtkLabel('Description'); + $this->imageLabel = new GtkLabel('Image'); + + // Set the labels' size. + $this->nameLabel->set_size_request(100, -1); + $this->typeLabel->set_size_request(100, -1); + $this->categoryLabel->set_size_request(100, -1); + $this->priceLabel->set_size_request(100, -1); + $this->inventoryLabel->set_size_request(100, -1); + $this->availLabel->set_size_request(100, -1); + $this->widthLabel->set_size_request(100, -1); + $this->heightLabel->set_size_request(100, -1); + $this->depthLabel->set_size_request(100, -1); + $this->weightLabel->set_size_request(100, -1); + $this->descLabel->set_size_request(100, -1); + $this->imageLabel->set_size_request(100, -1); + + // Set the size of the text view also. + $this->descView->set_size_request(300, 150); + // Force the text to wrap. + $this->descView->set_wrap_mode(Gtk::WRAP_WORD); + + // Next align each label within the parent container. + $this->nameLabel->set_alignment(0, .5); + $this->typeLabel->set_alignment(0, .5); + $this->categoryLabel->set_alignment(0, .5); + $this->priceLabel->set_alignment(0, .5); + $this->inventoryLabel->set_alignment(0, .5); + $this->availLabel->set_alignment(0, .5); + $this->widthLabel->set_alignment(0, .5); + $this->heightLabel->set_alignment(0, .5); + $this->depthLabel->set_alignment(0, .5); + $this->weightLabel->set_alignment(0, .5); + $this->descLabel->set_alignment(0, .5); + $this->imageLabel->set_alignment(0, .5); + + // Make all of the labels use markup. + $this->nameLabel->set_use_markup(true); + $this->typeLabel->set_use_markup(true); + $this->categoryLabel->set_use_markup(true); + $this->priceLabel->set_use_markup(true); + $this->inventoryLabel->set_use_markup(true); + $this->availLabel->set_use_markup(true); + $this->widthLabel->set_use_markup(true); + $this->heightLabel->set_use_markup(true); + $this->depthLabel->set_use_markup(true); + $this->weightLabel->set_use_markup(true); + $this->descLabel->set_use_markup(true); + $this->imageLabel->set_use_markup(true); + + // Attach them to the table. + $expandFill = GTK::EXPAND|GTK::FILL; + $this->attach($this->nameLabel, 0, 1, 0, 1, 0, 0); + $this->attach($this->typeLabel, 0, 1, 1, 2, 0, 0); + $this->attach($this->categoryLabel, 0, 1, 2, 3, 0, 0); + $this->attach($this->priceLabel, 0, 1, 3, 4, 0, 0); + $this->attach($this->inventoryLabel, 0, 1, 5, 6, 0, 0); + $this->attach($this->availLabel, 0, 1, 6, 7, 0, 0); + $this->attach($this->widthLabel, 0, 1, 7, 8, 0, 0); + $this->attach($this->heightLabel, 0, 1, 8, 9, 0, 0); + $this->attach($this->depthLabel, 0, 1, 9, 10, 0, 0); + $this->attach($this->weightLabel, 0, 1, 10, 11, 0, 0); + + // Attach the entries too. + $this->attachWithAlign($this->nameEntry, 1, 2, 0, 1, Gtk::FILL, 0); + $this->attachWithAlign($this->typeCombo, 1, 2, 1, 2, GTK::FILL, 0); + //$this->attach($this->typeCombo, 1, 2, 1, 2, 0, 0); + $this->attachWithAlign($this->categoryCombo, 1, 2, 2, 3, Gtk::FILL, 0); + $this->attachWithAlign($this->priceSpin, 1, 2, 3, 4, Gtk::FILL, 0); + $this->attachWithAlign($this->inventorySpin, 1, 2, 5, 6, Gtk::FILL, 0); + $this->attachWithAlign($this->availCombo, 1, 2, 6, 7, Gtk::FILL, 0); + $this->attachWithAlign($this->widthSpin, 1, 2, 7, 8, Gtk::FILL, 0); + $this->attachWithAlign($this->heightSpin, 1, 2, 8, 9, Gtk::FILL, 0); + $this->attachWithAlign($this->depthSpin, 1, 2, 9, 10, Gtk::FILL, 0); + $this->attachWithAlign($this->weightSpin, 1, 2, 10, 11, Gtk::FILL, 0); + + // Attach the image widgets. + $this->attachWithAlign($this->imageContainer, 2, 4, 0, 4, Gtk::FILL, 0); + $this->attachWithAlign($this->imageLabel, 2, 4, 4, 5, Gtk::FILL, 0); + $this->attachWithAlign($this->imagePathEntry, 3, 4, 4, 5, Gtk::FILL, 0); + + // Attach the description widgets. + $this->attachWithAlign($this->descLabel, 2, 3, 5, 6, Gtk::FILL, 0); + $this->attachWithAlign($this->descView, 2, 4, 6, 11, Gtk::FILL, 0); + + // Attache the buttons. + $this->attachWithAlign($reset, 0, 1, 11, 12, Gtk::FILL, 0); + $this->attachWithAlign($save, 3, 4, 11, 12, Gtk::FILL, 0); + + // Associate the mnemonics. + $this->nameLabel->set_mnemonic_widget($this->nameEntry); + $this->nameEntry->connect_simple('mnemonic_activate', array($this, 'reportError'), $this->nameLabel); + } + + /** + * Attaches a widget to the table inside of a GtkAlignment. + * + * This method makes it easy to left align items within a table. + * Simply call this method like you would attach. + * + * @access public + * @see attach + * @return void + */ + public function attachWithAlign($widget, $row1, $row2, $col1, $col2, $xEF, $yEF) + { + $align = new GtkAlignment(0,0,0,.5); + $align->add($widget); + $this->attach($align, $row1, $row2, $col1, $col2, $xEF, $yEF); + } + + /** + * Marks a label up as red text to indicate an error. + * + * @access public + * @param boolean $unknown Seriously, no idea what it means. + * @param object $label The GtkLabel to markup. + * @return void + */ + public function reportError(GtkLabel $label) + { + $label->set_label(self::ERROR_MARKUP_OPEN . $label->get_label() . self::ERROR_MARKUP_CLOSE); + } + + /** + * Clears the error markup from the given label. + * + * @access public + * @param $label The GtkLabel to remove markup from. + * @return void + */ + public function clearError(GtkLabel $label) + { + $text = $label->get_label(); + $text = str_replace(self::ERROR_MARKUP_OPEN, '', $text); + $text = str_replace(self::ERROR_MARKUP_CLOSE, '', $text); + + $label->set_label($text); + } + + /** + * Load the given product into the tool. + * + * @access public + * @param object $product A Crisscott_Product instance. + * @return void + */ + public function loadProduct(Crisscott_Product $product) + { + // First set the product as the current product. + $this->product = $product; + + // Next reset the tool. + $this->resetProduct(); + + // Finally make the notebook page active. + require_once 'Crisscott/MainNotebook.php'; + $notebook = Crisscott_MainNotebook::singleton(); + $notebook->set_current_page($notebook->page_num($notebook->pages['Product Edit'])); + } + + /** + * Sets the values of the tool to the values of the current + * product. + * + * @access public + * @return void + */ + public function resetProduct() + { + // Make sure that there is a product. + if (!isset($this->product)) { + require_once 'Crisscott/Product.php'; + $this->product = new Crisscott_Product(); + } + + // Update the tools in the widget. + $this->nameEntry->set_text($this->product->name); + $this->_setComboActive($this->typeCombo, $this->product->type); + + $inv = Crisscott_Inventory::singleton(); + $cat = $inv->getCategoryById($this->product->categoryId); + $this->_setComboActive($this->categoryCombo, $cat->name); + + $this->priceSpin->set_value($this->product->price); + $this->inventorySpin->set_value($this->product->inventory); + $this->availCombo->set_active($this->product->availability); + $this->widthSpin->set_value($this->product->width); + $this->heightSpin->set_value($this->product->height); + $this->depthSpin->set_value($this->product->depth); + $this->weightSpin->set_value($this->product->weight); + $this->imagePathEntry->set_text($this->product->imagePath); + + if ($this->imageContainer->get_child()) { + $this->imageContainer->remove($this->imageContainer->get_child()); + } + + try { + $pixbuf = GdkPixbuf::new_from_file($this->product->imagePath); + $this->imageContainer->add(GtkImage::new_from_pixbuf($pixbuf)); + $this->imageContainer->show_all(); + } catch (Exception $e) { + // Don't do anything special + } + + $buffer = $this->descView->get_buffer(); + $buffer->set_text($this->product->description); + } + + /** + * Sets the active item in a combo box. + * + * The combo box will be searched for the value given and + * the matching item will be made active. The method returns + * after the first match. + * + * This works for combos created with or without new_text(). + * + * @access private + * @param object $combo + * @param mixed $value + * @return void + */ + private function _setComboActive(GtkComboBox $combo, $value) + { + // Get the underlying model. + $model = $combo->get_model(); + // Get the first iter. + $iter = $model->get_iter_first(); + // Loop through the items. + for ($iter; $model->iter_is_valid($iter); $model->iter_next($iter)) { + // Check for a match. + if ($value == $model->get_value($iter, 0)) { + // A match! Set the active item and get out. + $combo->set_active_iter($iter); + return; + } + } + } + + /** + * Copies the data from the tool to the product. Then asks + * the product to save the data. + * + * @access public + * @return void + */ + public function saveProduct() + { + // Don't save the product if data is being transmitted. + require_once 'Crisscott/Inventory.php'; + if (Crisscott_Inventory::$transmitting) { + // Dialog flags. + $flags = Gtk::DIALOG_MODAL | Gtk::DIALOG_DESTROY_WITH_PARENT; + + // Create the message. + $message = "Products cannot be updated while\n"; + $message.= "data is being transmitted."; + + // Popup a dialog to alert the user. + $dialog = new GtkMessageDialog(null, $flags, Gtk::MESSAGE_WARNING, + Gtk::BUTTONS_CLOSE, $message); + + // Close the dialog when the user clicks the button. + $dialog->connect_simple('response', array($dialog, 'destroy')); + + // Run the dialog. + $dialog->run(); + + // Return false to indicate that the product wasn't updated. + return false; + } + + // Set the product properties. + $this->product->name = $this->nameEntry->get_text(); + $this->product->type = $this->typeCombo->get_active_text(); + + $inv = Crisscott_Inventory::singleton(); + $cat = $inv->getCategoryByName($this->categoryCombo->get_active_text()); + $this->product->categoryId = $cat->categoryId; + + $this->product->price = $this->priceSpin->get_value(); + $this->product->inventory = $this->inventorySpin->get_value(); + $this->product->availability = (boolean)$this->availCombo->get_active(); + $this->product->width = $this->widthSpin->get_value(); + $this->product->height = $this->heightSpin->get_value(); + $this->product->depth = $this->depthSpin->get_value(); + $this->product->weight = $this->weightSpin->get_value(); + $this->product->imagePath = $this->imagePathEntry->get_text(); + + $buffer = $this->descView->get_buffer(); + $this->product->description = $buffer->get_text($buffer->get_start_iter(), $buffer->get_end_iter()); + + // Validate the new values. + $valid = $this->product->validate(); + + // Create a map of all the values and labels. + $labelMap = array( + 'name' => $this->nameLabel, + 'type' => $this->typeLabel, + 'category' => $this->categoryLabel, + 'price' => $this->priceLabel, + 'inventory' => $this->inventoryLabel, + 'avail' => $this->availLabel, + 'width' => $this->widthLabel, + 'height' => $this->heightLabel, + 'depth' => $this->depthLabel, + 'weight' => $this->weightLabel, + 'desc' => $this->descLabel, + 'image' => $this->imageLabel + ); + + // Reset all of the labels. + foreach ($labelMap as $label) { + $this->clearError($label); + } + + // If there are invalid values, markup the labels. + if (is_array($valid)) { + foreach ($valid as $labelKey) { + $this->reportError($labelMap[$labelKey]); + } + + // Validatig the data was not successful. + return false; + } + + try { + // Try to save the data. + $this->product->save(); + + // Mark the buffer as saved. + $this->descView->get_buffer()->set_modified(false); + + // Update the inventory instance. + $inv = Crisscott_Inventory::singleton(); + $inv->refreshInventory(); + + // Also update the product tree. + $pt = Crisscott_Tools_ProductTree::singleton(); + $pt->updateModel(); + + // Let the main window know that something has changed. + require_once 'Crisscott/MainWindow.php'; + Crisscott_MainWindow::$sent = false; + + } catch (Exception $e) { + throw $e; + return false; + } + } + + /** + * Creates or returns a singleton instance of this class. + * + * @static + * @access public + * @return object + */ + public static function singleton() + { + if (!isset(self::$instance)) { + $class = __CLASS__; + self::$instance = new $class; + } + return self::$instance; + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/Crisscott/Tools/ProductSummary.php b/Crisscott/Tools/ProductSummary.php new file mode 100644 index 0000000..c982380 --- /dev/null +++ b/Crisscott/Tools/ProductSummary.php @@ -0,0 +1,255 @@ +set_size_request(60, -1); + $type->set_size_request(60, -1); + $category->set_size_request(60, -1); + $price->set_size_request(60, -1); + + // Next align each label within the parent container. + $name->set_alignment(0, .5); + $type->set_alignment(0, .5); + $category->set_alignment(0, .5); + $price->set_alignment(0, .5); + + // Attach them to the table. + $expandFill = GTK::EXPAND|GTK::FILL; + $this->attach($name, 0, 1, 0, 1, 0, $expandFill); + $this->attach($type, 0, 1, 1, 2, 0, $expandFill); + $this->attach($category, 0, 1, 2, 3, 0, $expandFill); + $this->attach($price, 0, 1, 3, 4, 0, $expandFill); + + // Create the labels for the attributes. + $this->productName = new GtkLabel(); + $this->productType = new GtkLabel(); + $this->productCategory = new GtkLabel(); + $this->productPrice = new GtkLabel(); + + // Allow the labels to wrap. + $this->productName->set_line_wrap(true); + $this->productType->set_line_wrap(true); + $this->productCategory->set_line_wrap(true); + $this->productPrice->set_line_wrap(true); + + // Left align them. + $this->productName->set_alignment(0, .5); + $this->productType->set_alignment(0, .5); + $this->productCategory->set_alignment(0, .5); + $this->productPrice->set_alignment(0, .5); + + // Attach them to the table. + $this->attach($this->productName, 1, 2, 0, 1); + $this->attach($this->productType, 1, 2, 1, 2); + $this->attach($this->productCategory, 1, 2, 2, 3); + $this->attach($this->productPrice, 1, 2, 3, 4); + + // Attach a place holder for the image. + $this->productImage = new GtkFrame('Image'); + // The image's size can be fixed. + $this->productImage->set_size_request(100, 100); + $this->attach($this->productImage, 2, 3, 0, 4, 0, $expandFill); + + // Now that everything is set up, summarize the product. + require_once 'Crisscott/Product.php'; + $product = new Crisscott_Product(); + if (!empty($product)) { + $this->displaySummary($product); + } + } + + /** + * Displays a summary of the given product. + * + * When given a valid product object, this method updates the + * labels and image to match the values of the given product. + * + * @access public + * @param object $product A Crisscott_Product instance. + * @return void + */ + public function displaySummary(Crisscott_Product $product) + { + $this->product = $product; + + // Set the attribute labels to the values of the product. + $this->productName->set_text($product->name); + $this->productType->set_text($product->type); + + // Get the category information. + require_once 'Crisscott/Inventory.php'; + $inv = Crisscott_Inventory::singleton(); + $cat = $inv->getCategoryById($product->categoryId); + // Set the category name. + $this->productCategory->set_text($cat->name); + + // Set the product price. + $this->productPrice->set_text($product->price); + + // Remove the current product image. + if ($this->productImage->get_child()) { + $this->productImage->remove($this->productImage->get_child()); + } + + // Try to add the product image. + try { + // Create a pixbuf. + $pixbuf = GdkPixbuf::new_from_file($product->imagePath); + + // Scale the image. + $pixbuf = $pixbuf->scale_simple(80, 100, Gdk::INTERP_BILINEAR); + + // Create an image from the pixbuf. + $this->productImage->add(GtkImage::new_from_pixbuf($pixbuf)); + // Show the image. + $this->productImage->show_all(); + } catch (Exception $e) { + // Just fail silently. + } + } + + /** + * Returns the currently shown product. + * + * @access public + * @return object The current product. + */ + public function getProduct() + { + return $this->product; + } + + /** + * Loads the current product for editing. + * + * @access public + * @return void + */ + public function editProduct() + { + require_once 'Crisscott/Tools/ProductEdit.php'; + $productEdit = Crisscott_Tools_ProductEdit::singleton(); + + $productEdit->loadProduct($this->product); + } + + /** + * Creates or returns a singleton instance of this class. + * + * @static + * @access public + * @return object + */ + public static function singleton() + { + if (!isset(self::$instance)) { + $class = __CLASS__; + self::$instance = new $class; + } + return self::$instance; + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/Crisscott/Tools/ProductTree.php b/Crisscott/Tools/ProductTree.php new file mode 100644 index 0000000..05ab732 --- /dev/null +++ b/Crisscott/Tools/ProductTree.php @@ -0,0 +1,140 @@ +updateModel(); + } + + public function updateModel() + { + // Create and set the model. + $this->set_model($this->_createModel()); + + // Next set up the view column and cell renderer. + $this->_setupColumn(); + + // Finally, set up the selection. + $this->_setupSelection(); + } + + private function _createModel() + { + // Set up the model. + // Each row should have the row name and the prouct_id. + // If the row is a category the product_id should be zero. + $model = new GtkTreeStore(Gtk::TYPE_STRING, Gtk::TYPE_LONG); + + // Get a singleton of the Inventory object. + require_once 'Crisscott/Inventory.php'; + $inventory = Crisscott_Inventory::singleton(); + + // Add all of the categories. + foreach ($inventory->categories as $category) { + $catIter = $model->append(null, array($category->name, 0)); + // Add all of the products for the category. + foreach ($category->getProducts() as $product) { + $model->append($catIter, array($product['product_name'], $product['product_id'])); + } + } + + return $model; + } + + private function _setupColumn() + { + // Add the name column. + $column = new GtkTreeViewColumn(); + $column->set_title('Products'); + + // Create a renderer for the column. + $cellRenderer = new GtkCellRendererText(); + $column->pack_start($cellRenderer, true); + $column->add_attribute($cellRenderer, 'text', 0); + + // Make the text ellipsize. + $cellRenderer->set_property('ellipsize', Pango::ELLIPSIZE_END); + + // Make the column sort on the product name. + $column->set_sort_column_id(0); + + // Insert the column. + $this->insert_column($column, 0); + } + + private function _setupSelection() + { + // Get the selection object. + $selection = $this->get_selection(); + + // Set the selection to browse mode. + $selection->set_mode(Gtk::SELECTION_BROWSE); + + // Create a signal handler to process the selection. + $selection->connect('changed', array($this, 'sendToSummary')); + } + + public function sendToSummary($selection) + { + // Get the selected row. + list($model, $iter) = $selection->get_selected(); + + // Create a product. + require_once 'Crisscott/Product.php'; + $product = new Crisscott_Product($model->get_value($iter, 1)); + + // Get the singleton product summary. + require_once 'Crisscott/Tools/ProductSummary.php'; + $productSummary = Crisscott_Tools_ProductSummary::singleton(); + $productSummary->displaySummary($product); + } + + /** + * Creates or returns a singleton instance of this class. + * + * @static + * @access public + * @return object + */ + public static function singleton() + { + if (!isset(self::$instance)) { + $class = __CLASS__; + self::$instance = new $class; + } + return self::$instance; + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/Crisscott/Tools/ProgressDialog.php b/Crisscott/Tools/ProgressDialog.php new file mode 100644 index 0000000..1283327 --- /dev/null +++ b/Crisscott/Tools/ProgressDialog.php @@ -0,0 +1,89 @@ +connect_simple('response', array($this, 'destroy')); + // The static properties must also be unset. + $this->connect_simple('destroy', array($this, 'cleanUp')); + + // Add a progress bar. + $this->progress = new GtkProgressBar(); + $this->vbox->pack_start($this->progress); + } + + /** + * Creates or returns a singleton instance of this class. + * + * @static + * @access public + * @param string $title The dialog window's title. + * @param object $parent The parent window. + * @return object + */ + public static function singleton($title = 'Progress', $parent = null) + { + if (!isset(self::$instance)) { + $class = __CLASS__; + self::$instance = new $class($title, $parent); + } + return self::$instance; + } + + public function cleanUp() + { + // Unset the static instance variable. + self::$instance = null; + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/Crisscott/Tools/StatusBar.php b/Crisscott/Tools/StatusBar.php new file mode 100644 index 0000000..0267292 --- /dev/null +++ b/Crisscott/Tools/StatusBar.php @@ -0,0 +1,38 @@ + \ No newline at end of file diff --git a/Crisscott/Tools/Toolbar.php b/Crisscott/Tools/Toolbar.php new file mode 100644 index 0000000..58e5ede --- /dev/null +++ b/Crisscott/Tools/Toolbar.php @@ -0,0 +1,130 @@ +createButtons(); + } + + protected function createButtons() + { + // Create a button to make new products, categories and + // contributors. + $new = new GtkMenuToolButton(GtkImage::new_from_stock(Gtk::STOCK_NEW, Gtk::ICON_SIZE_SMALL_TOOLBAR), 'New'); + $newMenu = new GtkMenu(); + + // Create the menu items. + $product = new GtkMenuItem('Product'); + $newMenu->append($product); + $category = new GtkMenuItem('Category'); + $newMenu->append($category); + $contrib = new GtkMenuItem('Contributor'); + $newMenu->append($contrib); + + // Set the menu as the menu for the new button. + $newMenu->show_all(); + $new->set_menu($newMenu); + + // Add the button to the toolbar. + $this->add($new); + + // Create the signal handlers for the new menu. + require_once 'Crisscott/MainWindow.php'; + //$application = Crisscott_MainWindow::singleton(); + + $new->connect_simple('clicked', array($application, 'newProduct')); + $product->connect_simple('activate', array($application, 'newProduct')); + $category->connect_simple('activate', array($application, 'newCategory')); + $contrib->connect_simple('activate', array($application, 'newContrib')); + + // Create a toggle button that will connect to the database. + //$database = GtkToggleToolButton::new_from_stock(Gtk::STOCK_CONNECT); + //$database->set_label('Connect to Database'); + + // Add the button to the toolbar. + //$this->add($database); + + // Create a button that will transmit the inventory. + $send = new GtkToggleToolButton(); + // Identify the button with a Crisscott logo. + $send->set_icon_widget(GtkImage::new_from_file('Crisscott/images/menuItem.png')); + // Label the button "Send". + $send->set_label('Send'); + + // Add the button to the toolbar. + $this->add($send); + + // Connect a method to start and stop sending the data. + $send->connect_simple('toggled', array($this, 'toggleTransmit'), $send); + + // Create two buttons for sorting the product list. + $sortA = new GtkRadioToolButton(null, 'Ascending'); + $sortA->set_icon_widget(GtkImage::new_from_stock(Gtk::STOCK_SORT_ASCENDING, Gtk::ICON_SIZE_LARGE_TOOLBAR)); + $sortA->set_label('Sort Asc'); + + $sortD = new GtkRadioToolButton($sortA, 'Descending'); + $sortD->set_icon_widget(GtkImage::new_from_stock(Gtk::STOCK_SORT_DESCENDING, Gtk::ICON_SIZE_LARGE_TOOLBAR)); + $sortD->set_label('Sort Desc'); + + // Add the two buttons. + $this->add($sortA); + $this->add($sortD); + } + + /** + * Turns the inventory transmission on or off. + * + * @access public + * @param object $button A GtkToolButton. + */ + public function toggleTransmit(GtkToolButton $button) + { + // Check to see if data is currently being transmitted. + require_once 'Crisscott/Inventory.php'; + if (isset(Crisscott_Inventory::$transmitId)) { + // Remove the idle. + //Gtk::idle_remove(Crisscott_Inventory::$transmitId); + Gtk::timeout_remove(Crisscott_Inventory::$transmitId); + + // Remove the handler id. + Crisscott_Inventory::$transmitId = null; + + // Hide the dialog. + require_once 'Crisscott/Tools/ProgressDialog.php'; + $dialog = Crisscott_Tools_ProgressDialog::singleton(); + $dialog->hide_all(); + + // Turn of the button. + $button->set_active(false); + } else { + // Create a new idle and capture the handler id. + //Crisscott_Inventory::$transmitId = Gtk::idle_add(500, array('Crisscott_Inventory', 'transmitInventory'), $button); + Crisscott_Inventory::$transmitId = Gtk::timeout_add(500, array('Crisscott_Inventory', 'transmitInventory'), $button); + + // Make sure the button is active. + $button->set_active(true); + } + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim: et tw=78 + * vi: ts=1 sw=1 + */ +?> \ No newline at end of file diff --git a/Crisscott/run.php b/Crisscott/run.php new file mode 100644 index 0000000..dd6bcfa --- /dev/null +++ b/Crisscott/run.php @@ -0,0 +1,14 @@ +getMessage() . "\n"; +} + +set_exception_handler('exceptionHandler'); + +require_once 'Crisscott/SplashScreen.php'; +$csSplash = new Crisscott_SplashScreen(); +$csSplash->start(); +?> \ No newline at end of file diff --git a/CrisscottChapter09/Category.php b/CrisscottChapter09/Category.php new file mode 100644 index 0000000..35873be --- /dev/null +++ b/CrisscottChapter09/Category.php @@ -0,0 +1,118 @@ +categoryId = $categoryId; + } else { + throw new Exception('Cannot instantiate category. Invalid categoryId: ' . $categoryId); + } + + // Get the category name. + require_once 'Crisscott/DB.php'; + $db = Crisscott_DB::singleton(); + + $query = 'SELECT category_name '; + $query.= 'FROM categories '; + $query.= 'WHERE category_id = ' . $this->categoryId; + + $row = $db->query($query)->current(); + $this->name = $row['category_name']; + + // Get all of the products for this category. + $this->_getProducts(); + + // Set the specs for the category. + $this->_setSpecs(); + } + + private function _getProducts() + { + require_once 'Crisscott/DB.php'; + $db = Crisscott_DB::singleton(); + + $query = 'SELECT product_id, product_name, description, '; + $query.= ' product_type, category_id, inventory, available, '; + $query.= ' width, height, depth, weight, '; + $query.= ' price '; + $query.= 'FROM products '; + $query.= ' LEFT JOIN product_price '; + $query.= ' USING (product_id) '; + $query.= 'WHERE category_id = ' . $this->categoryId; + $query.= ' AND (currency = \'USD\' OR currency IS NULL) '; + + $this->products = $db->query($query); + } + + private function _setSpecs() + { + $this->specs = array(); + + if (!$this->products->numRows()) { + return; + } + + $this->specs['Total Products'] = $this->products->numRows(); + + $totalPrice = 0; + foreach ($this->products as $product) { + $totalPrice += $product['price']; + } + $this->specs['Avg. Price (USD)'] = number_format($totalPrice / $this->products->numRows(), 2); + + $totalWeight = 0; + foreach ($this->products as $product) { + $totalWeight += $product['weight']; + } + $this->specs['Avg. Weight (Ounces)'] = number_format($totalWeight / $this->products->numRows(), 2); + } + /** + * Returns a list of category specs. + * + * @static + * @access public + * @return object An iterator of specs. + */ + public static function getCategorySpecs() + { + return array('Total Products', 'Avg. Price (USD)', 'Avg. Weight (Ounces)'); + } + + public function getSpecValueByName($spec) + { + return $this->specs[$spec]; + } + + public function getProducts() + { + return $this->products; + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/CrisscottChapter09/Contributor.php b/CrisscottChapter09/Contributor.php new file mode 100644 index 0000000..ec9404d --- /dev/null +++ b/CrisscottChapter09/Contributor.php @@ -0,0 +1,330 @@ +init($contributorId); + } + } + + /** + * Grabs the values from the database and assigns them to + * the proper member variables. + * + * The contributor data is stored in the database. A singleton + * database instance is used to connect to the database and get + * the contributor values. + * + * @access protected + * @param integer $contributorId + * @return void + */ + protected function init($contributorId) + { + // Check the contributorId. + if (!is_numeric($contributorId)) { + throw new Exception('Invalid contributor id: ' . $contributorId); + } + + // Get a singleton DB instance. + require_once 'Crisscott/DB.php'; + $db = Crisscott_DB::singleton(); + + // Create the query. + $query = 'SELECT first_name, middle_name, last_name, '; + $query.= ' website, email, street1, street2, city, '; + $query.= ' state, country, postal '; + $query.= 'FROM contributor '; + $query.= 'WHERE contributor_id = ' . $contributorId . ' '; + + // Submit the query. + $result = $db->query($query); + + // If the query failed, we wouldn't be here. + $this->contributorId = $contributorId; + $this->firstName = $result['first_name']; + $this->middleName = $result['middle_name']; + $this->lastName = $result['last_name']; + $this->website = $result['website']; + $this->email = $result['email']; + $this->street1 = $result['street1']; + $this->street2 = $result['street2']; + $this->city = $result['city']; + $this->state = $result['state']; + $this->country = $result['country']; + $this->postal = $result['postal']; + } + + /** + * Checks the data to see that it is valid data. + * + * @access public + * @return mixed true if all data is valid or an array of invalid elements. + */ + public function validate() + { + $retArray = array(); + + if ($this->firstName != 'tester') { + $retArray[] = 'firstName'; + } + if ($this->lastName != 'test') { + $retArray[] = 'lastName'; + } + + return $retArray; + } + + /** + * Writes the contributor data to the database. + * + * @access public + * @return void + */ + public function save() + { + return true; + // Get a singleton DB instance. + require_once 'Crisscott/DB.php'; + $db = Crisscott_DB::singleton(); + + // Get the query. + if (isset($this->contributorId)) { + $query = $this->_getUpdateQuery(); + $getNewId = false; + } else { + $query = $this->_getInsertQuery(); + $getNewId = true; + } + + // Submit the query; + $db->query($query); + + // Do we need to get the contributorId? + if ($getNewId) { + $this->_getNewId(); + } + } + + /** + * Creates the query for updating information already + * in the database. + * + * @access private + * @return string + */ + private function _getUpdateQuery() + { + $query = 'UPDATE contributor '; + $query.= 'SET '; + $query.= ' first_name = \'' . $this->firstName . '\', '; + $query.= ' middle_name = \'' . $this->middleName . '\', '; + $query.= ' last_name = \'' . $this->lastName . '\', '; + $query.= ' website = \'' . $this->website . '\', '; + $query.= ' email = \'' . $this->email . '\', '; + $query.= ' street1 = \'' . $this->street1 . '\', '; + $query.= ' street2 = \'' . $this->street2 . '\', '; + $query.= ' city = \'' . $this->city . '\', '; + $query.= ' state = \'' . $this->state . '\', '; + $query.= ' country = \'' . $this->country . '\', '; + $query.= ' postal = \'' . $this->postal . '\' '; + $query.= 'WHERE contributorId = ' . $this->contirbutorId . ' '; + + return $query; + } + + /** + * Creates the query for inserting information into + * the database. + * + * @access private + * @return string + */ + private function _getInsertQuery() + { + $query = 'INSERT INTO contributor '; + $query.= '(first_name, middle_name, last_name, website, email, '; + $query.= ' street1, street2, city, state, country, postal) '; + $query.= 'VALUES ( '; + $query.= '\'' . $this->firstName . '\', '; + $query.= '\'' . $this->middleName . '\', '; + $query.= '\'' . $this->lastName . '\', '; + $query.= '\'' . $this->website . '\', '; + $query.= '\'' . $this->email . '\', '; + $query.= '\'' . $this->street1 . '\', '; + $query.= '\'' . $this->street2 . '\', '; + $query.= '\'' . $this->city . '\', '; + $query.= '\'' . $this->state . '\', '; + $query.= '\'' . $this->country . '\', '; + $query.= '\'' . $this->postal . '\' '; + $query.= ') '; + + return $query; + } + + /** + * Sets the contributor id by looking up the value from + * the database. + * + * @access private + * @return void + */ + private function _getNewId() + { + // Get a singleton DB instance. + require_once 'Crisscott/DB.php'; + $db = Crisscott_DB::singleton(); + + // Create the query. + $query = 'SELECT contributor_id '; + $query.= 'FROM contributor '; + $query.= 'WHERE first_name = \'' . $this->firstName . '\' '; + $query.= ' AND middle_name = \'' . $this->middleName . '\', '; + $query.= ' AND last_name = \'' . $this->lastName . '\' '; + $query.= ' AND website = \'' . $this->website . '\' '; + $query.= ' AND email = \'' . $this->email . '\' '; + $query.= ' AND street1 = \'' . $this->street1 . '\' '; + $query.= ' AND street2 = \'' . $this->street2 . '\' '; + $query.= ' AND city = \'' . $this->city . '\' '; + $query.= ' AND state = \'' . $this->state . '\' '; + $query.= ' AND country = \'' . $this->country . '\' '; + $query.= ' AND postal = \'' . $this->postal . '\' '; + + $result = $db->query($query); + + $this->contributorId = $result['contributor_id']; + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim: et tw=78 + * vi: ts=1 sw=1 + */ +?> \ No newline at end of file diff --git a/CrisscottChapter09/DB.php b/CrisscottChapter09/DB.php new file mode 100644 index 0000000..39e3eb6 --- /dev/null +++ b/CrisscottChapter09/DB.php @@ -0,0 +1,81 @@ +db = DB::connect($dsn); + if (PEAR::isError(self::$instance->db)) { + throw new Exception('Failed to connect to database: ' . self::$instance->db->getMessage() . "\n" . self::$instance->db->getUserInfo()); + } + } + return self::$instance; + } + + public function query($sql) + { + // Execute the query. + $result = $this->db->query($sql); + + // Check for errors. + if (PEAR::isError($result)) { + throw new Exception($result->getMessage()); + } elseif ($result == DB_OK) { + return true; + } else { + require_once 'Crisscott/DB/Result.php'; + return new Crisscott_DB_Result($result); + } + } + + public function prepare($sql) + { + $result = $this->db->prepare($sql); + + // Check for errors. + if (PEAR::isError($result)) { + throw new Exception($result->getMessage()); + } else { + return $result; + } + } + + public function execute($handle, $values) + { + $result = $this->db->execute($handle, $values); + + // Check for errors. + if (PEAR::isError($result)) { + var_dump($result); + throw new Exception($result->getMessage()); + } elseif ($result == DB_OK) { + return true; + } else { + return new Crisscott_DB_Result($result); + } + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/CrisscottChapter09/Inventory.php b/CrisscottChapter09/Inventory.php new file mode 100644 index 0000000..fab77a0 --- /dev/null +++ b/CrisscottChapter09/Inventory.php @@ -0,0 +1,113 @@ +refreshInventory(); + } + + public function refreshInventory() + { + // Get the categories. + require_once 'Crisscott/DB.php'; + $db = Crisscott_DB::singleton(); + + $query = 'SELECT category_id '; + $query.= 'FROM categories '; + + require_once 'Crisscott/Category.php'; + foreach ($db->query($query) as $row) { + $this->categories[] = new Crisscott_Category($row['category_id']); + } + } + + /** + * Creates or returns a singleton instance of this class. + * + * @static + * @access public + * @return object + */ + public static function singleton() + { + if (!isset(self::$instance)) { + $class = __CLASS__; + self::$instance = new $class; + } + return self::$instance; + } + + /** + * Returns the category with the given name. + * + * @access public + * @param string $category + * @return object + */ + public function getCategoryByName($category) + { + foreach ($this->categories as $cat) { + if ($cat->name == $category) { + return $cat; + } + } + + return null; + } + + /** + * Returns the category with the given id. + * + * @access public + * @param integer $category + * @return object + */ + public function getCategoryById($category) + { + foreach ($this->categories as $cat) { + if ($cat->categoryId == $category) { + return $cat; + } + } + + return null; + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/CrisscottChapter09/Iterator.php b/CrisscottChapter09/Iterator.php new file mode 100644 index 0000000..8993e2e --- /dev/null +++ b/CrisscottChapter09/Iterator.php @@ -0,0 +1,38 @@ +key = 0; + } + + public function current() { + return $this->current; + } + + public function key() { + return $this->key; + } + + public function next() { + return $this->goToNext(); + } + + + abstract protected function goToPrev(); + + abstract protected function goToNext(); + + public function valid() { + return isset($this->current); + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/CrisscottChapter09/MainNotebook.php b/CrisscottChapter09/MainNotebook.php new file mode 100644 index 0000000..d5f12c2 --- /dev/null +++ b/CrisscottChapter09/MainNotebook.php @@ -0,0 +1,84 @@ +pages = array(); + foreach ($titles as $title) { + $pageNum = $this->append_page(new GtkVBox(), new GtkLabel($title, true)); + $page = $this->get_nth_page($pageNum); + $this->pages[$title] = $page; + } + + $this->set_show_tabs(false); + + // Add a productediting instance to the notebook. + require_once 'Crisscott/Tools/ProductEdit.php'; + $this->pages['Product _Edit']->add(Crisscott_Tools_ProductEdit::singleton()); + + // Add an category summary instance. + require_once 'Crisscott/Tools/CategorySummary.php'; + require_once 'Crisscott/Inventory.php'; + $this->pages['_Category Summary']->add(new Crisscott_Tools_CategorySummary(Crisscott_Inventory::singleton())); + + // Add a contributoredit instance. + require_once 'Crisscott/Tools/ContributorEdit.php'; + $this->pages['Contributor Edit']->add(new Crisscott_Tools_ContributorEdit()); + + // Add the news article tool. + require_once 'Crisscott/Tools/NewsArticle.php'; + $news = Crisscott_Tools_NewsArticle::singleton(); + $this->pages['News Story']->add($news); + } + + /** + * Creates or returns a singleton instance of this class. + * + * @static + * @access public + * @return object + */ + public static function singleton() + { + if (!isset(self::$instance)) { + $class = __CLASS__; + self::$instance = new $class; + } + return self::$instance; + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/CrisscottChapter09/MainWindow.php b/CrisscottChapter09/MainWindow.php new file mode 100644 index 0000000..0dd6477 --- /dev/null +++ b/CrisscottChapter09/MainWindow.php @@ -0,0 +1,91 @@ +set_size_request(500, 300); + $this->set_position(Gtk::WIN_POS_CENTER); + $this->set_title('Criscott PIMS'); + + $this->_populate(); + + $this->maximize(); + $this->set_icon_from_file('Crisscott/images/logo.png'); + + $this->connect_simple('destroy', array('gtk', 'main_quit')); + } + + private function _populate() + { + $table = new GtkTable(5, 3); + + $expandFill = GTK::EXPAND|GTK::FILL; + + $table->attach(new GtkFrame('MENU'), 0, 2, 0, 1, $expandFill, 0, 0, 0); + $table->attach(new GtkFrame('TOOLBAR'), 0, 2, 1, 2, $expandFill, 0, 0, 0); + + require_once 'Crisscott/Tools/ProductTree.php'; + $productTree = Crisscott_Tools_ProductTree::singleton(); + $productTree->set_size_request(150, 150); + + $table->attach($productTree, 0, 1, 2, 3, 0, $expandFill, 0, 0); + + require_once 'Crisscott/Tools/NewsFeed.php'; + $feed = 'chapter9/news.rss'; + $news = Crisscott_Tools_NewsFeed::singleton(); + $news->setInput($feed); + $news->showList(); + $news->set_size_request(150, -1); + + $table->attach($news, 0, 1, 3, 4, 0, $expandFill, 0, 0); + + $table2 = new GtkTable(2, 2); + + $productSummary = new GtkFrame('PRODUCT SUMMARY'); + $productSummary->set_size_request(-1, 150); + + // Add the product summary tool. + require_once 'Crisscott/Tools/ProductSummary.php'; + $this->productSummary = Crisscott_Tools_ProductSummary::singleton(); + $productSummary->add($this->productSummary); + + $table2->attach($productSummary, 0, 1, 0, 1, $expandFill, 0, 1, 1); + + $inventorySummary = new GtkFrame('INVENTORY SUMMARY'); + $inventorySummary->set_size_request(-1, 150); + + $table2->attach($inventorySummary, 1, 2, 0, 1, $expandFill, 0, 1, 1); + + require_once 'Crisscott/MainNotebook.php'; + $this->mainNotebook = Crisscott_MainNotebook::singleton(); + $table2->attach($this->mainNotebook, 0, 2, 1, 2, $expandFill, $expandFill, 1, 1); + + $table->attach($table2, 1, 2, 2, 4, $expandFill, $expandFill, 0, 0); + + require_once 'Crisscott/Tools/StatusBar.php'; + $table->attach(Crisscott_Tools_StatusBar::singleton(), 0, 2, 4, 5, $expandFill, 0, 0, 0); + + $this->add($table); + } + + public function connectToServer() + { + sleep(1); + } + + public function connectToLocalDB() + { + sleep(1); + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/CrisscottChapter09/Product.php b/CrisscottChapter09/Product.php new file mode 100644 index 0000000..2698ebc --- /dev/null +++ b/CrisscottChapter09/Product.php @@ -0,0 +1,308 @@ +init($productId); + } + + protected function init($productId) + { + // Check the product id. + if (!is_numeric($productId)) { + throw new Exception('Cannot initialize product. Invalid productId: ' . $productId); + } else { + $this->productId = $productId; + } + + require_once 'Crisscott/DB.php'; + $db = Crisscott_DB::singleton(); + + $query = 'SELECT product_name, description, product_type, '; + $query.= ' category_id, inventory, available, width, height, '; + $query.= ' depth, weight, '; + $query.= ' price '; + $query.= 'FROM products '; + $query.= ' LEFT JOIN product_price '; + $query.= ' USING (product_id) '; + $query.= 'WHERE product_id = ' . $this->productId . ' '; + $query.= ' AND (currency = \'USD\' OR currency IS NULL) '; + + $row = $db->query($query)->current(); + if (count($row)) { + $this->name = $row['product_name']; + $this->description = $row['description']; + $this->type = $row['product_type']; + $this->categoryId = $row['category_id']; + $this->inventory = $row['inventory']; + $this->availability = $row['available'] == 't'; + $this->width = $row['width']; + $this->height = $row['height']; + $this->depth = $row['depth']; + $this->weight = $row['weight']; + $this->price = $row['price']; + $this->currency = $row['currency']; + } + } + + /** + * Checks to see if the values of the product are valid. + * + * @access public + * @return mixed true or an array of the invalid fields. + */ + public function validate() + { + $invalidFields = array(); + + // Check the easy fields first. + // All of these fields must be numbers. + $numbers = array('price', + 'inventory', + 'width', + 'height', + 'depth', + 'weight' + ); + foreach ($numbers as $numField) { + if (!is_numeric($this->$numField)) { + $invalidFields[] = $numField; + } + } + + // Check the length of the product name. + if (strlen($this->name) > 50 || strlen($this->name) < 1) { + $invalidFields[] = 'name'; + } + + // Check that the availability is a boolean. + if (!is_bool($this->availability)) { + $invalidFields[] = 'availability'; + } + + // Check the product type. + $validTypes = array('Shippable', 'Digital'); + if (!in_array($this->type, $validTypes)) { + $invalidFields[] = 'type'; + } + + // Check the category. + if (empty($this->categoryId) || !is_numeric($this->categoryId)) { + $invalidFields[] = 'category'; + } + + // The description can't really be invalid. + if (count($invalidFields)) { + return $invalidFields; + } else { + return true; + } + } + + public function save() + { + // Save the product information to the database. + if (isset($this->productId) && $this->productId > 0) { + return $this->updateProduct(); + } else { + return $this->saveNewProduct(); + } + } + + protected function updateProduct() + { + require_once 'Crisscott/DB.php'; + $db = Crisscott_DB::singleton(); + + $query = 'UPDATE products '; + $query = 'SET '; + $query.= ' product_name = ?, '; + $query.= ' description = ?, '; + $query.= ' product_type = ?, '; + $query.= ' category_id = ?, '; + $query.= ' inventory = ?, '; + $query.= ' available = ?, '; + $query.= ' width = ?, '; + $query.= ' height = ?, '; + $query.= ' depth = ?, '; + $query.= ' weight = ? '; + $query.= 'WHERE product_id = ? '; + + $stmt = $db->prepare($query); + + $prodArray = array( + $this->name, + $this->description, + $this->type, + $this->categoryId, + $this->inventory, + $this->availability, + $this->width, + $this->height, + $this->depth, + $this->weight, + $this->productId + ); + + $db->execute($stmt, $prodArray); + + return true; + } + + protected function saveNewProduct() + { + require_once 'Crisscott/DB.php'; + $db = Crisscott_DB::singleton(); + + $query = 'INSERT INTO products '; + $query.= '(product_name, description, product_type, '; + $query.= ' category_id, inventory, available, width, height, '; + $query.= ' depth, weight) '; + $query.= 'VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) '; + + $stmt = $db->prepare($query); + + $prodArray = array( + $this->name, + $this->description, + $this->type, + $this->categoryId, + $this->inventory, + $this->availability, + $this->width, + $this->height, + $this->depth, + $this->weight + ); + + + $db->execute($stmt, $prodArray); + + // Get the new product id. + $query = 'SELECT MAX(product_id) '; + $query.= 'FROM products '; + + $this->productId = reset($db->query($query)->current()); + + return true; + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/CrisscottChapter09/SplashScreen.php b/CrisscottChapter09/SplashScreen.php new file mode 100644 index 0000000..e1b535a --- /dev/null +++ b/CrisscottChapter09/SplashScreen.php @@ -0,0 +1,105 @@ +set_decorated(false); + + //$this->set_size_request(300, 100); + $this->set_position(Gtk::WIN_POS_CENTER); + + // Set the background. + $style = $this->style->copy(); + $style->bg[Gtk::STATE_NORMAL] = $style->white; + $this->set_style($style); + + $this->_populate(); + + $this->set_keep_above(true); + + $this->connect_simple_after('show', array($this, 'startMainWindow')); + } + + private function _populate() + { + $frame = new GtkFrame(); + $hBox = new GtkHBox(); + $vBox = new GtkVBox(); + $logoBox = new GtkHBox(); + $statusBox = new GtkHBox(); + + $frame->set_shadow_type(Gtk::SHADOW_ETCHED_OUT); + + $logo = new GtkLabel('Crisscott Product Information Management System'); + $logo->set_use_markup(true); + + $this->status = new GtkLabel('Initializing Main Window'); + + $vBox->pack_start($logoBox, true, true, 10); + $vBox->pack_start($statusBox, true, true, 10); + + $logoBox->pack_start($logo); + $statusBox->pack_start($this->status); + + // Add a logo image. + $logoImg = GtkImage::new_from_file('Crisscott/images/logo.png'); + + $hBox->pack_start($logoImg, false, false, 10); + $hBox->pack_start($vBox, false, false, 10); + $frame->add($hBox); + + $this->add($frame); + } + + + public function start() + { + $this->show_all(); + gtk::main(); + } + + public function startMainWindow() + { + // Update the GUI. + while (gtk::events_pending()) gtk::main_iteration(); + // Give the user enough time to at least see the message. + + require_once 'Crisscott/MainWindow.php'; + $main = new Crisscott_MainWindow(); + + $this->status->set_text('Connecting to server...'); + while (gtk::events_pending()) gtk::main_iteration(); + + if ($main->connectToServer()) { + $this->status->set_text('Connecting to server... OK'); + } + while (gtk::events_pending()) gtk::main_iteration(); + + $this->status->set_text('Connecting to local database...'); + while (gtk::events_pending()) gtk::main_iteration(); + + if ($main->connectToLocalDB()) { + $this->status->set_text('Connecting to local database... OK'); + } + while (gtk::events_pending()) gtk::main_iteration(); + + $main->show_all(); + + $this->set_keep_above(false); + $this->hide(); + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/CrisscottChapter09/Tools/CategorySummary.php b/CrisscottChapter09/Tools/CategorySummary.php new file mode 100644 index 0000000..bf5f7d8 --- /dev/null +++ b/CrisscottChapter09/Tools/CategorySummary.php @@ -0,0 +1,140 @@ +attachColHeaders(); + + // If an inventory was passed, add the data to the table. + if (!empty($inventory)) { + $this->summarizeInventory($inventory); + } + } + + /** + * Summarizes all the categories in the inventory. + * + * First clears out the table. Next re-attches the column + * headers. Finally each category is added as its own row. + * + * @access public + * @param object $inventory A Crisscott_Inventory instance. + * @return void + */ + public function summarizeInventory(Crisscott_Inventory $inventory) + { + // Clear out the table. + $this->clear(); + + // Re-attach the headers. + $this->attachColHeaders(); + + // Add a row for each category. + foreach ($inventory->categories as $category) { + $this->summarizeCategory($category); + } + } + + /** + * Attaches column headers to the table. + * + * @access protected + * @return void + */ + protected function attachColHeaders() + { + require_once 'Crisscott/Category.php'; + foreach (Crisscott_Category::getCategorySpecs() as $key => $spec) { + $label = new GtkLabel($spec); + $label->set_angle(90); + $label->set_alignment(.5, 1); + + // Leave the first cell empty. + $this->attach($label, $key + 1, $key + 2, 0, 1, 0, GTK::FILL, 10, 10); + } + + // Increment the last row. + $this->lastRow++; + + } + + /** + * Adds a row of data for the given category. + * + * @access public + * @param object $category A Crisscott_Category instance. + * @return void + */ + public function summarizeCategory(Crisscott_Category $category) + { + // First attach the category name. + $nameLabel = new GtkLabel($category->name); + $nameLabel->set_alignment(0, .5); + $this->attach($nameLabel, 0, 1, $this->lastRow, $this->lastRow + 1, GTK::FILL, 0, 10, 10); + + // Next attach the spec values. + foreach (Crisscott_Category::getCategorySpecs() as $key => $spec) { + $value = $category->getSpecValueByName($spec); + $this->attach(new GtkLabel($value), $key + 1, $key + 2, $this->lastRow, $this->lastRow + 1, 0, 0, 1, 1); + } + + // Increment the last row. + $this->lastRow++; + } + + /** + * Clears all cells of the table. + * + * @access protected + * @return void + */ + protected function clear() + { + foreach ($this->get_children() as $child) { + $this->remove($child); + } + + // Reset the last row. + $this->lastRow = 0; + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim: et tw=78 + * vi: ts=1 sw=1 + */ +?> \ No newline at end of file diff --git a/CrisscottChapter09/Tools/ContributorEdit.php b/CrisscottChapter09/Tools/ContributorEdit.php new file mode 100644 index 0000000..d6b96a4 --- /dev/null +++ b/CrisscottChapter09/Tools/ContributorEdit.php @@ -0,0 +1,557 @@ +'; + const ERROR_MARKUP_CLOSE = ''; + + /** + * The contributor currently being modified. + * + * @access public + * @var object + */ + public $contributor; + + /** + * A label for the contributor's first name. + * + * @access private + * @var object + */ + private $firstNameLabel; + + /** + * A label for the contributor's middle name. + * + * @access private + * @var object + */ + private $middleNameLabel; + + /** + * A label for the contributor's last name. + * + * @access private + * @var object + */ + private $lastNameLabel; + + /** + * A label for the contributor's web address. + * + * @access private + * @var object + */ + private $websiteLabel; + + /** + * A label for the contributor's email address. + * + * @access private + * @var object + */ + private $emailLabel; + + /** + * A label for the contributor's street address. + * + * @access private + * @var object + */ + private $street1Label; + + /** + * A label for the contributor's street address. + * + * @access private + * @var object + */ + private $street2Label; + + /** + * A label for the contributor's city. + * + * @access private + * @var object + */ + private $cityLabel; + + /** + * A label for the contributor's state. + * + * @access private + * @var object + */ + private $stateLabel; + + /** + * A label for the contributor's country. + * + * @access private + * @var object + */ + private $countryLabel; + + /** + * A label for the contributor's postal code. + * + * @access private + * @var object + */ + private $postalLabel; + + /** + * A entry for the contributor's first name. + * + * @access private + * @var object + */ + private $firstNameEntry; + + /** + * A entry for the contributor's middle name. + * + * @access private + * @var object + */ + private $middleNameEntry; + + /** + * A entry for the contributor's last name. + * + * @access private + * @var object + */ + private $lastNameEntry; + + /** + * A entry for the contributor's web address. + * + * @access private + * @var object + */ + private $websiteEntry; + + /** + * A entry for the contributor's email address. + * + * @access private + * @var object + */ + private $emailEntry; + + /** + * A entry for the contributor's street address. + * + * @access private + * @var object + */ + private $street1Entry; + + /** + * A entry for the contributor's street address. + * + * @access private + * @var object + */ + private $street2Entry; + + /** + * A entry for the contributor's city. + * + * @access private + * @var object + */ + private $cityEntry; + + /** + * A entry for the contributor's state. + * + * @access private + * @var object + */ + private $stateEntry; + + /** + * A entry for the contributor's country. + * + * @access private + * @var object + */ + private $countryComboBox; + + /** + * A entry for the contributor's postal code. + * + * @access private + * @var object + */ + private $postalEntry; + + /** + * Constructor. Calls the methods to create the tool and + * sets up the needed callbacks. + * + * @access public + * @param object $contributor A Crisscott_Contributor instance. (Optional) + * @return void + */ + public function __construct($contributor = null) + { + // Call the parent constructor. + parent::__construct(7, 4); + + // Layout the tool. + $this->_layoutTool(); + + // Connect the needed callbacks. + + // Prepopulate the fields if a contributor is given. + if (empty($contributor) || is_a($contributor, 'Crisscott_Contributor')) { + require_once 'Crisscott/Contributor.php'; + $contributor = new Crisscott_Contributor(); + } + $this->populateFields($contributor); + } + + /** + * Laysout the labels, entries and buttons. + * + * This tool consists of several labels with corresponding entries + * and two buttons. One button resets the fields the other submits + * the information to change the contributors values. + * + * @access private + * @return void + */ + private function _layoutTool() + { + // First create the labels that identify the fields. + $this->firstNameLabel = new GtkLabel('First Name'); + $this->middleNameLabel = new GtkLabel('Middle Name'); + $this->lastNameLabel = new GtkLabel('Last Name'); + $this->emailLabel = new GtkLabel('Email Address'); + $this->websiteLabel = new GtkLabel('Website'); + $this->street1Label = new GtkLabel('Street 1'); + $this->street2Label = new GtkLabel('Street 2'); + $this->cityLabel = new GtkLabel('City'); + $this->stateLabel = new GtkLabel('State'); + $this->countryLabel = new GtkLabel('Country'); + $this->postalLabel = new GtkLabel('Postal Code'); + + // Next add the labels to the table. + // The labels will be added in two columns. + // First column. + $this->attach($this->firstNameLabel, 0, 1, 0, 1, GTK::FILL, 0); + $this->attach($this->middleNameLabel, 0, 1, 1, 2, GTK::FILL, 0); + $this->attach($this->lastNameLabel, 0, 1, 2, 3, GTK::FILL, 0); + $this->attach($this->emailLabel, 0, 1, 3, 4, GTK::FILL, 0); + $this->attach($this->websiteLabel, 0, 1, 4, 5, GTK::FILL, 0); + + // Second column. + $this->attach($this->street1Label, 2, 3, 0, 1, GTK::FILL, 0); + $this->attach($this->street2Label, 2, 3, 1, 2, GTK::FILL, 0); + $this->attach($this->cityLabel, 2, 3, 2, 3, GTK::FILL, 0); + $this->attach($this->stateLabel, 2, 3, 3, 4, GTK::FILL, 0); + $this->attach($this->countryLabel, 2, 3, 4, 5, GTK::FILL, 0); + $this->attach($this->postalLabel, 2, 3, 5, 6, GTK::FILL, 0); + + // Right align all of the labels. + $this->firstNameLabel->set_alignment(1, .5); + $this->middleNameLabel->set_alignment(1, .5); + $this->lastNameLabel->set_alignment(1, .5); + $this->emailLabel->set_alignment(1, .5); + $this->websiteLabel->set_alignment(1, .5); + $this->street1Label->set_alignment(1, .5); + $this->street2Label->set_alignment(1, .5); + $this->cityLabel->set_alignment(1, .5); + $this->stateLabel->set_alignment(1, .5); + $this->countryLabel->set_alignment(1, .5); + $this->postalLabel->set_alignment(1, .5); + + // Turn on markup + $this->firstNameLabel->set_use_markup(true); + $this->middleNameLabel->set_use_markup(true); + $this->lastNameLabel->set_use_markup(true); + $this->emailLabel->set_use_markup(true); + $this->websiteLabel->set_use_markup(true); + $this->street1Label->set_use_markup(true); + $this->street2Label->set_use_markup(true); + $this->cityLabel->set_use_markup(true); + $this->stateLabel->set_use_markup(true); + $this->countryLabel->set_use_markup(true); + $this->postalLabel->set_use_markup(true); + + // Next create all of the data collection widgets. + $this->firstNameEntry = new GtkEntry(); + $this->middleNameEntry = new GtkEntry(); + $this->lastNameEntry = new GtkEntry(); + $this->emailEntry = new GtkEntry(); + $this->websiteEntry = new GtkEntry(); + $this->street1Entry = new GtkEntry(); + $this->street2Entry = new GtkEntry(); + $this->cityEntry = new GtkEntry(); + $this->stateEntry = new GtkEntry(); + $this->postalEntry = new GtkEntry(); + + // The country should be a combobox. + $this->countryComboBox = GtkComboBox::new_text(); + $this->countryComboBox->append_text('United States'); + $this->countryComboBox->prepend_text('Canada'); + $this->countryComboBox->insert_text(1, 'United Kingdom'); + $this->countryComboBox->set_active(0); + + // Next add the entrys to the table. + // The entrys will be added in two columns. + // First column. + $this->attach($this->firstNameEntry, 1, 2, 0, 1, 0, 0); + $this->attach($this->middleNameEntry, 1, 2, 1, 2, 0, 0); + $this->attach($this->lastNameEntry, 1, 2, 2, 3, 0, 0); + $this->attach($this->emailEntry, 1, 2, 3, 4, 0, 0); + $this->attach($this->websiteEntry, 1, 2, 4, 5, 0, 0); + + // Second column. + $this->attach($this->street1Entry, 3, 4, 0, 1, 0, 0); + $this->attach($this->street2Entry, 3, 4, 1, 2, 0, 0); + $this->attach($this->cityEntry, 3, 4, 2, 3, 0, 0); + $this->attach($this->stateEntry, 3, 4, 3, 4, 0, 0); + $this->attach($this->countryComboBox, 3, 4, 4, 5, 0, 0); + $this->attach($this->postalEntry, 3, 4, 5, 6, 0, 0); + + // Help the user out with the state by using a GtkEntryCompletion. + $stateCompletion = new GtkEntryCompletion(); + $stateCompletion->set_model(self::createStateList()); + $stateCompletion->set_text_column(0); + $this->stateEntry->set_completion($stateCompletion); + $stateCompletion->set_inline_completion(true); + + // Add the save and clear buttons. + $save = GtkButton::new_from_stock('gtk-save'); + $reset = GtkButton::new_from_stock('gtk-undo'); + $save->connect_simple('clicked', array($this, 'saveContributor')); + $reset->connect_simple('clicked', array($this, 'resetContributor')); + + $this->attach($reset, 0, 1, 6, 7, 0, 0); + $this->attach($save, 3, 4, 6, 7, 0, 0); + } + + /** + * Creates a one column list store that contains the US states + * and Canadian provinces. + * + * This list can be used for combo boxes, tree, or entry + * completions. + * + * @static + * @access public + * @return object A GtkListStore + */ + public static function createStateList() + { + $listStore = new GtkListStore(GTK::TYPE_STRING); + $iter = $listStore->append(); + $listStore->set($iter, 0, 'Alabama'); + $iter = $listStore->append(); + $listStore->set($iter, 0, 'Alaska'); + $iter = $listStore->append(); + $listStore->set($iter, 0, 'Arizona'); + $iter = $listStore->append(); + $listStore->set($iter, 0, 'Arkansas'); + $iter = $listStore->append(); + $listStore->set($iter, 0, 'California'); + $iter = $listStore->append(); + $listStore->set($iter, 0, 'Colorodo'); + + return $listStore; + } + + /* + * + * When a contributor is edited, it is stored in a member variable + * and then its values are used to populate the fields. + * + * @access public + * @param object $contributor A Crisscott_Contributor instance. + * @return void + */ + public function populateFields(Crisscott_Contributor $contributor) + { + // Populate the fields. + $this->firstNameEntry->set_text($contributor->firstName); + $this->middleNameEntry->set_text($contributor->middleName); + $this->lastNameEntry->set_text($contributor->lastName); + $this->emailEntry->set_text($contributor->email); + $this->websiteEntry->set_text($contributor->website); + + $this->street1Entry->set_text($contributor->street1); + $this->street2Entry->set_text($contributor->street2); + $this->cityEntry->set_text($contributor->city); + $this->stateEntry->set_text($contributor->state); + $this->postalEntry->set_text($contributor->postal); + + // Set the active element for the country combo box. + $model = $this->countryComboBox->get_model(); + $iter = $model->get_iter_first(); + for ($iter; $model->iter_is_valid($iter); $model->iter_next($iter)) { + if ($this->contributor->country == $model->get_value($iter, 0)) { + $this->countryComboBox->set_active_iter($iter); + } + } + + // Keep a hold of the contributor. + $this->contributor = $contributor; + } + + /** + * Resets the fields with the original contributor data. + * + * This method basically is an undo for all changes that + * have been made since the last save. It re-grabs the + * values from the contributor and populates them again. + * + * @uses populateFields + * + * @access public + * @return void + */ + public function resetContributor() + { + // Make sure we have a contributor already. + if (!isset($this->contributor)) { + require_once 'Crisscott/Contributor.php'; + $this->contributor = new Crisscott_Contributor(); + $this->contributor->country = 'United States'; + } + + // Reset the fields to the original value. + $this->populateFields($this->contributor); + } + + /** + * Grabs, validates, and saves the contributor information. + * + * First this method collects the data values from the widgets + * and then assigns them to the contributor object. Next the + * values are validated using the contributors validate method. + * If all is ok, the contributor is told to write the data to + * the database. + * + * @access public + * @return boolean true on success. + */ + public function saveContributor() + { + // First grab all of the values. + $this->contributor->firstName = $this->firstNameEntry->get_text(); + $this->contributor->middleName = $this->firstNameEntry->get_text(); + $this->contributor->lastName = $this->lastNameEntry->get_text(); + $this->contributor->website = $this->websiteEntry->get_text(); + $this->contributor->email = $this->emailEntry->get_text(); + $this->contributor->street1 = $this->street1Entry->get_text(); + $this->contributor->street1 = $this->street1Entry->get_text(); + $this->contributor->city = $this->cityEntry->get_text(); + $this->contributor->state = $this->stateEntry->get_text(); + //$this->contributor->country = $this->countryComboBox->get_text(); + $this->contributor->postal = $this->postalEntry->get_text(); + + // Next validate the data. + $valid = $this->contributor->validate(); + + // Create a map of all the values and labels. + $labelMap = array('firstName' => $this->firstNameLabel, + 'middleName' => $this->middleNameLabel, + 'lastName' => $this->lastNameLabel, + 'website' => $this->websiteLabel, + 'email' => $this->emailLabel, + 'street1' => $this->street1Label, + 'street2' => $this->street2Label, + 'city' => $this->cityLabel, + 'state' => $this->stateLabel, + 'country' => $this->countryLabel, + 'postal' => $this->postalLabel + ); + + // Reset all of the labels. + foreach ($labelMap as $label) { + $this->clearError($label); + } + + // If there are invalid values, markup the labels. + if (is_array($valid)) { + foreach ($valid as $labelKey) { + $this->reportError($labelMap[$labelKey]); + } + + // Saving the data was not successful. + return false; + } + + // Try to save the data. + return $this->contributor->save(); + } + + /** + * Marks a label up as red text to indicate an error. + * + * @access public + * @param object $label The GtkLabel to markup. + * @return void + */ + public function reportError(GtkLabel $label) + { + require_once 'Crisscott/Tools/StatusBar.php'; + $status = Crisscott_Tools_StatusBar::singleton(); + var_dump($status->push(rand(), 'Error: ' . $label->get_label())); + + $label->set_label(self::ERROR_MARKUP_OPEN . $label->get_label() . self::ERROR_MARKUP_CLOSE); + } + + /** + * Clears the error markup from the given label. + * + * @access public + * @param $label The GtkLabel to remove markup from. + * @return void + */ + public function clearError(GtkLabel $label) + { + require_once 'Crisscott/Tools/StatusBar.php'; + $status = Crisscott_Tools_StatusBar::singleton(); + $contextId = $status->get_context_id('Error: ' . $label->get_label()); + $status->pop($contextId); + + $text = $label->get_label(); + $text = str_replace(self::ERROR_MARKUP_OPEN, '', $text); + $text = str_replace(self::ERROR_MARKUP_CLOSE, '', $text); + + $label->set_label($text); + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim: et tw=78 + * vi: ts=1 sw=1 + */ +?> \ No newline at end of file diff --git a/CrisscottChapter09/Tools/NewsArticle.php b/CrisscottChapter09/Tools/NewsArticle.php new file mode 100644 index 0000000..12d93f7 --- /dev/null +++ b/CrisscottChapter09/Tools/NewsArticle.php @@ -0,0 +1,201 @@ +_layout(); + } + + /** + * Lays out the tool. Creates the widgets used by the tool + * and adds them to the container. + * + * @access private + * @return void + */ + private function _layout() + { + // Create a label for the headline. + $this->headline = new GtkLabel(); + + // Create a view for the article. + $this->view = new GtkTextView(); + + // Get the buffer from the view. + $this->buffer = $this->view->get_buffer(); + + // Get a tag for making text bold and dark blue. + $this->tag = new GtkTextTag(); + // Set the tag properties + + // Make the tag part of the buffers tag table. + $tagTable = $this->buffer->get_tag_table(); + $tagTable->add($this->tag); + + // The text in this view should not be editable. + $this->view->set_editable(false); + + // Since the user can't edit the text there is not point in + // letting them see the cursor. + $this->view->set_cursor_visible(false); + + // Pack everything together. + $this->pack_start($this->headline, false, false, 5); + $this->pack_start($this->view); + } + + /** + * Sets the headline and the article text. + * + * @uses setHeadline + * @uses setBody + * + * @access public + * @param string $headline + * @param string $text + * @return void + */ + public function setArticle($headline, $text) + { + // Set the headline. + $this->setHeadline($headline); + + // Set the body. + $this->setBody($text); + } + + /** + * Sets the text of the headline and makes it look like a + * headline. + * + * @access public + * @param string $headline + * @return void + */ + public function setHeadline($headline) + { + // Add some markup to make the headline appear like + // a headline. + $headline = '' . $headline; + $headline.= ''; + + // Set the text of the headline label. + $this->headline->set_text($headline); + + // Make sure the headline is set to use the markup that was added. + $this->headline->set_use_markup(true); + + } + + /** + * Sets the given text as the text of the buffer. + * + * Any time that "Crisscott" is found in the article body, it is + * formatted so that it appears bold and dark blue. This is done + * using tags. + * + * @access public + * @param string $body + * @return void + */ + public function setBody($body) + { + // Do some special formatting of any instances of + // Crisscott found in the article body. + $lastCrisscott = 0; + while ($pos = strpos($body, 'Crisscott', $lastCrisscott)) { + $wordStart = $this->buffer->get_iter_at_offset($pos); + $wordEnd = $this->buffer->get_iter_at_offset($pos); + $wordEnd->forward_word_end(); + + // Apply the tag. + $this->buffer->apply_tag($this->tag, $wordStart, $wordEnd); + + // Update the strpos offset. + $lastCrisscott = $pos; + } + + // Set the article text in the buffer. + $this->buffer->set_text($body); + } + + /** + * Creates or returns a singleton instance of this class. + * + * @static + * @access public + * @return object + */ + public static function singleton() + { + if (!isset(self::$instance)) { + $class = __CLASS__; + self::$instance = new $class; + } + return self::$instance; + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/CrisscottChapter09/Tools/NewsFeed.php b/CrisscottChapter09/Tools/NewsFeed.php new file mode 100644 index 0000000..552d5d9 --- /dev/null +++ b/CrisscottChapter09/Tools/NewsFeed.php @@ -0,0 +1,196 @@ +rss = new XML_RSS(); + + // Set the input if given. + if (isset($handle)) { + $this->setInput($handle); + } + + // Add the tree column. + $this->addColumn(); + + // Set up the selection to load a selected item. + $selection = $this->get_selection(); + $selection->connect('changed', array($this, 'loadArticle')); + } + + /** + * Sets the input that will be parsed. + * + * @access public + * @param mixed $handle Feed handle. See XML_RSS. + * @return void + */ + public function setInput($handle) + { + $this->rss->setInput($handle); + } + + /** + * Parses the feed and turns it into a list. + * + * @access public + * @return object + */ + public function createList() + { + // Parse the feed. + $this->rss->parse(); + + // Create a list store with four columns. + $listStore = new GtkListStore(Gtk::TYPE_STRING, + Gtk::TYPE_STRING, + Gtk::TYPE_STRING, + Gtk::TYPE_LONG + ); + + // Add a row for each item in the feed. + foreach ($this->rss->getItems() as $item) { + $rowData = array($item['title'], + $item['dc:date'], + $item['description'], + Pango::WEIGHT_BOLD + ); + $listStore->append($rowData); + } + + return $listStore; + } + + /** + * Adds the list to the view to show the feed. + * + * @access public + * @return void + */ + public function showList() + { + // Add the list to the view. + $this->set_model($this->createList()); + } + + /** + * Adds the column to the view and sets the display + * properties. + * + * @access protected + * @return void + */ + protected function addColumn() + { + // Create the column. + $column = new GtkTreeViewColumn(); + $column->set_title('News'); + + // Create a cell renderer. + $cellRenderer = new GtkCellRendererText(); + + // Pack the cell renderer. + $column->pack_start($cellRenderer, true); + $column->add_attribute($cellRenderer, 'text', 0); + $column->add_attribute($cellRenderer, 'weight', 3); + + // Sort the column by date. + $column->set_sort_column_id(1); + + // Add the column to the tree. + $this->append_column($column); + } + + /** + * Loads a selected news item. + * + * @access public + * @param object $selection The selected row of the list. + * @return void + */ + public function loadArticle($selection) + { + // Unbold the selected item. + list($model, $iter) = $selection->get_selected(); + $model->set($iter, 3, Pango::WEIGHT_NORMAL); + + // Get a singleton news article tool. + require_once 'Crisscott/Tools/NewsArticle.php'; + $newsArticle = Crisscott_Tools_NewsArticle::singleton(); + + // Set the article. + $headline = $model->get_value($iter, 0); + $body = $model->get_value($iter, 2); + $newsArticle->setArticle($headline, $body); + + // Bring the news story tab to the front. + require_once 'Crisscott/MainNotebook.php'; + $notebook = Crisscott_MainNotebook::singleton(); + + // Get the page index. + $index = array_search('News Story', array_keys($notebook->pages)); + $notebook->set_current_page($index); + } + + /** + * Creates or returns a singleton instance of this class. + * + * @static + * @access public + * @return object + */ + public static function singleton() + { + if (!isset(self::$instance)) { + $class = __CLASS__; + self::$instance = new $class; + } + return self::$instance; + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/CrisscottChapter09/Tools/ProductEdit.php b/CrisscottChapter09/Tools/ProductEdit.php new file mode 100644 index 0000000..1b2c2e7 --- /dev/null +++ b/CrisscottChapter09/Tools/ProductEdit.php @@ -0,0 +1,613 @@ +'; + const ERROR_MARKUP_CLOSE = ''; + + /** + * Singleton instance of this object. + * + * @access public + * @var object + */ + public static $instance; + + /** + * The current/last product being edited. + * + * @access public + * @var object + */ + public $product; + + /** + * A label for the Name entry. + * + * @access public + * @var object + */ + public $nameLabel; + + /** + * A label for the Type entry. + * + * @access public + * @var object + */ + public $typeLabel; + + /** + * A label for the Category entry. + * + * @access public + * @var object + */ + public $categoryLabel; + + /** + * A label for the Price entry. + * + * @access public + * @var object + */ + public $priceLabel; + + /** + * A label for the Description entry. + * + * @access public + * @var object + */ + public $descLabel; + + /** + * A label for the Inventory entry. + * + * @access public + * @var object + */ + public $inventoryLabel; + + /** + * A label for the Availability entry. + * + * @access public + * @var object + */ + public $availLabel; + + /** + * A label for the Width entry. + * + * @access public + * @var object + */ + public $widthLabel; + + /** + * A label for the Height entry. + * + * @access public + * @var object + */ + public $heigthLabel; + + /** + * A label for the Depth entry. + * + * @access public + * @var object + */ + public $depthLabel; + + /** + * A label for the Weight entry. + * + * @access public + * @var object + */ + public $weightLabel; + + /** + * A entry for the Name. + * + * @access public + * @var object + */ + public $nameEntry; + + /** + * A entry for the Type. + * + * @access public + * @var object + */ + public $typeEntry; + + /** + * A entry for the Category. + * + * @access public + * @var object + */ + public $categoryEntry; + + /** + * A entry for the Price. + * + * @access public + * @var object + */ + public $priceEntry; + + /** + * A entry for the Description. + * + * @access public + * @var object + */ + public $descEntry; + + /** + * A entry for the Inventory. + * + * @access public + * @var object + */ + public $inventoryEntry; + + /** + * A entry for the Availability. + * + * @access public + * @var object + */ + public $availEntry; + + /** + * A entry for the Width. + * + * @access public + * @var object + */ + public $widthSpin; + + /** + * A entry for the Height. + * + * @access public + * @var object + */ + public $heightSpin; + + /** + * A entry for the Depth. + * + * @access public + * @var object + */ + public $depthEntry; + + /** + * A entry for the Weight. + * + * @access public + * @var object + */ + public $weightEntry; + + /** + * Constructor. Sets up the tool. + * + * @access public + * @param object $product An optional product to edit. + * @return void + */ + public function __construct($product = null) + { + // Set up the rows and columns. + parent::__construct(6, 4); + + // Layout the tools. + $this->_layout(); + + // Add product if one was passed in. + if (isset($product)) { + $this->loadProduct($product); + } else { + $this->resetProduct(); + } + } + + private function _layout() + { + // Set up the data entry widgets. + $this->nameEntry = new GtkEntry(); + $this->typeCombo = GtkComboBox::new_text(); + $this->categoryCombo = GtkComboBox::new_text(); + $this->priceSpin = new GtkSpinButton(new GtkAdjustment(1, 1, 1000, .01, 10), .5); + $this->inventorySpin = new GtkSpinButton(new GtkAdjustment(1, 0, 100, 1, 10), .5); + $this->availCombo = GtkComboBox::new_text(); + $this->widthSpin = new GtkSpinButton(new GtkAdjustment(1, 1, 50, .1, 5), .5); + $this->heightSpin = new GtkSpinButton(new GtkAdjustment(1, 1, 50, .1, 5), .5); + $this->depthSpin = new GtkSpinButton(new GtkAdjustment(1, 1, 50, .1, 5), .5); + $this->weightSpin = new GtkSpinButton(new GtkAdjustment(1, 1, 50, .1, 5), .5); + + // Add two options for the type. + $this->typeCombo->append_text('Digital'); + $this->typeCombo->append_text('Shippable'); + // Make the first category the default. + $this->typeCombo->set_active(0); + + // Add an entry for each category in the inventory. + require_once 'Crisscott/Inventory.php'; + $inventory = Crisscott_Inventory::singleton(); + foreach ($inventory->categories as $cat) { + $this->categoryCombo->append_text($cat->name); + } + // Make the first category the default. + $this->categoryCombo->set_active(0); + + // Add yes/no options for the avialability. + $this->availCombo->append_text('NO'); + $this->availCombo->append_text('YES'); + // Make yes the default. + $this->availCombo->set_active(0); + + // Set the number of decimal places in the spin buttons. + $this->priceSpin->set_digits(2); + $this->inventorySpin->set_digits(0); + $this->widthSpin->set_digits(1); + $this->heightSpin->set_digits(1); + $this->depthSpin->set_digits(1); + $this->weightSpin->set_digits(1); + + // Create the description text view. + $this->descView = new GtkTextView(); + + // We need save and cancel buttons. + $save = GtkButton::new_from_stock('gtk-save'); + $reset = GtkButton::new_from_stock('gtk-undo'); + + // Connect the buttons to useful methods. + $save->connect_simple('clicked', array($this, 'saveProduct')); + $reset->connect_simple('clicked', array($this, 'resetProduct')); + + // Set up the labels. + $this->nameLabel = new GtkLabel('_Name', true); + $this->typeLabel = new GtkLabel('Type'); + $this->categoryLabel = new GtkLabel('Category'); + $this->priceLabel = new GtkLabel('Price'); + $this->inventoryLabel = new GtkLabel('Inventory'); + $this->availLabel = new GtkLabel('Availability'); + $this->widthLabel = new GtkLabel('Width'); + $this->heightLabel = new GtkLabel('Height'); + $this->depthLabel = new GtkLabel('Depth'); + $this->weightLabel = new GtkLabel('Weight'); + $this->descLabel = new GtkLabel('Description'); + + // Set the labels' size. + $this->nameLabel->set_size_request(100, -1); + $this->typeLabel->set_size_request(100, -1); + $this->categoryLabel->set_size_request(100, -1); + $this->priceLabel->set_size_request(100, -1); + $this->inventoryLabel->set_size_request(100, -1); + $this->availLabel->set_size_request(100, -1); + $this->widthLabel->set_size_request(100, -1); + $this->heightLabel->set_size_request(100, -1); + $this->depthLabel->set_size_request(100, -1); + $this->weightLabel->set_size_request(100, -1); + $this->descLabel->set_size_request(100, -1); + + // Set the size of the text view also. + $this->descView->set_size_request(300, 300); + // Force the text to wrap. + $this->descView->set_wrap_mode(Gtk::WRAP_WORD); + + // Next align each label within the parent container. + $this->nameLabel->set_alignment(0, .5); + $this->typeLabel->set_alignment(0, .5); + $this->categoryLabel->set_alignment(0, .5); + $this->priceLabel->set_alignment(0, .5); + $this->inventoryLabel->set_alignment(0, .5); + $this->availLabel->set_alignment(0, .5); + $this->widthLabel->set_alignment(0, .5); + $this->heightLabel->set_alignment(0, .5); + $this->depthLabel->set_alignment(0, .5); + $this->weightLabel->set_alignment(0, .5); + $this->descLabel->set_alignment(0, .5); + + // Make all of the labels use markup. + $this->nameLabel->set_use_markup(true); + $this->typeLabel->set_use_markup(true); + $this->categoryLabel->set_use_markup(true); + $this->priceLabel->set_use_markup(true); + $this->inventoryLabel->set_use_markup(true); + $this->availLabel->set_use_markup(true); + $this->widthLabel->set_use_markup(true); + $this->heightLabel->set_use_markup(true); + $this->depthLabel->set_use_markup(true); + $this->weightLabel->set_use_markup(true); + $this->descLabel->set_use_markup(true); + + // Attach them to the table. + $expandFill = GTK::EXPAND|GTK::FILL; + $this->attach($this->nameLabel, 0, 1, 0, 1, 0, 0); + $this->attach($this->typeLabel, 0, 1, 1, 2, 0, 0); + $this->attach($this->categoryLabel, 0, 1, 2, 3, 0, 0); + $this->attach($this->priceLabel, 0, 1, 3, 4, 0, 0); + $this->attach($this->inventoryLabel, 0, 1, 5, 6, 0, 0); + $this->attach($this->availLabel, 0, 1, 6, 7, 0, 0); + $this->attach($this->widthLabel, 0, 1, 7, 8, 0, 0); + $this->attach($this->heightLabel, 0, 1, 8, 9, 0, 0); + $this->attach($this->depthLabel, 0, 1, 9, 10, 0, 0); + $this->attach($this->weightLabel, 0, 1, 10, 11, 0, 0); + + // Attach the entries too. + $this->attachWithAlign($this->nameEntry, 1, 2, 0, 1, Gtk::FILL, 0); + $this->attachWithAlign($this->typeCombo, 1, 2, 1, 2, GTK::FILL, 0); + //$this->attach($this->typeCombo, 1, 2, 1, 2, 0, 0); + $this->attachWithAlign($this->categoryCombo, 1, 2, 2, 3, Gtk::FILL, 0); + $this->attachWithAlign($this->priceSpin, 1, 2, 3, 4, Gtk::FILL, 0); + $this->attachWithAlign($this->inventorySpin, 1, 2, 5, 6, Gtk::FILL, 0); + $this->attachWithAlign($this->availCombo, 1, 2, 6, 7, Gtk::FILL, 0); + $this->attachWithAlign($this->widthSpin, 1, 2, 7, 8, Gtk::FILL, 0); + $this->attachWithAlign($this->heightSpin, 1, 2, 8, 9, Gtk::FILL, 0); + $this->attachWithAlign($this->depthSpin, 1, 2, 9, 10, Gtk::FILL, 0); + $this->attachWithAlign($this->weightSpin, 1, 2, 10, 11, Gtk::FILL, 0); + + // Attache the description widgets. + $this->attachWithAlign($this->descLabel, 2, 3, 0, 1, Gtk::FILL, 0); + $this->attachWithAlign($this->descView, 2, 4, 1, 11, Gtk::FILL, 0); + + // Attache the buttons. + $this->attachWithAlign($reset, 0, 1, 11, 12, Gtk::FILL, 0); + $this->attachWithAlign($save, 3, 4, 11, 12, Gtk::FILL, 0); + + // Associate the mnemonics. + $this->nameLabel->set_mnemonic_widget($this->nameEntry); + $this->nameEntry->connect_simple('mnemonic_activate', array($this, 'reportError'), $this->nameLabel); + } + + /** + * Attaches a widget to the table inside of a GtkAlignment. + * + * This method makes it easy to left align items within a table. + * Simply call this method like you would attach. + * + * @access public + * @see attach + * @return void + */ + public function attachWithAlign($widget, $row1, $row2, $col1, $col2, $xEF, $yEF) + { + $align = new GtkAlignment(0,0,0,.5); + $align->add($widget); + $this->attach($align, $row1, $row2, $col1, $col2, $xEF, $yEF); + } + + /** + * Marks a label up as red text to indicate an error. + * + * @access public + * @param boolean $unknown Seriously, no idea what it means. + * @param object $label The GtkLabel to markup. + * @return void + */ + public function reportError(GtkLabel $label) + { + $label->set_label(self::ERROR_MARKUP_OPEN . $label->get_label() . self::ERROR_MARKUP_CLOSE); + } + + /** + * Clears the error markup from the given label. + * + * @access public + * @param $label The GtkLabel to remove markup from. + * @return void + */ + public function clearError(GtkLabel $label) + { + $text = $label->get_label(); + $text = str_replace(self::ERROR_MARKUP_OPEN, '', $text); + $text = str_replace(self::ERROR_MARKUP_CLOSE, '', $text); + + $label->set_label($text); + } + + /** + * Load the given product into the tool. + * + * @access public + * @param object $product A Crisscott_Product instance. + * @return void + */ + public function loadProduct(Crisscott_Product $product) + { + // First set the product as the current product. + $this->product = $product; + + // Next reset the tool. + $this->resetProduct(); + } + + /** + * Sets the values of the tool to the values of the current + * product. + * + * @access public + * @return void + */ + public function resetProduct() + { + // Make sure that there is a product. + if (!isset($this->product)) { + require_once 'Crisscott/Product.php'; + $this->loadProduct(new Crisscott_Product()); + } + + // Update the tools in the widget. + $this->nameEntry->set_text($this->product->name); + $this->_setComboActive($this->typeCombo, $this->product->type); + + $inv = Crisscott_Inventory::singleton(); + $cat = $inv->getCategoryById($this->product->categoryId); + $this->_setComboActive($this->categoryCombo, $cat->name); + + $this->priceSpin->set_value($this->product->price); + $this->inventorySpin->set_value($this->product->inventory); + $this->availCombo->set_active($this->product->availability); + $this->widthSpin->set_value($this->product->width); + $this->heightSpin->set_value($this->product->height); + $this->depthSpin->set_value($this->product->depth); + $this->weightSpin->set_value($this->product->weight); + + $buffer = $this->descView->get_buffer(); + $buffer->set_text($this->product->description); + } + + /** + * Sets the active item in a combo box. + * + * The combo box will be searched for the value given and + * the matching item will be made active. The method returns + * after the first match. + * + * This works for combos created with or without new_text(). + * + * @access private + * @param object $combo + * @param mixed $value + * @return void + */ + private function _setComboActive(GtkComboBox $combo, $value) + { + // Get the underlying model. + $model = $combo->get_model(); + // Get the first iter. + $iter = $model->get_iter_first(); + // Loop through the items. + for ($iter; $model->iter_is_valid($iter); $model->iter_next($iter)) { + // Check for a match. + if ($value == $model->get_value($iter, 0)) { + // A match! Set the active item and get out. + $combo->set_active_iter($iter); + return; + } + } + } + + /** + * Copies the data from the tool to the product. Then asks + * the product to save the data. + * + * @access public + * @return void + */ + public function saveProduct() + { + // Set the product properties. + $this->product->name = $this->nameEntry->get_text(); + $this->product->type = $this->typeCombo->get_active_text(); + + $inv = Crisscott_Inventory::singleton(); + $cat = $inv->getCategoryByName($this->categoryCombo->get_active_text()); + $this->product->categoryId = $cat->categoryId; + + $this->product->price = $this->priceSpin->get_value(); + $this->product->inventory = $this->inventorySpin->get_value(); + $this->product->availability = (boolean)$this->availCombo->get_active(); + $this->product->width = $this->widthSpin->get_value(); + $this->product->height = $this->heightSpin->get_value(); + $this->product->depth = $this->depthSpin->get_value(); + $this->product->weight = $this->weightSpin->get_value(); + + $buffer = $this->descView->get_buffer(); + $this->product->description = $buffer->get_text($buffer->get_start_iter(), $buffer->get_end_iter()); + + // Validate the new values. + $valid = $this->product->validate(); + + // Create a map of all the values and labels. + $labelMap = array( + 'name' => $this->nameLabel, + 'type' => $this->typeLabel, + 'category' => $this->categoryLabel, + 'price' => $this->priceLabel, + 'inventory' => $this->inventoryLabel, + 'avail' => $this->availLabel, + 'width' => $this->widthLabel, + 'height' => $this->heightLabel, + 'depth' => $this->depthLabel, + 'weight' => $this->weightLabel, + 'desc' => $this->descLabel + ); + + // Reset all of the labels. + foreach ($labelMap as $label) { + $this->clearError($label); + } + + // If there are invalid values, markup the labels. + if (is_array($valid)) { + foreach ($valid as $labelKey) { + $this->reportError($labelMap[$labelKey]); + } + + // Validatig the data was not successful. + return false; + } + + try { + // Try to save the data. + $this->product->save(); + + // Mark the buffer as saved. + $this->descView->get_buffer()->set_modified(false); + + // Update the inventory instance. + $inv = Crisscott_Inventory::singleton(); + $inv->refreshInventory(); + + // Also update the product tree. + $pt = Crisscott_Tools_ProductTree::singleton(); + $pt->updateModel(); + + } catch (Exception $e) { + throw $e; + return false; + } + } + + /** + * Creates or returns a singleton instance of this class. + * + * @static + * @access public + * @return object + */ + public static function singleton() + { + if (!isset(self::$instance)) { + $class = __CLASS__; + self::$instance = new $class; + } + return self::$instance; + } + +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/CrisscottChapter09/Tools/ProductSummary.php b/CrisscottChapter09/Tools/ProductSummary.php new file mode 100644 index 0000000..1b2679b --- /dev/null +++ b/CrisscottChapter09/Tools/ProductSummary.php @@ -0,0 +1,196 @@ +set_size_request(60, -1); + $type->set_size_request(60, -1); + $category->set_size_request(60, -1); + $price->set_size_request(60, -1); + + // Next align each label within the parent container. + $name->set_alignment(0, .5); + $type->set_alignment(0, .5); + $category->set_alignment(0, .5); + $price->set_alignment(0, .5); + + // Attach them to the table. + $expandFill = GTK::EXPAND|GTK::FILL; + $this->attach($name, 0, 1, 0, 1, 0, $expandFill); + $this->attach($type, 0, 1, 1, 2, 0, $expandFill); + $this->attach($category, 0, 1, 2, 3, 0, $expandFill); + $this->attach($price, 0, 1, 3, 4, 0, $expandFill); + + // Create the labels for the attributes. + $this->productName = new GtkLabel(); + $this->productType = new GtkLabel(); + $this->productCategory = new GtkLabel(); + $this->productPrice = new GtkLabel(); + + // Allow the labels to wrap. + $this->productName->set_line_wrap(true); + $this->productType->set_line_wrap(true); + $this->productCategory->set_line_wrap(true); + $this->productPrice->set_line_wrap(true); + + // Left align them. + $this->productName->set_alignment(0, .5); + $this->productType->set_alignment(0, .5); + $this->productCategory->set_alignment(0, .5); + $this->productPrice->set_alignment(0, .5); + + // Attach them to the table. + $this->attach($this->productName, 1, 2, 0, 1); + $this->attach($this->productType, 1, 2, 1, 2); + $this->attach($this->productCategory, 1, 2, 2, 3); + $this->attach($this->productPrice, 1, 2, 3, 4); + + // Attach a place holder for the image. + $this->productImage = new GtkFrame('Image'); + // The image's size can be fixed. + $this->productImage->set_size_request(100, 100); + $this->attach($this->productImage, 2, 3, 0, 4, 0, $expandFill); + + // Now that everything is set up, summarize the product. + require_once 'Crisscott/Product.php'; + $product = new Crisscott_Product(); + if (!empty($product)) { + $this->displaySummary($product); + } + } + + /** + * Displays a summary of the given product. + * + * When given a valid product object, this method updates the + * labels and image to match the values of the given product. + * + * @access public + * @param object $product A Crisscott_Product instance. + * @return void + */ + public function displaySummary(Crisscott_Product $product) + { + // Set the attribute labels to the values of the + // product. + $this->productName->set_text($product->name); + $this->productType->set_text($product->type); + + $inv = Crisscott_Inventory::singleton(); + $cat = $inv->getCategoryById($product->categoryId); + $this->productCategory->set_text($cat->name); + + $this->productPrice->set_text($product->price); + } + + /** + * Creates or returns a singleton instance of this class. + * + * @static + * @access public + * @return object + */ + public static function singleton() + { + if (!isset(self::$instance)) { + $class = __CLASS__; + self::$instance = new $class; + } + return self::$instance; + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/CrisscottChapter09/Tools/ProductTree.php b/CrisscottChapter09/Tools/ProductTree.php new file mode 100644 index 0000000..a087321 --- /dev/null +++ b/CrisscottChapter09/Tools/ProductTree.php @@ -0,0 +1,137 @@ +updateModel(); + } + + public function updateModel() + { + // Create and set the model. + $this->set_model($this->_createModel()); + + // Next set up the view column and cell renderer. + $this->_setupColumn(); + + // Finally, set up the selection. + $this->_setupSelection(); + } + + private function _createModel() + { + // Set up the model. + // Each row should have the row name and the prouct_id. + // If the row is a category the product_id should be zero. + $model = new GtkTreeStore(Gtk::TYPE_STRING, Gtk::TYPE_LONG); + + // Get a singleton of the Inventory object. + require_once 'Crisscott/Inventory.php'; + $inventory = Crisscott_Inventory::singleton(); + + // Add all of the categories. + foreach ($inventory->categories as $category) { + $catIter = $model->append(null, array($category->name, 0)); + // Add all of the products for the category. + foreach ($category->getProducts() as $product) { + $model->append($catIter, array($product['product_name'], $product['product_id'])); + } + } + + return $model; + } + + private function _setupColumn() + { + // Add the name column. + $column = new GtkTreeViewColumn(); + $column->set_title('Products'); + + // Create a renderer for the column. + $cellRenderer = new GtkCellRendererText(); + $column->pack_start($cellRenderer, true); + $column->add_attribute($cellRenderer, 'text', 0); + + // Make the column sort on the product name. + $column->set_sort_column_id(0); + + // Insert the column. + $this->insert_column($column, 0); + } + + private function _setupSelection() + { + // Get the selection object. + $selection = $this->get_selection(); + + // Set the selection to browse mode. + $selection->set_mode(Gtk::SELECTION_BROWSE); + + // Create a signal handler to process the selection. + $selection->connect('changed', array($this, 'sendToSummary')); + } + + public function sendToSummary($selection) + { + // Get the selected row. + list($model, $iter) = $selection->get_selected(); + + // Create a product. + require_once 'Crisscott/Product.php'; + $product = new Crisscott_Product($model->get_value($iter, 1)); + + // Get the singleton product summary. + require_once 'Crisscott/Tools/ProductSummary.php'; + $productSummary = Crisscott_Tools_ProductSummary::singleton(); + $productSummary->displaySummary($product); + } + + /** + * Creates or returns a singleton instance of this class. + * + * @static + * @access public + * @return object + */ + public static function singleton() + { + if (!isset(self::$instance)) { + $class = __CLASS__; + self::$instance = new $class; + } + return self::$instance; + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/CrisscottChapter09/Tools/StatusBar.php b/CrisscottChapter09/Tools/StatusBar.php new file mode 100644 index 0000000..0267292 --- /dev/null +++ b/CrisscottChapter09/Tools/StatusBar.php @@ -0,0 +1,38 @@ + \ No newline at end of file diff --git a/CrisscottChapter09/db/Result.php b/CrisscottChapter09/db/Result.php new file mode 100644 index 0000000..6cf1e72 --- /dev/null +++ b/CrisscottChapter09/db/Result.php @@ -0,0 +1,69 @@ +result = $result; + $this->key = 0; + $this->rowCount = $this->result->numRows(); + } + } + + public function current() + { + return ($this->current = $this->result->fetchRow(DB_FETCHMODE_ASSOC,$this->key)); + } + + public function goToPrev() + { + if (--$this->key >= 0) { + return ($this->current = $this->result->fetchRow(DB_FETCHMODE_ASSOC,$this->key)); + } else { + return false; + } + } + + public function goToNext() + { + if (++$this->key < $this->rowCount) { + return ($this->current = $this->result->fetchRow(DB_FETCHMODE_ASSOC,$this->key)); + } else { + return false; + } + } + + public function valid() + { + return $this->key < $this->rowCount; + } + + public function numRows() + { + return $this->rowCount; + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/CrisscottChapter09/run.php b/CrisscottChapter09/run.php new file mode 100644 index 0000000..3ca7567 --- /dev/null +++ b/CrisscottChapter09/run.php @@ -0,0 +1,14 @@ +getMessage() . "\n"; +} + +set_exception_handler('exceptionHandler'); + +require_once 'Crisscott/SplashScreen.php'; +$csSplash = new Crisscott_SplashScreen(); +$csSplash->start(); +?> \ No newline at end of file diff --git a/CrisscottChapter11/Category.php b/CrisscottChapter11/Category.php new file mode 100644 index 0000000..35873be --- /dev/null +++ b/CrisscottChapter11/Category.php @@ -0,0 +1,118 @@ +categoryId = $categoryId; + } else { + throw new Exception('Cannot instantiate category. Invalid categoryId: ' . $categoryId); + } + + // Get the category name. + require_once 'Crisscott/DB.php'; + $db = Crisscott_DB::singleton(); + + $query = 'SELECT category_name '; + $query.= 'FROM categories '; + $query.= 'WHERE category_id = ' . $this->categoryId; + + $row = $db->query($query)->current(); + $this->name = $row['category_name']; + + // Get all of the products for this category. + $this->_getProducts(); + + // Set the specs for the category. + $this->_setSpecs(); + } + + private function _getProducts() + { + require_once 'Crisscott/DB.php'; + $db = Crisscott_DB::singleton(); + + $query = 'SELECT product_id, product_name, description, '; + $query.= ' product_type, category_id, inventory, available, '; + $query.= ' width, height, depth, weight, '; + $query.= ' price '; + $query.= 'FROM products '; + $query.= ' LEFT JOIN product_price '; + $query.= ' USING (product_id) '; + $query.= 'WHERE category_id = ' . $this->categoryId; + $query.= ' AND (currency = \'USD\' OR currency IS NULL) '; + + $this->products = $db->query($query); + } + + private function _setSpecs() + { + $this->specs = array(); + + if (!$this->products->numRows()) { + return; + } + + $this->specs['Total Products'] = $this->products->numRows(); + + $totalPrice = 0; + foreach ($this->products as $product) { + $totalPrice += $product['price']; + } + $this->specs['Avg. Price (USD)'] = number_format($totalPrice / $this->products->numRows(), 2); + + $totalWeight = 0; + foreach ($this->products as $product) { + $totalWeight += $product['weight']; + } + $this->specs['Avg. Weight (Ounces)'] = number_format($totalWeight / $this->products->numRows(), 2); + } + /** + * Returns a list of category specs. + * + * @static + * @access public + * @return object An iterator of specs. + */ + public static function getCategorySpecs() + { + return array('Total Products', 'Avg. Price (USD)', 'Avg. Weight (Ounces)'); + } + + public function getSpecValueByName($spec) + { + return $this->specs[$spec]; + } + + public function getProducts() + { + return $this->products; + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/CrisscottChapter11/Contributor.php b/CrisscottChapter11/Contributor.php new file mode 100644 index 0000000..ec9404d --- /dev/null +++ b/CrisscottChapter11/Contributor.php @@ -0,0 +1,330 @@ +init($contributorId); + } + } + + /** + * Grabs the values from the database and assigns them to + * the proper member variables. + * + * The contributor data is stored in the database. A singleton + * database instance is used to connect to the database and get + * the contributor values. + * + * @access protected + * @param integer $contributorId + * @return void + */ + protected function init($contributorId) + { + // Check the contributorId. + if (!is_numeric($contributorId)) { + throw new Exception('Invalid contributor id: ' . $contributorId); + } + + // Get a singleton DB instance. + require_once 'Crisscott/DB.php'; + $db = Crisscott_DB::singleton(); + + // Create the query. + $query = 'SELECT first_name, middle_name, last_name, '; + $query.= ' website, email, street1, street2, city, '; + $query.= ' state, country, postal '; + $query.= 'FROM contributor '; + $query.= 'WHERE contributor_id = ' . $contributorId . ' '; + + // Submit the query. + $result = $db->query($query); + + // If the query failed, we wouldn't be here. + $this->contributorId = $contributorId; + $this->firstName = $result['first_name']; + $this->middleName = $result['middle_name']; + $this->lastName = $result['last_name']; + $this->website = $result['website']; + $this->email = $result['email']; + $this->street1 = $result['street1']; + $this->street2 = $result['street2']; + $this->city = $result['city']; + $this->state = $result['state']; + $this->country = $result['country']; + $this->postal = $result['postal']; + } + + /** + * Checks the data to see that it is valid data. + * + * @access public + * @return mixed true if all data is valid or an array of invalid elements. + */ + public function validate() + { + $retArray = array(); + + if ($this->firstName != 'tester') { + $retArray[] = 'firstName'; + } + if ($this->lastName != 'test') { + $retArray[] = 'lastName'; + } + + return $retArray; + } + + /** + * Writes the contributor data to the database. + * + * @access public + * @return void + */ + public function save() + { + return true; + // Get a singleton DB instance. + require_once 'Crisscott/DB.php'; + $db = Crisscott_DB::singleton(); + + // Get the query. + if (isset($this->contributorId)) { + $query = $this->_getUpdateQuery(); + $getNewId = false; + } else { + $query = $this->_getInsertQuery(); + $getNewId = true; + } + + // Submit the query; + $db->query($query); + + // Do we need to get the contributorId? + if ($getNewId) { + $this->_getNewId(); + } + } + + /** + * Creates the query for updating information already + * in the database. + * + * @access private + * @return string + */ + private function _getUpdateQuery() + { + $query = 'UPDATE contributor '; + $query.= 'SET '; + $query.= ' first_name = \'' . $this->firstName . '\', '; + $query.= ' middle_name = \'' . $this->middleName . '\', '; + $query.= ' last_name = \'' . $this->lastName . '\', '; + $query.= ' website = \'' . $this->website . '\', '; + $query.= ' email = \'' . $this->email . '\', '; + $query.= ' street1 = \'' . $this->street1 . '\', '; + $query.= ' street2 = \'' . $this->street2 . '\', '; + $query.= ' city = \'' . $this->city . '\', '; + $query.= ' state = \'' . $this->state . '\', '; + $query.= ' country = \'' . $this->country . '\', '; + $query.= ' postal = \'' . $this->postal . '\' '; + $query.= 'WHERE contributorId = ' . $this->contirbutorId . ' '; + + return $query; + } + + /** + * Creates the query for inserting information into + * the database. + * + * @access private + * @return string + */ + private function _getInsertQuery() + { + $query = 'INSERT INTO contributor '; + $query.= '(first_name, middle_name, last_name, website, email, '; + $query.= ' street1, street2, city, state, country, postal) '; + $query.= 'VALUES ( '; + $query.= '\'' . $this->firstName . '\', '; + $query.= '\'' . $this->middleName . '\', '; + $query.= '\'' . $this->lastName . '\', '; + $query.= '\'' . $this->website . '\', '; + $query.= '\'' . $this->email . '\', '; + $query.= '\'' . $this->street1 . '\', '; + $query.= '\'' . $this->street2 . '\', '; + $query.= '\'' . $this->city . '\', '; + $query.= '\'' . $this->state . '\', '; + $query.= '\'' . $this->country . '\', '; + $query.= '\'' . $this->postal . '\' '; + $query.= ') '; + + return $query; + } + + /** + * Sets the contributor id by looking up the value from + * the database. + * + * @access private + * @return void + */ + private function _getNewId() + { + // Get a singleton DB instance. + require_once 'Crisscott/DB.php'; + $db = Crisscott_DB::singleton(); + + // Create the query. + $query = 'SELECT contributor_id '; + $query.= 'FROM contributor '; + $query.= 'WHERE first_name = \'' . $this->firstName . '\' '; + $query.= ' AND middle_name = \'' . $this->middleName . '\', '; + $query.= ' AND last_name = \'' . $this->lastName . '\' '; + $query.= ' AND website = \'' . $this->website . '\' '; + $query.= ' AND email = \'' . $this->email . '\' '; + $query.= ' AND street1 = \'' . $this->street1 . '\' '; + $query.= ' AND street2 = \'' . $this->street2 . '\' '; + $query.= ' AND city = \'' . $this->city . '\' '; + $query.= ' AND state = \'' . $this->state . '\' '; + $query.= ' AND country = \'' . $this->country . '\' '; + $query.= ' AND postal = \'' . $this->postal . '\' '; + + $result = $db->query($query); + + $this->contributorId = $result['contributor_id']; + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim: et tw=78 + * vi: ts=1 sw=1 + */ +?> \ No newline at end of file diff --git a/CrisscottChapter11/DB.php b/CrisscottChapter11/DB.php new file mode 100644 index 0000000..39e3eb6 --- /dev/null +++ b/CrisscottChapter11/DB.php @@ -0,0 +1,81 @@ +db = DB::connect($dsn); + if (PEAR::isError(self::$instance->db)) { + throw new Exception('Failed to connect to database: ' . self::$instance->db->getMessage() . "\n" . self::$instance->db->getUserInfo()); + } + } + return self::$instance; + } + + public function query($sql) + { + // Execute the query. + $result = $this->db->query($sql); + + // Check for errors. + if (PEAR::isError($result)) { + throw new Exception($result->getMessage()); + } elseif ($result == DB_OK) { + return true; + } else { + require_once 'Crisscott/DB/Result.php'; + return new Crisscott_DB_Result($result); + } + } + + public function prepare($sql) + { + $result = $this->db->prepare($sql); + + // Check for errors. + if (PEAR::isError($result)) { + throw new Exception($result->getMessage()); + } else { + return $result; + } + } + + public function execute($handle, $values) + { + $result = $this->db->execute($handle, $values); + + // Check for errors. + if (PEAR::isError($result)) { + var_dump($result); + throw new Exception($result->getMessage()); + } elseif ($result == DB_OK) { + return true; + } else { + return new Crisscott_DB_Result($result); + } + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/CrisscottChapter11/Inventory.php b/CrisscottChapter11/Inventory.php new file mode 100644 index 0000000..fab77a0 --- /dev/null +++ b/CrisscottChapter11/Inventory.php @@ -0,0 +1,113 @@ +refreshInventory(); + } + + public function refreshInventory() + { + // Get the categories. + require_once 'Crisscott/DB.php'; + $db = Crisscott_DB::singleton(); + + $query = 'SELECT category_id '; + $query.= 'FROM categories '; + + require_once 'Crisscott/Category.php'; + foreach ($db->query($query) as $row) { + $this->categories[] = new Crisscott_Category($row['category_id']); + } + } + + /** + * Creates or returns a singleton instance of this class. + * + * @static + * @access public + * @return object + */ + public static function singleton() + { + if (!isset(self::$instance)) { + $class = __CLASS__; + self::$instance = new $class; + } + return self::$instance; + } + + /** + * Returns the category with the given name. + * + * @access public + * @param string $category + * @return object + */ + public function getCategoryByName($category) + { + foreach ($this->categories as $cat) { + if ($cat->name == $category) { + return $cat; + } + } + + return null; + } + + /** + * Returns the category with the given id. + * + * @access public + * @param integer $category + * @return object + */ + public function getCategoryById($category) + { + foreach ($this->categories as $cat) { + if ($cat->categoryId == $category) { + return $cat; + } + } + + return null; + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/CrisscottChapter11/Iterator.php b/CrisscottChapter11/Iterator.php new file mode 100644 index 0000000..8993e2e --- /dev/null +++ b/CrisscottChapter11/Iterator.php @@ -0,0 +1,38 @@ +key = 0; + } + + public function current() { + return $this->current; + } + + public function key() { + return $this->key; + } + + public function next() { + return $this->goToNext(); + } + + + abstract protected function goToPrev(); + + abstract protected function goToNext(); + + public function valid() { + return isset($this->current); + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/CrisscottChapter11/MainNotebook.php b/CrisscottChapter11/MainNotebook.php new file mode 100644 index 0000000..d5f12c2 --- /dev/null +++ b/CrisscottChapter11/MainNotebook.php @@ -0,0 +1,84 @@ +pages = array(); + foreach ($titles as $title) { + $pageNum = $this->append_page(new GtkVBox(), new GtkLabel($title, true)); + $page = $this->get_nth_page($pageNum); + $this->pages[$title] = $page; + } + + $this->set_show_tabs(false); + + // Add a productediting instance to the notebook. + require_once 'Crisscott/Tools/ProductEdit.php'; + $this->pages['Product _Edit']->add(Crisscott_Tools_ProductEdit::singleton()); + + // Add an category summary instance. + require_once 'Crisscott/Tools/CategorySummary.php'; + require_once 'Crisscott/Inventory.php'; + $this->pages['_Category Summary']->add(new Crisscott_Tools_CategorySummary(Crisscott_Inventory::singleton())); + + // Add a contributoredit instance. + require_once 'Crisscott/Tools/ContributorEdit.php'; + $this->pages['Contributor Edit']->add(new Crisscott_Tools_ContributorEdit()); + + // Add the news article tool. + require_once 'Crisscott/Tools/NewsArticle.php'; + $news = Crisscott_Tools_NewsArticle::singleton(); + $this->pages['News Story']->add($news); + } + + /** + * Creates or returns a singleton instance of this class. + * + * @static + * @access public + * @return object + */ + public static function singleton() + { + if (!isset(self::$instance)) { + $class = __CLASS__; + self::$instance = new $class; + } + return self::$instance; + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/CrisscottChapter11/MainWindow.php b/CrisscottChapter11/MainWindow.php new file mode 100644 index 0000000..65d989a --- /dev/null +++ b/CrisscottChapter11/MainWindow.php @@ -0,0 +1,122 @@ +set_size_request(500, 300); + $this->set_position(Gtk::WIN_POS_CENTER); + $this->set_title('Criscott PIMS'); + + $this->_populate(); + + $this->maximize(); + $this->set_icon_from_file('Crisscott/images/logo.png'); + + $this->connect_simple('destroy', array('gtk', 'main_quit')); + } + + private function _populate() + { + $table = new GtkTable(5, 3); + + $expandFill = GTK::EXPAND|GTK::FILL; + + require_once 'Crisscott/Tools/Menu.php'; + $table->attach(new Crisscott_Tools_Menu(), 0, 2, 0, 1, $expandFill, 0, 0, 0); + + require_once 'Crisscott/Tools/Toolbar.php'; + $table->attach(new Crisscott_Tools_Toolbar(), 0, 2, 1, 2, $expandFill, 0, 0, 0); + + // Get a singleton instance of the product tree. + require_once 'Crisscott/Tools/ProductTree.php'; + $productTree = Crisscott_Tools_ProductTree::singleton(); + + // Create a scrolled window for the product tree. + $scrolledWindow = new GtkScrolledWindow(); + + // Set the size of the scrolled window. + $scrolledWindow->set_size_request(150, 150); + + // Set the scrollbar policy. + $scrolledWindow->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC); + + // Add the product tree to the scrolled window. + $scrolledWindow->add($productTree); + + // Attach the scrolled window to the tree. + $table->attach($scrolledWindow, 0, 1, 2, 3, 0, $expandFill, 0, 0); + + require_once 'Crisscott/Tools/NewsFeed.php'; + $feed = 'chapter9/news.rss'; + $news = Crisscott_Tools_NewsFeed::singleton(); + $news->setInput($feed); + $news->showList(); + $news->set_size_request(150, -1); + + $table->attach($news, 0, 1, 3, 4, 0, $expandFill, 0, 0); + + $table2 = new GtkTable(2, 2); + + $productSummary = new GtkFrame('PRODUCT SUMMARY'); + $productSummary->set_size_request(-1, 150); + + // Add the product summary tool. + require_once 'Crisscott/Tools/ProductSummary.php'; + $this->productSummary = Crisscott_Tools_ProductSummary::singleton(); + $productSummary->add($this->productSummary); + + $table2->attach($productSummary, 0, 1, 0, 1, $expandFill, 0, 1, 1); + + $inventorySummary = new GtkFrame('INVENTORY SUMMARY'); + $inventorySummary->set_size_request(-1, 150); + + $table2->attach($inventorySummary, 1, 2, 0, 1, $expandFill, 0, 1, 1); + + require_once 'Crisscott/MainNotebook.php'; + $this->mainNotebook = Crisscott_MainNotebook::singleton(); + $table2->attach($this->mainNotebook, 0, 2, 1, 2, $expandFill, $expandFill, 1, 1); + + $table->attach($table2, 1, 2, 2, 4, $expandFill, $expandFill, 0, 0); + + require_once 'Crisscott/Tools/StatusBar.php'; + $table->attach(Crisscott_Tools_StatusBar::singleton(), 0, 2, 4, 5, $expandFill, 0, 0, 0); + + $this->add($table); + } + + public function connectToServer() + { + sleep(1); + } + + public function connectToLocalDB() + { + sleep(1); + } + + static public function quit() + { + // Check to see if the data has been modified + // or sent. If it is modified or not sent, don't + // exit. + if (!self::$modified && self::$sent) { + gtk::main_quit(); + return true; + } + return false; + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/CrisscottChapter11/Product.php b/CrisscottChapter11/Product.php new file mode 100644 index 0000000..2698ebc --- /dev/null +++ b/CrisscottChapter11/Product.php @@ -0,0 +1,308 @@ +init($productId); + } + + protected function init($productId) + { + // Check the product id. + if (!is_numeric($productId)) { + throw new Exception('Cannot initialize product. Invalid productId: ' . $productId); + } else { + $this->productId = $productId; + } + + require_once 'Crisscott/DB.php'; + $db = Crisscott_DB::singleton(); + + $query = 'SELECT product_name, description, product_type, '; + $query.= ' category_id, inventory, available, width, height, '; + $query.= ' depth, weight, '; + $query.= ' price '; + $query.= 'FROM products '; + $query.= ' LEFT JOIN product_price '; + $query.= ' USING (product_id) '; + $query.= 'WHERE product_id = ' . $this->productId . ' '; + $query.= ' AND (currency = \'USD\' OR currency IS NULL) '; + + $row = $db->query($query)->current(); + if (count($row)) { + $this->name = $row['product_name']; + $this->description = $row['description']; + $this->type = $row['product_type']; + $this->categoryId = $row['category_id']; + $this->inventory = $row['inventory']; + $this->availability = $row['available'] == 't'; + $this->width = $row['width']; + $this->height = $row['height']; + $this->depth = $row['depth']; + $this->weight = $row['weight']; + $this->price = $row['price']; + $this->currency = $row['currency']; + } + } + + /** + * Checks to see if the values of the product are valid. + * + * @access public + * @return mixed true or an array of the invalid fields. + */ + public function validate() + { + $invalidFields = array(); + + // Check the easy fields first. + // All of these fields must be numbers. + $numbers = array('price', + 'inventory', + 'width', + 'height', + 'depth', + 'weight' + ); + foreach ($numbers as $numField) { + if (!is_numeric($this->$numField)) { + $invalidFields[] = $numField; + } + } + + // Check the length of the product name. + if (strlen($this->name) > 50 || strlen($this->name) < 1) { + $invalidFields[] = 'name'; + } + + // Check that the availability is a boolean. + if (!is_bool($this->availability)) { + $invalidFields[] = 'availability'; + } + + // Check the product type. + $validTypes = array('Shippable', 'Digital'); + if (!in_array($this->type, $validTypes)) { + $invalidFields[] = 'type'; + } + + // Check the category. + if (empty($this->categoryId) || !is_numeric($this->categoryId)) { + $invalidFields[] = 'category'; + } + + // The description can't really be invalid. + if (count($invalidFields)) { + return $invalidFields; + } else { + return true; + } + } + + public function save() + { + // Save the product information to the database. + if (isset($this->productId) && $this->productId > 0) { + return $this->updateProduct(); + } else { + return $this->saveNewProduct(); + } + } + + protected function updateProduct() + { + require_once 'Crisscott/DB.php'; + $db = Crisscott_DB::singleton(); + + $query = 'UPDATE products '; + $query = 'SET '; + $query.= ' product_name = ?, '; + $query.= ' description = ?, '; + $query.= ' product_type = ?, '; + $query.= ' category_id = ?, '; + $query.= ' inventory = ?, '; + $query.= ' available = ?, '; + $query.= ' width = ?, '; + $query.= ' height = ?, '; + $query.= ' depth = ?, '; + $query.= ' weight = ? '; + $query.= 'WHERE product_id = ? '; + + $stmt = $db->prepare($query); + + $prodArray = array( + $this->name, + $this->description, + $this->type, + $this->categoryId, + $this->inventory, + $this->availability, + $this->width, + $this->height, + $this->depth, + $this->weight, + $this->productId + ); + + $db->execute($stmt, $prodArray); + + return true; + } + + protected function saveNewProduct() + { + require_once 'Crisscott/DB.php'; + $db = Crisscott_DB::singleton(); + + $query = 'INSERT INTO products '; + $query.= '(product_name, description, product_type, '; + $query.= ' category_id, inventory, available, width, height, '; + $query.= ' depth, weight) '; + $query.= 'VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) '; + + $stmt = $db->prepare($query); + + $prodArray = array( + $this->name, + $this->description, + $this->type, + $this->categoryId, + $this->inventory, + $this->availability, + $this->width, + $this->height, + $this->depth, + $this->weight + ); + + + $db->execute($stmt, $prodArray); + + // Get the new product id. + $query = 'SELECT MAX(product_id) '; + $query.= 'FROM products '; + + $this->productId = reset($db->query($query)->current()); + + return true; + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/CrisscottChapter11/SplashScreen.php b/CrisscottChapter11/SplashScreen.php new file mode 100644 index 0000000..e1b535a --- /dev/null +++ b/CrisscottChapter11/SplashScreen.php @@ -0,0 +1,105 @@ +set_decorated(false); + + //$this->set_size_request(300, 100); + $this->set_position(Gtk::WIN_POS_CENTER); + + // Set the background. + $style = $this->style->copy(); + $style->bg[Gtk::STATE_NORMAL] = $style->white; + $this->set_style($style); + + $this->_populate(); + + $this->set_keep_above(true); + + $this->connect_simple_after('show', array($this, 'startMainWindow')); + } + + private function _populate() + { + $frame = new GtkFrame(); + $hBox = new GtkHBox(); + $vBox = new GtkVBox(); + $logoBox = new GtkHBox(); + $statusBox = new GtkHBox(); + + $frame->set_shadow_type(Gtk::SHADOW_ETCHED_OUT); + + $logo = new GtkLabel('Crisscott Product Information Management System'); + $logo->set_use_markup(true); + + $this->status = new GtkLabel('Initializing Main Window'); + + $vBox->pack_start($logoBox, true, true, 10); + $vBox->pack_start($statusBox, true, true, 10); + + $logoBox->pack_start($logo); + $statusBox->pack_start($this->status); + + // Add a logo image. + $logoImg = GtkImage::new_from_file('Crisscott/images/logo.png'); + + $hBox->pack_start($logoImg, false, false, 10); + $hBox->pack_start($vBox, false, false, 10); + $frame->add($hBox); + + $this->add($frame); + } + + + public function start() + { + $this->show_all(); + gtk::main(); + } + + public function startMainWindow() + { + // Update the GUI. + while (gtk::events_pending()) gtk::main_iteration(); + // Give the user enough time to at least see the message. + + require_once 'Crisscott/MainWindow.php'; + $main = new Crisscott_MainWindow(); + + $this->status->set_text('Connecting to server...'); + while (gtk::events_pending()) gtk::main_iteration(); + + if ($main->connectToServer()) { + $this->status->set_text('Connecting to server... OK'); + } + while (gtk::events_pending()) gtk::main_iteration(); + + $this->status->set_text('Connecting to local database...'); + while (gtk::events_pending()) gtk::main_iteration(); + + if ($main->connectToLocalDB()) { + $this->status->set_text('Connecting to local database... OK'); + } + while (gtk::events_pending()) gtk::main_iteration(); + + $main->show_all(); + + $this->set_keep_above(false); + $this->hide(); + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/CrisscottChapter11/Tools/CategorySummary.php b/CrisscottChapter11/Tools/CategorySummary.php new file mode 100644 index 0000000..bf5f7d8 --- /dev/null +++ b/CrisscottChapter11/Tools/CategorySummary.php @@ -0,0 +1,140 @@ +attachColHeaders(); + + // If an inventory was passed, add the data to the table. + if (!empty($inventory)) { + $this->summarizeInventory($inventory); + } + } + + /** + * Summarizes all the categories in the inventory. + * + * First clears out the table. Next re-attches the column + * headers. Finally each category is added as its own row. + * + * @access public + * @param object $inventory A Crisscott_Inventory instance. + * @return void + */ + public function summarizeInventory(Crisscott_Inventory $inventory) + { + // Clear out the table. + $this->clear(); + + // Re-attach the headers. + $this->attachColHeaders(); + + // Add a row for each category. + foreach ($inventory->categories as $category) { + $this->summarizeCategory($category); + } + } + + /** + * Attaches column headers to the table. + * + * @access protected + * @return void + */ + protected function attachColHeaders() + { + require_once 'Crisscott/Category.php'; + foreach (Crisscott_Category::getCategorySpecs() as $key => $spec) { + $label = new GtkLabel($spec); + $label->set_angle(90); + $label->set_alignment(.5, 1); + + // Leave the first cell empty. + $this->attach($label, $key + 1, $key + 2, 0, 1, 0, GTK::FILL, 10, 10); + } + + // Increment the last row. + $this->lastRow++; + + } + + /** + * Adds a row of data for the given category. + * + * @access public + * @param object $category A Crisscott_Category instance. + * @return void + */ + public function summarizeCategory(Crisscott_Category $category) + { + // First attach the category name. + $nameLabel = new GtkLabel($category->name); + $nameLabel->set_alignment(0, .5); + $this->attach($nameLabel, 0, 1, $this->lastRow, $this->lastRow + 1, GTK::FILL, 0, 10, 10); + + // Next attach the spec values. + foreach (Crisscott_Category::getCategorySpecs() as $key => $spec) { + $value = $category->getSpecValueByName($spec); + $this->attach(new GtkLabel($value), $key + 1, $key + 2, $this->lastRow, $this->lastRow + 1, 0, 0, 1, 1); + } + + // Increment the last row. + $this->lastRow++; + } + + /** + * Clears all cells of the table. + * + * @access protected + * @return void + */ + protected function clear() + { + foreach ($this->get_children() as $child) { + $this->remove($child); + } + + // Reset the last row. + $this->lastRow = 0; + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim: et tw=78 + * vi: ts=1 sw=1 + */ +?> \ No newline at end of file diff --git a/CrisscottChapter11/Tools/ContributorEdit.php b/CrisscottChapter11/Tools/ContributorEdit.php new file mode 100644 index 0000000..d6b96a4 --- /dev/null +++ b/CrisscottChapter11/Tools/ContributorEdit.php @@ -0,0 +1,557 @@ +'; + const ERROR_MARKUP_CLOSE = ''; + + /** + * The contributor currently being modified. + * + * @access public + * @var object + */ + public $contributor; + + /** + * A label for the contributor's first name. + * + * @access private + * @var object + */ + private $firstNameLabel; + + /** + * A label for the contributor's middle name. + * + * @access private + * @var object + */ + private $middleNameLabel; + + /** + * A label for the contributor's last name. + * + * @access private + * @var object + */ + private $lastNameLabel; + + /** + * A label for the contributor's web address. + * + * @access private + * @var object + */ + private $websiteLabel; + + /** + * A label for the contributor's email address. + * + * @access private + * @var object + */ + private $emailLabel; + + /** + * A label for the contributor's street address. + * + * @access private + * @var object + */ + private $street1Label; + + /** + * A label for the contributor's street address. + * + * @access private + * @var object + */ + private $street2Label; + + /** + * A label for the contributor's city. + * + * @access private + * @var object + */ + private $cityLabel; + + /** + * A label for the contributor's state. + * + * @access private + * @var object + */ + private $stateLabel; + + /** + * A label for the contributor's country. + * + * @access private + * @var object + */ + private $countryLabel; + + /** + * A label for the contributor's postal code. + * + * @access private + * @var object + */ + private $postalLabel; + + /** + * A entry for the contributor's first name. + * + * @access private + * @var object + */ + private $firstNameEntry; + + /** + * A entry for the contributor's middle name. + * + * @access private + * @var object + */ + private $middleNameEntry; + + /** + * A entry for the contributor's last name. + * + * @access private + * @var object + */ + private $lastNameEntry; + + /** + * A entry for the contributor's web address. + * + * @access private + * @var object + */ + private $websiteEntry; + + /** + * A entry for the contributor's email address. + * + * @access private + * @var object + */ + private $emailEntry; + + /** + * A entry for the contributor's street address. + * + * @access private + * @var object + */ + private $street1Entry; + + /** + * A entry for the contributor's street address. + * + * @access private + * @var object + */ + private $street2Entry; + + /** + * A entry for the contributor's city. + * + * @access private + * @var object + */ + private $cityEntry; + + /** + * A entry for the contributor's state. + * + * @access private + * @var object + */ + private $stateEntry; + + /** + * A entry for the contributor's country. + * + * @access private + * @var object + */ + private $countryComboBox; + + /** + * A entry for the contributor's postal code. + * + * @access private + * @var object + */ + private $postalEntry; + + /** + * Constructor. Calls the methods to create the tool and + * sets up the needed callbacks. + * + * @access public + * @param object $contributor A Crisscott_Contributor instance. (Optional) + * @return void + */ + public function __construct($contributor = null) + { + // Call the parent constructor. + parent::__construct(7, 4); + + // Layout the tool. + $this->_layoutTool(); + + // Connect the needed callbacks. + + // Prepopulate the fields if a contributor is given. + if (empty($contributor) || is_a($contributor, 'Crisscott_Contributor')) { + require_once 'Crisscott/Contributor.php'; + $contributor = new Crisscott_Contributor(); + } + $this->populateFields($contributor); + } + + /** + * Laysout the labels, entries and buttons. + * + * This tool consists of several labels with corresponding entries + * and two buttons. One button resets the fields the other submits + * the information to change the contributors values. + * + * @access private + * @return void + */ + private function _layoutTool() + { + // First create the labels that identify the fields. + $this->firstNameLabel = new GtkLabel('First Name'); + $this->middleNameLabel = new GtkLabel('Middle Name'); + $this->lastNameLabel = new GtkLabel('Last Name'); + $this->emailLabel = new GtkLabel('Email Address'); + $this->websiteLabel = new GtkLabel('Website'); + $this->street1Label = new GtkLabel('Street 1'); + $this->street2Label = new GtkLabel('Street 2'); + $this->cityLabel = new GtkLabel('City'); + $this->stateLabel = new GtkLabel('State'); + $this->countryLabel = new GtkLabel('Country'); + $this->postalLabel = new GtkLabel('Postal Code'); + + // Next add the labels to the table. + // The labels will be added in two columns. + // First column. + $this->attach($this->firstNameLabel, 0, 1, 0, 1, GTK::FILL, 0); + $this->attach($this->middleNameLabel, 0, 1, 1, 2, GTK::FILL, 0); + $this->attach($this->lastNameLabel, 0, 1, 2, 3, GTK::FILL, 0); + $this->attach($this->emailLabel, 0, 1, 3, 4, GTK::FILL, 0); + $this->attach($this->websiteLabel, 0, 1, 4, 5, GTK::FILL, 0); + + // Second column. + $this->attach($this->street1Label, 2, 3, 0, 1, GTK::FILL, 0); + $this->attach($this->street2Label, 2, 3, 1, 2, GTK::FILL, 0); + $this->attach($this->cityLabel, 2, 3, 2, 3, GTK::FILL, 0); + $this->attach($this->stateLabel, 2, 3, 3, 4, GTK::FILL, 0); + $this->attach($this->countryLabel, 2, 3, 4, 5, GTK::FILL, 0); + $this->attach($this->postalLabel, 2, 3, 5, 6, GTK::FILL, 0); + + // Right align all of the labels. + $this->firstNameLabel->set_alignment(1, .5); + $this->middleNameLabel->set_alignment(1, .5); + $this->lastNameLabel->set_alignment(1, .5); + $this->emailLabel->set_alignment(1, .5); + $this->websiteLabel->set_alignment(1, .5); + $this->street1Label->set_alignment(1, .5); + $this->street2Label->set_alignment(1, .5); + $this->cityLabel->set_alignment(1, .5); + $this->stateLabel->set_alignment(1, .5); + $this->countryLabel->set_alignment(1, .5); + $this->postalLabel->set_alignment(1, .5); + + // Turn on markup + $this->firstNameLabel->set_use_markup(true); + $this->middleNameLabel->set_use_markup(true); + $this->lastNameLabel->set_use_markup(true); + $this->emailLabel->set_use_markup(true); + $this->websiteLabel->set_use_markup(true); + $this->street1Label->set_use_markup(true); + $this->street2Label->set_use_markup(true); + $this->cityLabel->set_use_markup(true); + $this->stateLabel->set_use_markup(true); + $this->countryLabel->set_use_markup(true); + $this->postalLabel->set_use_markup(true); + + // Next create all of the data collection widgets. + $this->firstNameEntry = new GtkEntry(); + $this->middleNameEntry = new GtkEntry(); + $this->lastNameEntry = new GtkEntry(); + $this->emailEntry = new GtkEntry(); + $this->websiteEntry = new GtkEntry(); + $this->street1Entry = new GtkEntry(); + $this->street2Entry = new GtkEntry(); + $this->cityEntry = new GtkEntry(); + $this->stateEntry = new GtkEntry(); + $this->postalEntry = new GtkEntry(); + + // The country should be a combobox. + $this->countryComboBox = GtkComboBox::new_text(); + $this->countryComboBox->append_text('United States'); + $this->countryComboBox->prepend_text('Canada'); + $this->countryComboBox->insert_text(1, 'United Kingdom'); + $this->countryComboBox->set_active(0); + + // Next add the entrys to the table. + // The entrys will be added in two columns. + // First column. + $this->attach($this->firstNameEntry, 1, 2, 0, 1, 0, 0); + $this->attach($this->middleNameEntry, 1, 2, 1, 2, 0, 0); + $this->attach($this->lastNameEntry, 1, 2, 2, 3, 0, 0); + $this->attach($this->emailEntry, 1, 2, 3, 4, 0, 0); + $this->attach($this->websiteEntry, 1, 2, 4, 5, 0, 0); + + // Second column. + $this->attach($this->street1Entry, 3, 4, 0, 1, 0, 0); + $this->attach($this->street2Entry, 3, 4, 1, 2, 0, 0); + $this->attach($this->cityEntry, 3, 4, 2, 3, 0, 0); + $this->attach($this->stateEntry, 3, 4, 3, 4, 0, 0); + $this->attach($this->countryComboBox, 3, 4, 4, 5, 0, 0); + $this->attach($this->postalEntry, 3, 4, 5, 6, 0, 0); + + // Help the user out with the state by using a GtkEntryCompletion. + $stateCompletion = new GtkEntryCompletion(); + $stateCompletion->set_model(self::createStateList()); + $stateCompletion->set_text_column(0); + $this->stateEntry->set_completion($stateCompletion); + $stateCompletion->set_inline_completion(true); + + // Add the save and clear buttons. + $save = GtkButton::new_from_stock('gtk-save'); + $reset = GtkButton::new_from_stock('gtk-undo'); + $save->connect_simple('clicked', array($this, 'saveContributor')); + $reset->connect_simple('clicked', array($this, 'resetContributor')); + + $this->attach($reset, 0, 1, 6, 7, 0, 0); + $this->attach($save, 3, 4, 6, 7, 0, 0); + } + + /** + * Creates a one column list store that contains the US states + * and Canadian provinces. + * + * This list can be used for combo boxes, tree, or entry + * completions. + * + * @static + * @access public + * @return object A GtkListStore + */ + public static function createStateList() + { + $listStore = new GtkListStore(GTK::TYPE_STRING); + $iter = $listStore->append(); + $listStore->set($iter, 0, 'Alabama'); + $iter = $listStore->append(); + $listStore->set($iter, 0, 'Alaska'); + $iter = $listStore->append(); + $listStore->set($iter, 0, 'Arizona'); + $iter = $listStore->append(); + $listStore->set($iter, 0, 'Arkansas'); + $iter = $listStore->append(); + $listStore->set($iter, 0, 'California'); + $iter = $listStore->append(); + $listStore->set($iter, 0, 'Colorodo'); + + return $listStore; + } + + /* + * + * When a contributor is edited, it is stored in a member variable + * and then its values are used to populate the fields. + * + * @access public + * @param object $contributor A Crisscott_Contributor instance. + * @return void + */ + public function populateFields(Crisscott_Contributor $contributor) + { + // Populate the fields. + $this->firstNameEntry->set_text($contributor->firstName); + $this->middleNameEntry->set_text($contributor->middleName); + $this->lastNameEntry->set_text($contributor->lastName); + $this->emailEntry->set_text($contributor->email); + $this->websiteEntry->set_text($contributor->website); + + $this->street1Entry->set_text($contributor->street1); + $this->street2Entry->set_text($contributor->street2); + $this->cityEntry->set_text($contributor->city); + $this->stateEntry->set_text($contributor->state); + $this->postalEntry->set_text($contributor->postal); + + // Set the active element for the country combo box. + $model = $this->countryComboBox->get_model(); + $iter = $model->get_iter_first(); + for ($iter; $model->iter_is_valid($iter); $model->iter_next($iter)) { + if ($this->contributor->country == $model->get_value($iter, 0)) { + $this->countryComboBox->set_active_iter($iter); + } + } + + // Keep a hold of the contributor. + $this->contributor = $contributor; + } + + /** + * Resets the fields with the original contributor data. + * + * This method basically is an undo for all changes that + * have been made since the last save. It re-grabs the + * values from the contributor and populates them again. + * + * @uses populateFields + * + * @access public + * @return void + */ + public function resetContributor() + { + // Make sure we have a contributor already. + if (!isset($this->contributor)) { + require_once 'Crisscott/Contributor.php'; + $this->contributor = new Crisscott_Contributor(); + $this->contributor->country = 'United States'; + } + + // Reset the fields to the original value. + $this->populateFields($this->contributor); + } + + /** + * Grabs, validates, and saves the contributor information. + * + * First this method collects the data values from the widgets + * and then assigns them to the contributor object. Next the + * values are validated using the contributors validate method. + * If all is ok, the contributor is told to write the data to + * the database. + * + * @access public + * @return boolean true on success. + */ + public function saveContributor() + { + // First grab all of the values. + $this->contributor->firstName = $this->firstNameEntry->get_text(); + $this->contributor->middleName = $this->firstNameEntry->get_text(); + $this->contributor->lastName = $this->lastNameEntry->get_text(); + $this->contributor->website = $this->websiteEntry->get_text(); + $this->contributor->email = $this->emailEntry->get_text(); + $this->contributor->street1 = $this->street1Entry->get_text(); + $this->contributor->street1 = $this->street1Entry->get_text(); + $this->contributor->city = $this->cityEntry->get_text(); + $this->contributor->state = $this->stateEntry->get_text(); + //$this->contributor->country = $this->countryComboBox->get_text(); + $this->contributor->postal = $this->postalEntry->get_text(); + + // Next validate the data. + $valid = $this->contributor->validate(); + + // Create a map of all the values and labels. + $labelMap = array('firstName' => $this->firstNameLabel, + 'middleName' => $this->middleNameLabel, + 'lastName' => $this->lastNameLabel, + 'website' => $this->websiteLabel, + 'email' => $this->emailLabel, + 'street1' => $this->street1Label, + 'street2' => $this->street2Label, + 'city' => $this->cityLabel, + 'state' => $this->stateLabel, + 'country' => $this->countryLabel, + 'postal' => $this->postalLabel + ); + + // Reset all of the labels. + foreach ($labelMap as $label) { + $this->clearError($label); + } + + // If there are invalid values, markup the labels. + if (is_array($valid)) { + foreach ($valid as $labelKey) { + $this->reportError($labelMap[$labelKey]); + } + + // Saving the data was not successful. + return false; + } + + // Try to save the data. + return $this->contributor->save(); + } + + /** + * Marks a label up as red text to indicate an error. + * + * @access public + * @param object $label The GtkLabel to markup. + * @return void + */ + public function reportError(GtkLabel $label) + { + require_once 'Crisscott/Tools/StatusBar.php'; + $status = Crisscott_Tools_StatusBar::singleton(); + var_dump($status->push(rand(), 'Error: ' . $label->get_label())); + + $label->set_label(self::ERROR_MARKUP_OPEN . $label->get_label() . self::ERROR_MARKUP_CLOSE); + } + + /** + * Clears the error markup from the given label. + * + * @access public + * @param $label The GtkLabel to remove markup from. + * @return void + */ + public function clearError(GtkLabel $label) + { + require_once 'Crisscott/Tools/StatusBar.php'; + $status = Crisscott_Tools_StatusBar::singleton(); + $contextId = $status->get_context_id('Error: ' . $label->get_label()); + $status->pop($contextId); + + $text = $label->get_label(); + $text = str_replace(self::ERROR_MARKUP_OPEN, '', $text); + $text = str_replace(self::ERROR_MARKUP_CLOSE, '', $text); + + $label->set_label($text); + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim: et tw=78 + * vi: ts=1 sw=1 + */ +?> \ No newline at end of file diff --git a/CrisscottChapter11/Tools/Menu.php b/CrisscottChapter11/Tools/Menu.php new file mode 100644 index 0000000..094c54d --- /dev/null +++ b/CrisscottChapter11/Tools/Menu.php @@ -0,0 +1,129 @@ +file = new GtkMenuItem('File'); + $this->append($this->file); + + $this->help = new GtkMenuItem('Help'); + $this->append($this->help); + + // Create the sub menus. + $this->createSubMenus(); + } + + protected function createSubMenus() + { + // Create the file menu and items. + $fileMenu = new GtkMenu(); + $new = new GtkImageMenuItem(Gtk::STOCK_NEW); + $open = new GtkImageMenuItem(Gtk::STOCK_OPEN); + $send = new GtkImageMenuItem('Send'); + $send->set_image(GtkImage::new_from_file('Crisscott/images/menuItemGrey.png')); + $save = new GtkMenuItem('Save'); + $quit = new GtkMenuItem('Quit'); + + // Add the four items to the file menu. + $fileMenu->append($new); + $fileMenu->append($open); + $fileMenu->append($send); + + // Create a sub menu for the new item. + $newMenu = new GtkMenu(); + $product = new GtkMenuItem('Product'); + $category = new GtkMenuItem('Category'); + $contrib = new GtkMenuItem('Contributor'); + + // Make the new menu detachable. + $newMenu->append(new GtkTearoffMenuItem()); + $newMenu->append($product); + $newMenu->append($category); + $newMenu->append($contrib); + + $new->set_submenu($newMenu); + + // Add a separator. + $fileMenu->append(new GtkSeparatorMenuItem()); + + // Add some check items. + $server = new GtkCheckMenuItem('Connect to Server'); + $database = new GtkCheckMenuItem('Connect to Database'); + + $fileMenu->append($server); + $fileMenu->append($database); + + // Add a separator. + $fileMenu->append(new GtkSeparatorMenuItem()); + + // Add three noise levels. + $quiet = new GtkRadioMenuItem(null, 'Quiet'); + $normal = new GtkRadioMenuItem($quiet, 'Normal'); + $verbose = new GtkRadioMenuItem($quiet, 'Verbose'); + + $fileMenu->append($quiet); + $fileMenu->append($normal); + $fileMenu->append($verbose); + + // Add a separator. + $fileMenu->append(new GtkSeparatorMenuItem()); + + // Finish of the menu. + $fileMenu->append($save); + $fileMenu->append($quit); + + // Connect some signal handlers. + $quit->connect_simple('activate', array('Crisscott_MainWindow', 'quit')); + + // Create the help menu and items. + $helpMenu = new GtkMenu(); + $help = new GtkMenuItem('Help'); + $about = new GtkMenuItem('About'); + + // Add both items to the help menu. + $helpMenu->append($help); + $helpMenu->append($about); + + // Make the two menus submenus for the menu items. + $this->file->set_submenu($fileMenu); + $this->help->set_submenu($helpMenu); + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim: et tw=78 + * vi: ts=1 sw=1 + */ +?> \ No newline at end of file diff --git a/CrisscottChapter11/Tools/NewsArticle.php b/CrisscottChapter11/Tools/NewsArticle.php new file mode 100644 index 0000000..12d93f7 --- /dev/null +++ b/CrisscottChapter11/Tools/NewsArticle.php @@ -0,0 +1,201 @@ +_layout(); + } + + /** + * Lays out the tool. Creates the widgets used by the tool + * and adds them to the container. + * + * @access private + * @return void + */ + private function _layout() + { + // Create a label for the headline. + $this->headline = new GtkLabel(); + + // Create a view for the article. + $this->view = new GtkTextView(); + + // Get the buffer from the view. + $this->buffer = $this->view->get_buffer(); + + // Get a tag for making text bold and dark blue. + $this->tag = new GtkTextTag(); + // Set the tag properties + + // Make the tag part of the buffers tag table. + $tagTable = $this->buffer->get_tag_table(); + $tagTable->add($this->tag); + + // The text in this view should not be editable. + $this->view->set_editable(false); + + // Since the user can't edit the text there is not point in + // letting them see the cursor. + $this->view->set_cursor_visible(false); + + // Pack everything together. + $this->pack_start($this->headline, false, false, 5); + $this->pack_start($this->view); + } + + /** + * Sets the headline and the article text. + * + * @uses setHeadline + * @uses setBody + * + * @access public + * @param string $headline + * @param string $text + * @return void + */ + public function setArticle($headline, $text) + { + // Set the headline. + $this->setHeadline($headline); + + // Set the body. + $this->setBody($text); + } + + /** + * Sets the text of the headline and makes it look like a + * headline. + * + * @access public + * @param string $headline + * @return void + */ + public function setHeadline($headline) + { + // Add some markup to make the headline appear like + // a headline. + $headline = '' . $headline; + $headline.= ''; + + // Set the text of the headline label. + $this->headline->set_text($headline); + + // Make sure the headline is set to use the markup that was added. + $this->headline->set_use_markup(true); + + } + + /** + * Sets the given text as the text of the buffer. + * + * Any time that "Crisscott" is found in the article body, it is + * formatted so that it appears bold and dark blue. This is done + * using tags. + * + * @access public + * @param string $body + * @return void + */ + public function setBody($body) + { + // Do some special formatting of any instances of + // Crisscott found in the article body. + $lastCrisscott = 0; + while ($pos = strpos($body, 'Crisscott', $lastCrisscott)) { + $wordStart = $this->buffer->get_iter_at_offset($pos); + $wordEnd = $this->buffer->get_iter_at_offset($pos); + $wordEnd->forward_word_end(); + + // Apply the tag. + $this->buffer->apply_tag($this->tag, $wordStart, $wordEnd); + + // Update the strpos offset. + $lastCrisscott = $pos; + } + + // Set the article text in the buffer. + $this->buffer->set_text($body); + } + + /** + * Creates or returns a singleton instance of this class. + * + * @static + * @access public + * @return object + */ + public static function singleton() + { + if (!isset(self::$instance)) { + $class = __CLASS__; + self::$instance = new $class; + } + return self::$instance; + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/CrisscottChapter11/Tools/NewsFeed.php b/CrisscottChapter11/Tools/NewsFeed.php new file mode 100644 index 0000000..db82404 --- /dev/null +++ b/CrisscottChapter11/Tools/NewsFeed.php @@ -0,0 +1,199 @@ +rss = new XML_RSS(); + + // Set the input if given. + if (isset($handle)) { + $this->setInput($handle); + } + + // Add the tree column. + $this->addColumn(); + + // Set up the selection to load a selected item. + $selection = $this->get_selection(); + $selection->connect('changed', array($this, 'loadArticle')); + } + + /** + * Sets the input that will be parsed. + * + * @access public + * @param mixed $handle Feed handle. See XML_RSS. + * @return void + */ + public function setInput($handle) + { + $this->rss->setInput($handle); + } + + /** + * Parses the feed and turns it into a list. + * + * @access public + * @return object + */ + public function createList() + { + // Parse the feed. + $this->rss->parse(); + + // Create a list store with four columns. + $listStore = new GtkListStore(Gtk::TYPE_STRING, + Gtk::TYPE_STRING, + Gtk::TYPE_STRING, + Gtk::TYPE_LONG + ); + + // Add a row for each item in the feed. + foreach ($this->rss->getItems() as $item) { + $rowData = array($item['title'], + $item['dc:date'], + $item['description'], + Pango::WEIGHT_BOLD + ); + $listStore->append($rowData); + } + + return $listStore; + } + + /** + * Adds the list to the view to show the feed. + * + * @access public + * @return void + */ + public function showList() + { + // Add the list to the view. + $this->set_model($this->createList()); + } + + /** + * Adds the column to the view and sets the display + * properties. + * + * @access protected + * @return void + */ + protected function addColumn() + { + // Create the column. + $column = new GtkTreeViewColumn(); + $column->set_title('News'); + + // Create a cell renderer. + $cellRenderer = new GtkCellRendererText(); + + // Make the text ellipsize. + $cellRenderer->set_property('ellipsize', Pango::ELLIPSIZE_END); + + // Pack the cell renderer. + $column->pack_start($cellRenderer, true); + $column->add_attribute($cellRenderer, 'text', 0); + $column->add_attribute($cellRenderer, 'weight', 3); + + // Sort the column by date. + $column->set_sort_column_id(1); + + // Add the column to the tree. + $this->append_column($column); + } + + /** + * Loads a selected news item. + * + * @access public + * @param object $selection The selected row of the list. + * @return void + */ + public function loadArticle($selection) + { + // Unbold the selected item. + list($model, $iter) = $selection->get_selected(); + $model->set($iter, 3, Pango::WEIGHT_NORMAL); + + // Get a singleton news article tool. + require_once 'Crisscott/Tools/NewsArticle.php'; + $newsArticle = Crisscott_Tools_NewsArticle::singleton(); + + // Set the article. + $headline = $model->get_value($iter, 0); + $body = $model->get_value($iter, 2); + $newsArticle->setArticle($headline, $body); + + // Bring the news story tab to the front. + require_once 'Crisscott/MainNotebook.php'; + $notebook = Crisscott_MainNotebook::singleton(); + + // Get the page index. + $index = array_search('News Story', array_keys($notebook->pages)); + $notebook->set_current_page($index); + } + + /** + * Creates or returns a singleton instance of this class. + * + * @static + * @access public + * @return object + */ + public static function singleton() + { + if (!isset(self::$instance)) { + $class = __CLASS__; + self::$instance = new $class; + } + return self::$instance; + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/CrisscottChapter11/Tools/ProductEdit.php b/CrisscottChapter11/Tools/ProductEdit.php new file mode 100644 index 0000000..1b2c2e7 --- /dev/null +++ b/CrisscottChapter11/Tools/ProductEdit.php @@ -0,0 +1,613 @@ +'; + const ERROR_MARKUP_CLOSE = ''; + + /** + * Singleton instance of this object. + * + * @access public + * @var object + */ + public static $instance; + + /** + * The current/last product being edited. + * + * @access public + * @var object + */ + public $product; + + /** + * A label for the Name entry. + * + * @access public + * @var object + */ + public $nameLabel; + + /** + * A label for the Type entry. + * + * @access public + * @var object + */ + public $typeLabel; + + /** + * A label for the Category entry. + * + * @access public + * @var object + */ + public $categoryLabel; + + /** + * A label for the Price entry. + * + * @access public + * @var object + */ + public $priceLabel; + + /** + * A label for the Description entry. + * + * @access public + * @var object + */ + public $descLabel; + + /** + * A label for the Inventory entry. + * + * @access public + * @var object + */ + public $inventoryLabel; + + /** + * A label for the Availability entry. + * + * @access public + * @var object + */ + public $availLabel; + + /** + * A label for the Width entry. + * + * @access public + * @var object + */ + public $widthLabel; + + /** + * A label for the Height entry. + * + * @access public + * @var object + */ + public $heigthLabel; + + /** + * A label for the Depth entry. + * + * @access public + * @var object + */ + public $depthLabel; + + /** + * A label for the Weight entry. + * + * @access public + * @var object + */ + public $weightLabel; + + /** + * A entry for the Name. + * + * @access public + * @var object + */ + public $nameEntry; + + /** + * A entry for the Type. + * + * @access public + * @var object + */ + public $typeEntry; + + /** + * A entry for the Category. + * + * @access public + * @var object + */ + public $categoryEntry; + + /** + * A entry for the Price. + * + * @access public + * @var object + */ + public $priceEntry; + + /** + * A entry for the Description. + * + * @access public + * @var object + */ + public $descEntry; + + /** + * A entry for the Inventory. + * + * @access public + * @var object + */ + public $inventoryEntry; + + /** + * A entry for the Availability. + * + * @access public + * @var object + */ + public $availEntry; + + /** + * A entry for the Width. + * + * @access public + * @var object + */ + public $widthSpin; + + /** + * A entry for the Height. + * + * @access public + * @var object + */ + public $heightSpin; + + /** + * A entry for the Depth. + * + * @access public + * @var object + */ + public $depthEntry; + + /** + * A entry for the Weight. + * + * @access public + * @var object + */ + public $weightEntry; + + /** + * Constructor. Sets up the tool. + * + * @access public + * @param object $product An optional product to edit. + * @return void + */ + public function __construct($product = null) + { + // Set up the rows and columns. + parent::__construct(6, 4); + + // Layout the tools. + $this->_layout(); + + // Add product if one was passed in. + if (isset($product)) { + $this->loadProduct($product); + } else { + $this->resetProduct(); + } + } + + private function _layout() + { + // Set up the data entry widgets. + $this->nameEntry = new GtkEntry(); + $this->typeCombo = GtkComboBox::new_text(); + $this->categoryCombo = GtkComboBox::new_text(); + $this->priceSpin = new GtkSpinButton(new GtkAdjustment(1, 1, 1000, .01, 10), .5); + $this->inventorySpin = new GtkSpinButton(new GtkAdjustment(1, 0, 100, 1, 10), .5); + $this->availCombo = GtkComboBox::new_text(); + $this->widthSpin = new GtkSpinButton(new GtkAdjustment(1, 1, 50, .1, 5), .5); + $this->heightSpin = new GtkSpinButton(new GtkAdjustment(1, 1, 50, .1, 5), .5); + $this->depthSpin = new GtkSpinButton(new GtkAdjustment(1, 1, 50, .1, 5), .5); + $this->weightSpin = new GtkSpinButton(new GtkAdjustment(1, 1, 50, .1, 5), .5); + + // Add two options for the type. + $this->typeCombo->append_text('Digital'); + $this->typeCombo->append_text('Shippable'); + // Make the first category the default. + $this->typeCombo->set_active(0); + + // Add an entry for each category in the inventory. + require_once 'Crisscott/Inventory.php'; + $inventory = Crisscott_Inventory::singleton(); + foreach ($inventory->categories as $cat) { + $this->categoryCombo->append_text($cat->name); + } + // Make the first category the default. + $this->categoryCombo->set_active(0); + + // Add yes/no options for the avialability. + $this->availCombo->append_text('NO'); + $this->availCombo->append_text('YES'); + // Make yes the default. + $this->availCombo->set_active(0); + + // Set the number of decimal places in the spin buttons. + $this->priceSpin->set_digits(2); + $this->inventorySpin->set_digits(0); + $this->widthSpin->set_digits(1); + $this->heightSpin->set_digits(1); + $this->depthSpin->set_digits(1); + $this->weightSpin->set_digits(1); + + // Create the description text view. + $this->descView = new GtkTextView(); + + // We need save and cancel buttons. + $save = GtkButton::new_from_stock('gtk-save'); + $reset = GtkButton::new_from_stock('gtk-undo'); + + // Connect the buttons to useful methods. + $save->connect_simple('clicked', array($this, 'saveProduct')); + $reset->connect_simple('clicked', array($this, 'resetProduct')); + + // Set up the labels. + $this->nameLabel = new GtkLabel('_Name', true); + $this->typeLabel = new GtkLabel('Type'); + $this->categoryLabel = new GtkLabel('Category'); + $this->priceLabel = new GtkLabel('Price'); + $this->inventoryLabel = new GtkLabel('Inventory'); + $this->availLabel = new GtkLabel('Availability'); + $this->widthLabel = new GtkLabel('Width'); + $this->heightLabel = new GtkLabel('Height'); + $this->depthLabel = new GtkLabel('Depth'); + $this->weightLabel = new GtkLabel('Weight'); + $this->descLabel = new GtkLabel('Description'); + + // Set the labels' size. + $this->nameLabel->set_size_request(100, -1); + $this->typeLabel->set_size_request(100, -1); + $this->categoryLabel->set_size_request(100, -1); + $this->priceLabel->set_size_request(100, -1); + $this->inventoryLabel->set_size_request(100, -1); + $this->availLabel->set_size_request(100, -1); + $this->widthLabel->set_size_request(100, -1); + $this->heightLabel->set_size_request(100, -1); + $this->depthLabel->set_size_request(100, -1); + $this->weightLabel->set_size_request(100, -1); + $this->descLabel->set_size_request(100, -1); + + // Set the size of the text view also. + $this->descView->set_size_request(300, 300); + // Force the text to wrap. + $this->descView->set_wrap_mode(Gtk::WRAP_WORD); + + // Next align each label within the parent container. + $this->nameLabel->set_alignment(0, .5); + $this->typeLabel->set_alignment(0, .5); + $this->categoryLabel->set_alignment(0, .5); + $this->priceLabel->set_alignment(0, .5); + $this->inventoryLabel->set_alignment(0, .5); + $this->availLabel->set_alignment(0, .5); + $this->widthLabel->set_alignment(0, .5); + $this->heightLabel->set_alignment(0, .5); + $this->depthLabel->set_alignment(0, .5); + $this->weightLabel->set_alignment(0, .5); + $this->descLabel->set_alignment(0, .5); + + // Make all of the labels use markup. + $this->nameLabel->set_use_markup(true); + $this->typeLabel->set_use_markup(true); + $this->categoryLabel->set_use_markup(true); + $this->priceLabel->set_use_markup(true); + $this->inventoryLabel->set_use_markup(true); + $this->availLabel->set_use_markup(true); + $this->widthLabel->set_use_markup(true); + $this->heightLabel->set_use_markup(true); + $this->depthLabel->set_use_markup(true); + $this->weightLabel->set_use_markup(true); + $this->descLabel->set_use_markup(true); + + // Attach them to the table. + $expandFill = GTK::EXPAND|GTK::FILL; + $this->attach($this->nameLabel, 0, 1, 0, 1, 0, 0); + $this->attach($this->typeLabel, 0, 1, 1, 2, 0, 0); + $this->attach($this->categoryLabel, 0, 1, 2, 3, 0, 0); + $this->attach($this->priceLabel, 0, 1, 3, 4, 0, 0); + $this->attach($this->inventoryLabel, 0, 1, 5, 6, 0, 0); + $this->attach($this->availLabel, 0, 1, 6, 7, 0, 0); + $this->attach($this->widthLabel, 0, 1, 7, 8, 0, 0); + $this->attach($this->heightLabel, 0, 1, 8, 9, 0, 0); + $this->attach($this->depthLabel, 0, 1, 9, 10, 0, 0); + $this->attach($this->weightLabel, 0, 1, 10, 11, 0, 0); + + // Attach the entries too. + $this->attachWithAlign($this->nameEntry, 1, 2, 0, 1, Gtk::FILL, 0); + $this->attachWithAlign($this->typeCombo, 1, 2, 1, 2, GTK::FILL, 0); + //$this->attach($this->typeCombo, 1, 2, 1, 2, 0, 0); + $this->attachWithAlign($this->categoryCombo, 1, 2, 2, 3, Gtk::FILL, 0); + $this->attachWithAlign($this->priceSpin, 1, 2, 3, 4, Gtk::FILL, 0); + $this->attachWithAlign($this->inventorySpin, 1, 2, 5, 6, Gtk::FILL, 0); + $this->attachWithAlign($this->availCombo, 1, 2, 6, 7, Gtk::FILL, 0); + $this->attachWithAlign($this->widthSpin, 1, 2, 7, 8, Gtk::FILL, 0); + $this->attachWithAlign($this->heightSpin, 1, 2, 8, 9, Gtk::FILL, 0); + $this->attachWithAlign($this->depthSpin, 1, 2, 9, 10, Gtk::FILL, 0); + $this->attachWithAlign($this->weightSpin, 1, 2, 10, 11, Gtk::FILL, 0); + + // Attache the description widgets. + $this->attachWithAlign($this->descLabel, 2, 3, 0, 1, Gtk::FILL, 0); + $this->attachWithAlign($this->descView, 2, 4, 1, 11, Gtk::FILL, 0); + + // Attache the buttons. + $this->attachWithAlign($reset, 0, 1, 11, 12, Gtk::FILL, 0); + $this->attachWithAlign($save, 3, 4, 11, 12, Gtk::FILL, 0); + + // Associate the mnemonics. + $this->nameLabel->set_mnemonic_widget($this->nameEntry); + $this->nameEntry->connect_simple('mnemonic_activate', array($this, 'reportError'), $this->nameLabel); + } + + /** + * Attaches a widget to the table inside of a GtkAlignment. + * + * This method makes it easy to left align items within a table. + * Simply call this method like you would attach. + * + * @access public + * @see attach + * @return void + */ + public function attachWithAlign($widget, $row1, $row2, $col1, $col2, $xEF, $yEF) + { + $align = new GtkAlignment(0,0,0,.5); + $align->add($widget); + $this->attach($align, $row1, $row2, $col1, $col2, $xEF, $yEF); + } + + /** + * Marks a label up as red text to indicate an error. + * + * @access public + * @param boolean $unknown Seriously, no idea what it means. + * @param object $label The GtkLabel to markup. + * @return void + */ + public function reportError(GtkLabel $label) + { + $label->set_label(self::ERROR_MARKUP_OPEN . $label->get_label() . self::ERROR_MARKUP_CLOSE); + } + + /** + * Clears the error markup from the given label. + * + * @access public + * @param $label The GtkLabel to remove markup from. + * @return void + */ + public function clearError(GtkLabel $label) + { + $text = $label->get_label(); + $text = str_replace(self::ERROR_MARKUP_OPEN, '', $text); + $text = str_replace(self::ERROR_MARKUP_CLOSE, '', $text); + + $label->set_label($text); + } + + /** + * Load the given product into the tool. + * + * @access public + * @param object $product A Crisscott_Product instance. + * @return void + */ + public function loadProduct(Crisscott_Product $product) + { + // First set the product as the current product. + $this->product = $product; + + // Next reset the tool. + $this->resetProduct(); + } + + /** + * Sets the values of the tool to the values of the current + * product. + * + * @access public + * @return void + */ + public function resetProduct() + { + // Make sure that there is a product. + if (!isset($this->product)) { + require_once 'Crisscott/Product.php'; + $this->loadProduct(new Crisscott_Product()); + } + + // Update the tools in the widget. + $this->nameEntry->set_text($this->product->name); + $this->_setComboActive($this->typeCombo, $this->product->type); + + $inv = Crisscott_Inventory::singleton(); + $cat = $inv->getCategoryById($this->product->categoryId); + $this->_setComboActive($this->categoryCombo, $cat->name); + + $this->priceSpin->set_value($this->product->price); + $this->inventorySpin->set_value($this->product->inventory); + $this->availCombo->set_active($this->product->availability); + $this->widthSpin->set_value($this->product->width); + $this->heightSpin->set_value($this->product->height); + $this->depthSpin->set_value($this->product->depth); + $this->weightSpin->set_value($this->product->weight); + + $buffer = $this->descView->get_buffer(); + $buffer->set_text($this->product->description); + } + + /** + * Sets the active item in a combo box. + * + * The combo box will be searched for the value given and + * the matching item will be made active. The method returns + * after the first match. + * + * This works for combos created with or without new_text(). + * + * @access private + * @param object $combo + * @param mixed $value + * @return void + */ + private function _setComboActive(GtkComboBox $combo, $value) + { + // Get the underlying model. + $model = $combo->get_model(); + // Get the first iter. + $iter = $model->get_iter_first(); + // Loop through the items. + for ($iter; $model->iter_is_valid($iter); $model->iter_next($iter)) { + // Check for a match. + if ($value == $model->get_value($iter, 0)) { + // A match! Set the active item and get out. + $combo->set_active_iter($iter); + return; + } + } + } + + /** + * Copies the data from the tool to the product. Then asks + * the product to save the data. + * + * @access public + * @return void + */ + public function saveProduct() + { + // Set the product properties. + $this->product->name = $this->nameEntry->get_text(); + $this->product->type = $this->typeCombo->get_active_text(); + + $inv = Crisscott_Inventory::singleton(); + $cat = $inv->getCategoryByName($this->categoryCombo->get_active_text()); + $this->product->categoryId = $cat->categoryId; + + $this->product->price = $this->priceSpin->get_value(); + $this->product->inventory = $this->inventorySpin->get_value(); + $this->product->availability = (boolean)$this->availCombo->get_active(); + $this->product->width = $this->widthSpin->get_value(); + $this->product->height = $this->heightSpin->get_value(); + $this->product->depth = $this->depthSpin->get_value(); + $this->product->weight = $this->weightSpin->get_value(); + + $buffer = $this->descView->get_buffer(); + $this->product->description = $buffer->get_text($buffer->get_start_iter(), $buffer->get_end_iter()); + + // Validate the new values. + $valid = $this->product->validate(); + + // Create a map of all the values and labels. + $labelMap = array( + 'name' => $this->nameLabel, + 'type' => $this->typeLabel, + 'category' => $this->categoryLabel, + 'price' => $this->priceLabel, + 'inventory' => $this->inventoryLabel, + 'avail' => $this->availLabel, + 'width' => $this->widthLabel, + 'height' => $this->heightLabel, + 'depth' => $this->depthLabel, + 'weight' => $this->weightLabel, + 'desc' => $this->descLabel + ); + + // Reset all of the labels. + foreach ($labelMap as $label) { + $this->clearError($label); + } + + // If there are invalid values, markup the labels. + if (is_array($valid)) { + foreach ($valid as $labelKey) { + $this->reportError($labelMap[$labelKey]); + } + + // Validatig the data was not successful. + return false; + } + + try { + // Try to save the data. + $this->product->save(); + + // Mark the buffer as saved. + $this->descView->get_buffer()->set_modified(false); + + // Update the inventory instance. + $inv = Crisscott_Inventory::singleton(); + $inv->refreshInventory(); + + // Also update the product tree. + $pt = Crisscott_Tools_ProductTree::singleton(); + $pt->updateModel(); + + } catch (Exception $e) { + throw $e; + return false; + } + } + + /** + * Creates or returns a singleton instance of this class. + * + * @static + * @access public + * @return object + */ + public static function singleton() + { + if (!isset(self::$instance)) { + $class = __CLASS__; + self::$instance = new $class; + } + return self::$instance; + } + +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/CrisscottChapter11/Tools/ProductSummary.php b/CrisscottChapter11/Tools/ProductSummary.php new file mode 100644 index 0000000..1b2679b --- /dev/null +++ b/CrisscottChapter11/Tools/ProductSummary.php @@ -0,0 +1,196 @@ +set_size_request(60, -1); + $type->set_size_request(60, -1); + $category->set_size_request(60, -1); + $price->set_size_request(60, -1); + + // Next align each label within the parent container. + $name->set_alignment(0, .5); + $type->set_alignment(0, .5); + $category->set_alignment(0, .5); + $price->set_alignment(0, .5); + + // Attach them to the table. + $expandFill = GTK::EXPAND|GTK::FILL; + $this->attach($name, 0, 1, 0, 1, 0, $expandFill); + $this->attach($type, 0, 1, 1, 2, 0, $expandFill); + $this->attach($category, 0, 1, 2, 3, 0, $expandFill); + $this->attach($price, 0, 1, 3, 4, 0, $expandFill); + + // Create the labels for the attributes. + $this->productName = new GtkLabel(); + $this->productType = new GtkLabel(); + $this->productCategory = new GtkLabel(); + $this->productPrice = new GtkLabel(); + + // Allow the labels to wrap. + $this->productName->set_line_wrap(true); + $this->productType->set_line_wrap(true); + $this->productCategory->set_line_wrap(true); + $this->productPrice->set_line_wrap(true); + + // Left align them. + $this->productName->set_alignment(0, .5); + $this->productType->set_alignment(0, .5); + $this->productCategory->set_alignment(0, .5); + $this->productPrice->set_alignment(0, .5); + + // Attach them to the table. + $this->attach($this->productName, 1, 2, 0, 1); + $this->attach($this->productType, 1, 2, 1, 2); + $this->attach($this->productCategory, 1, 2, 2, 3); + $this->attach($this->productPrice, 1, 2, 3, 4); + + // Attach a place holder for the image. + $this->productImage = new GtkFrame('Image'); + // The image's size can be fixed. + $this->productImage->set_size_request(100, 100); + $this->attach($this->productImage, 2, 3, 0, 4, 0, $expandFill); + + // Now that everything is set up, summarize the product. + require_once 'Crisscott/Product.php'; + $product = new Crisscott_Product(); + if (!empty($product)) { + $this->displaySummary($product); + } + } + + /** + * Displays a summary of the given product. + * + * When given a valid product object, this method updates the + * labels and image to match the values of the given product. + * + * @access public + * @param object $product A Crisscott_Product instance. + * @return void + */ + public function displaySummary(Crisscott_Product $product) + { + // Set the attribute labels to the values of the + // product. + $this->productName->set_text($product->name); + $this->productType->set_text($product->type); + + $inv = Crisscott_Inventory::singleton(); + $cat = $inv->getCategoryById($product->categoryId); + $this->productCategory->set_text($cat->name); + + $this->productPrice->set_text($product->price); + } + + /** + * Creates or returns a singleton instance of this class. + * + * @static + * @access public + * @return object + */ + public static function singleton() + { + if (!isset(self::$instance)) { + $class = __CLASS__; + self::$instance = new $class; + } + return self::$instance; + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/CrisscottChapter11/Tools/ProductTree.php b/CrisscottChapter11/Tools/ProductTree.php new file mode 100644 index 0000000..05ab732 --- /dev/null +++ b/CrisscottChapter11/Tools/ProductTree.php @@ -0,0 +1,140 @@ +updateModel(); + } + + public function updateModel() + { + // Create and set the model. + $this->set_model($this->_createModel()); + + // Next set up the view column and cell renderer. + $this->_setupColumn(); + + // Finally, set up the selection. + $this->_setupSelection(); + } + + private function _createModel() + { + // Set up the model. + // Each row should have the row name and the prouct_id. + // If the row is a category the product_id should be zero. + $model = new GtkTreeStore(Gtk::TYPE_STRING, Gtk::TYPE_LONG); + + // Get a singleton of the Inventory object. + require_once 'Crisscott/Inventory.php'; + $inventory = Crisscott_Inventory::singleton(); + + // Add all of the categories. + foreach ($inventory->categories as $category) { + $catIter = $model->append(null, array($category->name, 0)); + // Add all of the products for the category. + foreach ($category->getProducts() as $product) { + $model->append($catIter, array($product['product_name'], $product['product_id'])); + } + } + + return $model; + } + + private function _setupColumn() + { + // Add the name column. + $column = new GtkTreeViewColumn(); + $column->set_title('Products'); + + // Create a renderer for the column. + $cellRenderer = new GtkCellRendererText(); + $column->pack_start($cellRenderer, true); + $column->add_attribute($cellRenderer, 'text', 0); + + // Make the text ellipsize. + $cellRenderer->set_property('ellipsize', Pango::ELLIPSIZE_END); + + // Make the column sort on the product name. + $column->set_sort_column_id(0); + + // Insert the column. + $this->insert_column($column, 0); + } + + private function _setupSelection() + { + // Get the selection object. + $selection = $this->get_selection(); + + // Set the selection to browse mode. + $selection->set_mode(Gtk::SELECTION_BROWSE); + + // Create a signal handler to process the selection. + $selection->connect('changed', array($this, 'sendToSummary')); + } + + public function sendToSummary($selection) + { + // Get the selected row. + list($model, $iter) = $selection->get_selected(); + + // Create a product. + require_once 'Crisscott/Product.php'; + $product = new Crisscott_Product($model->get_value($iter, 1)); + + // Get the singleton product summary. + require_once 'Crisscott/Tools/ProductSummary.php'; + $productSummary = Crisscott_Tools_ProductSummary::singleton(); + $productSummary->displaySummary($product); + } + + /** + * Creates or returns a singleton instance of this class. + * + * @static + * @access public + * @return object + */ + public static function singleton() + { + if (!isset(self::$instance)) { + $class = __CLASS__; + self::$instance = new $class; + } + return self::$instance; + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/CrisscottChapter11/Tools/StatusBar.php b/CrisscottChapter11/Tools/StatusBar.php new file mode 100644 index 0000000..0267292 --- /dev/null +++ b/CrisscottChapter11/Tools/StatusBar.php @@ -0,0 +1,38 @@ + \ No newline at end of file diff --git a/CrisscottChapter11/Tools/Toolbar.php b/CrisscottChapter11/Tools/Toolbar.php new file mode 100644 index 0000000..c6176b4 --- /dev/null +++ b/CrisscottChapter11/Tools/Toolbar.php @@ -0,0 +1,81 @@ +createButtons(); + } + + protected function createButtons() + { + // Create a button to make new products, categories and + // contributors. + $new = new GtkMenuToolButton(GtkImage::new_from_stock(Gtk::STOCK_NEW, Gtk::ICON_SIZE_SMALL_TOOLBAR), 'New'); + $newMenu = new GtkMenu(); + + // Create the menu items. + $product = new GtkMenuItem('Product'); + $newMenu->append($product); + $category = new GtkMenuItem('Category'); + $newMenu->append($category); + $contrib = new GtkMenuItem('Contributor'); + $newMenu->append($contrib); + + // Set the menu as the menu for the new button. + $newMenu->show_all(); + $new->set_menu($newMenu); + + // Add the button to the toolbar. + $this->add($new); + + // Create the signal handlers for the new menu. + require_once 'Crisscott/MainWindow.php'; + //$application = Crisscott_MainWindow::singleton(); + + $new->connect_simple('clicked', array($application, 'newProduct')); + $product->connect_simple('activate', array($application, 'newProduct')); + $category->connect_simple('activate', array($application, 'newCategory')); + $contrib->connect_simple('activate', array($application, 'newContrib')); + // Create a toggle button that will connect to the database. + $database = GtkToggleToolButton::new_from_stock(Gtk::STOCK_CONNECT); + $database->set_label('Connect to Database'); + + // Add the button to the toolbar. + $this->add($database); + + // Create two buttons for sorting the product list. + $sortA = new GtkRadioToolButton(null, 'Ascending'); + $sortA->set_icon_widget(GtkImage::new_from_stock(Gtk::STOCK_SORT_ASCENDING, Gtk::ICON_SIZE_LARGE_TOOLBAR)); + $sortA->set_label('Sort Asc'); + + $sortD = new GtkRadioToolButton($sortA, 'Descending'); + $sortD->set_icon_widget(GtkImage::new_from_stock(Gtk::STOCK_SORT_DESCENDING, Gtk::ICON_SIZE_LARGE_TOOLBAR)); + $sortD->set_label('Sort Desc'); + + // Add the two buttons. + $this->add($sortA); + $this->add($sortD); + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim: et tw=78 + * vi: ts=1 sw=1 + */ +?> \ No newline at end of file diff --git a/CrisscottChapter11/db/Result.php b/CrisscottChapter11/db/Result.php new file mode 100644 index 0000000..6cf1e72 --- /dev/null +++ b/CrisscottChapter11/db/Result.php @@ -0,0 +1,69 @@ +result = $result; + $this->key = 0; + $this->rowCount = $this->result->numRows(); + } + } + + public function current() + { + return ($this->current = $this->result->fetchRow(DB_FETCHMODE_ASSOC,$this->key)); + } + + public function goToPrev() + { + if (--$this->key >= 0) { + return ($this->current = $this->result->fetchRow(DB_FETCHMODE_ASSOC,$this->key)); + } else { + return false; + } + } + + public function goToNext() + { + if (++$this->key < $this->rowCount) { + return ($this->current = $this->result->fetchRow(DB_FETCHMODE_ASSOC,$this->key)); + } else { + return false; + } + } + + public function valid() + { + return $this->key < $this->rowCount; + } + + public function numRows() + { + return $this->rowCount; + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/CrisscottChapter11/run.php b/CrisscottChapter11/run.php new file mode 100644 index 0000000..3ca7567 --- /dev/null +++ b/CrisscottChapter11/run.php @@ -0,0 +1,14 @@ +getMessage() . "\n"; +} + +set_exception_handler('exceptionHandler'); + +require_once 'Crisscott/SplashScreen.php'; +$csSplash = new Crisscott_SplashScreen(); +$csSplash->start(); +?> \ No newline at end of file diff --git a/CrisscottChapter12/Category.php b/CrisscottChapter12/Category.php new file mode 100644 index 0000000..35873be --- /dev/null +++ b/CrisscottChapter12/Category.php @@ -0,0 +1,118 @@ +categoryId = $categoryId; + } else { + throw new Exception('Cannot instantiate category. Invalid categoryId: ' . $categoryId); + } + + // Get the category name. + require_once 'Crisscott/DB.php'; + $db = Crisscott_DB::singleton(); + + $query = 'SELECT category_name '; + $query.= 'FROM categories '; + $query.= 'WHERE category_id = ' . $this->categoryId; + + $row = $db->query($query)->current(); + $this->name = $row['category_name']; + + // Get all of the products for this category. + $this->_getProducts(); + + // Set the specs for the category. + $this->_setSpecs(); + } + + private function _getProducts() + { + require_once 'Crisscott/DB.php'; + $db = Crisscott_DB::singleton(); + + $query = 'SELECT product_id, product_name, description, '; + $query.= ' product_type, category_id, inventory, available, '; + $query.= ' width, height, depth, weight, '; + $query.= ' price '; + $query.= 'FROM products '; + $query.= ' LEFT JOIN product_price '; + $query.= ' USING (product_id) '; + $query.= 'WHERE category_id = ' . $this->categoryId; + $query.= ' AND (currency = \'USD\' OR currency IS NULL) '; + + $this->products = $db->query($query); + } + + private function _setSpecs() + { + $this->specs = array(); + + if (!$this->products->numRows()) { + return; + } + + $this->specs['Total Products'] = $this->products->numRows(); + + $totalPrice = 0; + foreach ($this->products as $product) { + $totalPrice += $product['price']; + } + $this->specs['Avg. Price (USD)'] = number_format($totalPrice / $this->products->numRows(), 2); + + $totalWeight = 0; + foreach ($this->products as $product) { + $totalWeight += $product['weight']; + } + $this->specs['Avg. Weight (Ounces)'] = number_format($totalWeight / $this->products->numRows(), 2); + } + /** + * Returns a list of category specs. + * + * @static + * @access public + * @return object An iterator of specs. + */ + public static function getCategorySpecs() + { + return array('Total Products', 'Avg. Price (USD)', 'Avg. Weight (Ounces)'); + } + + public function getSpecValueByName($spec) + { + return $this->specs[$spec]; + } + + public function getProducts() + { + return $this->products; + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/CrisscottChapter12/Contributor.php b/CrisscottChapter12/Contributor.php new file mode 100644 index 0000000..ec9404d --- /dev/null +++ b/CrisscottChapter12/Contributor.php @@ -0,0 +1,330 @@ +init($contributorId); + } + } + + /** + * Grabs the values from the database and assigns them to + * the proper member variables. + * + * The contributor data is stored in the database. A singleton + * database instance is used to connect to the database and get + * the contributor values. + * + * @access protected + * @param integer $contributorId + * @return void + */ + protected function init($contributorId) + { + // Check the contributorId. + if (!is_numeric($contributorId)) { + throw new Exception('Invalid contributor id: ' . $contributorId); + } + + // Get a singleton DB instance. + require_once 'Crisscott/DB.php'; + $db = Crisscott_DB::singleton(); + + // Create the query. + $query = 'SELECT first_name, middle_name, last_name, '; + $query.= ' website, email, street1, street2, city, '; + $query.= ' state, country, postal '; + $query.= 'FROM contributor '; + $query.= 'WHERE contributor_id = ' . $contributorId . ' '; + + // Submit the query. + $result = $db->query($query); + + // If the query failed, we wouldn't be here. + $this->contributorId = $contributorId; + $this->firstName = $result['first_name']; + $this->middleName = $result['middle_name']; + $this->lastName = $result['last_name']; + $this->website = $result['website']; + $this->email = $result['email']; + $this->street1 = $result['street1']; + $this->street2 = $result['street2']; + $this->city = $result['city']; + $this->state = $result['state']; + $this->country = $result['country']; + $this->postal = $result['postal']; + } + + /** + * Checks the data to see that it is valid data. + * + * @access public + * @return mixed true if all data is valid or an array of invalid elements. + */ + public function validate() + { + $retArray = array(); + + if ($this->firstName != 'tester') { + $retArray[] = 'firstName'; + } + if ($this->lastName != 'test') { + $retArray[] = 'lastName'; + } + + return $retArray; + } + + /** + * Writes the contributor data to the database. + * + * @access public + * @return void + */ + public function save() + { + return true; + // Get a singleton DB instance. + require_once 'Crisscott/DB.php'; + $db = Crisscott_DB::singleton(); + + // Get the query. + if (isset($this->contributorId)) { + $query = $this->_getUpdateQuery(); + $getNewId = false; + } else { + $query = $this->_getInsertQuery(); + $getNewId = true; + } + + // Submit the query; + $db->query($query); + + // Do we need to get the contributorId? + if ($getNewId) { + $this->_getNewId(); + } + } + + /** + * Creates the query for updating information already + * in the database. + * + * @access private + * @return string + */ + private function _getUpdateQuery() + { + $query = 'UPDATE contributor '; + $query.= 'SET '; + $query.= ' first_name = \'' . $this->firstName . '\', '; + $query.= ' middle_name = \'' . $this->middleName . '\', '; + $query.= ' last_name = \'' . $this->lastName . '\', '; + $query.= ' website = \'' . $this->website . '\', '; + $query.= ' email = \'' . $this->email . '\', '; + $query.= ' street1 = \'' . $this->street1 . '\', '; + $query.= ' street2 = \'' . $this->street2 . '\', '; + $query.= ' city = \'' . $this->city . '\', '; + $query.= ' state = \'' . $this->state . '\', '; + $query.= ' country = \'' . $this->country . '\', '; + $query.= ' postal = \'' . $this->postal . '\' '; + $query.= 'WHERE contributorId = ' . $this->contirbutorId . ' '; + + return $query; + } + + /** + * Creates the query for inserting information into + * the database. + * + * @access private + * @return string + */ + private function _getInsertQuery() + { + $query = 'INSERT INTO contributor '; + $query.= '(first_name, middle_name, last_name, website, email, '; + $query.= ' street1, street2, city, state, country, postal) '; + $query.= 'VALUES ( '; + $query.= '\'' . $this->firstName . '\', '; + $query.= '\'' . $this->middleName . '\', '; + $query.= '\'' . $this->lastName . '\', '; + $query.= '\'' . $this->website . '\', '; + $query.= '\'' . $this->email . '\', '; + $query.= '\'' . $this->street1 . '\', '; + $query.= '\'' . $this->street2 . '\', '; + $query.= '\'' . $this->city . '\', '; + $query.= '\'' . $this->state . '\', '; + $query.= '\'' . $this->country . '\', '; + $query.= '\'' . $this->postal . '\' '; + $query.= ') '; + + return $query; + } + + /** + * Sets the contributor id by looking up the value from + * the database. + * + * @access private + * @return void + */ + private function _getNewId() + { + // Get a singleton DB instance. + require_once 'Crisscott/DB.php'; + $db = Crisscott_DB::singleton(); + + // Create the query. + $query = 'SELECT contributor_id '; + $query.= 'FROM contributor '; + $query.= 'WHERE first_name = \'' . $this->firstName . '\' '; + $query.= ' AND middle_name = \'' . $this->middleName . '\', '; + $query.= ' AND last_name = \'' . $this->lastName . '\' '; + $query.= ' AND website = \'' . $this->website . '\' '; + $query.= ' AND email = \'' . $this->email . '\' '; + $query.= ' AND street1 = \'' . $this->street1 . '\' '; + $query.= ' AND street2 = \'' . $this->street2 . '\' '; + $query.= ' AND city = \'' . $this->city . '\' '; + $query.= ' AND state = \'' . $this->state . '\' '; + $query.= ' AND country = \'' . $this->country . '\' '; + $query.= ' AND postal = \'' . $this->postal . '\' '; + + $result = $db->query($query); + + $this->contributorId = $result['contributor_id']; + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim: et tw=78 + * vi: ts=1 sw=1 + */ +?> \ No newline at end of file diff --git a/CrisscottChapter12/DB.php b/CrisscottChapter12/DB.php new file mode 100644 index 0000000..39e3eb6 --- /dev/null +++ b/CrisscottChapter12/DB.php @@ -0,0 +1,81 @@ +db = DB::connect($dsn); + if (PEAR::isError(self::$instance->db)) { + throw new Exception('Failed to connect to database: ' . self::$instance->db->getMessage() . "\n" . self::$instance->db->getUserInfo()); + } + } + return self::$instance; + } + + public function query($sql) + { + // Execute the query. + $result = $this->db->query($sql); + + // Check for errors. + if (PEAR::isError($result)) { + throw new Exception($result->getMessage()); + } elseif ($result == DB_OK) { + return true; + } else { + require_once 'Crisscott/DB/Result.php'; + return new Crisscott_DB_Result($result); + } + } + + public function prepare($sql) + { + $result = $this->db->prepare($sql); + + // Check for errors. + if (PEAR::isError($result)) { + throw new Exception($result->getMessage()); + } else { + return $result; + } + } + + public function execute($handle, $values) + { + $result = $this->db->execute($handle, $values); + + // Check for errors. + if (PEAR::isError($result)) { + var_dump($result); + throw new Exception($result->getMessage()); + } elseif ($result == DB_OK) { + return true; + } else { + return new Crisscott_DB_Result($result); + } + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/CrisscottChapter12/Inventory.php b/CrisscottChapter12/Inventory.php new file mode 100644 index 0000000..fab77a0 --- /dev/null +++ b/CrisscottChapter12/Inventory.php @@ -0,0 +1,113 @@ +refreshInventory(); + } + + public function refreshInventory() + { + // Get the categories. + require_once 'Crisscott/DB.php'; + $db = Crisscott_DB::singleton(); + + $query = 'SELECT category_id '; + $query.= 'FROM categories '; + + require_once 'Crisscott/Category.php'; + foreach ($db->query($query) as $row) { + $this->categories[] = new Crisscott_Category($row['category_id']); + } + } + + /** + * Creates or returns a singleton instance of this class. + * + * @static + * @access public + * @return object + */ + public static function singleton() + { + if (!isset(self::$instance)) { + $class = __CLASS__; + self::$instance = new $class; + } + return self::$instance; + } + + /** + * Returns the category with the given name. + * + * @access public + * @param string $category + * @return object + */ + public function getCategoryByName($category) + { + foreach ($this->categories as $cat) { + if ($cat->name == $category) { + return $cat; + } + } + + return null; + } + + /** + * Returns the category with the given id. + * + * @access public + * @param integer $category + * @return object + */ + public function getCategoryById($category) + { + foreach ($this->categories as $cat) { + if ($cat->categoryId == $category) { + return $cat; + } + } + + return null; + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/CrisscottChapter12/Iterator.php b/CrisscottChapter12/Iterator.php new file mode 100644 index 0000000..8993e2e --- /dev/null +++ b/CrisscottChapter12/Iterator.php @@ -0,0 +1,38 @@ +key = 0; + } + + public function current() { + return $this->current; + } + + public function key() { + return $this->key; + } + + public function next() { + return $this->goToNext(); + } + + + abstract protected function goToPrev(); + + abstract protected function goToNext(); + + public function valid() { + return isset($this->current); + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/CrisscottChapter12/MainNotebook.php b/CrisscottChapter12/MainNotebook.php new file mode 100644 index 0000000..b17dcd7 --- /dev/null +++ b/CrisscottChapter12/MainNotebook.php @@ -0,0 +1,84 @@ +pages = array(); + foreach ($titles as $title) { + $pageNum = $this->append_page(new GtkVBox(), new GtkLabel($title, true)); + $page = $this->get_nth_page($pageNum); + $this->pages[$title] = $page; + } + + $this->set_show_tabs(false); + + // Add a productediting instance to the notebook. + require_once 'Crisscott/Tools/ProductEdit.php'; + $this->pages['Product Edit']->add(Crisscott_Tools_ProductEdit::singleton()); + + // Add an category summary instance. + require_once 'Crisscott/Tools/CategorySummary.php'; + require_once 'Crisscott/Inventory.php'; + $this->pages['Category Summary']->add(new Crisscott_Tools_CategorySummary(Crisscott_Inventory::singleton())); + + // Add a contributoredit instance. + require_once 'Crisscott/Tools/ContributorEdit.php'; + $this->pages['Contributor Edit']->add(new Crisscott_Tools_ContributorEdit()); + + // Add the news article tool. + require_once 'Crisscott/Tools/NewsArticle.php'; + $news = Crisscott_Tools_NewsArticle::singleton(); + $this->pages['News Story']->add($news); + } + + /** + * Creates or returns a singleton instance of this class. + * + * @static + * @access public + * @return object + */ + public static function singleton() + { + if (!isset(self::$instance)) { + $class = __CLASS__; + self::$instance = new $class; + } + return self::$instance; + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/CrisscottChapter12/MainWindow.php b/CrisscottChapter12/MainWindow.php new file mode 100644 index 0000000..65d989a --- /dev/null +++ b/CrisscottChapter12/MainWindow.php @@ -0,0 +1,122 @@ +set_size_request(500, 300); + $this->set_position(Gtk::WIN_POS_CENTER); + $this->set_title('Criscott PIMS'); + + $this->_populate(); + + $this->maximize(); + $this->set_icon_from_file('Crisscott/images/logo.png'); + + $this->connect_simple('destroy', array('gtk', 'main_quit')); + } + + private function _populate() + { + $table = new GtkTable(5, 3); + + $expandFill = GTK::EXPAND|GTK::FILL; + + require_once 'Crisscott/Tools/Menu.php'; + $table->attach(new Crisscott_Tools_Menu(), 0, 2, 0, 1, $expandFill, 0, 0, 0); + + require_once 'Crisscott/Tools/Toolbar.php'; + $table->attach(new Crisscott_Tools_Toolbar(), 0, 2, 1, 2, $expandFill, 0, 0, 0); + + // Get a singleton instance of the product tree. + require_once 'Crisscott/Tools/ProductTree.php'; + $productTree = Crisscott_Tools_ProductTree::singleton(); + + // Create a scrolled window for the product tree. + $scrolledWindow = new GtkScrolledWindow(); + + // Set the size of the scrolled window. + $scrolledWindow->set_size_request(150, 150); + + // Set the scrollbar policy. + $scrolledWindow->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC); + + // Add the product tree to the scrolled window. + $scrolledWindow->add($productTree); + + // Attach the scrolled window to the tree. + $table->attach($scrolledWindow, 0, 1, 2, 3, 0, $expandFill, 0, 0); + + require_once 'Crisscott/Tools/NewsFeed.php'; + $feed = 'chapter9/news.rss'; + $news = Crisscott_Tools_NewsFeed::singleton(); + $news->setInput($feed); + $news->showList(); + $news->set_size_request(150, -1); + + $table->attach($news, 0, 1, 3, 4, 0, $expandFill, 0, 0); + + $table2 = new GtkTable(2, 2); + + $productSummary = new GtkFrame('PRODUCT SUMMARY'); + $productSummary->set_size_request(-1, 150); + + // Add the product summary tool. + require_once 'Crisscott/Tools/ProductSummary.php'; + $this->productSummary = Crisscott_Tools_ProductSummary::singleton(); + $productSummary->add($this->productSummary); + + $table2->attach($productSummary, 0, 1, 0, 1, $expandFill, 0, 1, 1); + + $inventorySummary = new GtkFrame('INVENTORY SUMMARY'); + $inventorySummary->set_size_request(-1, 150); + + $table2->attach($inventorySummary, 1, 2, 0, 1, $expandFill, 0, 1, 1); + + require_once 'Crisscott/MainNotebook.php'; + $this->mainNotebook = Crisscott_MainNotebook::singleton(); + $table2->attach($this->mainNotebook, 0, 2, 1, 2, $expandFill, $expandFill, 1, 1); + + $table->attach($table2, 1, 2, 2, 4, $expandFill, $expandFill, 0, 0); + + require_once 'Crisscott/Tools/StatusBar.php'; + $table->attach(Crisscott_Tools_StatusBar::singleton(), 0, 2, 4, 5, $expandFill, 0, 0, 0); + + $this->add($table); + } + + public function connectToServer() + { + sleep(1); + } + + public function connectToLocalDB() + { + sleep(1); + } + + static public function quit() + { + // Check to see if the data has been modified + // or sent. If it is modified or not sent, don't + // exit. + if (!self::$modified && self::$sent) { + gtk::main_quit(); + return true; + } + return false; + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/CrisscottChapter12/Product.php b/CrisscottChapter12/Product.php new file mode 100644 index 0000000..98c2ff2 --- /dev/null +++ b/CrisscottChapter12/Product.php @@ -0,0 +1,317 @@ +init($productId); + } + + protected function init($productId) + { + // Check the product id. + if (!is_numeric($productId)) { + throw new Exception('Cannot initialize product. Invalid productId: ' . $productId); + } else { + $this->productId = $productId; + } + + require_once 'Crisscott/DB.php'; + $db = Crisscott_DB::singleton(); + + $query = 'SELECT product_name, description, product_type, '; + $query.= ' category_id, inventory, available, width, height, '; + $query.= ' depth, weight, image_path, '; + $query.= ' price '; + $query.= 'FROM products '; + $query.= ' LEFT JOIN product_price '; + $query.= ' USING (product_id) '; + $query.= 'WHERE product_id = ' . $this->productId . ' '; + $query.= ' AND (currency = \'USD\' OR currency IS NULL) '; + + $row = $db->query($query)->current(); + if (count($row)) { + $this->name = $row['product_name']; + $this->description = $row['description']; + $this->type = $row['product_type']; + $this->categoryId = $row['category_id']; + $this->inventory = $row['inventory']; + $this->availability = $row['available'] == 't'; + $this->width = $row['width']; + $this->height = $row['height']; + $this->depth = $row['depth']; + $this->weight = $row['weight']; + $this->price = $row['price']; + $this->currency = $row['currency']; + $this->imagePath = $row['image_path']; + } + } + + /** + * Checks to see if the values of the product are valid. + * + * @access public + * @return mixed true or an array of the invalid fields. + */ + public function validate() + { + $invalidFields = array(); + + // Check the easy fields first. + // All of these fields must be numbers. + $numbers = array('price', + 'inventory', + 'width', + 'height', + 'depth', + 'weight' + ); + foreach ($numbers as $numField) { + if (!is_numeric($this->$numField)) { + $invalidFields[] = $numField; + } + } + + // Check the length of the product name. + if (strlen($this->name) > 50 || strlen($this->name) < 1) { + $invalidFields[] = 'name'; + } + + // Check that the availability is a boolean. + if (!is_bool($this->availability)) { + $invalidFields[] = 'availability'; + } + + // Check the product type. + $validTypes = array('Shippable', 'Digital'); + if (!in_array($this->type, $validTypes)) { + $invalidFields[] = 'type'; + } + + // Check the category. + if (empty($this->categoryId) || !is_numeric($this->categoryId)) { + $invalidFields[] = 'category'; + } + + // Check the image path. + if (!empty($this->imagePath) && !@is_readable($this->imagePath)) { + $invalidFields[] = 'image'; + } + + // The description can't really be invalid. + if (count($invalidFields)) { + return $invalidFields; + } else { + return true; + } + } + + public function save() + { + // Save the product information to the database. + if (isset($this->productId) && $this->productId > 0) { + return $this->updateProduct(); + } else { + return $this->saveNewProduct(); + } + } + + protected function updateProduct() + { + require_once 'Crisscott/DB.php'; + $db = Crisscott_DB::singleton(); + + $query = 'UPDATE products '; + $query = 'SET '; + $query.= ' product_name = ?, '; + $query.= ' description = ?, '; + $query.= ' product_type = ?, '; + $query.= ' category_id = ?, '; + $query.= ' inventory = ?, '; + $query.= ' available = ?, '; + $query.= ' width = ?, '; + $query.= ' height = ?, '; + $query.= ' depth = ?, '; + $query.= ' weight = ?, '; + $query.= ' image_path = ? '; + $query.= 'WHERE product_id = ? '; + + $stmt = $db->prepare($query); + + $prodArray = array( + $this->name, + $this->description, + $this->type, + $this->categoryId, + $this->inventory, + $this->availability, + $this->width, + $this->height, + $this->depth, + $this->weight, + $this->imagePath, + $this->productId + ); + + $db->execute($stmt, $prodArray); + + return true; + } + + protected function saveNewProduct() + { + require_once 'Crisscott/DB.php'; + $db = Crisscott_DB::singleton(); + + $query = 'INSERT INTO products '; + $query.= '(product_name, description, product_type, '; + $query.= ' category_id, inventory, available, width, height, '; + $query.= ' depth, weight, image_path) '; + $query.= 'VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) '; + + $stmt = $db->prepare($query); + + $prodArray = array( + $this->name, + $this->description, + $this->type, + $this->categoryId, + $this->inventory, + $this->availability, + $this->width, + $this->height, + $this->depth, + $this->weight, + $this->imagePath + ); + + + $db->execute($stmt, $prodArray); + + // Get the new product id. + $query = 'SELECT MAX(product_id) '; + $query.= 'FROM products '; + + $this->productId = reset($db->query($query)->current()); + + return true; + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/CrisscottChapter12/SplashScreen.php b/CrisscottChapter12/SplashScreen.php new file mode 100644 index 0000000..e1b535a --- /dev/null +++ b/CrisscottChapter12/SplashScreen.php @@ -0,0 +1,105 @@ +set_decorated(false); + + //$this->set_size_request(300, 100); + $this->set_position(Gtk::WIN_POS_CENTER); + + // Set the background. + $style = $this->style->copy(); + $style->bg[Gtk::STATE_NORMAL] = $style->white; + $this->set_style($style); + + $this->_populate(); + + $this->set_keep_above(true); + + $this->connect_simple_after('show', array($this, 'startMainWindow')); + } + + private function _populate() + { + $frame = new GtkFrame(); + $hBox = new GtkHBox(); + $vBox = new GtkVBox(); + $logoBox = new GtkHBox(); + $statusBox = new GtkHBox(); + + $frame->set_shadow_type(Gtk::SHADOW_ETCHED_OUT); + + $logo = new GtkLabel('Crisscott Product Information Management System'); + $logo->set_use_markup(true); + + $this->status = new GtkLabel('Initializing Main Window'); + + $vBox->pack_start($logoBox, true, true, 10); + $vBox->pack_start($statusBox, true, true, 10); + + $logoBox->pack_start($logo); + $statusBox->pack_start($this->status); + + // Add a logo image. + $logoImg = GtkImage::new_from_file('Crisscott/images/logo.png'); + + $hBox->pack_start($logoImg, false, false, 10); + $hBox->pack_start($vBox, false, false, 10); + $frame->add($hBox); + + $this->add($frame); + } + + + public function start() + { + $this->show_all(); + gtk::main(); + } + + public function startMainWindow() + { + // Update the GUI. + while (gtk::events_pending()) gtk::main_iteration(); + // Give the user enough time to at least see the message. + + require_once 'Crisscott/MainWindow.php'; + $main = new Crisscott_MainWindow(); + + $this->status->set_text('Connecting to server...'); + while (gtk::events_pending()) gtk::main_iteration(); + + if ($main->connectToServer()) { + $this->status->set_text('Connecting to server... OK'); + } + while (gtk::events_pending()) gtk::main_iteration(); + + $this->status->set_text('Connecting to local database...'); + while (gtk::events_pending()) gtk::main_iteration(); + + if ($main->connectToLocalDB()) { + $this->status->set_text('Connecting to local database... OK'); + } + while (gtk::events_pending()) gtk::main_iteration(); + + $main->show_all(); + + $this->set_keep_above(false); + $this->hide(); + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/CrisscottChapter12/Tools/CategorySummary.php b/CrisscottChapter12/Tools/CategorySummary.php new file mode 100644 index 0000000..bf5f7d8 --- /dev/null +++ b/CrisscottChapter12/Tools/CategorySummary.php @@ -0,0 +1,140 @@ +attachColHeaders(); + + // If an inventory was passed, add the data to the table. + if (!empty($inventory)) { + $this->summarizeInventory($inventory); + } + } + + /** + * Summarizes all the categories in the inventory. + * + * First clears out the table. Next re-attches the column + * headers. Finally each category is added as its own row. + * + * @access public + * @param object $inventory A Crisscott_Inventory instance. + * @return void + */ + public function summarizeInventory(Crisscott_Inventory $inventory) + { + // Clear out the table. + $this->clear(); + + // Re-attach the headers. + $this->attachColHeaders(); + + // Add a row for each category. + foreach ($inventory->categories as $category) { + $this->summarizeCategory($category); + } + } + + /** + * Attaches column headers to the table. + * + * @access protected + * @return void + */ + protected function attachColHeaders() + { + require_once 'Crisscott/Category.php'; + foreach (Crisscott_Category::getCategorySpecs() as $key => $spec) { + $label = new GtkLabel($spec); + $label->set_angle(90); + $label->set_alignment(.5, 1); + + // Leave the first cell empty. + $this->attach($label, $key + 1, $key + 2, 0, 1, 0, GTK::FILL, 10, 10); + } + + // Increment the last row. + $this->lastRow++; + + } + + /** + * Adds a row of data for the given category. + * + * @access public + * @param object $category A Crisscott_Category instance. + * @return void + */ + public function summarizeCategory(Crisscott_Category $category) + { + // First attach the category name. + $nameLabel = new GtkLabel($category->name); + $nameLabel->set_alignment(0, .5); + $this->attach($nameLabel, 0, 1, $this->lastRow, $this->lastRow + 1, GTK::FILL, 0, 10, 10); + + // Next attach the spec values. + foreach (Crisscott_Category::getCategorySpecs() as $key => $spec) { + $value = $category->getSpecValueByName($spec); + $this->attach(new GtkLabel($value), $key + 1, $key + 2, $this->lastRow, $this->lastRow + 1, 0, 0, 1, 1); + } + + // Increment the last row. + $this->lastRow++; + } + + /** + * Clears all cells of the table. + * + * @access protected + * @return void + */ + protected function clear() + { + foreach ($this->get_children() as $child) { + $this->remove($child); + } + + // Reset the last row. + $this->lastRow = 0; + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim: et tw=78 + * vi: ts=1 sw=1 + */ +?> \ No newline at end of file diff --git a/CrisscottChapter12/Tools/ContributorEdit.php b/CrisscottChapter12/Tools/ContributorEdit.php new file mode 100644 index 0000000..d6b96a4 --- /dev/null +++ b/CrisscottChapter12/Tools/ContributorEdit.php @@ -0,0 +1,557 @@ +'; + const ERROR_MARKUP_CLOSE = ''; + + /** + * The contributor currently being modified. + * + * @access public + * @var object + */ + public $contributor; + + /** + * A label for the contributor's first name. + * + * @access private + * @var object + */ + private $firstNameLabel; + + /** + * A label for the contributor's middle name. + * + * @access private + * @var object + */ + private $middleNameLabel; + + /** + * A label for the contributor's last name. + * + * @access private + * @var object + */ + private $lastNameLabel; + + /** + * A label for the contributor's web address. + * + * @access private + * @var object + */ + private $websiteLabel; + + /** + * A label for the contributor's email address. + * + * @access private + * @var object + */ + private $emailLabel; + + /** + * A label for the contributor's street address. + * + * @access private + * @var object + */ + private $street1Label; + + /** + * A label for the contributor's street address. + * + * @access private + * @var object + */ + private $street2Label; + + /** + * A label for the contributor's city. + * + * @access private + * @var object + */ + private $cityLabel; + + /** + * A label for the contributor's state. + * + * @access private + * @var object + */ + private $stateLabel; + + /** + * A label for the contributor's country. + * + * @access private + * @var object + */ + private $countryLabel; + + /** + * A label for the contributor's postal code. + * + * @access private + * @var object + */ + private $postalLabel; + + /** + * A entry for the contributor's first name. + * + * @access private + * @var object + */ + private $firstNameEntry; + + /** + * A entry for the contributor's middle name. + * + * @access private + * @var object + */ + private $middleNameEntry; + + /** + * A entry for the contributor's last name. + * + * @access private + * @var object + */ + private $lastNameEntry; + + /** + * A entry for the contributor's web address. + * + * @access private + * @var object + */ + private $websiteEntry; + + /** + * A entry for the contributor's email address. + * + * @access private + * @var object + */ + private $emailEntry; + + /** + * A entry for the contributor's street address. + * + * @access private + * @var object + */ + private $street1Entry; + + /** + * A entry for the contributor's street address. + * + * @access private + * @var object + */ + private $street2Entry; + + /** + * A entry for the contributor's city. + * + * @access private + * @var object + */ + private $cityEntry; + + /** + * A entry for the contributor's state. + * + * @access private + * @var object + */ + private $stateEntry; + + /** + * A entry for the contributor's country. + * + * @access private + * @var object + */ + private $countryComboBox; + + /** + * A entry for the contributor's postal code. + * + * @access private + * @var object + */ + private $postalEntry; + + /** + * Constructor. Calls the methods to create the tool and + * sets up the needed callbacks. + * + * @access public + * @param object $contributor A Crisscott_Contributor instance. (Optional) + * @return void + */ + public function __construct($contributor = null) + { + // Call the parent constructor. + parent::__construct(7, 4); + + // Layout the tool. + $this->_layoutTool(); + + // Connect the needed callbacks. + + // Prepopulate the fields if a contributor is given. + if (empty($contributor) || is_a($contributor, 'Crisscott_Contributor')) { + require_once 'Crisscott/Contributor.php'; + $contributor = new Crisscott_Contributor(); + } + $this->populateFields($contributor); + } + + /** + * Laysout the labels, entries and buttons. + * + * This tool consists of several labels with corresponding entries + * and two buttons. One button resets the fields the other submits + * the information to change the contributors values. + * + * @access private + * @return void + */ + private function _layoutTool() + { + // First create the labels that identify the fields. + $this->firstNameLabel = new GtkLabel('First Name'); + $this->middleNameLabel = new GtkLabel('Middle Name'); + $this->lastNameLabel = new GtkLabel('Last Name'); + $this->emailLabel = new GtkLabel('Email Address'); + $this->websiteLabel = new GtkLabel('Website'); + $this->street1Label = new GtkLabel('Street 1'); + $this->street2Label = new GtkLabel('Street 2'); + $this->cityLabel = new GtkLabel('City'); + $this->stateLabel = new GtkLabel('State'); + $this->countryLabel = new GtkLabel('Country'); + $this->postalLabel = new GtkLabel('Postal Code'); + + // Next add the labels to the table. + // The labels will be added in two columns. + // First column. + $this->attach($this->firstNameLabel, 0, 1, 0, 1, GTK::FILL, 0); + $this->attach($this->middleNameLabel, 0, 1, 1, 2, GTK::FILL, 0); + $this->attach($this->lastNameLabel, 0, 1, 2, 3, GTK::FILL, 0); + $this->attach($this->emailLabel, 0, 1, 3, 4, GTK::FILL, 0); + $this->attach($this->websiteLabel, 0, 1, 4, 5, GTK::FILL, 0); + + // Second column. + $this->attach($this->street1Label, 2, 3, 0, 1, GTK::FILL, 0); + $this->attach($this->street2Label, 2, 3, 1, 2, GTK::FILL, 0); + $this->attach($this->cityLabel, 2, 3, 2, 3, GTK::FILL, 0); + $this->attach($this->stateLabel, 2, 3, 3, 4, GTK::FILL, 0); + $this->attach($this->countryLabel, 2, 3, 4, 5, GTK::FILL, 0); + $this->attach($this->postalLabel, 2, 3, 5, 6, GTK::FILL, 0); + + // Right align all of the labels. + $this->firstNameLabel->set_alignment(1, .5); + $this->middleNameLabel->set_alignment(1, .5); + $this->lastNameLabel->set_alignment(1, .5); + $this->emailLabel->set_alignment(1, .5); + $this->websiteLabel->set_alignment(1, .5); + $this->street1Label->set_alignment(1, .5); + $this->street2Label->set_alignment(1, .5); + $this->cityLabel->set_alignment(1, .5); + $this->stateLabel->set_alignment(1, .5); + $this->countryLabel->set_alignment(1, .5); + $this->postalLabel->set_alignment(1, .5); + + // Turn on markup + $this->firstNameLabel->set_use_markup(true); + $this->middleNameLabel->set_use_markup(true); + $this->lastNameLabel->set_use_markup(true); + $this->emailLabel->set_use_markup(true); + $this->websiteLabel->set_use_markup(true); + $this->street1Label->set_use_markup(true); + $this->street2Label->set_use_markup(true); + $this->cityLabel->set_use_markup(true); + $this->stateLabel->set_use_markup(true); + $this->countryLabel->set_use_markup(true); + $this->postalLabel->set_use_markup(true); + + // Next create all of the data collection widgets. + $this->firstNameEntry = new GtkEntry(); + $this->middleNameEntry = new GtkEntry(); + $this->lastNameEntry = new GtkEntry(); + $this->emailEntry = new GtkEntry(); + $this->websiteEntry = new GtkEntry(); + $this->street1Entry = new GtkEntry(); + $this->street2Entry = new GtkEntry(); + $this->cityEntry = new GtkEntry(); + $this->stateEntry = new GtkEntry(); + $this->postalEntry = new GtkEntry(); + + // The country should be a combobox. + $this->countryComboBox = GtkComboBox::new_text(); + $this->countryComboBox->append_text('United States'); + $this->countryComboBox->prepend_text('Canada'); + $this->countryComboBox->insert_text(1, 'United Kingdom'); + $this->countryComboBox->set_active(0); + + // Next add the entrys to the table. + // The entrys will be added in two columns. + // First column. + $this->attach($this->firstNameEntry, 1, 2, 0, 1, 0, 0); + $this->attach($this->middleNameEntry, 1, 2, 1, 2, 0, 0); + $this->attach($this->lastNameEntry, 1, 2, 2, 3, 0, 0); + $this->attach($this->emailEntry, 1, 2, 3, 4, 0, 0); + $this->attach($this->websiteEntry, 1, 2, 4, 5, 0, 0); + + // Second column. + $this->attach($this->street1Entry, 3, 4, 0, 1, 0, 0); + $this->attach($this->street2Entry, 3, 4, 1, 2, 0, 0); + $this->attach($this->cityEntry, 3, 4, 2, 3, 0, 0); + $this->attach($this->stateEntry, 3, 4, 3, 4, 0, 0); + $this->attach($this->countryComboBox, 3, 4, 4, 5, 0, 0); + $this->attach($this->postalEntry, 3, 4, 5, 6, 0, 0); + + // Help the user out with the state by using a GtkEntryCompletion. + $stateCompletion = new GtkEntryCompletion(); + $stateCompletion->set_model(self::createStateList()); + $stateCompletion->set_text_column(0); + $this->stateEntry->set_completion($stateCompletion); + $stateCompletion->set_inline_completion(true); + + // Add the save and clear buttons. + $save = GtkButton::new_from_stock('gtk-save'); + $reset = GtkButton::new_from_stock('gtk-undo'); + $save->connect_simple('clicked', array($this, 'saveContributor')); + $reset->connect_simple('clicked', array($this, 'resetContributor')); + + $this->attach($reset, 0, 1, 6, 7, 0, 0); + $this->attach($save, 3, 4, 6, 7, 0, 0); + } + + /** + * Creates a one column list store that contains the US states + * and Canadian provinces. + * + * This list can be used for combo boxes, tree, or entry + * completions. + * + * @static + * @access public + * @return object A GtkListStore + */ + public static function createStateList() + { + $listStore = new GtkListStore(GTK::TYPE_STRING); + $iter = $listStore->append(); + $listStore->set($iter, 0, 'Alabama'); + $iter = $listStore->append(); + $listStore->set($iter, 0, 'Alaska'); + $iter = $listStore->append(); + $listStore->set($iter, 0, 'Arizona'); + $iter = $listStore->append(); + $listStore->set($iter, 0, 'Arkansas'); + $iter = $listStore->append(); + $listStore->set($iter, 0, 'California'); + $iter = $listStore->append(); + $listStore->set($iter, 0, 'Colorodo'); + + return $listStore; + } + + /* + * + * When a contributor is edited, it is stored in a member variable + * and then its values are used to populate the fields. + * + * @access public + * @param object $contributor A Crisscott_Contributor instance. + * @return void + */ + public function populateFields(Crisscott_Contributor $contributor) + { + // Populate the fields. + $this->firstNameEntry->set_text($contributor->firstName); + $this->middleNameEntry->set_text($contributor->middleName); + $this->lastNameEntry->set_text($contributor->lastName); + $this->emailEntry->set_text($contributor->email); + $this->websiteEntry->set_text($contributor->website); + + $this->street1Entry->set_text($contributor->street1); + $this->street2Entry->set_text($contributor->street2); + $this->cityEntry->set_text($contributor->city); + $this->stateEntry->set_text($contributor->state); + $this->postalEntry->set_text($contributor->postal); + + // Set the active element for the country combo box. + $model = $this->countryComboBox->get_model(); + $iter = $model->get_iter_first(); + for ($iter; $model->iter_is_valid($iter); $model->iter_next($iter)) { + if ($this->contributor->country == $model->get_value($iter, 0)) { + $this->countryComboBox->set_active_iter($iter); + } + } + + // Keep a hold of the contributor. + $this->contributor = $contributor; + } + + /** + * Resets the fields with the original contributor data. + * + * This method basically is an undo for all changes that + * have been made since the last save. It re-grabs the + * values from the contributor and populates them again. + * + * @uses populateFields + * + * @access public + * @return void + */ + public function resetContributor() + { + // Make sure we have a contributor already. + if (!isset($this->contributor)) { + require_once 'Crisscott/Contributor.php'; + $this->contributor = new Crisscott_Contributor(); + $this->contributor->country = 'United States'; + } + + // Reset the fields to the original value. + $this->populateFields($this->contributor); + } + + /** + * Grabs, validates, and saves the contributor information. + * + * First this method collects the data values from the widgets + * and then assigns them to the contributor object. Next the + * values are validated using the contributors validate method. + * If all is ok, the contributor is told to write the data to + * the database. + * + * @access public + * @return boolean true on success. + */ + public function saveContributor() + { + // First grab all of the values. + $this->contributor->firstName = $this->firstNameEntry->get_text(); + $this->contributor->middleName = $this->firstNameEntry->get_text(); + $this->contributor->lastName = $this->lastNameEntry->get_text(); + $this->contributor->website = $this->websiteEntry->get_text(); + $this->contributor->email = $this->emailEntry->get_text(); + $this->contributor->street1 = $this->street1Entry->get_text(); + $this->contributor->street1 = $this->street1Entry->get_text(); + $this->contributor->city = $this->cityEntry->get_text(); + $this->contributor->state = $this->stateEntry->get_text(); + //$this->contributor->country = $this->countryComboBox->get_text(); + $this->contributor->postal = $this->postalEntry->get_text(); + + // Next validate the data. + $valid = $this->contributor->validate(); + + // Create a map of all the values and labels. + $labelMap = array('firstName' => $this->firstNameLabel, + 'middleName' => $this->middleNameLabel, + 'lastName' => $this->lastNameLabel, + 'website' => $this->websiteLabel, + 'email' => $this->emailLabel, + 'street1' => $this->street1Label, + 'street2' => $this->street2Label, + 'city' => $this->cityLabel, + 'state' => $this->stateLabel, + 'country' => $this->countryLabel, + 'postal' => $this->postalLabel + ); + + // Reset all of the labels. + foreach ($labelMap as $label) { + $this->clearError($label); + } + + // If there are invalid values, markup the labels. + if (is_array($valid)) { + foreach ($valid as $labelKey) { + $this->reportError($labelMap[$labelKey]); + } + + // Saving the data was not successful. + return false; + } + + // Try to save the data. + return $this->contributor->save(); + } + + /** + * Marks a label up as red text to indicate an error. + * + * @access public + * @param object $label The GtkLabel to markup. + * @return void + */ + public function reportError(GtkLabel $label) + { + require_once 'Crisscott/Tools/StatusBar.php'; + $status = Crisscott_Tools_StatusBar::singleton(); + var_dump($status->push(rand(), 'Error: ' . $label->get_label())); + + $label->set_label(self::ERROR_MARKUP_OPEN . $label->get_label() . self::ERROR_MARKUP_CLOSE); + } + + /** + * Clears the error markup from the given label. + * + * @access public + * @param $label The GtkLabel to remove markup from. + * @return void + */ + public function clearError(GtkLabel $label) + { + require_once 'Crisscott/Tools/StatusBar.php'; + $status = Crisscott_Tools_StatusBar::singleton(); + $contextId = $status->get_context_id('Error: ' . $label->get_label()); + $status->pop($contextId); + + $text = $label->get_label(); + $text = str_replace(self::ERROR_MARKUP_OPEN, '', $text); + $text = str_replace(self::ERROR_MARKUP_CLOSE, '', $text); + + $label->set_label($text); + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim: et tw=78 + * vi: ts=1 sw=1 + */ +?> \ No newline at end of file diff --git a/CrisscottChapter12/Tools/Menu.php b/CrisscottChapter12/Tools/Menu.php new file mode 100644 index 0000000..d0ec939 --- /dev/null +++ b/CrisscottChapter12/Tools/Menu.php @@ -0,0 +1,152 @@ +file = new GtkMenuItem('_File'); + $this->append($this->file); + + $this->edit = new GtkMenuItem('_Edit'); + $this->append($this->edit); + + $this->help = new GtkMenuItem('_Help'); + $this->append($this->help); + + // Create the sub menus. + $this->createSubMenus(); + } + + protected function createSubMenus() + { + // Create the file menu and items. + $fileMenu = new GtkMenu(); + $new = new GtkImageMenuItem(Gtk::STOCK_NEW); + $open = new GtkImageMenuItem(Gtk::STOCK_OPEN); + $send = new GtkImageMenuItem('Send'); + $send->set_image(GtkImage::new_from_file('Crisscott/images/menuItemGrey.png')); + $save = new GtkMenuItem('Save'); + $quit = new GtkMenuItem('Quit'); + + // Add the four items to the file menu. + $fileMenu->append($new); + $fileMenu->append($open); + $fileMenu->append($send); + + // Create a sub menu for the new item. + $newMenu = new GtkMenu(); + $product = new GtkMenuItem('Product'); + $category = new GtkMenuItem('Category'); + $contrib = new GtkMenuItem('Contributor'); + + // Make the new menu detachable. + $newMenu->append(new GtkTearoffMenuItem()); + $newMenu->append($product); + $newMenu->append($category); + $newMenu->append($contrib); + + $new->set_submenu($newMenu); + + // Add a separator. + $fileMenu->append(new GtkSeparatorMenuItem()); + + // Add some check items. + $server = new GtkCheckMenuItem('Connect to Server'); + $database = new GtkCheckMenuItem('Connect to Database'); + + $fileMenu->append($server); + $fileMenu->append($database); + + // Add a separator. + $fileMenu->append(new GtkSeparatorMenuItem()); + + // Add three noise levels. + $quiet = new GtkRadioMenuItem(null, 'Quiet'); + $normal = new GtkRadioMenuItem($quiet, 'Normal'); + $verbose = new GtkRadioMenuItem($quiet, 'Verbose'); + + $fileMenu->append($quiet); + $fileMenu->append($normal); + $fileMenu->append($verbose); + + // Add a separator. + $fileMenu->append(new GtkSeparatorMenuItem()); + + // Finish of the menu. + $fileMenu->append($save); + $fileMenu->append($quit); + + // Connect some signal handlers. + $quit->connect_simple('activate', array('Crisscott_MainWindow', 'quit')); + + $editMenu = new GtkMenu(); + $product = new GtkImageMenuItem('Current Product'); + + $editMenu->append($product); + + // Make the product menu item do something. + require_once 'Crisscott/Tools/ProductSummary.php'; + $summary = Crisscott_Tools_ProductSummary::singleton(); + + $product->connect_simple('activate', array($summary, 'editProduct')); + + // Create the help menu and items. + $helpMenu = new GtkMenu(); + $help = new GtkMenuItem('Help'); + $about = new GtkMenuItem('About'); + + // Add both items to the help menu. + $helpMenu->append($help); + $helpMenu->append($about); + + // Make the two menus submenus for the menu items. + $this->file->set_submenu($fileMenu); + $this->edit->set_submenu($editMenu); + $this->help->set_submenu($helpMenu); + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim: et tw=78 + * vi: ts=1 sw=1 + */ +?> \ No newline at end of file diff --git a/CrisscottChapter12/Tools/NewsArticle.php b/CrisscottChapter12/Tools/NewsArticle.php new file mode 100644 index 0000000..12d93f7 --- /dev/null +++ b/CrisscottChapter12/Tools/NewsArticle.php @@ -0,0 +1,201 @@ +_layout(); + } + + /** + * Lays out the tool. Creates the widgets used by the tool + * and adds them to the container. + * + * @access private + * @return void + */ + private function _layout() + { + // Create a label for the headline. + $this->headline = new GtkLabel(); + + // Create a view for the article. + $this->view = new GtkTextView(); + + // Get the buffer from the view. + $this->buffer = $this->view->get_buffer(); + + // Get a tag for making text bold and dark blue. + $this->tag = new GtkTextTag(); + // Set the tag properties + + // Make the tag part of the buffers tag table. + $tagTable = $this->buffer->get_tag_table(); + $tagTable->add($this->tag); + + // The text in this view should not be editable. + $this->view->set_editable(false); + + // Since the user can't edit the text there is not point in + // letting them see the cursor. + $this->view->set_cursor_visible(false); + + // Pack everything together. + $this->pack_start($this->headline, false, false, 5); + $this->pack_start($this->view); + } + + /** + * Sets the headline and the article text. + * + * @uses setHeadline + * @uses setBody + * + * @access public + * @param string $headline + * @param string $text + * @return void + */ + public function setArticle($headline, $text) + { + // Set the headline. + $this->setHeadline($headline); + + // Set the body. + $this->setBody($text); + } + + /** + * Sets the text of the headline and makes it look like a + * headline. + * + * @access public + * @param string $headline + * @return void + */ + public function setHeadline($headline) + { + // Add some markup to make the headline appear like + // a headline. + $headline = '' . $headline; + $headline.= ''; + + // Set the text of the headline label. + $this->headline->set_text($headline); + + // Make sure the headline is set to use the markup that was added. + $this->headline->set_use_markup(true); + + } + + /** + * Sets the given text as the text of the buffer. + * + * Any time that "Crisscott" is found in the article body, it is + * formatted so that it appears bold and dark blue. This is done + * using tags. + * + * @access public + * @param string $body + * @return void + */ + public function setBody($body) + { + // Do some special formatting of any instances of + // Crisscott found in the article body. + $lastCrisscott = 0; + while ($pos = strpos($body, 'Crisscott', $lastCrisscott)) { + $wordStart = $this->buffer->get_iter_at_offset($pos); + $wordEnd = $this->buffer->get_iter_at_offset($pos); + $wordEnd->forward_word_end(); + + // Apply the tag. + $this->buffer->apply_tag($this->tag, $wordStart, $wordEnd); + + // Update the strpos offset. + $lastCrisscott = $pos; + } + + // Set the article text in the buffer. + $this->buffer->set_text($body); + } + + /** + * Creates or returns a singleton instance of this class. + * + * @static + * @access public + * @return object + */ + public static function singleton() + { + if (!isset(self::$instance)) { + $class = __CLASS__; + self::$instance = new $class; + } + return self::$instance; + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/CrisscottChapter12/Tools/NewsFeed.php b/CrisscottChapter12/Tools/NewsFeed.php new file mode 100644 index 0000000..db82404 --- /dev/null +++ b/CrisscottChapter12/Tools/NewsFeed.php @@ -0,0 +1,199 @@ +rss = new XML_RSS(); + + // Set the input if given. + if (isset($handle)) { + $this->setInput($handle); + } + + // Add the tree column. + $this->addColumn(); + + // Set up the selection to load a selected item. + $selection = $this->get_selection(); + $selection->connect('changed', array($this, 'loadArticle')); + } + + /** + * Sets the input that will be parsed. + * + * @access public + * @param mixed $handle Feed handle. See XML_RSS. + * @return void + */ + public function setInput($handle) + { + $this->rss->setInput($handle); + } + + /** + * Parses the feed and turns it into a list. + * + * @access public + * @return object + */ + public function createList() + { + // Parse the feed. + $this->rss->parse(); + + // Create a list store with four columns. + $listStore = new GtkListStore(Gtk::TYPE_STRING, + Gtk::TYPE_STRING, + Gtk::TYPE_STRING, + Gtk::TYPE_LONG + ); + + // Add a row for each item in the feed. + foreach ($this->rss->getItems() as $item) { + $rowData = array($item['title'], + $item['dc:date'], + $item['description'], + Pango::WEIGHT_BOLD + ); + $listStore->append($rowData); + } + + return $listStore; + } + + /** + * Adds the list to the view to show the feed. + * + * @access public + * @return void + */ + public function showList() + { + // Add the list to the view. + $this->set_model($this->createList()); + } + + /** + * Adds the column to the view and sets the display + * properties. + * + * @access protected + * @return void + */ + protected function addColumn() + { + // Create the column. + $column = new GtkTreeViewColumn(); + $column->set_title('News'); + + // Create a cell renderer. + $cellRenderer = new GtkCellRendererText(); + + // Make the text ellipsize. + $cellRenderer->set_property('ellipsize', Pango::ELLIPSIZE_END); + + // Pack the cell renderer. + $column->pack_start($cellRenderer, true); + $column->add_attribute($cellRenderer, 'text', 0); + $column->add_attribute($cellRenderer, 'weight', 3); + + // Sort the column by date. + $column->set_sort_column_id(1); + + // Add the column to the tree. + $this->append_column($column); + } + + /** + * Loads a selected news item. + * + * @access public + * @param object $selection The selected row of the list. + * @return void + */ + public function loadArticle($selection) + { + // Unbold the selected item. + list($model, $iter) = $selection->get_selected(); + $model->set($iter, 3, Pango::WEIGHT_NORMAL); + + // Get a singleton news article tool. + require_once 'Crisscott/Tools/NewsArticle.php'; + $newsArticle = Crisscott_Tools_NewsArticle::singleton(); + + // Set the article. + $headline = $model->get_value($iter, 0); + $body = $model->get_value($iter, 2); + $newsArticle->setArticle($headline, $body); + + // Bring the news story tab to the front. + require_once 'Crisscott/MainNotebook.php'; + $notebook = Crisscott_MainNotebook::singleton(); + + // Get the page index. + $index = array_search('News Story', array_keys($notebook->pages)); + $notebook->set_current_page($index); + } + + /** + * Creates or returns a singleton instance of this class. + * + * @static + * @access public + * @return object + */ + public static function singleton() + { + if (!isset(self::$instance)) { + $class = __CLASS__; + self::$instance = new $class; + } + return self::$instance; + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/CrisscottChapter12/Tools/ProductEdit.php b/CrisscottChapter12/Tools/ProductEdit.php new file mode 100644 index 0000000..6d6ddae --- /dev/null +++ b/CrisscottChapter12/Tools/ProductEdit.php @@ -0,0 +1,664 @@ +'; + const ERROR_MARKUP_CLOSE = ''; + + /** + * Singleton instance of this object. + * + * @access public + * @var object + */ + public static $instance; + + /** + * The current/last product being edited. + * + * @access public + * @var object + */ + public $product; + + /** + * A label for the Name entry. + * + * @access public + * @var object + */ + public $nameLabel; + + /** + * A label for the Type entry. + * + * @access public + * @var object + */ + public $typeLabel; + + /** + * A label for the Category entry. + * + * @access public + * @var object + */ + public $categoryLabel; + + /** + * A label for the Price entry. + * + * @access public + * @var object + */ + public $priceLabel; + + /** + * A label for the Description entry. + * + * @access public + * @var object + */ + public $descLabel; + + /** + * A label for the Inventory entry. + * + * @access public + * @var object + */ + public $inventoryLabel; + + /** + * A label for the Availability entry. + * + * @access public + * @var object + */ + public $availLabel; + + /** + * A label for the Width entry. + * + * @access public + * @var object + */ + public $widthLabel; + + /** + * A label for the Height entry. + * + * @access public + * @var object + */ + public $heigthLabel; + + /** + * A label for the Depth entry. + * + * @access public + * @var object + */ + public $depthLabel; + + /** + * A label for the Weight entry. + * + * @access public + * @var object + */ + public $weightLabel; + + /** + * A label for the image. + * + * @access public + * @var object + */ + public $imageLabel; + + /** + * A entry for the Name. + * + * @access public + * @var object + */ + public $nameEntry; + + /** + * A entry for the Type. + * + * @access public + * @var object + */ + public $typeEntry; + + /** + * A entry for the Category. + * + * @access public + * @var object + */ + public $categoryEntry; + + /** + * A entry for the Price. + * + * @access public + * @var object + */ + public $priceEntry; + + /** + * A entry for the Description. + * + * @access public + * @var object + */ + public $descEntry; + + /** + * A entry for the Inventory. + * + * @access public + * @var object + */ + public $inventoryEntry; + + /** + * A entry for the Availability. + * + * @access public + * @var object + */ + public $availEntry; + + /** + * A entry for the Width. + * + * @access public + * @var object + */ + public $widthSpin; + + /** + * A entry for the Height. + * + * @access public + * @var object + */ + public $heightSpin; + + /** + * A entry for the Depth. + * + * @access public + * @var object + */ + public $depthEntry; + + /** + * A entry for the Weight. + * + * @access public + * @var object + */ + public $weightEntry; + + /** + * A container to hold the product image. + * + * @access public + * @var object + */ + public $imageContainer; + + /** + * A entry for the image path. + * + * @access public + * @var object + */ + public $imagePathEntry; + + /** + * Constructor. Sets up the tool. + * + * @access public + * @param object $product An optional product to edit. + * @return void + */ + public function __construct($product = null) + { + // Set up the rows and columns. + parent::__construct(6, 4); + + // Layout the tools. + $this->_layout(); + + // Add product if one was passed in. + if (isset($product)) { + $this->loadProduct($product); + } else { + $this->resetProduct(); + } + } + + private function _layout() + { + // Set up the data entry widgets. + $this->nameEntry = new GtkEntry(); + $this->typeCombo = GtkComboBox::new_text(); + $this->categoryCombo = GtkComboBox::new_text(); + $this->priceSpin = new GtkSpinButton(new GtkAdjustment(1, 1, 1000, .01, 10), .5); + $this->inventorySpin = new GtkSpinButton(new GtkAdjustment(1, 0, 100, 1, 10), .5); + $this->availCombo = GtkComboBox::new_text(); + $this->widthSpin = new GtkSpinButton(new GtkAdjustment(1, 1, 50, .1, 5), .5); + $this->heightSpin = new GtkSpinButton(new GtkAdjustment(1, 1, 50, .1, 5), .5); + $this->depthSpin = new GtkSpinButton(new GtkAdjustment(1, 1, 50, .1, 5), .5); + $this->weightSpin = new GtkSpinButton(new GtkAdjustment(1, 1, 50, .1, 5), .5); + $this->imageContainer = new GtkFrame(); + $this->imagePathEntry = new GtkEntry(); + + // Add two options for the type. + $this->typeCombo->append_text('Digital'); + $this->typeCombo->append_text('Shippable'); + // Make the first category the default. + $this->typeCombo->set_active(0); + + // Add an entry for each category in the inventory. + require_once 'Crisscott/Inventory.php'; + $inventory = Crisscott_Inventory::singleton(); + foreach ($inventory->categories as $cat) { + $this->categoryCombo->append_text($cat->name); + } + // Make the first category the default. + $this->categoryCombo->set_active(0); + + // Add yes/no options for the avialability. + $this->availCombo->append_text('NO'); + $this->availCombo->append_text('YES'); + // Make yes the default. + $this->availCombo->set_active(0); + + // Set the number of decimal places in the spin buttons. + $this->priceSpin->set_digits(2); + $this->inventorySpin->set_digits(0); + $this->widthSpin->set_digits(1); + $this->heightSpin->set_digits(1); + $this->depthSpin->set_digits(1); + $this->weightSpin->set_digits(1); + + // Create the description text view. + $this->descView = new GtkTextView(); + + // We need save and cancel buttons. + $save = GtkButton::new_from_stock('gtk-save'); + $reset = GtkButton::new_from_stock('gtk-undo'); + + // Connect the buttons to useful methods. + $save->connect_simple('clicked', array($this, 'saveProduct')); + $reset->connect_simple('clicked', array($this, 'resetProduct')); + + // Set up the labels. + $this->nameLabel = new GtkLabel('_Name', true); + $this->typeLabel = new GtkLabel('Type'); + $this->categoryLabel = new GtkLabel('Category'); + $this->priceLabel = new GtkLabel('Price'); + $this->inventoryLabel = new GtkLabel('Inventory'); + $this->availLabel = new GtkLabel('Availability'); + $this->widthLabel = new GtkLabel('Width'); + $this->heightLabel = new GtkLabel('Height'); + $this->depthLabel = new GtkLabel('Depth'); + $this->weightLabel = new GtkLabel('Weight'); + $this->descLabel = new GtkLabel('Description'); + $this->imageLabel = new GtkLabel('Image'); + + // Set the labels' size. + $this->nameLabel->set_size_request(100, -1); + $this->typeLabel->set_size_request(100, -1); + $this->categoryLabel->set_size_request(100, -1); + $this->priceLabel->set_size_request(100, -1); + $this->inventoryLabel->set_size_request(100, -1); + $this->availLabel->set_size_request(100, -1); + $this->widthLabel->set_size_request(100, -1); + $this->heightLabel->set_size_request(100, -1); + $this->depthLabel->set_size_request(100, -1); + $this->weightLabel->set_size_request(100, -1); + $this->descLabel->set_size_request(100, -1); + $this->imageLabel->set_size_request(100, -1); + + // Set the size of the text view also. + $this->descView->set_size_request(300, 150); + // Force the text to wrap. + $this->descView->set_wrap_mode(Gtk::WRAP_WORD); + + // Next align each label within the parent container. + $this->nameLabel->set_alignment(0, .5); + $this->typeLabel->set_alignment(0, .5); + $this->categoryLabel->set_alignment(0, .5); + $this->priceLabel->set_alignment(0, .5); + $this->inventoryLabel->set_alignment(0, .5); + $this->availLabel->set_alignment(0, .5); + $this->widthLabel->set_alignment(0, .5); + $this->heightLabel->set_alignment(0, .5); + $this->depthLabel->set_alignment(0, .5); + $this->weightLabel->set_alignment(0, .5); + $this->descLabel->set_alignment(0, .5); + $this->imageLabel->set_alignment(0, .5); + + // Make all of the labels use markup. + $this->nameLabel->set_use_markup(true); + $this->typeLabel->set_use_markup(true); + $this->categoryLabel->set_use_markup(true); + $this->priceLabel->set_use_markup(true); + $this->inventoryLabel->set_use_markup(true); + $this->availLabel->set_use_markup(true); + $this->widthLabel->set_use_markup(true); + $this->heightLabel->set_use_markup(true); + $this->depthLabel->set_use_markup(true); + $this->weightLabel->set_use_markup(true); + $this->descLabel->set_use_markup(true); + $this->imageLabel->set_use_markup(true); + + // Attach them to the table. + $expandFill = GTK::EXPAND|GTK::FILL; + $this->attach($this->nameLabel, 0, 1, 0, 1, 0, 0); + $this->attach($this->typeLabel, 0, 1, 1, 2, 0, 0); + $this->attach($this->categoryLabel, 0, 1, 2, 3, 0, 0); + $this->attach($this->priceLabel, 0, 1, 3, 4, 0, 0); + $this->attach($this->inventoryLabel, 0, 1, 5, 6, 0, 0); + $this->attach($this->availLabel, 0, 1, 6, 7, 0, 0); + $this->attach($this->widthLabel, 0, 1, 7, 8, 0, 0); + $this->attach($this->heightLabel, 0, 1, 8, 9, 0, 0); + $this->attach($this->depthLabel, 0, 1, 9, 10, 0, 0); + $this->attach($this->weightLabel, 0, 1, 10, 11, 0, 0); + + // Attach the entries too. + $this->attachWithAlign($this->nameEntry, 1, 2, 0, 1, Gtk::FILL, 0); + $this->attachWithAlign($this->typeCombo, 1, 2, 1, 2, GTK::FILL, 0); + //$this->attach($this->typeCombo, 1, 2, 1, 2, 0, 0); + $this->attachWithAlign($this->categoryCombo, 1, 2, 2, 3, Gtk::FILL, 0); + $this->attachWithAlign($this->priceSpin, 1, 2, 3, 4, Gtk::FILL, 0); + $this->attachWithAlign($this->inventorySpin, 1, 2, 5, 6, Gtk::FILL, 0); + $this->attachWithAlign($this->availCombo, 1, 2, 6, 7, Gtk::FILL, 0); + $this->attachWithAlign($this->widthSpin, 1, 2, 7, 8, Gtk::FILL, 0); + $this->attachWithAlign($this->heightSpin, 1, 2, 8, 9, Gtk::FILL, 0); + $this->attachWithAlign($this->depthSpin, 1, 2, 9, 10, Gtk::FILL, 0); + $this->attachWithAlign($this->weightSpin, 1, 2, 10, 11, Gtk::FILL, 0); + + // Attach the image widgets. + $this->attachWithAlign($this->imageContainer, 2, 4, 0, 4, Gtk::FILL, 0); + $this->attachWithAlign($this->imageLabel, 2, 4, 4, 5, Gtk::FILL, 0); + $this->attachWithAlign($this->imagePathEntry, 3, 4, 4, 5, Gtk::FILL, 0); + + // Attach the description widgets. + $this->attachWithAlign($this->descLabel, 2, 3, 5, 6, Gtk::FILL, 0); + $this->attachWithAlign($this->descView, 2, 4, 6, 11, Gtk::FILL, 0); + + // Attache the buttons. + $this->attachWithAlign($reset, 0, 1, 11, 12, Gtk::FILL, 0); + $this->attachWithAlign($save, 3, 4, 11, 12, Gtk::FILL, 0); + + // Associate the mnemonics. + $this->nameLabel->set_mnemonic_widget($this->nameEntry); + $this->nameEntry->connect_simple('mnemonic_activate', array($this, 'reportError'), $this->nameLabel); + } + + /** + * Attaches a widget to the table inside of a GtkAlignment. + * + * This method makes it easy to left align items within a table. + * Simply call this method like you would attach. + * + * @access public + * @see attach + * @return void + */ + public function attachWithAlign($widget, $row1, $row2, $col1, $col2, $xEF, $yEF) + { + $align = new GtkAlignment(0,0,0,.5); + $align->add($widget); + $this->attach($align, $row1, $row2, $col1, $col2, $xEF, $yEF); + } + + /** + * Marks a label up as red text to indicate an error. + * + * @access public + * @param boolean $unknown Seriously, no idea what it means. + * @param object $label The GtkLabel to markup. + * @return void + */ + public function reportError(GtkLabel $label) + { + $label->set_label(self::ERROR_MARKUP_OPEN . $label->get_label() . self::ERROR_MARKUP_CLOSE); + } + + /** + * Clears the error markup from the given label. + * + * @access public + * @param $label The GtkLabel to remove markup from. + * @return void + */ + public function clearError(GtkLabel $label) + { + $text = $label->get_label(); + $text = str_replace(self::ERROR_MARKUP_OPEN, '', $text); + $text = str_replace(self::ERROR_MARKUP_CLOSE, '', $text); + + $label->set_label($text); + } + + /** + * Load the given product into the tool. + * + * @access public + * @param object $product A Crisscott_Product instance. + * @return void + */ + public function loadProduct(Crisscott_Product $product) + { + // First set the product as the current product. + $this->product = $product; + + // Next reset the tool. + $this->resetProduct(); + + // Finally make the notebook page active. + require_once 'Crisscott/MainNotebook.php'; + $notebook = Crisscott_MainNotebook::singleton(); + $notebook->set_current_page($notebook->page_num($notebook->pages['Product Edit'])); + } + + /** + * Sets the values of the tool to the values of the current + * product. + * + * @access public + * @return void + */ + public function resetProduct() + { + // Make sure that there is a product. + if (!isset($this->product)) { + require_once 'Crisscott/Product.php'; + $this->product = new Crisscott_Product(); + } + + // Update the tools in the widget. + $this->nameEntry->set_text($this->product->name); + $this->_setComboActive($this->typeCombo, $this->product->type); + + $inv = Crisscott_Inventory::singleton(); + $cat = $inv->getCategoryById($this->product->categoryId); + $this->_setComboActive($this->categoryCombo, $cat->name); + + $this->priceSpin->set_value($this->product->price); + $this->inventorySpin->set_value($this->product->inventory); + $this->availCombo->set_active($this->product->availability); + $this->widthSpin->set_value($this->product->width); + $this->heightSpin->set_value($this->product->height); + $this->depthSpin->set_value($this->product->depth); + $this->weightSpin->set_value($this->product->weight); + $this->imagePathEntry->set_text($this->product->imagePath); + + $this->imageContainer->remove($this->imageContainer->get_child()); + try { + $pixbuf = GdkPixbuf::new_from_file($this->product->imagePath); + $this->imageContainer->add(GtkImage::new_from_pixbuf($pixbuf)); + $this->imageContainer->show_all(); + } catch (Exception $e) { + // Don't do anything special + } + + $buffer = $this->descView->get_buffer(); + $buffer->set_text($this->product->description); + } + + /** + * Sets the active item in a combo box. + * + * The combo box will be searched for the value given and + * the matching item will be made active. The method returns + * after the first match. + * + * This works for combos created with or without new_text(). + * + * @access private + * @param object $combo + * @param mixed $value + * @return void + */ + private function _setComboActive(GtkComboBox $combo, $value) + { + // Get the underlying model. + $model = $combo->get_model(); + // Get the first iter. + $iter = $model->get_iter_first(); + // Loop through the items. + for ($iter; $model->iter_is_valid($iter); $model->iter_next($iter)) { + // Check for a match. + if ($value == $model->get_value($iter, 0)) { + // A match! Set the active item and get out. + $combo->set_active_iter($iter); + return; + } + } + } + + /** + * Copies the data from the tool to the product. Then asks + * the product to save the data. + * + * @access public + * @return void + */ + public function saveProduct() + { + // Set the product properties. + $this->product->name = $this->nameEntry->get_text(); + $this->product->type = $this->typeCombo->get_active_text(); + + $inv = Crisscott_Inventory::singleton(); + $cat = $inv->getCategoryByName($this->categoryCombo->get_active_text()); + $this->product->categoryId = $cat->categoryId; + + $this->product->price = $this->priceSpin->get_value(); + $this->product->inventory = $this->inventorySpin->get_value(); + $this->product->availability = (boolean)$this->availCombo->get_active(); + $this->product->width = $this->widthSpin->get_value(); + $this->product->height = $this->heightSpin->get_value(); + $this->product->depth = $this->depthSpin->get_value(); + $this->product->weight = $this->weightSpin->get_value(); + $this->product->imagePath = $this->imagePathEntry->get_value(); + + $buffer = $this->descView->get_buffer(); + $this->product->description = $buffer->get_text($buffer->get_start_iter(), $buffer->get_end_iter()); + + // Validate the new values. + $valid = $this->product->validate(); + + // Create a map of all the values and labels. + $labelMap = array( + 'name' => $this->nameLabel, + 'type' => $this->typeLabel, + 'category' => $this->categoryLabel, + 'price' => $this->priceLabel, + 'inventory' => $this->inventoryLabel, + 'avail' => $this->availLabel, + 'width' => $this->widthLabel, + 'height' => $this->heightLabel, + 'depth' => $this->depthLabel, + 'weight' => $this->weightLabel, + 'desc' => $this->descLabel, + 'image' => $this->imageLabel + ); + + // Reset all of the labels. + foreach ($labelMap as $label) { + $this->clearError($label); + } + + // If there are invalid values, markup the labels. + if (is_array($valid)) { + foreach ($valid as $labelKey) { + $this->reportError($labelMap[$labelKey]); + } + + // Validatig the data was not successful. + return false; + } + + try { + // Try to save the data. + $this->product->save(); + + // Mark the buffer as saved. + $this->descView->get_buffer()->set_modified(false); + + // Update the inventory instance. + $inv = Crisscott_Inventory::singleton(); + $inv->refreshInventory(); + + // Also update the product tree. + $pt = Crisscott_Tools_ProductTree::singleton(); + $pt->updateModel(); + + } catch (Exception $e) { + throw $e; + return false; + } + } + + /** + * Creates or returns a singleton instance of this class. + * + * @static + * @access public + * @return object + */ + public static function singleton() + { + if (!isset(self::$instance)) { + $class = __CLASS__; + self::$instance = new $class; + } + return self::$instance; + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/CrisscottChapter12/Tools/ProductSummary.php b/CrisscottChapter12/Tools/ProductSummary.php new file mode 100644 index 0000000..6bdd2e0 --- /dev/null +++ b/CrisscottChapter12/Tools/ProductSummary.php @@ -0,0 +1,253 @@ +set_size_request(60, -1); + $type->set_size_request(60, -1); + $category->set_size_request(60, -1); + $price->set_size_request(60, -1); + + // Next align each label within the parent container. + $name->set_alignment(0, .5); + $type->set_alignment(0, .5); + $category->set_alignment(0, .5); + $price->set_alignment(0, .5); + + // Attach them to the table. + $expandFill = GTK::EXPAND|GTK::FILL; + $this->attach($name, 0, 1, 0, 1, 0, $expandFill); + $this->attach($type, 0, 1, 1, 2, 0, $expandFill); + $this->attach($category, 0, 1, 2, 3, 0, $expandFill); + $this->attach($price, 0, 1, 3, 4, 0, $expandFill); + + // Create the labels for the attributes. + $this->productName = new GtkLabel(); + $this->productType = new GtkLabel(); + $this->productCategory = new GtkLabel(); + $this->productPrice = new GtkLabel(); + + // Allow the labels to wrap. + $this->productName->set_line_wrap(true); + $this->productType->set_line_wrap(true); + $this->productCategory->set_line_wrap(true); + $this->productPrice->set_line_wrap(true); + + // Left align them. + $this->productName->set_alignment(0, .5); + $this->productType->set_alignment(0, .5); + $this->productCategory->set_alignment(0, .5); + $this->productPrice->set_alignment(0, .5); + + // Attach them to the table. + $this->attach($this->productName, 1, 2, 0, 1); + $this->attach($this->productType, 1, 2, 1, 2); + $this->attach($this->productCategory, 1, 2, 2, 3); + $this->attach($this->productPrice, 1, 2, 3, 4); + + // Attach a place holder for the image. + $this->productImage = new GtkFrame('Image'); + // The image's size can be fixed. + $this->productImage->set_size_request(100, 100); + $this->attach($this->productImage, 2, 3, 0, 4, 0, $expandFill); + + // Now that everything is set up, summarize the product. + require_once 'Crisscott/Product.php'; + $product = new Crisscott_Product(); + if (!empty($product)) { + $this->displaySummary($product); + } + } + + /** + * Displays a summary of the given product. + * + * When given a valid product object, this method updates the + * labels and image to match the values of the given product. + * + * @access public + * @param object $product A Crisscott_Product instance. + * @return void + */ + public function displaySummary(Crisscott_Product $product) + { + $this->product = $product; + + // Set the attribute labels to the values of the product. + $this->productName->set_text($product->name); + $this->productType->set_text($product->type); + + // Get the category information. + require_once 'Crisscott/Inventory.php'; + $inv = Crisscott_Inventory::singleton(); + $cat = $inv->getCategoryById($product->categoryId); + // Set the category name. + $this->productCategory->set_text($cat->name); + + // Set the product price. + $this->productPrice->set_text($product->price); + + // Remove the current product image. + $this->productImage->remove($this->productImage->get_child()); + + // Try to add the product image. + try { + // Create a pixbuf. + $pixbuf = GdkPixbuf::new_from_file($product->imagePath); + + // Scale the image. + $pixbuf = $pixbuf->scale_simple(80, 100, Gdk::INTERP_BILINEAR); + + // Create an image from the pixbuf. + $this->productImage->add(GtkImage::new_from_pixbuf($pixbuf)); + // Show the image. + $this->productImage->show_all(); + } catch (Exception $e) { + // Just fail silently. + } + } + + /** + * Returns the currently shown product. + * + * @access public + * @return object The current product. + */ + public function getProduct() + { + return $this->product; + } + + /** + * Loads the current product for editing. + * + * @access public + * @return void + */ + public function editProduct() + { + require_once 'Crisscott/Tools/ProductEdit.php'; + $productEdit = Crisscott_Tools_ProductEdit::singleton(); + + $productEdit->loadProduct($this->product); + } + + /** + * Creates or returns a singleton instance of this class. + * + * @static + * @access public + * @return object + */ + public static function singleton() + { + if (!isset(self::$instance)) { + $class = __CLASS__; + self::$instance = new $class; + } + return self::$instance; + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/CrisscottChapter12/Tools/ProductTree.php b/CrisscottChapter12/Tools/ProductTree.php new file mode 100644 index 0000000..05ab732 --- /dev/null +++ b/CrisscottChapter12/Tools/ProductTree.php @@ -0,0 +1,140 @@ +updateModel(); + } + + public function updateModel() + { + // Create and set the model. + $this->set_model($this->_createModel()); + + // Next set up the view column and cell renderer. + $this->_setupColumn(); + + // Finally, set up the selection. + $this->_setupSelection(); + } + + private function _createModel() + { + // Set up the model. + // Each row should have the row name and the prouct_id. + // If the row is a category the product_id should be zero. + $model = new GtkTreeStore(Gtk::TYPE_STRING, Gtk::TYPE_LONG); + + // Get a singleton of the Inventory object. + require_once 'Crisscott/Inventory.php'; + $inventory = Crisscott_Inventory::singleton(); + + // Add all of the categories. + foreach ($inventory->categories as $category) { + $catIter = $model->append(null, array($category->name, 0)); + // Add all of the products for the category. + foreach ($category->getProducts() as $product) { + $model->append($catIter, array($product['product_name'], $product['product_id'])); + } + } + + return $model; + } + + private function _setupColumn() + { + // Add the name column. + $column = new GtkTreeViewColumn(); + $column->set_title('Products'); + + // Create a renderer for the column. + $cellRenderer = new GtkCellRendererText(); + $column->pack_start($cellRenderer, true); + $column->add_attribute($cellRenderer, 'text', 0); + + // Make the text ellipsize. + $cellRenderer->set_property('ellipsize', Pango::ELLIPSIZE_END); + + // Make the column sort on the product name. + $column->set_sort_column_id(0); + + // Insert the column. + $this->insert_column($column, 0); + } + + private function _setupSelection() + { + // Get the selection object. + $selection = $this->get_selection(); + + // Set the selection to browse mode. + $selection->set_mode(Gtk::SELECTION_BROWSE); + + // Create a signal handler to process the selection. + $selection->connect('changed', array($this, 'sendToSummary')); + } + + public function sendToSummary($selection) + { + // Get the selected row. + list($model, $iter) = $selection->get_selected(); + + // Create a product. + require_once 'Crisscott/Product.php'; + $product = new Crisscott_Product($model->get_value($iter, 1)); + + // Get the singleton product summary. + require_once 'Crisscott/Tools/ProductSummary.php'; + $productSummary = Crisscott_Tools_ProductSummary::singleton(); + $productSummary->displaySummary($product); + } + + /** + * Creates or returns a singleton instance of this class. + * + * @static + * @access public + * @return object + */ + public static function singleton() + { + if (!isset(self::$instance)) { + $class = __CLASS__; + self::$instance = new $class; + } + return self::$instance; + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/CrisscottChapter12/Tools/StatusBar.php b/CrisscottChapter12/Tools/StatusBar.php new file mode 100644 index 0000000..0267292 --- /dev/null +++ b/CrisscottChapter12/Tools/StatusBar.php @@ -0,0 +1,38 @@ + \ No newline at end of file diff --git a/CrisscottChapter12/Tools/Toolbar.php b/CrisscottChapter12/Tools/Toolbar.php new file mode 100644 index 0000000..c6176b4 --- /dev/null +++ b/CrisscottChapter12/Tools/Toolbar.php @@ -0,0 +1,81 @@ +createButtons(); + } + + protected function createButtons() + { + // Create a button to make new products, categories and + // contributors. + $new = new GtkMenuToolButton(GtkImage::new_from_stock(Gtk::STOCK_NEW, Gtk::ICON_SIZE_SMALL_TOOLBAR), 'New'); + $newMenu = new GtkMenu(); + + // Create the menu items. + $product = new GtkMenuItem('Product'); + $newMenu->append($product); + $category = new GtkMenuItem('Category'); + $newMenu->append($category); + $contrib = new GtkMenuItem('Contributor'); + $newMenu->append($contrib); + + // Set the menu as the menu for the new button. + $newMenu->show_all(); + $new->set_menu($newMenu); + + // Add the button to the toolbar. + $this->add($new); + + // Create the signal handlers for the new menu. + require_once 'Crisscott/MainWindow.php'; + //$application = Crisscott_MainWindow::singleton(); + + $new->connect_simple('clicked', array($application, 'newProduct')); + $product->connect_simple('activate', array($application, 'newProduct')); + $category->connect_simple('activate', array($application, 'newCategory')); + $contrib->connect_simple('activate', array($application, 'newContrib')); + // Create a toggle button that will connect to the database. + $database = GtkToggleToolButton::new_from_stock(Gtk::STOCK_CONNECT); + $database->set_label('Connect to Database'); + + // Add the button to the toolbar. + $this->add($database); + + // Create two buttons for sorting the product list. + $sortA = new GtkRadioToolButton(null, 'Ascending'); + $sortA->set_icon_widget(GtkImage::new_from_stock(Gtk::STOCK_SORT_ASCENDING, Gtk::ICON_SIZE_LARGE_TOOLBAR)); + $sortA->set_label('Sort Asc'); + + $sortD = new GtkRadioToolButton($sortA, 'Descending'); + $sortD->set_icon_widget(GtkImage::new_from_stock(Gtk::STOCK_SORT_DESCENDING, Gtk::ICON_SIZE_LARGE_TOOLBAR)); + $sortD->set_label('Sort Desc'); + + // Add the two buttons. + $this->add($sortA); + $this->add($sortD); + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim: et tw=78 + * vi: ts=1 sw=1 + */ +?> \ No newline at end of file diff --git a/CrisscottChapter12/db/Result.php b/CrisscottChapter12/db/Result.php new file mode 100644 index 0000000..6cf1e72 --- /dev/null +++ b/CrisscottChapter12/db/Result.php @@ -0,0 +1,69 @@ +result = $result; + $this->key = 0; + $this->rowCount = $this->result->numRows(); + } + } + + public function current() + { + return ($this->current = $this->result->fetchRow(DB_FETCHMODE_ASSOC,$this->key)); + } + + public function goToPrev() + { + if (--$this->key >= 0) { + return ($this->current = $this->result->fetchRow(DB_FETCHMODE_ASSOC,$this->key)); + } else { + return false; + } + } + + public function goToNext() + { + if (++$this->key < $this->rowCount) { + return ($this->current = $this->result->fetchRow(DB_FETCHMODE_ASSOC,$this->key)); + } else { + return false; + } + } + + public function valid() + { + return $this->key < $this->rowCount; + } + + public function numRows() + { + return $this->rowCount; + } +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + */ +?> \ No newline at end of file diff --git a/CrisscottChapter12/run.php b/CrisscottChapter12/run.php new file mode 100644 index 0000000..3ca7567 --- /dev/null +++ b/CrisscottChapter12/run.php @@ -0,0 +1,14 @@ +getMessage() . "\n"; +} + +set_exception_handler('exceptionHandler'); + +require_once 'Crisscott/SplashScreen.php'; +$csSplash = new Crisscott_SplashScreen(); +$csSplash->start(); +?> \ No newline at end of file diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..6229038 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,27 @@ +Freeware License, some rights reserved + +Copyright (c) 2006 Scott Mattocks + +Permission is hereby granted, free of charge, to anyone obtaining a copy +of this software and associated documentation files (the "Software"), +to work with the Software within the limits of freeware distribution and fair use. +This includes the rights to use, copy, and modify the Software for personal use. +Users are also allowed and encouraged to submit corrections and modifications +to the Software for the benefit of other users. + +It is not allowed to reuse, modify, or redistribute the Software for +commercial use in any way, or for a user’s educational materials such as books +or blog articles without prior permission from the copyright holder. + +The above copyright notice and this permission notice need to be included +in all copies or substantial portions of the software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS OR APRESS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + diff --git a/README.md b/README.md new file mode 100644 index 0000000..8a86289 --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +#Apress Source Code + +This repository accompanies [*Pro PHP-GTK*](http://www.apress.com/9781590596135) by Scott Mattocks (Apress, 2006). + +![Cover image](9781590596135.jpg) + +Download the files as a zip using the green button, or clone the repository to your machine using Git. + +##Releases + +Release v1.0 corresponds to the code in the published book, without corrections or updates. + +##Contributions + +See the file Contributing.md for more information on how you can contribute to this repository. diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..f29c6eb --- /dev/null +++ b/README.txt @@ -0,0 +1 @@ +The directories named Crisscott* hold snap shots of the Crisscott application at different points in the book. The directory Crisscott/ holds the application as of the last chapter. diff --git a/contributing.md b/contributing.md new file mode 100644 index 0000000..f6005ad --- /dev/null +++ b/contributing.md @@ -0,0 +1,14 @@ +# Contributing to Apress Source Code + +Copyright for Apress source code belongs to the author(s). However, under fair use you are encouraged to fork and contribute minor corrections and updates for the benefit of the author(s) and other readers. + +## How to Contribute + +1. Make sure you have a GitHub account. +2. Fork the repository for the relevant book. +3. Create a new branch on which to make your change, e.g. +`git checkout -b my_code_contribution` +4. Commit your change. Include a commit message describing the correction. Please note that if your commit message is not clear, the correction will not be accepted. +5. Submit a pull request. + +Thank you for your contribution! \ No newline at end of file