diff --git a/2766.pdf b/2766.pdf new file mode 100644 index 0000000..9d1ebb6 Binary files /dev/null and b/2766.pdf differ diff --git a/2767.pdf b/2767.pdf new file mode 100644 index 0000000..b5dbba4 Binary files /dev/null and b/2767.pdf differ diff --git a/9781590596135.jpg b/9781590596135.jpg new file mode 100644 index 0000000..68885b6 Binary files /dev/null and b/9781590596135.jpg differ 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