#!/usr/bin/perl # Gournal - A notetaking app for TabletPCs running linux # Version 0.4.1 # Copyright (C) 2004 Chris Debenham # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # # Bring in what we need use strict; use Gtk2 -init; use Gtk2::GladeXML; use Gtk2::Helper; use Gnome2::Canvas; use Gnome2::Print; use IO::Socket; use Compress::Zlib; use XML::Mini::Document; use Data::Dumper; # Setup constants use constant TRUE => 1; use constant FALSE => 0; # Brush Types use constant NORMAL => 0; use constant TEXT => 1; use constant DATE => 2; use constant HILITE => 3; use constant ERASE => 4; use constant IMAGE => 5; # Setup variables my ($globals); $globals->{'basedir'} = $ENV{HOME} . "/.gournal/"; #$globals->{'sharedir'} = "/usr/share/gournal/"; $globals->{'sharedir'} = "./"; $globals->{'prefs'} = $globals->{'basedir'} . "config"; $globals->{'gladefile'} = $globals->{'sharedir'} . "gournal.glade"; # Initialise values/defaults $globals->{'DEBUG'} = TRUE; $globals->{'oldx'} = -100; $globals->{'oldy'} = -100; $globals->{'movement_flag'} = FALSE; $globals->{'brush_width'} = 2; $globals->{'brush_color'} = 0x000000ff; $globals->{'tool_type'} = NORMAL; $globals->{'canvas_text'} = 0; $globals->{'canvas_image'} = 0; $globals->{'changed_flag'} = FALSE; $globals->{'server_port'} = 5505; $globals->{'client_flag'} = FALSE; $globals->{'server_flag'} = FALSE; $globals->{'signal_freeze_flag'} = TRUE; $globals->{'font'} = "Sans 40"; $globals->{'tab_font'} = "Sans 4"; $globals->{'pagewidth'} = 900; $globals->{'pageheight'} = 1300; $globals->{'current_page'} = ""; $globals->{'line_spacing'} = 40; $globals->{'small_screen_mode'} = 0; $globals->{'tmpdir'} = $ENV{HOME} . "/Documents"; $globals->{'tmpfont'} = $globals->{'font'}; $globals->{'tmpwidth'} = $globals->{'pagewidth'}; $globals->{'tmpheight'} = $globals->{'pageheight'}; $globals->{'tmpspacing'} = $globals->{'line_spacing'}; $globals->{'tmpsmall_screen_mode'} = $globals->{'small_screen_mode'}; $globals->{'lastid'} = 0; $globals->{'auto_save'} = 1; my (@xv, @yv, @line, @hosts, @favourites); my ($io_id); # Structures that will contain all the pages, svg and items my ($pages, %svg, %canvasitem); # Check commandline arguments if ($ARGV[0]) { foreach (0 .. (@ARGV - 1)) { if ($ARGV[$_] eq "-d") { $globals->{'DEBUG'} = TRUE; } } } # Load GUI my $mainxml = Gtk2::GladeXML->new($globals->{'gladefile'}, 'windowMain'); $mainxml->signal_autoconnect_from_package(''); $mainxml->get_widget('windowMain') ->signal_connect("key_press_event", \&handle_key); my $sectionTabs = Gtk2::Notebook->new; $sectionTabs->popup_enable; $sectionTabs->set_scrollable(TRUE); $sectionTabs->set_tab_pos('top'); $mainxml->get_widget('vboxMain')->add($sectionTabs); if (!-e $globals->{'basedir'}) { mkdir $globals->{'basedir'}, 0777; } load_prefs(); $globals->{'server_port'} = $ARGV[0]; my $OUTPORT = $ARGV[1]; my $found = ""; load_dir(""); opendir(DIR, $globals->{'svgdir'}); foreach (sort (readdir(DIR))) { if (!($_ =~ /^\./)) { if (-d $globals->{'svgdir'} . $_) { load_dir($_); } } } closedir DIR; # If no existing pages then create a new blank one if ($found eq "") { my $newpage = add_page("", "Main"); set_page("Main", $newpage); } else { my ($section, $page) = split(/\//, $found, 2); $page =~ s/\.svgz$//g; set_page($section, $page); } $sectionTabs->signal_connect_after(switch_page => \&switch_section, undef); $globals->{'signal_freeze_flag'} = FALSE; $mainxml->get_widget('buttonBlack')->set_active(TRUE); if (!$globals->{'DEBUG'}) { $mainxml->get_widget('windowMain')->maximize; } if($globals->{small_screen_mode}) { $mainxml->get_widget('main_menubar')->hide; $mainxml->get_widget('toolbarMain')->set_style('GTK_TOOLBAR_ICONS'); } #turn_on_server(); # Gtk event loop Gtk2->main; # Should never get here exit(0); #### ## Helper functions #### # Only show debug output if in debug mode sub debug { my $text = shift; if ($globals->{'DEBUG'}) { print $text. "\n"; } } #### ## Interface handling functions #### # Confirm if the user wants to close us sub close_main { my $xml = Gtk2::GladeXML->new($globals->{'gladefile'}, 'dialogConfirmClose'); $xml->signal_autoconnect_from_package(''); if (!$globals->{'changed_flag'}) { $xml->get_widget('buttonCloseSave')->hide(); } return TRUE; } # Check if anything has changed, if so offer to save, otherwise close sub destroy_main { return exit_and_save(); # if ($globals->{'changed_flag'}) { # my $xml = # Gtk2::GladeXML->new($globals->{'gladefile'}, 'dialogConfirmClose'); # $xml->signal_autoconnect_from_package(''); # $xml->get_widget('buttonCloseCancel')->hide(); # } else { # exit_confirmed(); # } # return TRUE; } # Exit has been confirmed so quit now sub exit_confirmed { my $pagenum = 0; while (Gtk2->events_pending) { Gtk2->main_iteration; } Gtk2->main_quit; return FALSE; } # Save the current page and exit sub exit_and_save { save_page($globals->{'current_page'}); exit_confirmed(); } # Cancel current dialog sub cancel_dialog { my ($widget) = @_; $widget->get_toplevel->destroy(); } # Create a preferences dialog sub setup_gournal { $globals->{'prefs_xml'} = Gtk2::GladeXML->new($globals->{'gladefile'}, 'dialogPrefs'); $globals->{'prefs_xml'}->signal_autoconnect_from_package(''); # Don't allow cancel if there is no existing prefs $globals->{'prefs_xml'}->get_widget('spinWidth') ->set_value($globals->{'pagewidth'}); $globals->{'prefs_xml'}->get_widget('spinHeight') ->set_value($globals->{'pageheight'}); $globals->{'prefs_xml'}->get_widget('spinSpacing') ->set_value($globals->{'line_spacing'}); $globals->{'prefs_xml'}->get_widget('smallScreenMode') ->set_active($globals->{'small_screen_mode'}); $globals->{'prefs_xml'}->get_widget('entryFont') ->set_text($globals->{'font'}); $globals->{'prefs_xml'}->get_widget('entryDocDirectory') ->set_text($ENV{HOME} . "/Gournal"); my $store = $globals->{'prefs_xml'}->get_widget('treeHosts')->get_model(); if ($store) { $store->clear; } else { $store = Gtk2::ListStore->new('Glib::String'); $globals->{'prefs_xml'}->get_widget('treeHosts')->set_model($store); my $renderer = Gtk2::CellRendererText->new; $renderer->set(editable => TRUE); $renderer->signal_connect(edited => \&edit_favourite); my $column = Gtk2::TreeViewColumn->new_with_attributes("Hosts", $renderer, text => 0); $globals->{'prefs_xml'}->get_widget('treeHosts') ->append_column($column); } foreach my $item (@favourites) { $store->set($store->append, 0, $item); deubg("Favourite $item"); } my $response = $globals->{'prefs_xml'}->get_widget('dialogPrefs')->run(); if ($response eq "ok") { open(PREFS, ">" . $globals->{'prefs'}) || die("Couldn't create the config file : $!"); print PREFS "Width = " . $globals->{'tmpwidth'} . "\n"; print PREFS "Height = " . $globals->{'tmpheight'} . "\n"; print PREFS "Spacing = " . $globals->{'tmpspacing'} . "\n"; print PREFS "Font = " . $globals->{'tmpfont'} . "\n"; print PREFS "Documents = " . $globals->{'tmpdir'} . "\n"; print PREFS "SmallScreenMode = " . $globals->{'tmpsmall_screen_mode'} . "\n"; foreach my $fav (@favourites) { print PREFS "Host = " . $fav . "\n"; } close PREFS; } $globals->{'prefs_xml'}->get_widget('dialogPrefs')->destroy(); load_prefs(); } # Load the current preferences sub load_prefs { open(PREFS, $globals->{'prefs'}) || setup_gournal(); @favourites = (); while () { chomp; my @line = split(/=/); $line[0] =~ s/ *$//g; $line[1] =~ s/^ *//g; if ($line[0] eq "Width") { $globals->{'width'} = $line[1]; } elsif ($line[0] eq "Height") { $globals->{'height'} = $line[1]; } elsif ($line[0] eq "Spacing") { $globals->{'spacing'} = $line[1]; } elsif ($line[0] eq "SmallScreenMode") { $globals->{'small_screen_mode'} = $line[1]; } elsif ($line[0] eq "Font") { $globals->{'font'} = $line[1]; } elsif ($line[0] eq "Host") { push @favourites, $line[1]; } elsif ($line[0] eq "Documents") { $globals->{'svgdir'} = $line[1]; if (substr($globals->{'svgdir'}, -1) ne "/") { $globals->{'svgdir'} .= "/"; } } } close PREFS; } # Update preferences from dialog sub pref_changed { $globals->{'tmpwidth'} = $globals->{'prefs_xml'}->get_widget('spinWidth')->get_value_as_int(); $globals->{'tmpheight'} = $globals->{'prefs_xml'}->get_widget('spinHeight')->get_value_as_int(); $globals->{'tmpspacing'} = $globals->{'prefs_xml'}->get_widget('spinSpacing')->get_value_as_int(); $globals->{'tmpsmall_screen_mode'} = $globals->{'prefs_xml'}->get_widget('smallScreenMode')->get_value_as_int(); $globals->{'tmpfont'} = $globals->{'prefs_xml'}->get_widget('entryFont')->get_text(); $globals->{'tmpdir'} = $globals->{'prefs_xml'}->get_widget('entryDocDirectory')->get_text(); } # Show the help/about screen sub show_help { my $tmpxml = Gtk2::GladeXML->new($globals->{'gladefile'}, 'dialogAbout'); $tmpxml->signal_autoconnect_from_package(''); my $response = $tmpxml->get_widget('dialogAbout')->run(); $tmpxml->get_widget('dialogAbout')->destroy(); } #### ## Event related functions #### # Mouse button has been pressed sub button_press_event { my ($widget, $event) = @_; my ($x, $y) = $event->coords; my ($cx, $cy) = $widget->window_to_world($x, $y); #if ($event->button == 3) { #tool_erase(); #} my $pagename = $widget->{'section'} . "/" . $widget->{'pagename'}; if ($event->button == 1 || $event->button == 3) { my $id = time; if (int($globals->{'lastid'}) == $id) { $id = $globals->{'lastid'} + 0.1; } $globals->{'lastid'} = $id; if ($globals->{'tool_type'} == DATE) { $globals->{'changed_flag'} = TRUE; my @time = localtime(time); my $date = sprintf( "%d:%02d %d/%d/%02d", $time[2], $time[1], $time[3], ($time[4] + 1), ($time[5] - 100) ); $canvasitem{$pagename}->{$id} = Gnome2::Canvas::Item->new( $widget->root, 'Gnome2::Canvas::Text', fill_color => 'black', x => $cx, y => $cy, font => $globals->{'font'}, text => $date, anchor => 'south-west' ); my $tag = $svg{$pagename}->getElement('svg')->createChild('text'); $tag->attribute('x', $cx); $tag->attribute('y', $cy); $tag->attribute('name', $id); my $tmpfont=$globals->{'font'}; $tmpfont=~s/ /_/g; $tag->attribute('style', "font:" . $tmpfont); $tag->text($date); } elsif ($globals->{'tool_type'} == TEXT) { $globals->{'changed_flag'} = TRUE; $globals->{'canvas_text'} = $id; $canvasitem{$pagename}->{$id} = Gnome2::Canvas::Item->new( $widget->root, 'Gnome2::Canvas::Text', x => $cx, y => $cy, text => "", anchor => 'south-west', fill_color => 'black', font => $globals->{'font'} ); $globals->{'text_tag'} = $svg{$pagename}->getElement('svg')->createChild('text'); $globals->{'text_tag'}->attribute('x', $cx); $globals->{'text_tag'}->attribute('y', $cy); $globals->{'text_tag'}->attribute('name', $id); my $tmpfont=$globals->{'font'}; $tmpfont=~s/ /_/g; $globals->{'text_tag'} ->attribute('style', "font:" . $tmpfont); $globals->{'text_tag'}->text(""); } elsif ($globals->{'tool_type'} == ERASE) { } elsif ($globals->{'tool_type'} == IMAGE) { } else { $globals->{'changed_flag'} = TRUE; my @time = localtime(time); my $root = $widget->root; $globals->{'current_group'} = Gnome2::Canvas::Item->new( $root, 'Gnome2::Canvas::Group', x => 0, y => 0 ); draw_brush($widget, $event->coords); my ($x, $y) = $event->coords; my ($cx, $cy) = $widget->window_to_world($x, $y); @xv = ($cx); @yv = ($cy); $globals->{'movement_flag'} = FALSE; } } return TRUE; } # Mouse button has been released sub button_release_event { my ($widget, $event) = @_; my $pagename = $widget->{'section'} . "/" . $widget->{'pagename'}; debug("Action on $pagename"); if ( ($event->button == 1 || $event->button == 3) && ( $globals->{'tool_type'} == NORMAL || $globals->{'tool_type'} == HILITE) ) { if (!$globals->{'movement_flag'}) { draw_brush($widget, $globals->{'oldx'} + 1, $globals->{'oldy'} + 1); } $globals->{'oldx'} = -100; $globals->{'oldy'} = -100; my $filename = $widget->{user_data}; my $path = $svg{$pagename}->getElement('svg')->createChild('path'); my $brush_col = $globals->{'brush_color'}; $brush_col = int($globals->{'brush_color'} / 256); $brush_col = sprintf('#%06X', $brush_col); my $brush_trans = $globals->{'brush_color'}; $brush_trans = (($brush_trans / 256) - (int($brush_trans / 256))); my $style = sprintf('fill:none;stroke:' . $brush_col . ';stroke-opacity:' . $brush_trans . ';stroke-linecap:round' . ';stroke-linejoin:round' . ';stroke-width:' . $globals->{'brush_width'} . ";"); $path->attribute('style', $style); my $id = time; if (int($globals->{'lastid'}) == $id) { $id = $globals->{'lastid'} + 0.1; } $globals->{'lastid'} = $id; $path->attribute('name', $id); my $points = ""; my $i = 0; $points = "M " . $xv[0] . " " . $yv[0] . " "; for ($i = 1 ; $i < @xv ; $i++) { $points .= "L " . $xv[$i] . " " . $yv[$i] . " "; } $path->attribute('d', $points); if ($globals->{'client_flag'}) { send_item($path, $filename); } $globals->{'current_group'}->destroy; $globals->{'current_group'} = undef; my $root = $widget->root; $canvasitem{$pagename}->{$id} = Gnome2::Canvas::Item->new( $root, 'Gnome2::Canvas::Line', points => \@line, cap_style => 'round', join_style => 'round', fill_color_rgba => $globals->{'brush_color'}, width_units => $globals->{'brush_width'} ); $canvasitem{$pagename}->{$id}->{user_data} = $id; $canvasitem{$pagename}->{$id}->show(); @line = (); } if ($event->button == 3) { $mainxml->get_widget('buttonBlack')->set_active(TRUE); } if ($globals->{'tool_type'} == DATE) { $mainxml->get_widget('buttonBlack')->set_active(TRUE); } if ($globals->{'tool_type'} == ERASE) { my $filename = $widget->{user_data}; if ($globals->{'selected_item'}) { delete_item($filename, $globals->{'selected_item'}); } $globals->{'selected_item'} = FALSE; } elsif ($globals->{'tool_type'} == IMAGE) { my ($x, $y) = $event->coords; my ($cx, $cy) = $widget->window_to_world($x, $y); my $id = time; if (int($globals->{'lastid'}) == $id) { $id = $globals->{'lastid'} + 0.1; } $globals->{'lastid'} = $id; $canvasitem{$pagename}->{$id} = $globals->{'canvas_image'}; my $tag = $svg{$pagename}->getElement('svg')->createChild('image'); $tag->attribute('x', $cx); $tag->attribute('y', $cy-$globals->{'canvas_image'}->get('height')); $tag->attribute('width', $globals->{'canvas_image'}->get('width')); $tag->attribute('height', $globals->{'canvas_image'}->get('height')); $tag->attribute('name', $id); $tag->attribute('xlink:href', $globals->{'canvas_image'}->{user_data}); $mainxml->get_widget('buttonBlack')->set_active(TRUE); } return TRUE; } # Mouse has moved sub motion_notify_event { my ($widget, $event) = @_; my $pagename = $widget->{'section'} . "/" . $widget->{'pagename'}; my ($x, $y, $state); if ($event->is_hint) { (undef, $x, $y, $state) = $event->window->get_pointer; } else { $x = $event->x; $y = $event->y; $state = $event->state; } if ( (grep (/button1-mask/, @$state) || grep (/button3-mask/, @$state)) && ( $globals->{'tool_type'} == NORMAL || $globals->{'tool_type'} == HILITE) ) { $globals->{'movement_flag'} = TRUE; draw_brush($widget, $x, $y); } if ($globals->{'tool_type'} == ERASE) { my $canvas = get_current_canvas(); my $item = $canvas->get_item_at($x, $y); if (defined $item->{user_data}) { if ($globals->{'selected_item'}) { $canvasitem{$pagename}->{$globals->{'selected_item'}} ->set(fill_color_rgba => $globals->{'selected_item_colour'}); } $globals->{'selected_item'} = $item->{user_data}; $globals->{'selected_item_colour'} = $canvasitem{$pagename}->{$globals->{'selected_item'}} ->get('fill_color_rgba'); $canvasitem{$pagename}->{$globals->{'selected_item'}} ->set(fill_color_rgba => 0xff000088); if ( grep (/button1-mask/, @$state) || grep (/button3-mask/, @$state)) { delete_item($pagename, $globals->{'selected_item'}); $globals->{'selected_item'} = FALSE; } } else { if ($globals->{'selected_item'}) { $canvasitem{$pagename}->{$globals->{'selected_item'}} ->set(fill_color_rgba => $globals->{'selected_item_colour'}); $globals->{'selected_item'} = FALSE; } } } elsif ($globals->{'tool_type'} == IMAGE) { my ($cx, $cy) = $widget->window_to_world($x, $y); $globals->{'canvas_image'}->set('x' => $cx); $globals->{'canvas_image'}->set('y' => $cy); } return TRUE; } # Keyboard has been used sub handle_key { my ($widget, $event) = @_; debug("Handling key " . $event->keyval); if ($event->state =~ /control-mask/) { if ($event->keyval == 122) { finish_text(); undo_last(); return TRUE; } elsif ($event->keyval == 121) { finish_text(); redo_last(); return TRUE; } } else { if (($globals->{'tool_type'} == TEXT) && $globals->{'canvas_text'} != 0) { my $pagename = get_current_pagename(); if ($event->keyval == 65288) { my $newtext = substr( $canvasitem{$pagename}->{$globals->{'canvas_text'}} ->get('text'), 0, -1 ); $canvasitem{$pagename}->{$globals->{'canvas_text'}} ->set(text => $newtext); } else { my $char = ""; if ($event->keyval == 65293) { $char = "\n"; } else { $char = chr(Gtk2::Gdk->keyval_to_unicode($event->keyval)); } $canvasitem{$pagename}->{$globals->{'canvas_text'}}->set( text => $canvasitem{$pagename}->{$globals->{'canvas_text'}} ->get('text') . $char); } } return TRUE; } return FALSE; } #### ## Page related functions #### # Go to the next page within current section sub next_page { my $pagename = get_current_pagename(); my ($section, $page) = split(/\//, $pagename, 2); $pages->{$section}->{'_notebook'}->next_page; } # Go to the previous page within current section sub prev_page { my $pagename = get_current_pagename(); my ($section, $page) = split(/\//, $pagename, 2); $pages->{$section}->{'_notebook'}->prev_page; } # Initial load of page # svg/canvas not created yet, just the notebook tab sub load_page { my ($section, $filename) = @_; my $pagename = $filename; $pagename =~ s/\.svgz$//g; debug("Loading Page $section -> $pagename"); if (!defined $pages->{$section}->{$pagename}) { my $scroll = Gtk2::ScrolledWindow->new; $scroll->set_policy('automatic', 'automatic'); $scroll->{'section'} = $section; $scroll->{'pagename'} = $pagename; $scroll->show; $pages->{$section}->{$pagename}->{'scroll'} = $scroll; my $label = Gtk2::Label->new(qq{$pagename}); $label->set_use_markup(1); $pages->{$section}->{$pagename}->{'pagenum'} = $pages->{$section}->{'_notebook'}->append_page($scroll, $label); $pages->{$section}->{'_notebook'} ->get_nth_page($pages->{$section}->{$pagename}->{'pagenum'}) ->{'pagename'} = $pagename; $pages->{$section}->{'_notebook'} ->get_nth_page($pages->{$section}->{$pagename}->{'pagenum'}) ->{'section'} = $section; } } # Finish loading a page # Loads svg/canvas to existing tab sub finish_load { my ($section, $pagename) = @_; my $gz; debug("Finishing loading " . $pagename); my $page = $section . "/" . $pagename; my $filename = $page; $pages->{$section}->{$pagename}->{'canvas'} = new_canvas($section, $pagename); $pages->{$section}->{$pagename}->{'scroll'} ->add($pages->{$section}->{$pagename}->{'canvas'}); $pages->{$section}->{$pagename}->{'scroll'}->show_all(); if ($section eq "Main") { $filename = $pagename; } if ($gz = gzopen($globals->{'svgdir'} . $filename . ".svgz", "rb")) { my $xml = ""; while ($gz->gzread($_)) { $xml .= $_; } $gz->gzclose; $svg{$page} = XML::Mini::Document->new(); $svg{$page}->parse($xml); my $svg2 = $svg{$page}->getElement('svg'); my $topLevelChildren = $svg2->getAllChildren(); foreach my $child (@{$topLevelChildren}) { if ( $child->{'_attributes'}->{'visibility'} && $child->{'_attributes'}->{'visibility'} eq "hidden") { # ignore } else { load_item($section, $pagename, $child,"canvas"); } } } else { $svg{$page} = XML::Mini::Document->new(); my $header = $svg{$page}->getRoot()->header('xml'); $header->attribute('version', '1.0'); $svg{$page}->getRoot()->comment('Created with Gournal'); $svg{$page}->getRoot() ->docType( 'svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"' ); my $tmpsvg = $svg{$page}->getRoot()->createChild('svg'); $tmpsvg->attribute('width', $globals->{'pagewidth'}); $tmpsvg->attribute('height', $globals->{'pageheight'}); $tmpsvg->attribute('xmlns', "http://www.w3.org/2000/svg"); $tmpsvg->attribute('xmlns:xlink', "http://www.w3.org/1999/xlink"); $tmpsvg->attribute('version', "1.1"); } } # Save the given page to disk sub save_page { my ($pagename) = @_; debug("Saving page"); finish_text(); my ($section, $page) = split(/\//, $pagename); if ($section eq "Main") { $section = ""; } else { if ( (-e $globals->{'svgdir'} . $section) && (!-d $globals->{'svgdir'})) { debug("Error"); } elsif (!-e $globals->{'svgdir'} . $section) { mkdir $globals->{'svgdir'} . $section; } } my $filename = $globals->{'svgdir'} . $section . "/" . $page . ".svgz"; if ($pagename ne "") { debug("Saving $filename"); my $gz = gzopen($filename, "wb"); $gz->gzwrite($svg{$pagename}->toString); $gz->gzclose; debug("$pagename Saved"); $globals->{'changed_flag'} = FALSE; } } # Save the current page sub save_current_page { debug("Saving current pages"); finish_text(); my $pagename = get_current_pagename(); save_page($pagename); } # Clear the current page sub clear_page { my $page = get_current_pagename(); my ($section, $pagename) = split(/\//, $page, 2); $pages->{$section}->{$pagename}->{'canvas'}->destroy; $pages->{$section}->{$pagename}->{'canvas'} = new_canvas($section, $pagename); $pages->{$section}->{$pagename}->{'scroll'} ->add($pages->{$section}->{$pagename}->{'canvas'}); $svg{$page} = XML::Mini::Document->new(); my $header = $svg{$page}->getRoot()->header('xml'); $header->attribute('version', '1.0'); $svg{$page}->getRoot()->comment('Created with Gournal'); $svg{$page}->getRoot() ->docType( 'svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"' ); my $tmpsvg = $svg{$page}->getRoot()->createChild('svg'); $tmpsvg->attribute('width', $globals->{'pagewidth'}); $tmpsvg->attribute('height', $globals->{'pageheight'}); $globals->{'changed_flag'} = TRUE; } # Create a new blank page sub add_page { my ($widget, $section) = @_; if ($section eq "") { $section = $sectionTabs->get_nth_page($sectionTabs->get_current_page) ->{'section'}; } debug("Adding page to $section"); my @time = localtime(time); my $page = sprintf( "%04d-%02d-%02d@%02d:%02d", ($time[5] + 1900), ($time[4] + 1), $time[3], $time[2], $time[1] ); if (defined $pages->{$section}->{$page}) { $page = sprintf( "%04d-%02d-%02d@%02d:%02d:%02d", ($time[5] + 1900), ($time[4] + 1), $time[3], $time[2], $time[1], $time[0] ); } $globals->{'signal_freeze_flag'} = TRUE; load_page($section, $page . ".svgz"); set_page($section, $page); $globals->{'signal_freeze_flag'} = FALSE; return $page; } # Create a new canvas for given pagename sub new_canvas { my ($section, $page) = @_; debug("Creating new canvas $section/$page"); # Create Canvas my $canvas = Gnome2::Canvas->new_aa(); $canvas->set_scroll_region(0, 0, 600, 800); $canvas->set_center_scroll_region(FALSE); $canvas->set_pixels_per_unit(1); $canvas->signal_connect( motion_notify_event => \&motion_notify_event, undef ); $canvas->signal_connect(button_press_event => \&button_press_event, undef); $canvas->signal_connect( button_release_event => \&button_release_event, undef ); $canvas->{'section'} = $section; $canvas->{'pagename'} = $page; $canvas->show; my $root = $canvas->root; Gnome2::Canvas::Item->new( $root, 'Gnome2::Canvas::Rect', outline_color => 'black', fill_color => 'white', x1 => 0, y1 => 0, x2 => $globals->{'pagewidth'} - 100, y2 => $globals->{'pageheight'} - 100 ); # XXX: I'm disabling the lines manually for now # Gnome2::Canvas::Item->new( # $root, 'Gnome2::Canvas::Line', # points => [40, 0, 40, $globals->{'pageheight'} - 100], # fill_color => 'blue', # width_units => .5 # ); # for ( # my $i = 40 ; # $i < ($globals->{'pageheight'} - 100) ; # $i = $i + $globals->{'line_spacing'} # ) # { # Gnome2::Canvas::Item->new( # $root, 'Gnome2::Canvas::Line', # points => [0, $i, $globals->{'pagewidth'} - 100, $i], # fill_color => 'blue', # width_units => .5 # ); # } return $canvas; } # Confirm if the user wishes to delete the current page sub delete_page { my $pagename = get_current_pagename(); my $canvas = get_current_canvas(); my $xml = Gtk2::GladeXML->new($globals->{'gladefile'}, 'dialogDelete'); $xml->signal_autoconnect_from_package(''); $xml->get_widget('labelDelete') ->set_text( "Are you sure you want to\ndelete the page named\n" . $pagename . "?"); $xml->get_widget('dialogDelete')->{user_data} = $pagename; } # Deletion confirmed so delete the current page sub do_delete_page { my ($widget) = @_; my $pagename = $widget->get_toplevel->{user_data}; debug("Deleting " . $pagename); cancel_dialog($widget); my ($section, $page) = split(/\//, $pagename, 2); my $pagenum = $pages->{$section}->{$page}->{'pagenum'}; $mainxml->get_widget('notebookMain')->remove_page($pagenum); $pages->{$section}->{$page}->{'canvas'}->destroy; delete $svg{$pagename}; #system "rm " . $globals->{'svgdir'} . $filename . ".svgz"; unlink $globals->{'svgdir'} . $pagename . ".svgz"; } # Ask what the user wants to rename the page to sub rename_page { my $pagename = get_current_pagename(); my ($section, $page) = split(/\//, $pagename, 2); my $canvas = get_current_canvas(); my $xml = Gtk2::GladeXML->new($globals->{'gladefile'}, 'dialogRename'); $xml->signal_autoconnect_from_package(''); $xml->get_widget('labelRename') ->set_text("What do you want to\nrename " . $page . " to?"); $xml->get_widget('entryRename')->set_text($page); my $response = $xml->get_widget('dialogRename')->run(); if ($response eq "cancel") { $xml->get_widget('dialogRename')->destroy(); } else { my $newtext = $xml->get_widget('entryRename')->get_text(); $canvas->{'pagename'} = $newtext; my $label = Gtk2::Label->new(qq{$newtext}); $label->set_use_markup(1); $pages->{$section}->{'_notebook'} ->set_tab_label($pages->{$section}->{$page}->{'scroll'}, $label); rename($globals->{'svgdir'} . $section . "/" . $page.".svgz", $globals->{'svgdir'} . $section . "/" . $newtext . ".svgz"); $xml->get_widget('dialogRename')->destroy(); $svg{$section . "/" . $newtext} = $svg{$pagename}; delete $svg{$pagename}; $canvasitem{$section . "/" . $newtext} = $canvasitem{$pagename}; delete $canvasitem{$pagename}; $pages->{$section}->{$newtext} = $pages->{$section}->{$page}; delete $pages->{$section}->{$page}; $pages->{$section}->{$newtext}->{'canvas'}->{'pagename'}=$newtext; $pages->{$section}->{$newtext}->{'scroll'}->{'pagename'}=$newtext; } } # Get the pagename of the current page sub get_current_pagename { my $scroll = get_current_scroll(); return $scroll->{'section'} . "/" . $scroll->{'pagename'}; } # Get the current scolled window sub get_current_scroll { my $section = $sectionTabs->get_nth_page($sectionTabs->get_current_page)->{'section'}; my $page = $pages->{$section}->{'_notebook'} ->get_nth_page($pages->{$section}->{'_notebook'}->get_current_page) ->{'pagename'}; return $pages->{$section}->{$page}->{'scroll'}; } # Get the current canvas sub get_current_canvas { my $section = $sectionTabs->get_nth_page($sectionTabs->get_current_page)->{'section'}; my $page = $pages->{$section}->{'_notebook'} ->get_nth_page($pages->{$section}->{'_notebook'}->get_current_page) ->{'pagename'}; return $pages->{$section}->{$page}->{'canvas'}; } # Jump directly to the given page sub set_page { my ($section, $page) = @_; if (!exists $svg{$section . "/" . $page}) { $globals->{'current_page'} = $section . "/" . $page; finish_load($section, $page); } $sectionTabs->set_current_page($pages->{$section}->{'_pageid'}); $pages->{$section}->{'_notebook'} ->set_current_page($pages->{$section}->{$page}->{'pagenum'}); } # Called when page is changed # Checks if previous page changed and asks to save # Checks if current page is fully loaded and if not, loads sub switch_page { my ($widget) = @_; my $pagename = get_current_pagename(); my ($section, $page) = split(/\//, $pagename, 2); if ($globals->{'current_page'} ne $pagename) { if ($globals->{'changed_flag'}) { if(!$globals->{'auto_save'}) { # Save changes? debug("Confirming save changes"); my $xml = Gtk2::GladeXML->new($globals->{'gladefile'}, 'dialogSave'); $xml->signal_autoconnect_from_package(''); $xml->get_widget('labelSave') ->set_text("What do you want save the changes to " . $globals->{'current_page'}); my $response = $xml->get_widget('dialogSave')->run(); if ($response eq "ok") { save_page($globals->{'current_page'}); } else { # clear the changed_flag anyway $globals->{'changed_flag'} = FALSE; } $xml->get_widget('dialogSave')->destroy(); } else { save_page($globals->{'current_page'}); } } } $globals->{'current_page'} = $pagename; if (!$globals->{'signal_freeze_flag'}) { debug("Switching page"); if (!exists $svg{$pagename}) { finish_load($section, $page); } } } # Load an item from the svg to the canvas sub load_item { my ($section, $page, $child, $target,$pc) = @_; my $pagename = $section . "/" . $page; my ($root); if ($target eq "canvas") { $root = $pages->{$section}->{$page}->{'canvas'}->root; } if ($child->{'_name'} eq "path") { my ($styles); my $tmp = $child->{'_attributes'}->{'style'}; my @style = split(/;/, $tmp); foreach (@style) { my @tmp2 = split(/:/, $_); $styles->{$tmp2[0]} = $tmp2[1]; } if ($target eq "canvas") { my $tmppoints = $child->{'_attributes'}->{'d'}; $tmppoints =~ s/[ML] //g; my @points = split(/ /, $tmppoints); my $brush_col = $styles->{'stroke'}; $styles->{'stroke-opacity'} =~ s/,/./g; $brush_col =~ s/#//g; $brush_col = (hex($brush_col) + $styles->{'stroke-opacity'}) * 256; $canvasitem{$pagename}->{$child->{'_attributes'}->{'name'}} = Gnome2::Canvas::Item->new( $root, 'Gnome2::Canvas::Line', cap_style => 'round', join_style => 'round', points => \@points, fill_color_rgba => $brush_col, width_units => $styles->{'stroke-width'} ); $canvasitem{$pagename}->{$child->{'_attributes'}->{'name'}} ->{user_data} = $child->{'_attributes'}->{'name'}; } elsif ($target eq "print") { my @points = split(/[ML]/,$child->{'_attributes'}->{'d'}); my $firstpoint=TRUE; my $brush_col = $styles->{'stroke'}; $brush_col =~ s/#//g; my @colour=split(//,$brush_col); my $r = hex($colour[0].$colour[1])/256; my $g = hex($colour[2].$colour[3])/256; my $b = hex($colour[4].$colour[5])/256; my $a = $styles->{'stroke-opacity'}; $a =~ s/,/./g; $pc->setrgbcolor($r,$g,$b); $pc->setopacity($a); $pc->setlinecap(1); $pc->setlinejoin(1); $pc->setlinewidth($styles->{'stroke-width'}/2); foreach (@points) { if ($_ ne "") { my @point=split(/ /,$_); my $x=$point[1]/($globals->{'width'}/$globals->{'print_width'}); my $y=$point[2]/($globals->{'height'}/$globals->{'print_height'}); $y=$globals->{'print_height'}-$y; if ($point[1] ne "" ) { if ($firstpoint) { $pc->moveto($x,$y); $firstpoint=FALSE; } else { $pc->lineto($x,$y); } } } } $pc->stroke; } } elsif ($child->{'_name'} eq "polyline") { my ($styles); my $tmp = $child->{'_attributes'}->{'style'}; my @style = split(/; /, $tmp); foreach (@style) { my @tmp2 = split(/: /, $_); $styles->{$tmp2[0]} = $tmp2[1]; } my $tmppoints = $child->{'_attributes'}->{'points'}; $tmppoints =~ s/^points: //g; my @points = split(/[ ,]/, $tmppoints); if ($target eq "canvas") { $canvasitem{$pagename}->{$child->{'_attributes'}->{'name'}} = Gnome2::Canvas::Item->new( $root, 'Gnome2::Canvas::Line', cap_style => 'round', join_style => 'round', points => \@points, fill_color_rgba => $styles->{'stroke'}, width_units => $styles->{'stroke-width'} ); $canvasitem{$pagename}->{$child->{'_attributes'}->{'name'}} ->{user_data} = $child->{'_attributes'}->{'name'}; } else { debug ("Can't print polylines"); } } elsif ($child->{'_name'} eq "text") { my ($styles); my @style = split(/;/, $child->{'_attributes'}->{'style'}); foreach (@style) { my @tmp2 = split(/:/, $_); $styles->{$tmp2[0]} = $tmp2[1]; } my $data = $child->{'_children'}[0]->{'_contents'}; chomp($data); $styles->{'font'} =~ s/_/ /g; if ($target eq "canvas") { $canvasitem{$pagename}->{$child->{'_attributes'}->{'name'}} = Gnome2::Canvas::Item->new( $root, 'Gnome2::Canvas::Text', fill_color => 'black', x => $child->{'_attributes'}->{'x'}, y => $child->{'_attributes'}->{'y'}, font => $styles->{'font'}, text => $data, anchor => 'south-west' ); $canvasitem{$pagename}->{$child->{'_attributes'}->{'name'}} ->{user_data} = $child->{'_attributes'}->{'name'}; } elsif ($target eq "print") { $pc->setfont(Gnome2::Print::Font->find_closest_from_full_name($styles->{'font'})); $pc->setrgbcolor(0,0,0); my $x=$child->{'_attributes'}->{'x'}/($globals->{'width'}/$globals->{'print_width'}); my $y=$child->{'_attributes'}->{'y'}/($globals->{'height'}/$globals->{'print_height'}); $y=$globals->{'print_height'}-$y; $pc->moveto($x,$y); $pc->show($data); } } elsif ($child->{'_name'} eq "image") { my $filename = $child->{'_attributes'}->{'xlink:href'}; my $im = Gtk2::Gdk::Pixbuf->new_from_file($globals->{'svgdir'} . $section."/".$filename); if ($target eq "canvas") { $canvasitem{$pagename}->{$child->{'_attributes'}->{'name'}} = Gnome2::Canvas::Item->new( $root, 'Gnome2::Canvas::Pixbuf', "pixbuf", $im, "x", $child->{'_attributes'}->{'x'}, "y", $child->{'_attributes'}->{'y'}, "width", $child->{'_attributes'}->{'width'}, "height", $child->{'_attributes'}->{'height'}, "anchor", 'south-west', ); } elsif ($target eq "print") { my $x=$child->{'_attributes'}->{'x'}/($globals->{'width'}/$globals->{'print_width'}); my $y=$child->{'_attributes'}->{'y'}/($globals->{'height'}/$globals->{'print_height'}); $y=$globals->{'print_height'}-$y; $pc->gsave; $pc->translate($x,$y); $pc->scale ($im->get_width, $im->get_height); if ($im->get_has_alpha) { $pc->rgbaimage ($im->get_pixels, $im->get_width, $im->get_height, $im->get_rowstride); } else { $pc->rgbimage ($im->get_pixels, $im->get_width, $im->get_height, $im->get_rowstride); } $pc->grestore; } } } # Load a background image sub load_bg { my ($widget) = @_; my $xml = Gtk2::GladeXML->new($globals->{'gladefile'}, 'dialogFile'); $xml->signal_autoconnect_from_package(''); my $response = $xml->get_widget('dialogFile')->run(); if ($response eq "ok") { add_page("", ""); my $id = time; if (int($globals->{'lastid'}) == $id) { $id = $globals->{'lastid'} + 0.1; } $globals->{'lastid'} = $id; my $pagename = get_current_pagename(); my $canvas = get_current_canvas(); my $filename = $xml->get_widget('dialogFile')->get_filename(); $xml->get_widget('dialogFile')->destroy(); my $newfilename = $pagename . "@" . $id . ".png"; system( "convert -resize " . ($globals->{'pagewidth'} - 100) . "x" . ($globals->{'pageheight'} - 100) . " \"" . $filename . "\" \"" . $globals->{'svgdir'} . $newfilename . "\""); my $im = Gtk2::Gdk::Pixbuf->new_from_file($globals->{'svgdir'} . $newfilename); $canvasitem{$pagename}->{$id} = Gnome2::Canvas::Item->new( $canvas->root, 'Gnome2::Canvas::Pixbuf', "pixbuf", $im, "x", 0, "y", 0, "width", $im->get_width, "height", $im->get_height, "anchor", 'south-west', ); my $tag = $svg{$pagename}->getElement('svg')->createChild('image'); $tag->attribute('x', 0); $tag->attribute('y', 0); $tag->attribute('width', $im->get_width); $tag->attribute('height', $im->get_height); $tag->attribute('name', $id); $tag->attribute('xlink:href', $newfilename); } } # Print the current page sub print_page { debug("Print page"); my $job = Gnome2::Print::Job->new; my $dialog = Gnome2::Print::Dialog->new($job, "Print Current Page", 0); $dialog->signal_connect(delete_event => sub { $dialog->destroy; }); $dialog->signal_connect( response => sub { my ($d, $response, $job) = @_; my $conf = $d->get_config; my $j = Gnome2::Print::Job->new($conf); if ($response == 1) { # user hit 'Print' render_job($j); my $pc = Gnome2::Print::Context->new($conf); $j->render($pc); $pc->close; $dialog->destroy; my $done_dialog = Gtk2::Dialog->new("Page printing",$d,'destroy-with-parent','gtk-ok' => 'none'); my $label = Gtk2::Label->new("Page sent to printer"); $done_dialog->vbox->add($label); $done_dialog->show_all; $done_dialog->run; $done_dialog->destroy; } elsif ($response == 2) { # user hit 'Preview' render_job($j); my $preview = Gnome2::Print::JobPreview->new($j, "Preview Page"); $preview->set_property("allow-grow", 1); $preview->set_property("allow-shrink", 1); $preview->set_transient_for($d); $preview->show_all; } else { $d->destroy; } }, $job ); $dialog->run(); } # Render the current printjob sub render_job { my ($job) = @_; my $pagename = get_current_pagename(); my ($section,$page) = split (/\//,$pagename,2); my $conf = $job->get_config; ($globals->{'print_width'}, $globals->{'print_height'}) = $conf->get_page_size; my $pc = $job->get_context; # Begin the page $pc->beginpage("1"); my $svg2 = $svg{$pagename}->getElement('svg'); my $topLevelChildren = $svg2->getAllChildren(); foreach my $child (@{$topLevelChildren}) { if ( $child->{'_attributes'}->{'visibility'} && $child->{'_attributes'}->{'visibility'} eq "hidden") { # ignore } else { load_item($section, $page, $child,"print",$pc); } } # Finish the page $pc->showpage; $job->close; } #### ## Section related functions #### # Load a directory as a section sub load_dir { my ($dir) = @_; my $section = $dir; if ($section eq "") { $section = "Main"; } if (!defined $pages->{$dir}->{'_notebook'}) { debug("Loading Section " . $section); $pages->{$section}->{'_notebook'} = Gtk2::Notebook->new; $pages->{$section}->{'_notebook'}->popup_enable; $pages->{$section}->{'_notebook'}->set_scrollable(TRUE); $pages->{$section}->{'_notebook'}->set_tab_pos('top'); my $label = Gtk2::Label->new(qq{$section}); $label->set_use_markup(1); $pages->{$section}->{'_pageid'} = $sectionTabs->append_page($pages->{$section}->{'_notebook'}, $label); $sectionTabs->get_nth_page($pages->{$section}->{'_pageid'}) ->{'section'} = $section; $pages->{$section}->{'_notebook'}->show(); $sectionTabs->show_all(); opendir(DIR, $globals->{'svgdir'} . $dir); foreach (readdir DIR) { if ($_ =~ /\.svgz$/) { load_page($section, $_); $found = $section . "/" . $_; } elsif ($_ =~ /\.svg\.gz$/) { my $oldname = $_; my $newname = $_; $newname =~ s/\.svg\.gz$/\.svgz/g; rename $globals->{'svgdir'} . $dir . $oldname, $globals->{'svgdir'} . $dir . $newname; load_page($section,$newname); $found = $section . "/" . $newname; } } closedir DIR; $pages->{$section}->{'_notebook'} ->signal_connect_after(switch_page => \&switch_page, undef); } } # Called when section tab is changed sub switch_section { my ($widget) = @_; if (!$globals->{'signal_freeze_flag'}) { my $section = $sectionTabs->get_nth_page($sectionTabs->get_current_page) ->{'section'}; debug("Switching Section to $section"); my $subwidget = $pages->{$section}->{'_notebook'}; switch_page($subwidget); } } # Create a new time-named section sub add_section { my @time = localtime(time); my $section = sprintf( "%04d-%02d-%02d@%02d:%02d", ($time[5] + 1900), ($time[4] + 1), $time[3], $time[2], $time[1] ); if (exists $pages->{$section}) { $section = sprintf( "%04d-%02d-%02d@%02d:%02d:%02d", ($time[5] + 1900), ($time[4] + 1), $time[3], $time[2], $time[1], $time[0] ); } debug("Adding section $section"); $globals->{'signal_freeze_flag'} = TRUE; load_dir($section); add_page("", $section); $globals->{'signal_freeze_flag'} = FALSE; } #### ## Tool related functions #### # Set the brush width to fine sub brush_fine { my ($widget) = @_; if ($widget->get_active) { if (toggle_thick("buttonFine")) { $globals->{'brush_width'} = 1; } } } # Set the brush width to medium sub brush_medium { my ($widget) = @_; if ($widget->get_active) { if (toggle_thick("buttonMedium")) { $globals->{'brush_width'} = 2; } } } # Set the brush width to heavy sub brush_heavy { my ($widget) = @_; if ($widget->get_active) { if (toggle_thick("buttonHeavy")) { $globals->{'brush_width'} = 4; } } } # Set the brush width to fat sub brush_fat { my ($widget) = @_; if ($widget->get_active) { if (toggle_thick("buttonThick")) { $globals->{'brush_width'} = 20; } } } # Set the tool to black pen sub tool_black { my ($widget) = @_; if ($widget->get_active) { if (toggle_tool("buttonBlack")) { finish_text(); $globals->{'brush_color'} = 0x000000ff; $globals->{'tool_type'} = NORMAL; $mainxml->get_widget('buttonMedium')->set_active(TRUE); } } } # Set the tool to red pen sub tool_red { my ($widget) = @_; if ($widget->get_active) { if (toggle_tool("buttonRed")) { finish_text(); $globals->{'brush_color'} = 0xff0000ff; $globals->{'tool_type'} = NORMAL; $mainxml->get_widget('buttonMedium')->set_active(TRUE); } } } # Set the tool to whiteout sub tool_erase { my ($widget) = @_; if ($widget->get_active) { if (toggle_tool("buttonWhiteout")) { finish_text(); $globals->{'brush_color'} = 0xffffffff; $globals->{'tool_type'} = NORMAL; $mainxml->get_widget('buttonThick')->set_active(TRUE); } } } # Set the tool to delete sub tool_delete { my ($widget) = @_; if ($widget->get_active) { if (toggle_tool("buttonDelete")) { finish_text(); $globals->{'tool_type'} = ERASE; } } } # Set the tool to the highlighter sub tool_hilite { my ($widget) = @_; if ($widget->get_active) { if (toggle_tool("buttonHighlite")) { finish_text(); $globals->{'brush_color'} = 0xffff0080; $globals->{'tool_type'} = HILITE; $mainxml->get_widget('buttonThick')->set_active(TRUE); } } } # Zoom into the page sub zoom_in { my $canvas = get_current_canvas(); $canvas->set_pixels_per_unit(2); } # Return to normal zoom sub zoom_out { my $canvas = get_current_canvas(); $canvas->set_pixels_per_unit(1); } # Set tool to text sub add_text { my ($widget) = @_; if ($widget->get_active) { if (toggle_tool("buttonText")) { finish_text(); $globals->{'tool_type'} = TEXT; } } } # Set tool to timestamp sub add_date { if (toggle_tool("buttonTime")) { finish_text(); $globals->{'tool_type'} = DATE; } } # Finish the current textitem sub finish_text { if (defined $globals->{'text_tag'}) { my $pagename = get_current_pagename(); $globals->{'text_tag'}->text( $canvasitem{$pagename}->{$globals->{'canvas_text'}}->get('text')); undef $globals->{'text_tag'}; } } # Draw a rectangle on the screen sub draw_brush { my ($widget, $x, $y) = @_; my ($cx2, $cy2) = $widget->window_to_world($x, $y); $x = $cx2; $y = $cy2; push @xv, $x; push @yv, $y; push @line, $x, $y; if (($globals->{'oldx'} == -100) && ($globals->{'oldy'} == -100)) { $globals->{'oldx'} = $x; $globals->{'oldy'} = $y; } Gnome2::Canvas::Item->new( $globals->{'current_group'}, 'Gnome2::Canvas::Line', points => [$globals->{'oldx'}, $globals->{'oldy'}, $x, $y], cap_style => 'round', join_style => 'round', fill_color_rgba => $globals->{'brush_color'}, width_units => $globals->{'brush_width'} ); $globals->{'oldx'} = $x; $globals->{'oldy'} = $y; } # Undo the last change sub undo_last { my $pagename = get_current_pagename(); my $s = ($canvasitem{$pagename}); my @keys = sort keys(%$s); my $lastkey = pop(@keys); delete_item($pagename, $lastkey); $globals->{'changed_flag'} = TRUE; } # Redo the last undone change sub redo_last { my $pagename = get_current_pagename(); my $svg2 = $svg{$pagename}->getElement('svg'); my $topLevelChildren = $svg2->getAllChildren(); my @deleted = (); foreach my $child (@{$topLevelChildren}) { if ( $child->{'_attributes'}->{'visibility'} && $child->{'_attributes'}->{'visibility'} eq "hidden") { push @deleted, $child->{'_attributes'}->{'name'}; } } my @tmp = sort @deleted; my $itemnum = pop @tmp; if ($itemnum) { foreach my $child (@{$topLevelChildren}) { if ($child->{'_attributes'}->{'name'} eq $itemnum) { $child->attribute('visibility', "visible"); $child->attribute('name', time); my ($section, $page) = split(/\//, $pagename, 2); load_item($section, $page, $child,"canvas"); } } } $globals->{'changed_flag'} = TRUE; } # Choose the font for the text tool sub choose_font { my $xml = Gtk2::GladeXML->new($globals->{'gladefile'}, 'dialogFont'); $xml->signal_autoconnect_from_package(''); $xml->get_widget('dialogFont')->set_font_name($globals->{'font'}); my $response = $xml->get_widget('dialogFont')->run(); if ($response eq "cancel") { $xml->get_widget('dialogFont')->destroy(); } else { $globals->{'font'} = $xml->get_widget('dialogFont')->get_font_name; debug("Font chosen is $globals->{'font'}"); $xml->get_widget('dialogFont')->destroy(); } } # Delete given item on page sub delete_item { my ($pagename, $id) = @_; if (defined $canvasitem{$pagename}->{$id}) { $canvasitem{$pagename}->{$id}->destroy; delete $canvasitem{$pagename}->{$id}; my $svg2 = $svg{$pagename}->getElement('svg'); my $topLevelChildren = $svg2->getAllChildren(); foreach my $child (@{$topLevelChildren}) { if ( $child->{'_attributes'}->{'name'} && $child->{'_attributes'}->{'name'} eq $id) { $child->attribute('visibility', "hidden"); $child->attribute('name', time); } } } } # Add an image to the page sub add_image { my ($widget) = @_; if (toggle_tool("buttonPicture")) { my $xml = Gtk2::GladeXML->new($globals->{'gladefile'}, 'dialogFile'); $xml->signal_autoconnect_from_package(''); my $response = $xml->get_widget('dialogFile')->run(); if ($response eq "ok") { my $id = time; if (int($globals->{'lastid'}) == $id) { $id = $globals->{'lastid'} + 0.1; } $globals->{'lastid'} = $id; my $pagename = get_current_pagename(); my $filename = $xml->get_widget('dialogFile')->get_filename(); my $newfilename = $pagename . "@" . $id; system( "cp \"" . $filename . "\" \"" . $globals->{'svgdir'} . $newfilename . "\""); my $im = Gtk2::Gdk::Pixbuf->new_from_file( $globals->{'svgdir'} . $newfilename); my $canvas = get_current_canvas(); $globals->{'canvas_image'} = Gnome2::Canvas::Item->new( $canvas->root, 'Gnome2::Canvas::Pixbuf', "pixbuf", $im, "x", 0, "y", 0, "width", $im->get_width, "height", $im->get_height, "anchor", 'south-west', ); my ($section, $page) = split(/\//, $newfilename, 2); $globals->{'canvas_image'}->{user_data} = $page; $globals->{'tool_type'} = IMAGE; } $xml->get_widget('dialogFile')->destroy(); } } # Update display of brush width in toolbar sub toggle_thick { my ($button) = @_; if ($mainxml->get_widget($button)->get_active()) { foreach my $item ('buttonFine', 'buttonMedium', 'buttonThick', 'buttonHeavy') { if ($item ne $button) { if ($mainxml->get_widget($item)->get_active()) { $mainxml->get_widget($item)->set_active(FALSE); } } } } return $mainxml->get_widget($button)->get_active(); } # Update display of current tool in toolbar sub toggle_tool { my ($button) = @_; debug("$button chosen"); if ($mainxml->get_widget($button)->get_active()) { foreach my $item ( 'buttonBlack', 'buttonWhiteout', 'buttonHighlite', 'buttonText', 'buttonTime', 'buttonPicture', 'buttonDelete', 'buttonRed' ) { if ($item ne $button) { print STDERR "Item: $item\n"; if ($mainxml->get_widget($item)->get_active()) { $mainxml->get_widget($item)->set_active(FALSE); } } } } return $mainxml->get_widget($button)->get_active(); } #### ## Network functions #### # Handle input from network socket sub io_handler { my ($sock, $tag) = @_; debug("Handling network"); my $canvas = FALSE; my $new_sock = $sock->accept(); while (defined($_ = <$new_sock>)) { chomp; # Wait until the local user is finished what they are doing while (($globals->{'oldx'} != -100) && ($globals->{'oldy'} != -100) && (defined $globals->{'text_tag'})) { Gtk2->events_pending(); } my @line = split(/"/, $_); my %item; foreach (@line) { my @tmp = split(/=/, $_, 2); $item{$tmp[0]} = $tmp[1]; } if (!exists $svg{$item{'page'}}) { load_page($item{'page'}); $canvas = $mainxml->get_widget('notebookMain') ->get_nth_page( $mainxml->get_widget('notebookMain')->get_current_page) ->get_child; } else { my $pagenum = 0; while ( !( ($canvas) && ($pagenum < $mainxml->get_widget('notebookMain')->get_n_pages()) ) ) { $canvas = $mainxml->get_widget('notebookMain')->get_nth_page($pagenum) ->get_child; if ($canvas->{user_data} ne $item{'page'}) { $canvas = FALSE; } $pagenum++; } if (!$canvas) { load_page($item{'page'}); $canvas = $mainxml->get_widget('notebookMain') ->get_nth_page( $mainxml->get_widget('notebookMain')->get_current_page) ->get_child; debug("finish_load($item{'page'}, $canvas);"); } } my $item = $svg{$item{'page'}}->getElement('svg')->createChild('path'); foreach (keys %item) { if ($_ ne "page") { $item->attribute($_, $item{$_}); } } load_item($item{'page'}, $canvas, $item,"canvas"); } close($new_sock); return TRUE; } # Send changes over the network sub send_item { my ($item, $filename) = @_; debug("Outport: $OUTPORT"); foreach my $hostname (@hosts) { if ( my $server = IO::Socket::INET->new( Proto => "tcp", PeerAddr => $hostname, PeerPort => $OUTPORT ) ) { my $output = 'page=' . $filename; my $hash = $item->{'_attributes'}; foreach (keys %$hash) { $output .= '"' . $_ . '=' . $item->{'_attributes'}->{$_}; } print $server $output . "\n"; close($server); } } } # Turn on/off network client sub toggle_client { my $conxml = Gtk2::GladeXML->new($globals->{'gladefile'}, 'dialogConnect'); $conxml->signal_autoconnect_from_package(''); my @tmp = sort @hosts; @hosts = @tmp; my $table = new Gtk2::Table((int(@hosts / 3) + 1), 3, TRUE); my @button = (); for (my $row = 0 ; $row <= int(@hosts / 3) ; $row++) { foreach my $col (0 .. 2) { my $itemnum = $row * 3 + $col; if (defined $hosts[$itemnum]) { $button[$itemnum] = Gtk2::ToggleButton->new($hosts[$itemnum]); $button[$itemnum]->set_active(TRUE); $table->attach( $button[$itemnum], $col, $col + 1, $row, $row + 1, ['expand', 'fill'], ['expand', 'fill'], 0, 0 ); } } } $conxml->get_widget('vboxConnect')->pack_start($table, TRUE, TRUE, 0); $table->show_all(); my $response = $conxml->get_widget('dialogConnect')->run(); if ($response eq "ok") { my $found = FALSE; my @tmplist = (); for my $itemnum (0 .. (@hosts - 1)) { if ($hosts[$itemnum] eq $conxml->get_widget('entryConnect')->get_text()) { $found = TRUE; } if ($button[$itemnum]->get_active()) { if ($hosts[$itemnum] ne $conxml->get_widget('entryConnect')->get_text()) { push @tmplist, $hosts[$itemnum]; } } } if ( (!$found) && ($conxml->get_widget('entryConnect')->get_text() ne "")) { push @tmplist, $conxml->get_widget('entryConnect')->get_text(); } @hosts = sort @tmplist; } debug(@hosts); if (@hosts == 0) { $globals->{'client_flag'} = FALSE; } else { $globals->{'client_flag'} = TRUE; } my $vbox = Gtk2::VBox->new(FALSE, 0); if ($globals->{'client_flag'}) { my $icon = Gtk2::Image->new_from_stock('gtk-cancel', 'large_toolbar'); $vbox->pack_start($icon, TRUE, TRUE, 0); my $label = Gtk2::Label->new("Disconnect"); $vbox->pack_start($label, TRUE, TRUE, 0); } else { my $icon = Gtk2::Image->new_from_stock('gtk-ok', 'large_toolbar'); $vbox->pack_start($icon, TRUE, TRUE, 0); my $label = Gtk2::Label->new("Connect"); $vbox->pack_start($label, TRUE, TRUE, 0); } $conxml->get_widget('dialogConnect')->destroy(); my $child = $mainxml->get_widget('buttonClient')->get_child(); $child->destroy(); $mainxml->get_widget('buttonClient')->add($vbox); $mainxml->get_widget('buttonClient')->show_all; } # Turn on/off network server sub toggle_server { my $vbox = Gtk2::VBox->new(FALSE, 0); if ($globals->{'server_flag'}) { turn_off_server(); my $icon = Gtk2::Image->new_from_stock('gtk-no', 'large_toolbar'); $vbox->pack_start($icon, TRUE, TRUE, 0); my $label = Gtk2::Label->new("Deaf"); $vbox->pack_start($label, TRUE, TRUE, 0); } else { turn_on_server(); my $icon = Gtk2::Image->new_from_stock('gtk-yes', 'large_toolbar'); $vbox->pack_start($icon, TRUE, TRUE, 0); my $label = Gtk2::Label->new("Listening"); $vbox->pack_start($label, TRUE, TRUE, 0); } my $child = $mainxml->get_widget('buttonServer')->get_child(); $child->destroy(); $mainxml->get_widget('buttonServer')->add($vbox); $mainxml->get_widget('buttonServer')->show_all; } # Turn on the nework server sub turn_off_server { Gtk2::Helper->remove_watch($io_id); $mainxml->get_widget('buttonServer')->set_label("Deaf"); $globals->{'server_flag'} = FALSE; } # Turn off the nework server sub turn_on_server { debug("Turning on server"); my $sock = new IO::Socket::INET( Listen => SOMAXCONN, LocalPort => $globals->{'server_port'}, Resuse => 1, Proto => 'tcp' ) or die "Can't open socket: $!"; $sock->autoflush(1); $io_id = Gtk2::Helper->add_watch($sock->fileno, 'in', sub { io_handler($sock, $io_id); }); $globals->{'server_flag'} = TRUE; } # Edit the list of known servers sub edit_favourite { my ($widget, $num, $newtext) = @_; my $store = $globals->{'prefs_xml'}->get_widget('treeHosts')->get_model(); my $iter = $store->get_iter_from_string($num); if ($newtext == "") { $store->remove($iter); my @newfav = (); foreach (0 .. (@favourites - 1)) { if ($_ != $num) { push @newfav, $favourites[$_]; } } @favourites = @newfav; } else { $store->set($iter, 0, $newtext); $favourites[$num] = $newtext; } return TRUE; } # Add a server to the list of known servers sub add_favourite { my $store = $globals->{'prefs_xml'}->get_widget('treeHosts')->get_model(); my $newtext = $globals->{'prefs_xml'}->get_widget('entryAddFav')->get_text; $store->set($store->append, 0, $newtext); push @favourites, $newtext; }