#!/usr/bin/perl -w #{ # push (@INC,'/usr/lib/perl5/site_perl/5.6.1/i386-linux'); #} #I don't wan't to deal with any UTF8 shit for now #You are welcome to comment this out and give it a try at your own risk. #i have already had pigs have errors on a redhat 9 system (which generally is #mostly UTF8 enabled) saving files because the size of a file saved was not #equal to the size of the data written. (I had pasted some text with utf chars #into a pig. Anyway, i am not prepared to worry about this crap at the moment #when as far as I know, I am the only person using this prog. use bytes; use Data::Dumper; my $version="1.4, Thu/25/Jan/2005"; #my $version="1.1b, Fri/9/May/2003"; #description: a gtk/(gnome optional) desktop notes program which can also # run as an applet. #authors email address: Chris Sincock csincock@no.SPAM.PLEASE.airnet.com.au # (without the no.SPAM.PLEASE. bit) # # Copyright (C) 2002 Chris Sincock # # 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., 675 Mass Ave, Cambridge, MA 02139, USA. # At the time of writing of this program, you can also read it at # http://www.gnu.org/licenses/gpl.html. # use strict; use Gtk; use Gtk::Atoms; #use csincock::Gtk::Pixmap; use Carp; use Data::Dumper; #### #current known bugs/problems #### # - still can't resize a note below the width of it's title. # - can't seem to set the note windows to skip the gnome task list. # however individual users may be able to tell their window manager to # make them not appear in it (eg in sawfish this is no problem). # - at the moment, it 'sort-of' has a read-only mode. However I have yet to # go through and thoroughly check that it doesn't do stuff it shouldn't # do in read-only mode. Also, should the read only mode not allow you to # change anything? or should it just prohibit saving changes?... #fixed bugs or problems # - if you close the summary window _not_ via the close button, it fucks # up fixed by hiding the window in the delete_event handler, and # returning true. # Returning true in the handler stops the window getting a destroy event. # - moving notes by dragging the titlebar does not quite work properly with # large mouse movements. Fixed by making the note window modal while # it's being dragged # - it sort of carks it if you have no ~/.pigs directory or no .gnome/goats # - the drawing of notes is fucked - the caret and text drawn as you type # is fucked - this is something to do with the changed style. # - can't resize notes below some seemingly arbitrary size #### #not implemented yet ### # - alarms # - note groups # - i wan't to maybe add a resize button # - ability to make notes sticky, raise, and lower # - 'recent' and 'often used' menus # - menu of currently showing notes, so that you can for example have all # your notes set to be stacked below, but easily temporarily raise one # from that menu without having to move other windows out of the way. #### #changelog #### #version whatever, codename bugfixblah # - fixed bug - notes that were shaded when pigs exited were losing their # old (unshaded) height, so they were starting up the 'shaded' size #version 1.1b, codename "tweak2" # - fixed bug in client command show-note: show-note text=pattern wasn't # working #version 1.1a, codename "tweak" # - added option to show/hide window manager decorations (and this also # shows/hides the pigs own title bar) and to set the format of the window # title string. # - improved the settings dialog code to add labels for extra descriptions. # I really should update pigs to use my csincock::Settings.pm module as # it was based on the stuff here but has now been improved heaps. #version 1.0, codename "atlast" # - fixed so the applet doesn't push other things out of the way when # starting up This one was a real bastard to fix. Basically the applet # has to have a +ve width when it is first shown, so a temporary # placeholder label(".") is added. # other widgets which have width such as Gtk::Invisible() don't seem # to work. # - added client/server stuff. now you can do: # pigs -client new-note title,text # or for example: cat somefile | pigs -client new-note somefile,- # to add the contents of somefile as a new note called somefile # here's another example: pigs -client show-note 'title=tv tonight' # you can also use text= in that example, and the bit after the = is a # regular expression. Alternatively you can specify a note by number # with num=. # - it now uses a lock file ~/.pigs/lock, to stop multiple instances # running on the same set of notes - you can still run multiple instances # with different sets of notes by running a duplicate of pigs (or a link # to it) with a different name, for example, if you create a link to pigs # called pigs2, it will use ~/.pigs2/lock #version 0.9, codename "blah" # - got undos/redos working! # - undos really work! you can even undo undos and redos! # - added settings in the settings dialog for the number of undos, # and the idle save period. # - made teh full menu show in each pigs menu, except for 'restart' and 'exit' # - added 'undiscard' menu option for discarded notes. #version 0.8, codename "I should be in bed I have to be at work in 7 hours" # - changed the default configuration directory to use the program name, so that # for example, if run as pigs2, it will use ~/.pigs2. the ~/.pigs/locations file # is no longer used. # - added command line options to put it in applet (-applet)or non applet (-gui) mode #version 0.7, 5:44 am Sun 5th Jan/2003, codename "I have to go back to work tomorrow dagnabbit" # - backup files are now named more sensibly, and the backup directory can be # specified # - the import window can now filter out notes which are already in the current notes. # - the windows may now be resized down to quite a small size, 30x30 or so. # the reason for this limit is that if the window is resized to less than the width # of one character of text, and the note contains text, the whole gui locks up # totally, I think it is the scroll bar and text area recursively trying to calculate # their size, ie the text field is too small for one character, so every character # gets wrapped to the next line, where it also doesn't fit, so it gets wrapped again, # and so on and so on. # - there is now a settings window which nearly lets you set all the important settings. # - it even has apply/revert/ok/cancel options # - you can't edit the colours yet # - there is a plain gui mode that doesn't use an applet at all, just a toplevel window # - added 'none' options for font and colour, which should use the default theme settings. # - this isn't quite working yet for the colours. #version 0.6.1, 4:25 am Sat 4 Jan/2003 # - only the settings dialogs are really left to implement before it # can be called a complete notes program. All other unimplemented # features are non-essential. # - delete, colour, and font options are in the notes right-click menu, # and working # - added a 'use as default' option for the note menu # - delete functionality now accessible from the note menu - a note may # be immediately deleted, or just 'discarded' (marked for deletion). # - there is a 'discarded notes' option for viewing notes marked as discarded. # - can't undiscard a note yet but it will be very simple to do. #version 0.5, Fri 3, 1:30 am Fri 3 Jan/2003 # - both note colors and fonts are working # - the close button is improved # - added a 'discard empty notes' option to applet menu # - the functionality now exists to delete notes, though there is no # actual way (menu or somesuch) to do discard a particular note as yet. #version 0.4, Thu 2 # - Import Goats, Restore Pigs options working beautifully # - single-clicking on applet creates a new note, double clicking shows summary # - a small hard-coded xpm icon is now included, it is used unless alternative # icons are specified. # - made a class for the applet #version 0.3 #- double clicking a pig in the summary window toggles it between # shaded,visible, and hidden #- changed to use ~/.pigs/icon.png and ~/.pigs/icon-focused.png # for an icon in the panel (if found), otherwise it just uses a @ button. # I may make this a configurable option instead. #- made a lovely pig icon based on an image found at: www.pigs-etc.com #- now resizes properly when moved between panels of different sizes. #- got it integrated properly into gnome menus & panel, so that the panel can # even restart it properly now (using files /etc/CORBA/servers/pigs.gnorba # and /usr/share/applets/Utility/pigs.desktop) #- initial implementation of summary window # you can double click to show/unshade/hide a particular note. #- still can't delete notes. #### #version 0.2, 28/Dec/2002, 1220 lines #- implemented applet #- implemented fonts for different pigs #- implemented show/hide/shade/unshade all, accessible through applet. #### #version 0.1 #- I wasn't doing a changelog at this stage #### #comments below here may be out of date #todo #- stop multiple instances using same config #- command line options #- temp hide all/showall/restore option, to temporarily show/hide all notes # then restore the set you had showing already #- grouping of notes. so you can for example show/hide a whole group. #done: #- colours, fonts etc #- right-click menu for each note #- allow specifying a config file #- allow changing of notes file #- summary window #- applet icon #probs: #- can't resize pigs below about the width of the title #- can't have pigs positioned with -ve coordinates, ie with left or top going off screen #- it doesn't quite handle panels with pixmap backgrounds properly yet. #fixed probs #- the caret is drawn in a pig in the wrong place - I fixed this by # just reordering the setting of styles. Don't ask me why it worked. #- the applet starts up at a huge size and pushes other things out of the way if it is # first shown with a width of 0, ie the width of widgets inside it is 0 and the # border width of the applet's window is 0. The fix is to make sure it has +ve # width when shown. #---------------- #saving strategy #---------------- #- there is a timer which checks every few (configurable) seconds if any # changes have been made, and performs a save if necessary. my $debugging = 1; my $true=1; my $false=0; my $unimportantChangesBeforeSave=0; my $idleSavePeriod=1; #in seconds my @originalArgs=(@ARGV); my $doubleClickPeriod=600; #the interfaceMode defaults to applet, so it can be run as an applet #without command-line arguments #23 Jan 2004 - changed default to use config file setting or else 'nogui' #- gnome (>1.4) can get fucked (ie get fucked miguel and all the gnome #developers who are following him into the depths of shitholeness ). my $interfaceMode=""; #forward declaration of the Pig class package Pig; #actual declaration of the Sty class package Sty; my $progName=$0; my $appName=$progName; $0=~s|.*/||g;$0=join(" ",$0,@ARGV); $appName =~ s|^.*/||g; #print "progName is $progName\n"; #print "appName is $appName\n"; my $home=$ENV{"HOME"}; my $dotGoats="$home/.gnome/goats"; my $defaultConfigLocation="$home/.$appName"; my $defaultDotPrefs="$defaultConfigLocation/${appName}rc"; my $defaultDotNotes="$defaultConfigLocation/notes"; my $defaultBackups="$defaultConfigLocation/backups"; my $defaultHistory="$defaultConfigLocation/hist"; my $pigsIntroHelpText="Click on the applet or main window to create a new note\\n" ."or right-click for a menu of other options."; my $controlFile="$defaultConfigLocation/control"; my $lockFile="$defaultConfigLocation/lock"; my $keepHistFile=$true;#$false; #variables that represent the application state. my $lockHandle=undef; my $dotNotes=undef; my $dotPrefs=undef; my %pigs=(); #pig objects my %notes=(); #note data my %prefs=(); #prefs my $gtkInited=$false; my $visibleCount=0; my $prompting=0; my $promptWindow; my $prefChanges=0; my $noteChanges=0; my $importantChangeCount=0; my $unimportantChangeCount=0; my $saving=undef; my $configError=undef; my $notesError=undef; my $inhibitSaving=$false; my $pigsGUI=undef; my $pigsGUIMenu=undef; my $protectedMode=$true; my $applet=undef; my $backups=$defaultBackups; my $histdir=$defaultHistory; my $lastServerCheck=undef; my $serving=$false; #my $readOnly=$true; my $startdatestamp=&datestamp; sub datestamp() { my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isds)= localtime time; $year += 1900; my @months=qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec); my $monstr=$months[$mon]; $sec+=$hour * 3600 + ($min * 60); $isds = $isds ? ".+DST" : "-DST"; return sprintf( "%04d.%02d[%s].%02d.%05d%s", $year,($mon+1),$monstr,$mday,$sec,$isds); } #reads a prefs or notes file #if the file is a 'goats' file, goats are converted to pigs sub readFile() { my ($class,$file)=@_; my ($section,$prop,$val); print "reading file:$file\n"; if(open(INFILE,"<$file")) { for my $line () { chomp $line; if($line =~ /^([^=.]+)[.]([^=]+)=(.*)$/) { $section=$1; $prop=$2; $val=$3; # print "read line: $section,$prop,$val\n"; } elsif(! ($line =~ /^\s*$/)) { print STDERR "Invalid line in pigs file:'$line'\n"; } if($section =~ /^(pig\d+)$/ || ($section eq "pigs" && $prop eq "num_pigs") || ($section eq "pigs" && $prop eq "last_pig")) { #it is note data # print "it is note data\n"; $notes{"$section.$prop"}=$val; } else { $prefs{"$section.$prop"}=$val; } } close INFILE; } else { print STDERR "Failed opening $file\n"; } } sub readGoatsFile() { my ($class,$file)=@_; my $section=""; my $pigsection=""; my $count=0; open(FILE,"<$file"); for my $line () { chomp $line; if($line =~ /^$/) { $section=""; } elsif ($line =~ /^[[]([^]]*)[]]/) { $section=$1; $pigsection=$section; if($section =~ /^goat/) { $pigsection="pig".substr($section,4); } #print "pigsection is $pigsection\n"; } elsif ($line =~ /^([^=]*)=(.*)$/) { my $prop=$1; my $val=$2; $prop =~ s/goat/pig/; if($pigsection =~ /^pig/) { #it is note data $notes{"$pigsection.$prop"}=$val; } else { if($pigsection =~ /options/i) { if($prop =~ /^(.*)_default$/i) { $prop="default_$1"; } } #it is preferences $prefs{"$pigsection.$prop"}=$val; } $count++; #print "$pigsection.$prop=$val\n"; } } close FILE; return $count; } sub readNotes() { my ($class,$path)=@_; $dotNotes=$path; Sty->readFile($dotNotes); } sub readPrefs() { my ($class,$path)=@_; $dotPrefs=$path; Sty->readFile($dotPrefs); } sub printHash() { my ($class,%hash)=@_; for my $key (sort keys %hash) { print "key:$key=$hash{$key}\n"; } } sub printNotes() { Sty->printHash(%notes); } sub printPrefs() { Sty->printHash(%prefs); } sub initGtk() { if(!$gtkInited) { init Gtk; $gtkInited=$true; } } #shows pigs, creating them if necessary sub getPig($) { my($class,$pigNum)=@_; my $pig=$pigs{$pigNum}; if(!$pig) { $pig=new Pig($pigNum); $pigs{$pigNum}=$pig; } return $pig; } sub discardOrDeleteNote() { my ($class,$pigNum)=@_; my $pig= $pigs{$pigNum}; if(Sty->getPrefFlag("options","delete_notes_immediately")) { Sty->deleteNote($pigNum); } else { Sty->markNoteAsDiscarded($pigNum); } } sub deleteNote() { my ($class,$pigNum)=@_; my $pig=$pigs{$pigNum}; delete $pigs{$pigNum}; print "deleted pig:$pigNum\n"; $pig->hide(); $pig->destroy; } sub markNoteAsDiscarded() { my ($class,$pigNum)=@_; my $pig= $pigs{$pigNum}; $pig->setFlag("discarded",$true); $pig->hide(); print "discarded pig:$pigNum\n"; } sub undiscard() { my ($class,$pigNum)=@_; my $pig= $pigs{$pigNum}; delete $notes{"pig$pigNum.discarded"}; print "undiscarded pig:$pigNum\n"; } sub deleteNotesMarkedAsDiscarded() { my ($class,$pigNum)=@_; for my $pig (values %pigs) { if($pig->getProp("discarded")) { delete $pigs{$pig->num}; } } } #this only returns a valid result after all pigs have been initialised by main() sub pigExists() { my ($class,$num)=@_; return defined($pigs{$num}); } sub getDefaultColour() { # print "getting default colour:\n"; my $def = Sty->getPref("options","default_colour"); # print "default colour is $def\n"; if($def && $def =~ /^\d+$/) { return $def; } elsif((!$def) || $def eq "random") { # print "returning default colour\n"; return Sty->getRandomColourNumber(); } } sub getRandomColourNumber() { my ($class)=@_; my %hash=(); for my $line (keys %prefs) { if($line =~ /^options.(colour|red|green|blue)(\d+)/) { $hash{$2}++; } } my @list=keys %hash; print "list of all colours is @list\n"; my $num=rand ($#list+1); $num = int($num); print "random number is $num\n"; $num= $list[int($num)]; print "selected random index: $num\n"; return $num; } sub getColourHash() { my ($class,$index)=@_; my $col = Sty->getColour($index); if($col && $col =~ /(\d+),(\d+),(\d+)/) { return {red=>$1,green=>$2,blue=>$3}; } else { return {red=>0,green=>0,blue=>0}; } } sub getColour() { my ($class,$index,$recursed)=@_; my $col=Sty->getPref("options","colour$index"); my ($r,$g,$b); if($col && $col =~ /(\d+),(\d+),(\d+)/) { return $col; } else { $r=Sty->getPref("options","red$index"); $g=Sty->getPref("options","green$index"); $b=Sty->getPref("options","blue$index"); } if(!($r || $g|| $b)) { if($recursed) { return "255,255,0"; } print "r,g,b are all null, getting a random colour\n"; my $col=getRandomColourNumber; return Sty->getColour($col,$true); } else { print "returning:$r,$g,$b\n"; return "$r,$g,$b"; } } sub makepaths() { $backups= Sty->getPref("options","backups_path",$defaultBackups); if(!$backups) { $backups = $defaultBackups; } mkdir $backups; $histdir= Sty->getPref("options","history_path",$defaultHistory); if(!$histdir) { $histdir = $defaultHistory; } mkdir $histdir; } sub backupNotes() { my ($class)=@_; my $datestamp=&datestamp; my $prefix=$dotNotes; $prefix =~ s|^/||; $prefix =~ s|/|-|g; my $str=`cat "$dotNotes"`; my $len=length($str); my $backupfilename=$backups."/$prefix$datestamp.bz2"; system("bzip2 -c < '$dotNotes' > '$backupfilename'"); my $bstr=`bunzip2 -c < "$backupfilename"`; my $blen=length($bstr); if($blen == $len) { print "backed up notes to $backupfilename\n"; return $true; } else { print STDERR "FAILED backing up notes! (perhaps there is no disk space?)\n"; return $false; } } sub flagVal($$) { my ($class,$val,$default)=@_; $default=$default||""; if(!defined($val)) { $val=$default; } return $val =~ /^(true|1)$/i; } ###################### ##currently used prefs accessed with getPref("options",$propname); ###################### # default_font_name #a font name string, the default font for new notes # default_size #w[,h] the default size. If h is missing, w is used # default_position #x[,y] the default pos. If y is missing, x is used # keep_defaults_font #true or false, # keep_defaults_colour #true or false, if this is true, then new notes # will keep 'default' as their colour, and font, and # if you change the default colour or font, those notes # will change also # keep_defaults_colour_random # notes_file #the file path to the notes file # icon_focussed #an image file name with path # icon_unfocussed #an image file name with path # default_colour #the number of the default colour, or 'random' # colour #format:r,g,b, specifies colour n, overrides red etc # red (n=1..3) #old method of specifying red portion of colour n # green #ditto for green portion # blue #ditto for blue portion. # delete_notes_immediately #true or false # dont_backup_notes #true or false # backups_path #a directory path # mainwindow_x # mainwindow_x # mainwindow_width # mainwindow_height # ###################### ##planned prefs ###################### # delete_verify # ################# #settings dialog ################# # # outline: like titledborder, says 'defaults' # label: the default font, colour, size, and position can be set by # configuring a note as you wish, then right click on it and # select "Use as default" # check: preserve default font as 'default' # check: preserve default colour as 'colour' # check: exclude 'random' # outline: like titledborder, says 'file paths' # label, file path entry + browse button: notes file # label, file path entry + browse button: icon, focussed # label, file path entry + browse button: icon, unfocussed # outline: says 'colours' # # # package csincock::Gtk::Pixmap; use Gtk; use Gtk::Gdk::Pixbuf; #use Gtk::Gdk::Rgb; sub new() { my ($class,@args)=@_; my $self= bless ({},$class); $self->{pixbuf}= Gtk::Gdk::Pixbuf->new(@args); $self->{pixmap}=new Gtk::Pixmap($self->render); return $self; } sub widget() { my ($self)=@_; return $self->{pixmap}; } #returns ($pixmap,$mask) sub render() { my ($self)=@_; my $image=$self->{pixbuf}; my ($pixmap,$mask)=$image->render_pixmap_and_mask(100); return ($pixmap,$mask); } sub showerr() { # system("exec-in-bg","pgprompt","tell","MESSAGE=".join(" ",@_)); } sub set_file() { my ($self,$filename,$pwidth,$pheight)=@_; $self->{pixbuf}=Gtk::Gdk::Pixbuf->new_from_file($filename); my $image=$self->{pixbuf}; print ::Dumper([@_]); my $newfilename=$filename; #system("exec-in-bg","pgprompt","tell","MESSAGE=pheight=".$pheight); if(defined($pwidth) && defined($pheight) && $pwidth>0&& $pheight>0) { #print STDERR "set_file $filename $pwidth $pheight\n"; my $psize=$pwidth<=$pheight?$pwidth:$pheight; my $iw=$image->get_width; my $ih=$image->get_height; &showerr("image w/h is $iw $ih\n"); my $aspect=$iw/$ih; &showerr("aspect=$aspect\n"); my $newh=$ih; my $neww=$iw; my $scaleUp=0; # print STDERR "scaleup prop is $scaleUp\n"; my $scalestr=$scaleUp?"-scaledup":""; # print STDERR "scaleup is $scaleUp\n"; my $needsScalingDown = ($iw > $psize || $ih>$psize); my $needsScalingUp = ($iw < $psize && $ih < $psize); my $needsScaling=$needsScalingDown || ($scaleUp && $needsScalingUp); #if($needsScalingDown) { print STDERR "it needs scaling down, dagnabbit\n"; } #if($needsScalingUp) { print STDERR "it needs scaling UP, dagnabbit [$psize [ $iw $ih ]\n"; } &showerr("psize=$psize"); if ($needsScalingDown || ($needsScalingUp&&$scaleUp)) #Down) { #ok it needs scaling, dagnabbit &showerr("it needs scaling, dagnabbit\n"); if ($iw>=$ih) { $neww=$psize; &showerr("calculating newh= int($neww / $aspect)"); $newh=int($neww/$aspect); } else { $newh=$psize; $neww=int($newh*$aspect); } } else { $neww=int($neww); $newh=int($newh); } $newh=$newh<1?1:$newh; $neww=$neww<1?1:$neww; $newfilename="/tmp/$ENV{USER}/.$appName-gui-icon.png"; my $extraflags=""; my $renderflags=""; &showerr("neww=$neww, newh=$newh\n"); system("makeicon.pl",$filename,$neww,$newh,$renderflags,$newfilename,$extraflags); #system("exec-in-bg","pgprompt","text","SELECTED=".join(" ","makeicon.pl",$filename,$neww,$newh,$renderflags,$newfilename,$extraflags)); } #system("exec-in-bg","pgprompt","tell","MESSAGE=".$newfilename); $self->{pixbuf}=Gtk::Gdk::Pixbuf->new_from_file($newfilename); $self->{pixmap}->set(($self->render)); } package PigSettingsWidget; sub revert() { my ($self)=@_; my $curValue=$self->getValue; my $oldValue=$self->{oldValue}; my $origValue=$self->{origValue}; if($curValue ne $origValue) { $self->setValue($origValue); $self->apply(); } } sub getValue() { return ""; } sub setValue() { my ($self,$value)=@_; } sub apply() { my ($self)=@_; my $oldValue=$self->{oldValue}; my $newValue=$self->getValue; if((!defined($oldValue)) || $newValue ne $oldValue) { print "applying...\n"; print "value has changed: prefkey=".$self->{prefkey}.", old value is $oldValue, new value is ".$newValue."\n"; Sty->setPref("options",$self->{prefkey},$newValue); my $cr=$self->{applyCodeRef}; if($cr) { &$cr($oldValue,$newValue); } $self->{oldValue}=$newValue; } return $true; } sub new() { my ($class,$prefkey,$applyCodeRef)=@_; my $self= bless ({},$class); $self->{prefkey}=$prefkey; $self->{applyCodeRef}=$applyCodeRef; $self->{value}=undef; $self->{widget}=undef; my $value=Sty->getPref("options",$self->{prefkey}); $self->{origValue}=$value; $self->{oldValue}=$value; return $self; } package PigSettingsWidget::Check; @PigSettingsWidget::Check::ISA = ("PigSettingsWidget"); sub new() { my ($proto,$prefkey,$applyCodeRef,$description)=@_; my $class= ref($proto) || $proto; my $self = $class->SUPER::new($prefkey,$applyCodeRef); bless ($self,$class); my $value=Sty->getPref("options",$prefkey); my $check = new Gtk::CheckButton($description); $check->set_active(defined($value) && $value eq "true"); $self->{origValue}=$value; $self->{oldValue}=$value; $self->{value}=$check; $self->{widget}=$check; return $self; } sub getValue() { my ($self)=@_; return $self->{value}->get_active() ? "true" : "false"; } sub setValue() { my ($self,$value)=@_; $self->{value}->set_active($value eq "true"); } package PigSettingsWidget::TextEntry; @PigSettingsWidget::TextEntry::ISA = ("PigSettingsWidget"); sub new() { my ($proto,$prefkey,$applyCodeRef,$description)=@_; my $class= ref($proto) || $proto; my $self = $class->SUPER::new($prefkey,$applyCodeRef); bless ($self,$class); my $value=Sty->getPref("options",$prefkey); my $entry = new Gtk::Entry(); $entry->set_text($value); $self->{origValue}=$value; $self->{oldValue}=$value; $self->{value}=$entry; $self->{widget}=$entry; return $self; } sub getValue() { my ($self)=@_; return $self->{value}->get_text; } sub setValue() { my ($self,$value)=@_; $self->{value}->set_text($value); } package PigSettingsWidget::PathEntry; @PigSettingsWidget::PathEntry::ISA = ("PigSettingsWidget"); sub new() { my ($proto,$prefkey,$applyCodeRef,$description)=@_; my $class= ref($proto) || $proto; my $self = $class->SUPER::new($prefkey,$applyCodeRef); bless ($self,$class); my $widget=new Gtk::HBox(); my $label=$description ? new Gtk::Label($description) :undef; my $button=new Gtk::Button("Browse..."); my $valueWidget=new Gtk::Entry(); if($label) { $widget->pack_start($label,0,0,0); $label->show; $label->set_justify('left'); } $widget->pack_start($valueWidget,1,1,1); $widget->pack_start($button,0,0,0); my $value=Sty->getPref("options",$prefkey); $widget->show; $button->show; $valueWidget->show; $valueWidget->set_text(defined($value) ? $value : ""); $self->{value}=$valueWidget; $self->{widget}=$widget; $button->signal_connect("clicked", sub { my $dialog=new Gtk::FileSelection("Select a file"); my $filename = $valueWidget->get_text; if($filename) { $dialog->set_filename($filename); } Sty->showADialogForFarksSake( $dialog, $dialog->ok_button, $dialog->cancel_button, sub { $valueWidget->set_text($dialog->get_filename); }); }); return $self; } sub getValue() { my ($self)=@_; return $self->{value}->get_text(); } sub setValue() { my ($self,$value)=@_; $self->{value}->set_text($value); } package Sty; sub getPref() { my ($class,$section,$property,$defaultVal) =@_; # print "get:$section.$property=$prefs{$section.$property}\n"; my $val= $prefs{"$section.$property"}; if(!defined($val)) { return $defaultVal; } return $val; } sub getPrefFlag() { my ($class,$section,$property,$default) =@_; # print "get:$section.$property=$prefs{$section.$property}\n"; my $val = $prefs{"$section.$property"}; return Sty->flagVal($val,$default); } sub setPref() { my ($class,$section,$property,$value) =@_; my $oldValue=$prefs{"$section.$property"}; if(defined($oldValue) && $oldValue eq $value) { return; } #print "$section.$property=$value\n"; $prefs{"$section.$property"} = $value; Sty->savePrefs(); } sub setPrefFlag() { my ($class,$section,$property,$value) =@_; $class->setPref($section,$property, $value ? "true" : "false"); } sub getPigProperty { my ($class,$pigNum,$property) =@_; return $notes{"pig$pigNum.$property"}; } sub unshadeAll() { Sty->doToAllPigs("-:shaded:all"); } sub shadeAll() { Sty->doToAllPigs("+:shaded:all"); } sub showAll() { Sty->doToAllPigs("-:hidden:all"); } sub hideAll() { Sty->doToAllPigs("+:hidden:all"); } sub updateDecorations() { Sty->doToAllPigs("",sub { my $pig=shift;if($pig) { $pig->updateDecorations; } }); } sub doToAllPigs() { my ($class,$mode,$coderef)=@_; print "doToAllPigs:$mode,$coderef\n"; if($mode) { my $matches= $mode =~ /^([-+]|toggle)[:](hidden|shaded|sticky|titled)[:](.*)/; print "matches:$matches\n"; if(!($coderef || $matches)) { print "invalid show/hide mode:$mode\n"; return; } my $action=$1; my $flagname=$2; my $group=$3; my $numPigs = $notes{"pigs.num_pigs"}; print "flag name is $flagname\n"; print "action is $action\n"; $coderef=sub() { my $pig=shift; my $flag=$pig->getFlag($flagname,$false); my $include=$true; #for now this is true, ie it only acts as 'all' my $tog=$action eq "toggle"; if($include) { my $act=$action; if($tog) { $act = $flag ? "-" : "+"; } my $needToChange= ($act eq "+" && (!$flag)) || ($act eq "-" && $flag); if($needToChange) { if ($act eq "+") { if($flagname eq "hidden") { $pig->hide(); } elsif($flagname eq "shaded") { $pig->shade(); } elsif($flagname eq "titled") { $pig->showtitle(); } #todo: sticky } elsif($act eq "-") { if($flagname eq "hidden") { $pig->show(); } elsif($flagname eq "shaded") { $pig->unshade(); } elsif($flagname eq "titled") { $pig->hidetitle(); } } } } }; } if($coderef) { for my $num (sort { $a <=> $b } keys %pigs) { #print "pig:$num\n"; #todo: check group here #possible implement the 'temp' feature by having notes in a # 'temp' group. my $pig=Sty->getPig($num); if($pig->getFlag("discarded")) { next; } &$coderef($pig); } } } sub save() { my ($class,$file,$savePrefs,$saveNotes)=@_; my $uCount=$unimportantChangeCount; my $iCount=$importantChangeCount; my $pCount=$prefChanges; my $nCount=$noteChanges; if($saving|| $inhibitSaving) { return; } $saving=$true; my $notDefaultFile=$file; #print STDERR "notdefaultfiel is $notDefaultFile\n"; if(! defined $file) { print "Pigs::save(); No file specified.\n"; return; } print "saving file...$file\n"; #return; my $outfile=$file; #if(!$notDefaultFile) #{ $outfile.="-new"; #} my $section=""; my %data=(); if(!($saveNotes||$savePrefs)) { print "Pigs:: config file not saved\n"; return; } if($saveNotes) { %data=(%data,%notes); } if($savePrefs) { %data=(%data,%prefs); } my $saveStr=""; for my $key (sort keys %data) { # print "saving $key=$data{$key}\n"; # if($key =~ /([^.]*)[.](.*)/) # { # my $propname=$2; my $saveToFile=$false; if($key =~ /^pig(\d+)[.]/) { #it is note data my $num=$1; $saveToFile=$saveNotes && Sty->pigExists($num); } elsif($key eq "pigs.num_pigs") { #this key is obsolete, so don't save it } else { #it is preferences $saveToFile=$savePrefs; } if($saveToFile) { $saveStr .= "$key=$data{$key}\n"; } # } } if(!open (OUTFILE, ">$outfile")) { print STDERR "$progName: Failed saving file $outfile!"; } else { if($saveNotes) { my @nums=sort { $a <=> $b } keys %pigs; my $lastpig=$nums[$#nums]; $saveStr .= "pigs.last_pig=$lastpig\n"; } $saveStr .="\n"; print OUTFILE $saveStr; close OUTFILE; } if($savePrefs) { $prefChanges-=$pCount; } if($saveNotes) { $noteChanges-=$nCount; } #if(!$notDefaultFile) #{ my $filesize=(stat($outfile))[7]; my $len=length($saveStr); if($filesize == $len) { rename $outfile,"$file"; } else { my $message = "Failed saving $progName file $outfile($filesize of $len written)!"; print STDERR $message."\n"; system("pgprompt tell MESSAGE='$message' &"); } #} # print "done.\n"; $saving=undef; } sub makeFilePath() { my ($class,$path)=@_; if(-d $path) { print STDERR "Failed creating path for file:'$path'. The path already exists and is a directory\n"; return $false; } else { my $dir= $path; $dir =~ s|[/][^/]*$||; # print "dir is $dir\n"; if(! -d $dir) { print "creating dir:$dir\n"; if(!mkdir $dir) { print STDERR "Failed creating directory: '$dir'\n"; return $false; } } } return $true; } sub saveNotes() { if($dotNotes && Sty->makeFilePath($dotNotes)) { Sty->save($dotNotes,$false,$true); } } sub savePrefs() { if($dotPrefs && Sty->makeFilePath($dotPrefs)) { Sty->save($dotPrefs,$true,$false); } } #sub saveGoats() #{ # Sty->exportGoats($dotGoats,$true,$true); #} sub saveIfNecessary() { my ($class)=@_; if($prefChanges > 0) { Sty->savePrefs(); } if($noteChanges > 0) { Sty->saveNotes(); } } sub forceSave() { my ($class)=@_; Sty->savePrefs(); Sty->saveNotes(); } sub showPigProperties() { my ($class,$num)=@_; my $text=undef; my $summary=""; for my $key (sort keys %notes) { if($key =~ /^pig(\d+)[.](.*)/) { my $n=$1; my $prop=$2; # print "n is $n, looking for $num, prop is $prop\n"; if($n == $num) { # print "for fuck sake\n"; my $val=$notes{$key}; # print "key is $key\n"; if($prop eq "text") { $text = Sty->untransmogrifyText($val); } else { $summary .="$prop=$val\n"; } } } } $summary .="text=$text\n"; my $dialog=new Gtk::Dialog(); my $textbox=new Gtk::Text; $textbox->insert(undef,undef,undef,$summary); $dialog->vbox->pack_start($textbox,1,1,1); my $button=new Gtk::Button("Close"); $dialog->action_area->pack_start($button,0,0,0); $button->signal_connect("clicked", sub { $dialog->hide; destroy $dialog; }); $dialog->show_all; } sub setPigProperty { my ($class,$pigNum,$property,$value) =@_; #print STDERR " $class / $pigNum / $property / $value\n"; my $key="pig$pigNum.$property"; my $modkey="pig$pigNum.modified"; my $oldval=$notes{$key}; if((!defined($oldval)) ||($value ne $oldval)) { #print "oldval:$oldval,value:$value\n"; # print "key is $key\n"; # print "value is $value \n"; # print "$key=$value\n";# , old value = $oldval\n"; $oldval= defined($oldval) ? $oldval : ""; $notes{$key} = $value; $noteChanges++; my $istextprop = $property eq "text" || $property eq "title"; if($istextprop || $property eq "alarm") { $importantChangeCount++; if($keepHistFile) { open(HIST,">> $histdir/$startdatestamp.txt"); print HIST "-$key=$oldval\n+$key=$value\n"; close HIST; } } else { $unimportantChangeCount++; } #if $unimportantChangesBeforeSave is > 0 #then we check immediately if a save is now necessary. #if it is <= 0, then the save is just done later by the idle #timer if($unimportantChangesBeforeSave > 0) { Sty->saveIfNecessary(); } } } sub hidden() { $visibleCount--; if(($interfaceMode eq "cmdline") && $visibleCount<= 0) { print "last pig closed... exiting.\n"; Gtk->exit(0); } } sub shown() { $visibleCount++; } sub promptForTitle() { Sty->initGtk; my ($sty,$pig)=@_; my $title=$pig->getProp("title"); if(!$promptWindow) { $promptWindow=new Gtk::Window -toplevel; $promptWindow->set_title("Question"); my $label=new Gtk::Label ("Enter Pig Title:"); my $okButton = new Gtk::Button("OK"); my $cancelButton = new Gtk::Button("Cancel"); my $text = new Gtk::Entry; my $vbox = new Gtk::VBox(0,0); my $bbox = new Gtk::HBox(0,0); my $separator = new Gtk::HSeparator(); $promptWindow->add($vbox); $vbox->pack_start($label, 0, 0, 0); $vbox->pack_start($text, 1, 1, 0); $vbox->pack_start($separator, 0, 0, 0); $vbox->pack_start($bbox, 0, 0, 0); $bbox->pack_start($okButton, 1, 1, 0); $bbox->pack_start($cancelButton, 1, 1, 0); $vbox->border_width(10); $bbox->border_width(0); $promptWindow->border_width(0); $label->set_alignment(0, 0.0); $okButton->signal_connect("clicked", sub { $promptWindow->{pig}->changeTitle($text->get_text); $promptWindow->hide(); }); $cancelButton->signal_connect("clicked", sub { $promptWindow->hide(); }); $promptWindow->{text}=$text; $okButton->can_default(1); $okButton->grab_default; } my $entry=$promptWindow->{text}; $entry->set_text($title); my $len= length($entry->get_text); $entry->select_region($len,$len); $entry->set_position($len); # $entry->set_point($len); # $okButton->grab_default; # $entry->can_default(1); $entry->can_focus(1); $entry->grab_focus; $promptWindow->{pig}=$pig; $promptWindow->show_all; } sub restart() { my ($class)=@_; my $cmd="(sleep 2 ; $progName ".join(" ",@originalArgs)." &) &"; system($cmd); Sty->quit(); } sub quit() { my ($class)=@_; print "Exiting because of 'quit'\n"; $class->releaseLock(); if($applet) { $applet->hide(); # print "callig applet->remove()\n"; $applet->remove(); # print "callign appletwidget_gtk_main_quit\n"; Gnome::AppletWidget->gtk_main_quit; } if($gtkInited) { Gtk->exit(0); } } sub deleteEmptyNotes() { my ($class)=@_; for my $num (sort { $a <=> $b } keys %pigs) { my $pig=Sty->getPig($num); my $text = $pig->getProp("text"); my $title = $pig->getProp("title"); my $alarm = $pig->getFlag("alarm"); if(!($text || $title || $alarm)) { print "discarding pig:$num, text=$text, title=$title, alarm=$alarm\n"; Sty->deleteNote($num); } } } sub parseIntPair() { my ($class,$str,$defaultA,$defaultB)=@_; my ($a,$b); if(!defined($defaultB)) { $defaultB = $defaultA; } if(!$str) { $a=$defaultA; $b=$defaultB; } elsif($str =~ /^(\d+),(\d+)$/) { $a=$1; $b=$2; } elsif($str =~ /^(\d+)$/) { $a=$str; $b=$str; } else { $a=$defaultA; $b=$defaultB; } return ($a,$b); } sub showNewNote() { my ($class,$visible,$title,$text)=@_; $inhibitSaving=$true; my @pigNums=(sort { $a <=> $b } keys %pigs); my $lastPig=@pigNums ? $pigNums[$#pigNums] : 0; my $newPigNum=$lastPig+1; # print "new pig with number:$newPigNum\n"; my $pig=Sty->getPig($newPigNum); # $notes{"pigs.num_pigs"}=$newPigNum; $pig->setProp("title",defined($title) ? $title : ""); $pig->setProp("text",defined($text) ? $text : ""); my $pos=Sty->getPref("options","default_position"); my $pixels=Sty->getPref("options","default_size"); my ($x,$y)=Sty->parseIntPair($pos,20,20); # print "x,y is $x,$y\n"; my ($w,$h)=Sty->parseIntPair($pixels,100); # print "w,h is $w,$h\n"; $pig->setProp("wrap",Sty->getPref("options","default_wrap")); $pig->setProp("scrolled",Sty->getPref("options","default_scrolled")); $pig->setProp("titled",Sty->getPref("options","default_titled")); $pig->setProp("x",$x); $pig->setProp("y",$y); $pig->setProp("width",$w); $pig->setProp("height",$h); $pig->setProp("created",&datestamp); my $keepDefaultFonts=Sty->getPrefFlag("options","keep_defaults_font",$true); my $keepDefaultColours=Sty->getPrefFlag("options","keep_defaults_colour",$true); my $keepRandom=Sty->getPrefFlag("options","keep_defaults_colour_random",$false); my $col=Sty->getPref("options","default_colour"); if($col eq "random") { if(!$keepRandom) { $col=Sty->getDefaultColour(); } } elsif($col ne "none") { if($keepDefaultColours) { $col= "default"; } } $pig->setProp("colour",$col); my $font=Sty->getPref("options","default_font_name"); if($keepDefaultFonts) { $font="default"; } $pig->setProp("font_name","default"); $pig->setFlag("hidden",$true); $inhibitSaving=$false; if($visible) { $pig->show(); } } sub useAsDefault() { my ($class,$pig)=@_; my $pigFont=$pig->getProp("font_name"); if($pigFont ne "default") { Sty->setPref("options","default_font_name",$pigFont); } Sty->setPref("options","default_position",$pig->getProp("x").",".$pig->getProp("y")); Sty->setPref("options","default_size",$pig->getProp("width").",".$pig->getProp("height")); Sty->setPref("options","default_scrolled",$pig->getProp("scrolled")); Sty->setPref("options","default_titled",$pig->getProp("titled")); Sty->setPref("options","default_wrap",$pig->getProp("wrap")); my $pigColor = $pig->getProp("colour"); if ($pigColor ne "default") { Sty->setPref("options","default_colour",$pigColor); } } #strategy for finding configuration: #if specified on command line with -f, use that file #otherwise, look in ~/.pigs/locations #otherwise, look in ~/.pigs/ #otherwise, look in ~/.gnome/goats and import #otherwise, create in ~/.pigs sub findAndLoadConfig() { my ($class,$configFile)=@_; my $configSource=undef; if($configFile) { $configSource="'$configFile', specified on the command line"; } # else#(!$configFile) # { # my $locations="$defaultConfigLocation/locations"; # if(open(LOCATIONS,"<$locations")) # { # for my $line () # { # chomp $line; # if($line =~ /^$appName[=](.*)$/) # { # $configFile=$1; # $configSource="'$configFile', specified in $locations"; # last; # } # } # close LOCATIONS; # } # } if($configFile && -e $configFile && -f $configFile) { print "Using config file $configSource\n"; Sty->readPrefs($configFile); } elsif($configSource) { #the config file was specified, but is invalid\n"; $configError="The config file was specified, but not found ($configSource)"; } else { $configFile=$defaultDotPrefs; if(-e $configFile && -f $configFile) { $configSource="'$configFile', the default pigs config location"; Sty->readPrefs($configFile); } else { Sty->createDefaultPrefs(); } } } sub transmogrifyText() { my ($class,$pigText)=@_; $pigText=~ s/\\/\\\\/g; $pigText=~ s/\n/\\n/g; return $pigText; } sub untransmogrifyText() { my ($class,$pigText)=@_; my @chars=split //,$pigText; $pigText=""; my $inslash=undef; for my $c (@chars) { if($inslash) { if($c eq 'n') { $c="\n"; } $pigText.=$c; $inslash=undef; } elsif($c eq '\\') { $inslash=1; } else { $pigText.=$c; } } return $pigText; } my $notesInited=$false; sub initNotes() { my ($class)=@_; if($notesInited) { return; } my %hash=(); #first, build a list of what pigs exist for my $line (sort keys %notes) { if($line =~ /pig(\d+)/) { $hash{$1}++; } } my @list=(sort keys %hash); for my $num (@list) { my $pig=Sty->getPig($num); } #ok now all the pigs are inited, from this point on it should be OK for the #rest of the application to create/remove pigs. for my $num (@list) { my $pig=Sty->getPig($num); my $hidden = $pig->getFlag("hidden",$false); if($hidden || $pig->getFlag("discarded")) { #print "the dang pig is hidden\n"; } else { #print "showing pig: $num\n"; $pig->show(); } } $notesInited=$true; } sub createDefaultPrefs() { $dotPrefs="$defaultDotPrefs"; $dotNotes="$defaultConfigLocation/notes"; Sty->makeFilePath($dotPrefs); Sty->makeFilePath($dotNotes); print "creating default prefs file $dotPrefs\n"; %prefs=( 'options.default_font_name'=>'none',#use the current gtk theme's font 'options.default_size'=>100, 'options.notes_file'=>"$dotNotes", 'options.default_colour'=>1, 'options.default_size'=>"100,100", 'options.default_position'=>"10,10", 'options.colour1'=>"255,255,0", 'options.colour2'=>"102,255,255", 'options.colour3'=>"180,255,180", ); if(!(-e $dotNotes && -f $dotNotes)) { if(-e $dotGoats && -f $dotGoats) { print "reading goats $dotGoats\n"; Sty->readGoatsFile($dotGoats); } else { %notes=( 'pigs.num_pigs'=>'1', 'pig1.title'=>'Welcome to Pigs', 'pig1.text'=>$pigsIntroHelpText, 'pig1.width'=>300, 'pig1.height'=>100, 'pig1.x'=>100, 'pig1.y'=>100, 'pig1.colour'=>'default', 'pig1.font_name'=>'default' ); %pigs=(); } initNotes; $importantChangeCount+=1; # print "saving imported goats or welcome note:$dotNotes\n"; Sty->saveNotes(); } # print "saving created prefs: $dotPrefs\n"; # don't save yet, as the pigs structure wont have been created yet. Sty->savePrefs(); $importantChangeCount+=1; } sub findAndLoadNotes() { $notesError=undef; $dotNotes=Sty->getPref("options","notes_file"); if(!$dotNotes) { $dotNotes = $defaultDotNotes; } if(-f $dotNotes) { print "Using notes file $dotNotes as specified in config file.\n"; } else { $notesError="The notes file '$dotNotes' specified in the config file does not exist\n"; print STDERR $notesError; } Sty->makepaths(); if(!$notesError) { if(!Sty->getPrefFlag("options","dont_backup_notes")) { if(! Sty->backupNotes()) { $notesError="FAILED backing up notes! (perhaps nodisk space?)\nRead-only mode will be enabled\n"; } } Sty->readNotes($dotNotes); } # print "notes:\n"; # printNotes(); # print "prefs:\n"; # printPrefs(); return $notesError; } sub idleSave() { my ($class,$forceStartup)=@_; my $oldPeriod=$idleSavePeriod; my $newPeriod=Sty->getPref("options","idle_save_period"); if(!defined($newPeriod)) { $newPeriod=2; Sty->setPref("options","idle_save_period",$newPeriod); } if(defined($newPeriod) && $newPeriod > 0) { &saveIfNecessary(); } if($forceStartup || $newPeriod != $oldPeriod) { #need to change the timer (stop this one and start another) my $newTime=($newPeriod > 0 ? $newPeriod * 1000 : 1000); Gtk->timeout_add($newTime, \&idleSave); return 0; } else { return 1; } } use Fcntl ':flock'; # import LOCK_* constants sub getLock() { my ($class)=@_; $class->makeFilePath($lockFile); if(!stat $lockFile) { print "creating lock file $lockFile\n"; system("echo > $lockFile"); } print "opening lock file $lockFile\n"; if(open(LOCK, "<$lockFile")) { print "locking lock file $lockFile\n"; if(!flock(LOCK,LOCK_EX| LOCK_NB)) { print STDERR "Could not lock $lockFile, the server may already be running\n"; $lockHandle=undef; return $false; } $lockHandle=\*LOCK; return $true; } else { print STDERR "Could not open lock $lockFile\n"; return $false; } } sub releaseLock() { my ($self)=@_; if($lockHandle) { flock($lockHandle,LOCK_UN); close $lockHandle; } } sub client() { my ($class,@args)=@_; if(!@args) { print "Missing command\n"; } if(! -f $controlFile) { $class->MakeFilePath($controlFile); } if(open(CONTROL, ">>$controlFile")) { flock(CONTROL,LOCK_EX); # and, in case someone appended # while we were waiting... seek(CONTROL,0,2); my $line=join(",",@args); my $readstdin=$line =~ /^(.*),-$/; if($readstdin) { $line=$1; } print CONTROL $line; if($readstdin) { my $heredoc=""; my $num; my @list=(); my %hash=(); print STDERR "Command-line argument was '-', so reading input from stdin:"; for my $line () { if($line =~ /^EOF(\d*)/) { $hash{$1}++; } push @list,$line; } close STDIN; my $heredocnum=0; while (defined($hash{$heredoc})) { $heredocnum++; $heredoc="EOF$heredocnum"; } print CONTROL ",<quit(); }, "restart" , sub { Sty->restart(); }, "show-summary" , sub { Sty->showNotDiscardedSummary(); }, "-new-note" , sub { Sty->showNewNote($true,@_); }, "-new-hidden-note" , sub { Sty->showNewNote($false,@_); }, "show-all" , sub { Sty->showAll(); }, "hide-all" , sub { Sty->hideAll(); }, "shade-all" , sub { Sty->shadeAll(); }, "unshade-all" , sub { Sty->unshadeAll(); }, "show-discarded-summary" , sub { Sty->showDiscardedSummary;}, "show-settings" , sub { Sty->showSettings; }, "-delete-empty-notes" , sub { Sty->deleteEmptyNotes; }, "-delete-discarded-notes" , sub { Sty->deleteDiscardedNotes; }, "-save" , sub { Sty->forceSave(); }, "show-note" , sub { Sty->noteAction("show",@_); }, "summarise-notes" , sub { Sty->showSummary(undef,@_); }, "hide-note" , sub { Sty->noteAction("hide",@_); }, # "raise-note" , sub { Sty->noteAction("raise",@_); } "help" , sub { Sty->printClientHelp(); } ); #this method takes a control command and executes it. sub serve() { my ($class,$cmd,@args)=@_; if(!$cmd) { print STDERR "No command specified\n"; } else { print "serve:$cmd:(".join(",",@args).")\n"; for (my $i=0;$i+1<=$#commands;$i+=2) { my $okcmd=$commands[$i]; my $protected=substr($okcmd,0,1) eq "-"; if($protected) { $okcmd=substr($okcmd,1); } my $routine=$commands[$i+1]; if($cmd =~ /^$okcmd$/i) { if($protectedMode && $protected) { print STDERR "Sorry, you cant execute that command right now, $progName is running in read-only mode\n"; return; } if($routine) { &$routine(@args); } else { print STDERR "Not implemented yet\n"; return; } } } } } sub printCommands() { my ($class)=@_; for (my $i=0;$i+1<=$#commands;$i+=2) { my $okcmd=$commands[$i]; my $protected=substr($okcmd,0,1) eq "-"; if($protected) { $okcmd=substr($okcmd,1); } print $okcmd."\n"; } } #this method performs any commands that have been added to the #control file, using $progName -client sub server() { my ($class)=@_; if($serving) { return; } $serving=$true; # print "checking $controlFile\n"; my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size, $atime,$mtime,$ctime,$blksize,$blocks) = stat($controlFile); if(defined $lastServerCheck && $lastServerCheck >= $mtime) { # print "no changes to control file\n"; $serving=$false; return; } #if(! -f $controlFile) #{ #$class->makeFilePath($controlFile); #system("touch $controlFile"); #} if(open(CONTROL, "+<$controlFile")) #need the + here so the truncate works. { $lastServerCheck= $mtime; print "mtime is $mtime\n"; print "last server check is $lastServerCheck\n"; print "Locking Control file $controlFile\n"; flock(CONTROL,LOCK_EX); my $cmd=undef; my $endbit=undef; my @list=(); my $hereinput=""; for my $line () { # print "read line:$line\n"; if(defined($cmd) && $endbit) { if($line =~ /^$endbit/) { # print "got end bit $endbit, calling serve\n"; Sty->serve($cmd,@list,$hereinput); $endbit=undef; $cmd=undef; $hereinput=""; } else { # print "added input line for here document:$line\n"; $hereinput.=$line; } } elsif($line) { chomp $line; @list=split /,/,$line; $cmd=shift @list; my $lastbit=$list[$#list]; if(defined($lastbit) && $lastbit =~ /<<([^\s]+)[\s]*$/) { $endbit=$1; pop @list; $hereinput=""; } Sty->serve($cmd,@list); } } print "truncating file\n"; seek (CONTROL, 0,0); truncate(CONTROL,0); flock(CONTROL,LOCK_UN); ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size, $atime,$mtime,$ctime,$blksize,$blocks) =stat ($controlFile); $lastServerCheck=$mtime; close CONTROL; } else { #print "Couldn't open control file $controlFile: $!\n"; } $serving=$false; } sub noteAction() { my ($class,$action,$filterString)=@_; my @list=$class->findNotes($filterString); for my $pig (@list) { $pig->action($action); } } sub notesAction() { my ($class,$action,$filterString)=@_; my @list=$class->findNotes($filterString); $class->action($action,@list); } package PigFilter; sub new() { my ($class,$str)=@_; my $self= bless({},$class); my $cr=sub { return $false; }; if($str =~ /^title=(.*)$/) { my $re=$1; $cr = sub { my $pig=shift; return $pig->getProp("title") =~ /$re/; }; } elsif ($str =~ /^num=(\d+)$/) { my $num=$1; $cr = sub { my $pig=shift; return $pig->num == $num; }; } elsif ($str =~ /^text=(.*)$/) { my $re=$1; $cr = sub { my $pig=shift; print "comparing pig text: ".$pig->getProp("text")."\n"; return $pig->getProp("text") =~ /$re/; }; } $self->{acceptRoutine}=$cr; return $self; } sub accept() { my ($self,@args)=@_; my $cr=$self->{acceptRoutine}; return $cr && &$cr(@args); } package Sty; sub findNotes() { my ($class,$filterString)=@_; print STDERR "filter string: '$filterString'\n"; my $filt=new PigFilter($filterString); my @list=(); for my $pig (values %pigs) { if($filt->accept($pig)) { push @list, $pig; } } return @list; } sub main() { my ($class,$interfaceMode,$configFile,$notesFile,@extraArgs)=@_; if($interfaceMode eq "client") { Sty->client(@extraArgs); print STDERR "Exiting client\n"; exit(0); } my $lockOK=$class->getLock(); if(!$lockOK) { return; } Sty->findAndLoadConfig($configFile); if(!$configError) { my $err = Sty->findAndLoadNotes($notesFile); if($err) { print STDERR "$err\n"; } } if(!$interfaceMode) { $interfaceMode=Sty->getPref("options","interfaceMode","nogui") || "nogui"; } GnomeSux::init($appName,$interfaceMode); $protectedMode=($configError || $notesError); if((!$configError) && (!$notesError)) { #this has to be done before a history file is created for this session system("bzip2 $histdir/*.txt &"); my $numPigs=$notes{"pigs.num_pigs"}; my $lastPigs=$notes{"pigs.last_pig"};#these values are checks only if(!(defined($numPigs) || defined($lastPigs))) { #this should only only happen if there was some error loading the pigs #or configuration, so we unset dotPrefs and dotNotes to avoid making things #worse. $dotPrefs=undef; $dotNotes=undef; $notesError="no pigs.num_pigs or pigs.last_pig defined in notes file\n"; print STDERR "$notesError"; } else { initNotes; saveIfNecessary(); Sty->idleSave($true);#force idle timer startup } } if($notesError || $configError) { my $err = $configError."\n".$notesError; system("pgprompt tell MESSAGE='$err' &"); } # elsif($notesError) # { # # } if($interfaceMode eq "applet" || $interfaceMode =~ /^gui|embedded/i) { $pigsGUI=new PigsGUI($interfaceMode); } Gtk->timeout_add(500, sub { if(Sty->getPrefFlag("options","act_as_server",$true)) { Sty->server(); } return 1; }); if($interfaceMode eq "applet") { Gnome::AppletWidget->gtk_main; } else { Gtk->main; } } sub changePixmap() { my ($class)=@_; if($pigsGUI) { $pigsGUI->updatePixmap(); } } # clist my $clist_selected_row = -1; my $clist_last_selected_row = 0; my $clist_rows = 0; my $summaryWindow=undef; my $summaryList=undef; my $importerWindow=undef; sub buildSummaryList() { my ($class,$filter,$piglist)=@_; if(!$summaryList) { print "error: summary window not created yet\n"; return; } $summaryList->freeze(); $summaryList->clear(); $clist_selected_row=0; my $row=0; my @piglist=(); if(defined($piglist)) { @piglist=map { $_->num() } @{$piglist}; } else { @piglist= keys %pigs; } for my $num (sort { $b <=> $a } @piglist) { my $pig=Sty->getPig($num); my $show=$true; if($filter) { $show=&$filter($pig); } if($show) { my $maxSummaryLen=300; $summaryList->append( ( "$num" , $pig->getProp("title"), $pig->untransmogrifyText(substr($pig->getProp("text"),0,$maxSummaryLen)), $pig->getFlag("hidden") ? "Hidden":"Visible" ) ); #if(defined($selected) && $num == $selected) # { #$clist_selected_row=$row; #} } $row++; } $summaryList->select_row($clist_selected_row,0); $summaryList->moveto($clist_selected_row,0,0.5,0.0); $summaryList->thaw(); } sub showImporter() { my ($class,$path,$noDuplicates)=@_; my $cmd="pgprompt file MESSAGE='select the file' CANCEL=''"; if($path) { $cmd.=" SELECTED='$path'"; } print "$cmd\n"; my $file=`$cmd`; chomp $file; if(!$file) { return; } my $mode="pigs"; my $grepResult=`egrep '^\\[(goats|pigs)\\]' '$file'`; chomp $grepResult; if($grepResult) { $mode="goats"; } my @lines=(); my $filestatus=$true; if($mode eq "pigs") { print "reading as pigs file\n"; $filestatus= open (NEWPIGS,"cat $file | egrep '^pig.*[.](text|title)=' |"); if($filestatus) { @lines=(); } chomp @lines; } elsif($mode eq "goats") { print "reading as goats file\n"; my $section=""; $filestatus= open (NEWPIGS,"<$file"); if($filestatus) { for my $line () { chomp $line; if($line=~/[\[](.+)[\]]/) { $section=$1; } elsif($line =~ /^$/) { $section=undef; next; } elsif($section && $line =~ /^text=|title=/) { @lines=(@lines, "$section.$line"); } } } } if(!$filestatus) { system("pgprompt MESSAGE='Import failed.' &"); return; } close NEWPIGS; my %p=(); my @l=(); for my $line (@lines) { print $line."\n"; if($line =~ /^(pig|goat)(\d+)[.]([^=]+)=(.*)/) { my ($num,$prop,$val)=($2,$3,$4); $p{"pig$num.$prop"}=$val; if($prop eq "text") { @l=(@l,$2); # print "found pig number:$1\n"; } } } my %rhash=(); for my $num (keys %pigs) { my $t=Sty->getPigProperty($num,"text"); my $tt=Sty->getPigProperty($num,"title"); my $noteval="title=${tt}\ntext=$t\n"; $rhash{$noteval}++; } #################################################### ## build the gui #################################################### my $window = new Gtk::Window -toplevel; $importerWindow=$window; $window->signal_connect("delete_event", sub { $window->hide(); return 1;}); $window->set_title("Import note"); $window->border_width(0); $window->signal_connect("destroy", sub { destroy $window;}); my $box1 = new Gtk::VBox(0,0); $window->add($box1); my $countLabel=new Gtk::Label(""); $countLabel->set_alignment(0,0.0); $box1->pack_start($countLabel,0,0,0); my $message=new Gtk::Label("Click on a note number to import it."); $message->set_alignment(0,0.0); $box1->pack_start($message,0,0,0); my $closeButton=new Gtk::Button("Close"); $closeButton->signal_connect('clicked', sub { $window->hide();}); # $closeButton->can_default(1); # $closeButton->grab_default; my $listBox=new Gtk::VBox(); my $listBox2=new Gtk::VBox(); my $duplicates=0; my @dups=(); for my $num (@l) { my $blabel=new Gtk::Label("$num:".$p{"pig$num.title"}); my $button=new Gtk::Button; $button->add($blabel); # print $p{"pig$num.text"}."\n"; my $t=$p{"pig$num.text"}; my $tt=$p{"pig$num.title"}; my $ttrans= Sty->untransmogrifyText($t); my $label=new Gtk::Label($ttrans); my $noteval="title=${tt}\ntext=$t\n"; my $box; $button->show; $label->show; if($rhash{$noteval}) { $box=$listBox2; @dups=(@dups,$box); } else { $box=$listBox; } $box->pack_start($button,0,0,0); $box->pack_start($label,0,0,0); $button->signal_connect("clicked", sub { Sty->showNewNote($true,$p{"pig$num.title"},$p{"pig$num.text"}); }); $blabel->set_alignment(0,0.0); $label->set_alignment(0,0.0); $label->set_justify('left'); } my $separator1=new Gtk::HSeparator(); $separator1->show; my $dupmessage=new Gtk::Label("Notes below here are duplicates.\nYou already have them in your current set of notes.\n"); $dupmessage->show; my $scrolled_win = new Gtk::ScrolledWindow(undef, undef); #$scrolled_win->set_policy('automatic', 'automatic'); $scrolled_win->add_with_viewport($listBox); $scrolled_win->show; my $contents=$scrolled_win; if((!$noDuplicates) && @dups) { my $top=new Gtk::Frame();$top->show; my $bottom=new Gtk::Frame();$bottom->show; # my $listBox2=new Gtk::VBox; $listBox2->show; my $scrolled_win2 = new Gtk::ScrolledWindow(undef, undef);$scrolled_win2->show; my $bottomBox=new Gtk::VBox();$bottomBox->show; $scrolled_win2->set_policy('automatic', 'automatic'); $contents=new Gtk::VPaned(); $contents->show; # $contents->pack_start($top,0,0,0); $contents->add1($top); $top->add($scrolled_win); # $contents->pack_start($bottom,0,0,0); $contents->add2($bottom); $bottom->add($bottomBox); $bottomBox->pack_start($dupmessage,0,0,0); $bottomBox->pack_start($separator1,0,0,0); $bottomBox->pack_start($scrolled_win2,1,1,1); $scrolled_win2->add_with_viewport($listBox2); $bottom->show_all; $top->show_all; } else { $contents=$scrolled_win; } $box1->pack_start($contents, 1, 1, 0); $box1->pack_start(new Gtk::HSeparator(),0,0,0); $box1->pack_start($closeButton,0,0,0); $countLabel->set_text(@l." notes, ".@dups." duplicates"); $listBox->show_all; $listBox2->show_all; $window->set_usize(600,600); $window->set_position(1); $window->show_all; } my @settingsWidgets=(); #the format of this list is a bunch of pairs, a string followed by undef or a coderef (sub{}) #the string describes a widget and it's format is type:property:Label:Blurb #type is one of: #sf=start frame (a titled outline), ef=end frame, c=checkbox, p=pathentry (defined below) # the property is the property associated with the widgets value #the label is generally attached to the widget. #the blurb is used in the case of the frame to provide an extra header of text for the frame. #the coderef is one that you want to be executed when the dialog is apply'ed or ok'ed # (if the value of the widget changed) my @settingsDescription=( "sf::Default note Font,Colour,Size and Position:" ."The default font,colour,size and position can be set by configuring a note\n" ."as you wish, then selecting 'Use as default' from the note menu.",undef, "ef:::",undef, "sf::Preserving Defaults:" ."These options control what happens to notes when you later change the default\n" ."font or colour. If you have not set a custom font or colour for a note, and you\n" ."change the default font or colour, these options control if the fonts or colours\n" ."of that note changes also, or if each note stays as it is ( as it was at the time\n" ."it was created.",undef, "c:keep_defaults_font:When default font is changed, change all notes with no custom font set:",undef, "c:keep_defaults_colour:When default colour is changed, change all notes with no custom colour set:",undef, "c:keep_defaults_colour_random:Notes created with colour 'random' stay random:",undef, "ef::",undef, "sf::Deleting notes:",undef, "c:delete_notes_immediately: By default, don't mark notes as discarded, delete them immediately",undef, "ef:::",undef, "sf::Notes File:This setting will not take effect until $appName is restarted",undef, "p:notes_file::", undef, "ef::",undef, "sf::Auto Save:Set the period (in seconds) at which the notes will be saved \n" ."if there are changes. The default is 2. Regardless of this setting, the notes\n" ."are saved when the focus leaves a note",undef, "e:idle_save_period:Number of seconds between saves",undef, "ef::",undef, "sf::Other settings",undef, "c:show_wm_decorations:Show window-manager decorations", sub { Sty->updateDecorations; }, "c:show_scroll:Show scrollbars by default", sub { #Sty->updateScrollbars; }, "e:window_name_format::The string to use for the pig's window name:",undef, "ef::",undef, "sf::Undos:Set the maximum number of undos (single edits) that are stored \n" ."in memory for the whole application.\n" ."One undo is used for each single edit, such as a character typed or deleted,\n" ."Or some selected text being deleted, or some text pasted\n" ."This number is approximate only, as the act of Undoing also counts as an edit\n" ."(so that you can redo it later)",undef, "e:number_of_undos:Number of edits which are remembered (approximate).",undef, "ef::",undef, "sf::Backups:By default, your notes are backed up into ~/.$appName/backups/ \n". "each time $appName is run",undef, "c:dont_backup_notes:Don't backup notes",undef, "p:backups_path:Backups directory.:",undef, "ef::",undef, "sf::Program Icons:",undef, "p:icon_unfocussed:Normal.:", sub { Sty->changePixmap(); }, "p:icon_focussed:Hilighted.:",undef, "ef::",undef, "l::",undef ); sub initSettings() { my ($class,$vbox)=@_; @settingsWidgets=(); my $nums= @settingsDescription; # print "number of settings:$nums\n"; my $curbox=$vbox; for(my $num=0;$num+1<$nums;$num+=2) { my $desc=$settingsDescription[$num]; my $cr=$settingsDescription[$num+1]; my ($type,$prop,$label,$extraDescription)=split /:/,$desc; my $widg=undef; $label =~ s/[.]$/:/; if($type eq "c") { $widg = new PigSettingsWidget::Check ($prop,$cr,$label); } elsif($type eq "p") { $widg = new PigSettingsWidget::PathEntry ($prop,$cr,$label); } elsif($type eq "e") { $widg = new PigSettingsWidget::TextEntry ($prop,$cr,$label); } elsif($type eq "l") { my $l=new Gtk::Label($label); $l->show; $vbox->pack_start($l,0,0,0); } elsif($type eq "sf") { my $frame = new Gtk::Frame($label); $curbox->pack_start($frame,1,1,1); $curbox = new Gtk::VBox(); if($extraDescription) { my $l=new Gtk::Label($extraDescription); $l->show; $l->set_justify('left'); $l->set_alignment(0,0.0); $curbox->pack_start($l,0,0,0); $extraDescription=undef; } $frame->show; $curbox->show; $frame->add($curbox); } elsif($type eq "ef") { $curbox = $vbox; } if($widg) { $widg->{widget}->show; my $packbox=$curbox; @settingsWidgets=(@settingsWidgets,$widg); if($extraDescription) { my $labelledbox=new Gtk::HBox(1,1); my $l=new Gtk::Label($extraDescription); $l->show; $l->set_justify('left'); $l->set_alignment(0,0.5); $labelledbox->show; $curbox->pack_start($labelledbox,1,1,1); $labelledbox->pack_start($l,1,1,1); $packbox=$labelledbox; } $packbox->pack_start($widg->{widget},1,1,1); } } } sub applySettings() { for my $widg (@settingsWidgets) { if($widg) { $widg->apply(); } } } sub revertSettings() { for my $widg (@settingsWidgets) { if($widg) { $widg->revert(); } } } sub showSettings() { my ($class)=@_; my $dialog=new Gtk::Dialog(); $dialog->set_title("Pigs Settings"); my $vbox=$dialog->vbox(); $vbox->border_width(10); Sty->initSettings($vbox); my $aa=$dialog->action_area(); my $apply=new Gtk::Button("Apply"); my $revert=new Gtk::Button("Revert"); my $cancel=new Gtk::Button("Cancel"); my $ok = new Gtk::Button("OK"); $revert->signal_connect("clicked", sub { print "revertB\n";Sty->revertSettings;}); $apply->signal_connect("clicked", sub {print "applyB\n";Sty->applySettings;}); $ok->signal_connect("clicked", sub { print "okB\n";Sty->applySettings;}); $cancel->signal_connect("clicked", sub { print "cancelB\n";}); $aa->pack_start($apply,1,1,1); $aa->pack_start($revert,1,1,1); $aa->pack_start(new Gtk::VSeparator(),1,1,1); $aa->pack_start($ok,1,1,1); $aa->pack_start($cancel,1,1,1); $revert->show; $ok->show; $cancel->show; $apply->show; Sty->showADialogForFarksSake($dialog, $ok, $cancel, sub { print "ok\n"; }, sub { print "not ok\n"; }); } #this function takes care of showing a dialog which you have #already constructed. you pass it the ok button, the cancel button, #and code refs for anything extra you want to do on ok or cancel. #you don't need to hide or dispose the dialog in those coderefs, #this subroutine handles setting that up. It doesn't however make #the cancel coderef get executed if the window is closed, just #if the cancel button is pressed. sub showADialogForFarksSake() { my ($class,$dialog,$okbutton,$cancelbutton,$ok_coderef,$cancel_coderef)=@_; my $dialog_ok=$false; if($okbutton) { $okbutton->signal_connect("clicked", sub { $dialog_ok=$true; $dialog->hide(); destroy $dialog; }); } if($cancelbutton) { $cancelbutton->signal_connect("clicked", sub { $dialog_ok=$false; $dialog->hide(); destroy $dialog; }); } $dialog->signal_connect("destroy", sub { if($dialog_ok) { if($ok_coderef) { &$ok_coderef; } } else { if($cancel_coderef) { &$cancel_coderef; } } }); $dialog->set_modal($true); $dialog->show; } sub chooseFont() { my ($class,$fontname,$pig)=@_; my $wasDefault=$fontname eq "default"; my $defaultFont = Sty->getPref("options","default_font_name"); if($fontname eq "default") { $fontname = $defaultFont; } if(!($fontname && $fontname ne "default")) { $fontname = "none"; } my $font_dialog=new Gtk::FontSelectionDialog("Select a font"); $font_dialog->set_font_name($fontname); my $applySub= sub { my $font = $font_dialog->get_font_name; if(!$font) { print "font name is undefined\n"; } else { if($pig) { if($font ne $fontname) { my $keepDefaults=Sty->getPrefFlag("options","keep_defaults_font",$true); if(!($keepDefaults && $font eq $defaultFont)) { $pig->setProp("font_name",$font); $pig->setupStyle(); } } } else { Sty->setPref("default_font_name",$font); } } }; $font_dialog->apply_button->show; $font_dialog->apply_button->signal_connect("clicked", $applySub); # $font_dialog->cancel_button->signal_connect Sty->showADialogForFarksSake($font_dialog, $font_dialog->ok_button, $font_dialog->cancel_button,$applySub); $font_dialog->show; } sub showNotDiscardedSummary { my ($class,$pigs) = @_; Sty->showSummary(sub { my $pig=shift; return !$pig->getFlag("discarded")},$pigs); } sub showDiscardedSummary { my ($class,$pigs) = @_; Sty->showSummary(sub { my $pig=shift; return $pig->getFlag("discarded")},$pigs); } sub showSummary { my ($class,$filter,$filterstring,$piglist) = @_; my (@titles, @text, $clist, $box1, $box2, $button, $separator); @titles = ("#", "Title", "Text", "Visible"); if(defined($filterstring) && !defined($piglist)) { $piglist=[ ($class->findNotes($filterstring) )]; } my $i; #Gtk::Window->new("-toplevel")->show; if (not defined $summaryWindow) { $summaryWindow = new Gtk::Window -toplevel; $summaryWindow->signal_connect("delete_event", sub { $summaryWindow->hide(); return 1; }); $summaryWindow->set_title("Pigs summary"); $summaryWindow->border_width(0); $box1 = new Gtk::VBox(0,0); $summaryWindow->add($box1); $box1->show; my $scrolled_win = new Gtk::ScrolledWindow(undef, undef); $scrolled_win->set_policy('automatic', 'automatic'); $summaryList = new_with_titles Gtk::CList(@titles); my $clist=$summaryList; $clist->column_titles_show(); $box2 = new Gtk::VBox(0, 10); $box2->border_width(10); $box1->pack_start($box2, 1, 1, 0); $box2->show; my $defaultWidth=600; sub selectclistrow($$) { my ($cl,$row)=@_; $cl->set_focus_row($row); $clist_last_selected_row=$clist_selected_row; $clist_selected_row=$row; }; $clist->signal_connect('select_row', sub { my($widget, $row, $column, $event) = @_; #print STDERR "select_row:".::Dumper($event)."\n"; &selectclistrow($clist,$row); }); $clist->set_column_width(0, 30); $clist->set_column_width(1, 200); $clist->set_column_width(2, 260); $clist->set_column_width(3, 40); $clist->set_selection_mode('browse'); $clist_selected_row=0; $clist->set_button_actions(1,'ignored'); $clist->set_button_actions(3,'selects'); $clist->border_width(5); $scrolled_win->add($clist); $box2->pack_start($scrolled_win, 1, 1, 0); $clist->show; $scrolled_win->show; $separator = new Gtk::HSeparator; $separator->show; $box1->pack_start($separator, 0, 1, 0); $box2 = new Gtk::VBox(0, 10); $box2->border_width(10); $box1->pack_start($box2, 0, 1, 0); $box2->show; $button = new Gtk::Button('close'); $button->signal_connect('clicked', sub { $summaryWindow->hide();}); $button->can_default(1); $button->grab_default; $box2->pack_start($button, 1, 1, 0); $button->show; $summaryWindow->set_usize($defaultWidth,$defaultWidth); $summaryWindow->set_position(1); $summaryWindow->set_events( [keys %{$summaryWindow->get_events}, 'button_press_mask', 'button_release_mask']); $clist->set_events( [keys %{$clist->get_events}, 'button_press_mask', 'button_release_mask']); $clist->signal_connect("button_press_event", sub { my ($widget,$event)=@_; # my $row=$clist_selected_row; my $action=""; #TODO: change this mapping from button->action to #be based on prefs my ($row, $col); #my $ctree = $form->{$object}; ($row, $col) = $clist->get_selection_info($event->{'x'}, $event->{'y'}); # &selectclistrow($clist,$row); $clist->select_row($row,-1); #print STDERR ::Dumper($event)."\n"; if($clist_selected_row == $clist_last_selected_row && $event->{type} eq "2button_press") { $action="showhide"; } elsif($event->{button}==3)#right click { #$clist-> $action="menu"; } if($action) { my $num=$clist->get_text($row,0); my $pig=Sty->getPig($num); if($action eq "showhide") { $pig->showOrUnshade(); my $newtext=$pig->getFlag("hidden") ? "Hidden":"Visible" ; $clist->set_text($row,3,$newtext); } elsif($action eq "menu") { $pig->popupMenu($event); } } return 1; } ); } Sty->buildSummaryList($filter,$piglist); $summaryWindow->show_all; } #################################################################### #################################################################### #################################################################### package Pig; my $pigPopup=undef; my $popupPig=undef; my $colour1Item; my $colour2Item; my $colour3Item; my $colourNoneItem; my $colourRandomItem; my $colourDefaultItem; my $undiscardItem; sub initPopup() { if( !$pigPopup) { $colour1Item= new Gtk::MenuItem("Colour 1"); $colour2Item= new Gtk::MenuItem("Colour 2"); $colour3Item= new Gtk::MenuItem("Colour 3"); $colourNoneItem= new Gtk::MenuItem("None"); $colourRandomItem= new Gtk::MenuItem("Random"); $colourDefaultItem= new Gtk::MenuItem("Default"); $pigPopup = new Gtk::Menu(); $pigPopup->set_title("Pig Menu"); # my $settingsItem = new Gtk::MenuItem("Settings..."); my $newItem = new Gtk::MenuItem("New Pig"); my $hideItem = new Gtk::MenuItem("(un)Hide"); my $shadeItem = new Gtk::MenuItem("(un)Shade"); my $scrollItem = new Gtk::MenuItem("(un)Scroll"); my $toggleTitleItem = new Gtk::MenuItem("(un)Title"); my $wrapItem = new Gtk::MenuItem("(un)Wrap"); my $titleItem = new Gtk::MenuItem("Title..."); my $fontMenuItem = new Gtk::MenuItem("Font..."); my $fontMenu = new Gtk::Menu(); my $defaultfontItem = new Gtk::MenuItem("Default"); my $nofontItem = new Gtk::MenuItem("None"); my $choosefontItem = new Gtk::MenuItem("Choose..."); my $colourItem = new Gtk::MenuItem("Color..."); my $colourMenu = new Gtk::Menu(); my $propsItem = new Gtk::MenuItem("Properties..."); # my $summaryItem = new Gtk::MenuItem("Summary..."); # my $discardedItem = new Gtk::MenuItem("Discarded Notes..."); $undiscardItem = new Gtk::MenuItem("Undiscard"); my $defaultItem = new Gtk::MenuItem("Use as Default..."); my $deleteMenu = new Gtk::Menu(); my $deleteMenuItem = new Gtk::MenuItem("Delete"); my $sureMenu = new Gtk::Menu(); my $sureMenuItem = new Gtk::MenuItem("Are you sure?"); my $deleteItem = new Gtk::MenuItem("Yes delete it."); my $deleteMoreItem= new Gtk::MenuItem("More options..."); my $deleteMoreMenu=new Gtk::Menu(); my $discardItem = new Gtk::MenuItem("Just mark it as discarded."); my $destroyItem = new Gtk::MenuItem("Totally delete it right now."); # $summaryItem->signal_connect('activate', sub { Sty->showNotDiscardedSummary($popupPig->num); } ); # $discardedItem->signal_connect('activate', sub { Sty->showDiscardedSummary($popupPig->num); } ); $titleItem->signal_connect('activate', sub { if($popupPig) { $popupPig->editTitle(); } }); $deleteItem->signal_connect('activate', sub { if($popupPig) { $popupPig->discard(); } }); $discardItem->signal_connect('activate', sub { if($popupPig) { Sty->markNoteAsDiscarded($popupPig->num); } }); $destroyItem->signal_connect('activate', sub { if($popupPig) { Sty->deleteNote($popupPig->num); } }); $hideItem->signal_connect('activate', sub { if($popupPig) { $popupPig->hide(); } }); $newItem->signal_connect('activate', sub { Sty->showNewNote($true,"",""); }); $shadeItem->signal_connect('activate', sub { if($popupPig) { $popupPig->toggleShade(); } }); $scrollItem->signal_connect('activate', sub { if($popupPig) { $popupPig->toggleScroll(); } }); $toggleTitleItem->signal_connect('activate', sub { if($popupPig) { $popupPig->toggleTitle(); } }); $wrapItem->signal_connect('activate', sub { if($popupPig) { $popupPig->toggleWrap(); } }); $propsItem->signal_connect('activate', sub { if($popupPig) { $popupPig->showProperties(); } }); $defaultItem->signal_connect('activate', sub { if($popupPig) { $popupPig->useAsDefault(); } }); $undiscardItem->signal_connect('activate', sub { if($popupPig) { $popupPig->undiscard(); } }); $nofontItem->signal_connect('activate', sub { if($popupPig) { $popupPig->setFont("none"); } }); $defaultfontItem->signal_connect('activate', sub { if($popupPig) { $popupPig->setFont("default"); } }); $choosefontItem->signal_connect('activate', sub { if($popupPig) { my $oldfont=$popupPig->getProp("font_name"); Sty->chooseFont($oldfont,$popupPig); # if($font && $font ne $oldfont) # { # $popupPig->setProp("font_name",$font); # $popupPig->setupStyle(); # } } }); # $settingsItem->signal_connect('activate', sub { Sty->showSettings(); } ); $colour1Item->signal_connect('activate', sub { if($popupPig) { $popupPig->setProp("colour",1); $popupPig->setupStyle(); } }); $colour2Item->signal_connect('activate', sub { if($popupPig) { $popupPig->setProp("colour",2); $popupPig->setupStyle(); } }); $colour3Item->signal_connect('activate', sub { if($popupPig) { $popupPig->setProp("colour",3); $popupPig->setupStyle(); } }); $colourRandomItem->signal_connect('activate', sub { if($popupPig) { $popupPig->setProp("colour","random"); $popupPig->setupStyle(); } }); $colourDefaultItem->signal_connect('activate', sub { if($popupPig) { $popupPig->setProp("colour","default"); $popupPig->setupStyle(); } }); $colourNoneItem->signal_connect('activate', sub { if($popupPig) { $popupPig->setProp("colour","none"); $popupPig->setupStyle(); } }); $hideItem->show; $newItem->show; $titleItem->show; $deleteItem->show; $discardItem->show; $undiscardItem->show; $destroyItem->show; $shadeItem->show; $scrollItem->show; $toggleTitleItem->show; $wrapItem->show; $fontMenuItem->show; $nofontItem->show; $choosefontItem->show; $defaultfontItem->show; $defaultItem->show; $colourItem->show; # $settingsItem->show; $propsItem->show; # $discardedItem->show; $deleteMenuItem->show; $sureMenuItem->show; # $summaryItem->show; $colourNoneItem->show; $colourDefaultItem->show; $colourRandomItem->show; $colour1Item->show; $colour2Item->show; $colour3Item->show; $deleteMoreItem->show; $deleteMoreMenu->show; my $sep1=new Gtk::MenuItem(); my $sep2=new Gtk::MenuItem(); my $sep3=new Gtk::MenuItem(); my $sep4=new Gtk::MenuItem(); my $sep5=new Gtk::MenuItem(); my $sep6=new Gtk::MenuItem(); $sep1->show; $sep2->show; $sep3->show; $sep4->show; $sep5->show; $sep6->show; $pigPopup->append($undiscardItem); # $pigPopup->append($summaryItem); # $pigPopup->append($discardedItem); # $pigPopup->append($settingsItem); # $pigPopup->append($sep3); $pigPopup->append($newItem); $pigPopup->append($sep6); $pigPopup->append($hideItem); $pigPopup->append($shadeItem); $pigPopup->append($scrollItem); $pigPopup->append($toggleTitleItem); $pigPopup->append($wrapItem); $pigPopup->append($sep1); $pigPopup->append($propsItem); $pigPopup->append($titleItem); $pigPopup->append($defaultItem); $pigPopup->append($sep2); $pigPopup->append($fontMenuItem); $fontMenuItem->set_submenu($fontMenu); $fontMenu->append($defaultfontItem); $fontMenu->append($nofontItem); $fontMenu->append($choosefontItem); $pigPopup->append($colourItem); $colourItem->set_submenu($colourMenu); $colourMenu->append($colour1Item); $colourMenu->append($colour2Item); $colourMenu->append($colour3Item); $colourMenu->append($colourNoneItem); $colourMenu->append($colourRandomItem); $colourMenu->append($colourDefaultItem); $pigPopup->append($sep4); $pigPopup->append($deleteMenuItem); $deleteMenuItem->set_submenu($deleteMenu); $deleteMenu->append($sureMenuItem); $sureMenuItem->set_submenu($sureMenu); $sureMenu->append($deleteItem); $sureMenu->append($deleteMoreItem); $deleteMoreItem->set_submenu($deleteMoreMenu); $deleteMoreMenu->append($discardItem); $deleteMoreMenu->append($destroyItem); $pigPopup->append($sep5); my @items=( "Settings", sub { Sty->showSettings(); } , "-Summary", sub { Sty->showNotDiscardedSummary(); } , "-Discarded Notes...", sub { Sty->showDiscardedSummary(); } , "-Show All", sub { Sty->showAll(); } , "-Hide All", sub { Sty->hideAll(); } , "-Shade All", sub { Sty->shadeAll(); } , "-Unshade All", sub { Sty->unshadeAll(); } , "-Delete Empty Notes", sub { Sty->deleteEmptyNotes(); } , "-Delete Discarded Notes", sub { Sty->deleteNotesMarkedAsDiscarded(); } , "-Force Save", sub { Sty->forceSave(); } , "Exit", sub { Sty->quit; } ); for (my $i=0;$i+1<=$#items;$i+=2) { my $str=$items[$i]; my $protected=$str =~ /^-/; if($protected) { $str=substr($str,1); } if($protected && $protectedMode) { next; } my $cb=$items[$i+1]; my $item= new Gtk::MenuItem($str); $item->signal_connect('activate', $cb); $item->show; $pigPopup->append($item); } } } my @pigactions=( "show", sub { shift->show(); }, "hide", sub { shift->hide(); }, "shade", sub { shift->shade(); }, "unshade", sub { shift->unshade(); }, "hide", sub { shift->show(); }, "titled", sub { shift->showtitle(); }, "untitled", sub { shift->hidetitle(); } ); sub updateDecorations() { my ($self)=@_; print "pig ".$self->{num}."updating decorations\n"; my $showdecorations = Sty->getPrefFlag("options","show_wm_decorations",$false); if($self->window) { my $showhide=$self->window->visible; if($showhide) { $self->window->hide; } $showdecorations=$showdecorations?1:0; $self->window->window->set_decorations($showdecorations); if($showdecorations) { $self->hidetitle(); } else { $self->showtitle(); } if($showhide) { $self->window->show; } } } sub action() { my ($self,$cmd,@args)=@_; print "pigaction:$cmd:(".join(",",@args).")\n"; for (my $i=0;$i+1<=$#pigactions;$i+=2) { my $okcmd=$pigactions[$i]; my $cr=$pigactions[$i+1]; my $protected=substr($okcmd,0,1) eq "-"; if($protected) { $okcmd=substr($okcmd,1); } if($okcmd eq $cmd && $cr) { if($protectedMode && $protected) { print STDERR "Sorry, the note action '$cmd' cant be run because pigs is in read-only mode\n"; return; } else { &$cr($self,@args); return; } } } } sub undiscard() { my ($self)=@_; if($self->getProp("discarded")) { Sty->undiscard($self->num); } } sub getAllocatedColour() { my ($self,$widget,$color)=@_; # my $cm = $widget->window->get_colormap; # my $col= $cm->color_alloc($color); # print "button=". $widget->button."\n"; # print "label=".$widget->label."\n"; # print "child=".$widget->child."\n"; # my $w=$widget;#->child; # my $style=$w->style->copy(); # # $w->style->bg('normal',$col); # $w->style->fg('normal',$col); # $w->style->base('normal',$col); # $w->style->mid('normal',$col); # $w->style->light('normal',$col); # $w->style->text('normal',$col); # $w->set_style($style); } #takes pig number as argument sub new() { my $proto= shift; my $num=shift; my $class = ref($proto) || $proto; my $self ={}; $self->{num} = $num; $self->{undoing}=$false; $self->{undoPosition}=-1; my @hist=(); my @undos=(); $self->{undoHistory}=\@hist; $self->{undoUndoHistory}=\@undos; # print "set hist to ".\@hist.", is ".$self->{undoHistory}."\n"; bless($self,$class); return $self; } sub num() { my ($self)=@_; return $self->{num}; } sub getProp() { my ($self,$name)=@_; return Sty->getPigProperty($self->num,$name); } sub discard() { my ($self)=@_; if(!$self->getFlag("hidden")) { $self->hide(); } Sty->discardOrDeleteNote($self->num); } sub showProperties() { my($self)=@_; Sty->showPigProperties($self->num); } sub getFlag() { my ($self,$name,$default)=@_; my $val=Sty->getPigProperty($self->num,$name); #print STDERR "returnign flag value for [$val,$default]\n"; return Sty->flagVal($val,$default); } sub setFlag() { my ($self,$name,$value)=@_; return Sty->setPigProperty($self->num,$name,$value ? "true" : "false"); } sub setFont() { my ($self,$font)=@_; $self->setProp("font_name",$font); if($self->{window}) { $self->setupStyle(); } } #all pigs with no font_name set should use the default font. sub setupTextFont() { my ($self)=@_; my $text=$self->{text}; #print "setting up text font\n"; if($text) { my $pigFont=$self->getProp("font_name"); my $defaultFont=Sty->getPref("options","default_font_name"); #if($defaultFont) #{ # print "default font is $defaultFont\n"; #} my $font=$pigFont; if((!$font) || $font eq "default") { $font = $defaultFont; } if($font && $font eq "none") { $self->{font}=undef; } #print "trying font:$font\n"; else { my $loadedFont=Gtk::Gdk::Font->fontset_load($font); if(!$loadedFont) { $loadedFont=Gtk::Gdk::Font->load($font); } if(!$loadedFont) { $font=$defaultFont; if($font) { $loadedFont=Gtk::Gdk::Font->fontset_load($font); if(!$loadedFont) { $loadedFont=Gtk::Gdk::Font->load($font); } } } if($loadedFont) { $self->{font}=$loadedFont; } else { print "Could not load font.\n"; } } } } sub setupStyle() { my ($self,$leaveRandom)=@_; # print "setting up style: font is ".$self->getProp("font_name").", colour is : ".$self->getProp("colour")."\n"; $self->setupTextFont(); my ($r,$g,$b); if($leaveRandom && $self->getProp("colour") eq "random") { my $colour=$self->{text}->style->bg('normal'); ($r,$g,$b)=($colour->{red},$colour->{green},$colour->{blue}); } else { ($r,$g,$b)=$self->getActualColours(); } $self->setStyle($r,$g,$b,$self->{font}); } sub getActualColours() { my ($self)=@_; my $coldesc=$self->getProp("colour"); # print "coldesc is $coldesc\n"; if((!$coldesc) || ($coldesc eq "default")) { $coldesc=Sty->getDefaultColour; # print "coldesc set to $coldesc\n"; } if($coldesc =~ /random/) { $coldesc = Sty->getRandomColourNumber(); } if($coldesc =~ /^\d+$/) { $coldesc=Sty->getColour($coldesc); # print "coldesc rgb is $coldesc\n"; } if($coldesc =~ /(\d+),(\d+),(\d+)/) { # print "about to set colour $coldesc\n"; return ($1,$2,$3); } # if($coldesc =~ /none/) # { return (-1,-1,-1); # } } sub setStyle() { my ($self,$r,$g,$b,$font)=@_; my $style=$self->window->style; my $darker; my $modify_colour=$r >= 0 && $g >=0 && $b >= 0; if(((!defined($font)) && (!$modify_colour))) { $style=$self->window->style; } else { $style=$self->{origStyle}->copy(); } if($modify_colour) { my ($dr,$dg,$db)=($r<=35?$r:$r-35,$g<=35?$g:$g-35,$b<=35?$b:$b-35); $r*=256; $g*=256; $b*=256; $dr*=256; $dg*=256; $db*=256; my @hash={red=>$r,green=>$g,blue=>$b}; my @darkerhash={red=>$dr,green=>$dg,blue=>$db}; # print "darkening $r $g $b to $dr $dg $db\n"; my $colour = $self->window->get_colormap->color_alloc(@hash); $darker = $self->window->get_colormap->color_alloc(@darkerhash); $style->bg('normal',$colour); $style->base('normal',$colour); # $style->mid('normal',$colour); # $style->light('normal',$colour); } if(defined($font)) { $style->font($font); } #I know this seems very wasteful, copying all these style around, but otherwise, #they don't seem to have an effect half the time. $self->{text}->set_style($style->copy); $self->{text}->set_style($style->copy->copy); $self->{close_box}->set_style($style->copy); $self->{event_box}->set_style($style->copy); $self->{scrolled_win}->set_style($style->copy); if($darker) { $self->{event_box}->style->bg('normal',$darker); $self->{close_box}->style->bg('normal',$darker); my $copy = $self->{event_box}->style; $self->{event_box}->set_style($copy); $self->{event_box}->set_style($copy->copy); $copy = $self->{close_box}->style; $self->{close_box}->set_style($copy); $self->{close_box}->set_style($copy->copy); } } sub setProp() { my ($self,$name,$value)=@_; #print STDERR "setProp $self ==== $name ===== $value\n"; my $retval = Sty->setPigProperty($self->num,$name,$value); if($name ne "modified") { if($name eq "text" || $name eq "title") { $self->setProp("modified",&Sty::datestamp()); } } } sub useAsDefault() { my ($self)=@_; Sty->useAsDefault($self); } sub window() { my ($self)=@_; return $self->{window}; } #cycles between hidden,visible-unshaded,visible-shaded sub showOrUnshadeOrShadeOrHide() { my ($self)=@_; if(!$self->window || $self->getFlag("hidden")) #hidden { $self->unshade(); $self->show(); } elsif($self->getFlag("shaded")) { $self->hide(); } else { $self->shade(); } } #shows the window if it is hidden, unshades if it is shaded #hides the window if it is shown and not shaded sub showOrUnshade() { my ($self)=@_; if(!$self->window || $self->getFlag("hidden")) #hidden { $self->unshade(); $self->show(); } elsif($self->getFlag("shaded")) { $self->unshade(); } else { $self->hide(); } } sub initGUI() { my ($self)=@_; if(!$self->window) { #fucking enlightenment doesnt let you resize/move windows created #with -popup $self->createWindow("-toplevel"); } } sub show() { my ($self)=@_; $self->initGUI(); $self->window->show; $self->setFlag("hidden",$false); Sty->shown(); } sub destroy() { my ($self)=@_; my $win=$self->window; if($win) { $win->hide; $self->{window}=undef; # destroy $win; } } sub hide() { my $self=shift; # print "hide ".$self->getProp("title")."\n"; $self->setFlag("hidden",$true); if($self->window) { $self->window->hide(); } Sty->hidden(); } sub toggleShown() { my $self=shift; if($self->getFlag("hidden",$false)) { $self->show(); } else { $self->hide(); } } sub editTitle() { my ($self)=@_; Sty->promptForTitle($self); } sub changeTitle() { my ($self,$newTitle)=@_; $self->setProp("title",$newTitle); if($self->{label}) { $self->{label}->set_text($newTitle); # my ($wxx,$wyy,$www,$whh,$wdd) = $self->{label}->window->get_geometry(); # my ($w,$h)=$self->{label}->window->usize(); # $self->{label}->window->set_usize(10,$whh); # $self->{event_box}->set_size_request(10,$whh); # $window->set_title("pig".$self->num); } $self->updateTitle(); } sub untransmogrifyText() { my ($self,$pigText)=@_; return Sty->untransmogrifyText($pigText); } sub transmogrifyText() { my ($self,$pigText)=@_; return Sty->transmogrifyText($pigText); } sub setTextFromWindow() { my $self=shift; if($self->{text}) { my $text=$self->transmogrifyText($self->{text}->get_chars(0,-1)); $self->setProp("text",$text); } } sub putTextToWindow() { my $self=shift; my $caretpos=shift; if($self->{text}) { my $text=$self->untransmogrifyText($self->getProp("text")); $self->{text}->delete_text(0,-1); $self->{text}->insert(undef,undef,undef,$text); if(defined($caretpos) && $caretpos >= 0&& $caretpos < length($text)) { print "caret pos is $caretpos, length of text is ".length($text)."\n"; $self->{text}->set_position($caretpos); } } } my @resize_xpm_data = ( '9 9 2 1', ' c None', '. c #000000', ' ', ' ... ... ', ' .. .. ', ' . . . . ', ' . ', ' . . . . ', ' .. .. ', ' ... ... ', ' ' ); sub popupMenu() { my ($self,$evt)=@_; $self->initGUI; Pig->initPopup; $popupPig=$self; $pigPopup->set_style($self->{origStyle}); if($self->getProp("discarded")) { $undiscardItem->show; } else { $undiscardItem->hide; } $pigPopup->popup(undef,undef,1,$evt->{'time'}, undef); $colour1Item->signal_connect("realize", sub { $self->getAllocatedColour($pigPopup,Sty->getColourHash(1)); }); $colour2Item->signal_connect("realize", sub { $self->getAllocatedColour($colour2Item,Sty->getColourHash(2)); }); $colour3Item->signal_connect("realize", sub { $self->getAllocatedColour($colour3Item,Sty->getColourHash(3)); }); } package PigsWindow; sub new() { my ($class,$getPropHandler,$setPropHandler,$moveHandler,$resizeHandler)=@_; my $self = bless ({},$class); $self->{getPropHandler}=$getPropHandler; $self->{setPropHandler}=$setPropHandler; $self->{moveHandler}=$moveHandler; $self->{resizeHandler}=$resizeHandler; return $self; } sub setProp() { my ($self,$prop,$value)=@_; #print STDERR "PigsWindow setProp $self $prop $value\n"; my $handler=$self->{setPropHandler}; if($handler) { #print "calling setprop handler: $prop,$value\n"; &$handler($prop,$value); } } sub getProp() { my ($self,$prop)=@_; my $handler=$self->{getPropHandler}; if($handler) { return &$handler($prop); } return undef; } sub moved() { my ($self)=@_; #print STDERR "moved:\n"; my ($wxx,$wyy)=$self->{window}->window->get_origin(); #print STDERR "wxx=$wxx wyy=$wyy\n"; #print STDERR "$self setprop 'x' $wxx\n"; $self->setProp("x",$wxx); $self->setProp("y",$wyy); my $handler=$self->{resizeHandler}; if($handler) { #print STDERR "calling handler\n"; &$handler($wxx,$wyy); } } sub resized() { my ($self)=@_; if($self->{shading} && $self->{shading} > 0) { #print "clearing shading flag\n"; $self->{shading}--; } else { #print "not shading\n"; my ($wxx,$wyy,$www,$whh) = @{$self->{window}->allocation}; #print "resized: $wxx $wyy $www $whh\n"; $self->setProp("width",$www); my $shaded=$self->getProp("shaded"); #print "shaded is $shaded\n"; if((!defined($shaded)) || (!($shaded =~ /true/))) { #print "setting height\n"; $self->setProp("height",$whh); } my $handler=$self->{resizeHandler}; if($handler) { &$handler($www,$whh); } } } sub getSize() { my ($self)=@_; if($self->window) { my ($wxx,$wyy,$www,$whh,$wdd) = $self->window->window->get_geometry(); return ($www,$whh); } else { return ($self->getProp("width"),$self->getProp("height")); } } sub createWindow() { my ($self,$embeddedxid,$wintype)=@_; Sty->initGtk; # print "wintype is $wintype\n"; $wintype = "-toplevel" if !$wintype; # print ":wintype is $wintype\n"; my ($w,$h,$x,$y)=($self->getProp("width"),$self->getProp("height"),$self->getProp("x"),$self->getProp("y")); if(!defined($w)) { $w=100;} if(!defined($h)) { $h=100;} if(!defined($x)) { $x=100;} if(!defined($y)) { $y=100;} # print "wintype is $wintype\n"; if($embeddedxid) { print STDERR "creating plug: $embeddedxid\n"; } my $window=$embeddedxid?new Gtk::Plug($embeddedxid) : new Gtk::Window($wintype); $self->{window}=$window; $window->set_policy($true,$true,$false); $window->set_default_size(0,0); $window->set_default_size($w,$h); my $showdecorations = Sty->getPrefFlag("options","show_wm_decorations",$false); $showdecorations=$showdecorations?1:0; $self->window->signal_connect("realize", sub { $window->window->set_decorations($showdecorations); $window->set_uposition($x,$y); $window->window->move($x,$y); }); $window->set_events( [keys %{$self->window->get_events}, 'button_press_mask', 'button_release_mask','pointer_motion_mask', 'pointer_motion_hint_mask','focus_change_mask']); #if(0) #{ $self->window->signal_connect_after("size_allocate", sub { $self->resized(); return 0; }); $self->window->signal_connect_after("configure_event", sub { #print STDERR "createwindow:CONFIGURE EVENT\n"; $self->moved(); return 0; }); # } } sub window() { my ($self)=@_; return $self->{window}; } sub setDragHandle() { my ($self,$widget)=@_; # print "setting button press event hander on $widget\n"; #if(!$self->{clicktime}) { $self->{clicktime}=$evt->{time}; } $widget->signal_connect("button_press_event", sub { my ($widg,$evt) =@_; # print "event:".$evt->{'type'}."\n"; if($evt && defined($evt->{'type'}) ) { if( $evt->{'type'} eq "button_press") { if($evt->{'button'} == 1) { #the set_modal seems to make sure only the current window gets motion events. # $self->window->raise(); $self->{dragging}=1; my $x=$evt->{x}; my $y=$evt->{y}; my ($ox,$oy)= $self->window->window->get_origin; my ($eox,$eoy)= $widget->window->get_origin; $x+=$eox-$ox; $y+=$eoy-$oy; $self->{dragx}=$x; $self->{dragy}=$y; } } } return 1; }); my $finishedDragging= sub { my ($widg,$evt)=@_; # print $evt->{'type'}."\n"; if($self->{dragging}) { $self->window->set_modal($false); #if(fall_on_release) { $self->window->lower(); } $self->{dragging}=undef; my ($ox,$oy)=$self->window->window->get_origin(); $self->setProp("x",$ox); $self->setProp("y",$oy); return 1; } return 0; }; $widget->signal_connect("button_release_event", $finishedDragging); $self->window->signal_connect("button_release_event", $finishedDragging); $self->window->signal_connect("motion_notify_event", sub { my ($widg,$evt)=@_; if($self->{dragging}) { if($self->{dragging}==1) { $self->window->set_modal($true); } $self->{dragging}=2; my ($x,$y,$state)=$self->window->get_pointer(); my $dx=$x-$self->{dragx}; my $dy=$y-$self->{dragy}; my ($ox,$oy)=$self->window->window->get_origin(); $self->window->set_uposition($ox+$dx,$oy+$dy); } return 0; }); } package Pig; sub getWindowTitle() { my ($self)=@_; my $format=Sty->getPref("options","window_name_format","pig%n"); my %reps=(n=>$self->{num}, t=>$self->getProp("title"), ); for my $key(keys %reps) { my $val=$reps{$key}; $val =~ s/%/%%/g; $format =~ s/%$key/$val/g; } $format =~ s/%%/%/g; return $format; } sub updateTitle() { my ($self,$title)=@_; if($self->window) { $self->window->set_title($self->getWindowTitle()); } } #creates the window, but does not show it sub createWindow() { my ($self,$wintype)=@_; my ($pigNum)=$self->num; print "^wintype is $wintype\n"; my $windowObj=new PigsWindow( sub { my $prop=shift; return $self->getProp($prop); } , sub { my ($prop,$value)=@_; $self->setProp($prop,$value); }, undef, undef ); $windowObj->createWindow(undef,$wintype); my $window=$windowObj->window; my $box2 = new Gtk::VBox(0,0); my $box3 = new Gtk::HBox(0,0); my $button = new Gtk::Button(); my $event_box = new Gtk::EventBox(); $windowObj->setDragHandle($event_box); # $windowObj->setShadeHandle($event_box); my $label = new Gtk::Label($self->getProp("title")); my $text = new Gtk::Text(undef,undef); my $scrolled_win = new Gtk::ScrolledWindow(undef,undef); my $extra_event_box = new Gtk::EventBox(); # $window->set_policy($true,$true,$false); # $scrolled_win->set_size_request(30,30); $scrolled_win->set_usize(60,30); #geez this is a big drastic! exiting because a window was closed! #$window->signal_connect_after("delete_event", sub { #print "delete_event for pig note win\n"; Sty->quit; return 1; #}); # $window->signal_connect_after("destroy", sub { print "destroy\n"; $self->{window}=undef; Sty->quit(); return 1;}); $window->signal_connect_after("delete_event", sub { print "delete_event for pig note win\n"; $self->hide(); return 1; }); $text->set_usize(60,30); my $ctrlMask=4; my $shiftMask=1; $text->set_events( [keys %{$text->get_events}, 'key_press_mask']); $text->signal_connect("key_press_event", sub { my ($widg,$evt)=@_; # print "event:".$evt->{type}."\n"; # print "state:".$evt->{state}."\n"; my $str=$evt->{string}; my $keyval=$evt->{keyval}; # if($str) { print "+$str\n"; } # print "string:$str\n"; # print "keyval:$keyval\n"; if(($evt->{state} & $ctrlMask)==$ctrlMask) { if($keyval == ord('z')) { # print "undo".($self->{undoPosition})."\n"; $self->undo(); } elsif($keyval == ord('r')) { # print "redo".($self->{undoPosition})."\n"; $self->doredo(); } } return 0; }); $self->changeTitle($self->getProp("title")); $window->signal_connect("button_release_event", sub { my ($widg,$evt)=@_; if($evt->{type} eq "button_release" && $evt->{button} == 3) { $self->popupMenu($evt); } return 1; } ); $event_box->signal_connect("button_press_event", sub { my ($widg,$evt) =@_; # print "button press event:$evt\n"; if($evt && defined($evt->{'type'}) ) { if( $evt->{'type'} eq "2button_press") { if($evt->{'button'} != 1) { $self->toggleShade(); } else { $self->editTitle(); } return 1; } } return 0; }); $self->{window}=$window; $self->{origStyle}=$self->window->get_style; $self->{customStyle}=$self->window->style->copy(); $self->{label}=$label; $self->{event_box}=$event_box; $self->{scrolled_win}=$scrolled_win; $self->{text}=$text; $self->{close_box}=$extra_event_box; ####### # # / box3 -> { button, event_box } # box2 -+ # \ scrolled -> { text } # ####### $self->window->add($box2); $self->{titlebar}=$box3; $box2->pack_start($box3, 0, 0, 0); $box3->pack_start($extra_event_box, 0, 0, 0); $extra_event_box->add($button); $extra_event_box->show; $box3->pack_start($event_box, 1, 1, 0); $box2->pack_start($scrolled_win, 1, 1, 0 ); $scrolled_win->add($text); $event_box->add($label); $button->show; my $showdecorations = Sty->getPrefFlag("options","show_wm_decorations",$false); if(!$showdecorations) { $box3->show; } $event_box->show; $label->show; $scrolled_win->show; $box2->show; $label->set_alignment(0, 0.0); $box2->border_width(0); $box3->border_width(0); $window->border_width(0); $button->border_width(4); $button->set_usize(16,10); $window->set_events( [keys %{$self->window->get_events}, 'button_press_mask', 'button_release_mask','pointer_motion_mask', 'pointer_motion_hint_mask','focus_change_mask']); $self->window->set_style($self->{customStyle}); $window->set_title($self->getWindowTitle()); # my $w=$self->getProp("width"); # $w = defined($w) ? $w : 100; # my $h=$self->getProp("height"); # $h = defined($h) ? $h : 100; # $window->set_default_size($w,$h); $event_box->set_events(['button_press_mask']); $button->signal_connect("clicked", sub { $self->hide(); }); my $defaultscroll = Sty->getPrefFlag("options","show_scrollbar",$true); my $showscroll = $self->getFlag("scrolled",$defaultscroll?"true":"false"); my $scrollval=$showscroll ? 'automatic' : 'never'; $scrolled_win->set_policy( $scrollval,$scrollval ); my $defaultwrap = Sty->getPrefFlag("options","wrap_text",$true); my $wrap = $self->getFlag("wrap",$defaultwrap?"true":"false"); #print STDERR "SEtting line wrap to $wrap\n"; $self->{text}->line_wrap($wrap); my $ro = $self->getFlag("readonly",$false); if(!$ro) { $text->set_editable('1'); } $self->putTextToWindow(); $text->show; $text->can_default(1); $text->grab_default; $text->can_focus(1); $text->grab_focus; # $text->signal_connect("insert_text", sub { # print "insert text:\n"; # return 1; # }); # $text->signal_connect("delete_text", sub { # print "insert text:\n"; # return 1; # }); #this 'changed' handler should be below the putTextToWindow, so that #the initial putTextToWindow doesn't trigger this handler. $text->signal_connect("changed", sub { $self->changed(); return 1; }); $self->window->signal_connect("focus_out_event", sub { if($self->{text}) { $self->setTextFromWindow(); } Sty->saveIfNecessary(); return 0; }); # $self->window->signal_connect("realize", sub { # my $w=shift; $w->window->set_decorations(0); ## $w->set_wm_hints(['skip_taskbar','skip_winlist']); ## print "setting pig $self->{num} (".$self->getProp("title").") window position to : ".$self->getProp("x") .",".$self->getProp("y")."\n"; # $window->set_uposition($self->getProp("x"),$self->getProp("y")); ## print "moving pig $self->{num} (".$self->getProp("title").") window position to : ".$self->getProp("x") .",".$self->getProp("y")."\n"; # $window->window->move($self->getProp("x"),$self->getProp("y")); # #this is done in the realize handler to make sure the text widget already has teh default # #gnome style set up. # # # }); $event_box->signal_connect("realize", sub { if($self->getFlag("shaded",$false)) { $self->shade(); } }); if(!$self->getFlag("titled",$true)) { $self->hidetitle; } $self->setupStyle(); } package Sty; my $globalHistoryOffset=0; my @globalHistory=(); #remembers the specified old text value for the specified pig, #returns the global index of the history item - which can later #be used in getHistory sub addHistoryItem() { my ($class,$pignumber,$caretpos,$oldtext)=@_; my $globalHistoryMax=Sty->getPref("options","number_of_undos",300); $globalHistoryMax *=2;#2 slots for each specified by the user, as it #takes an extr slot when they actually do an undo #(so that they can then undo the undos) my $currentLength=@globalHistory; if($currentLength>= $globalHistoryMax) { my $popped=shift @globalHistory; # print "discarded history item:$popped\n"; $globalHistoryOffset++; } push(@globalHistory,"$pignumber:$caretpos:$oldtext"); my $num= $#globalHistory + $globalHistoryOffset; # print "returning new history item num:$num, for pig:".$pignumber."\n"; return $num; } sub getHistoryItem() { my ($class,$index)=@_; if($index < $globalHistoryOffset) { return undef; } $index -= $globalHistoryOffset; if($index < @globalHistory) { # print "returning old item $index (".$globalHistory[$index]."\n"; return $globalHistory[$index]; } else { return undef; } } sub getLastValidHistoryIndex() { my ($class)=@_; return $globalHistoryOffset; } package Pig; sub addHistoryItem() { my ($self,$caretPos,$oldText)=@_; my @hist=@{$self->{undoHistory}}; # print "hist is $hist\n"; # my @hist=@{$hist}; my $historyItem=Sty->addHistoryItem($self->num,$caretPos,$oldText); my $lastValid=Sty->getLastValidHistoryIndex; push @hist,$historyItem; while($hist[0] < $lastValid) { shift @hist; } $self->{undoHistory}=\@hist; # print "undo history is ". (join ",",@hist)."\n"; } sub addUndoUndoHistoryItem() { my ($self,$caretPos,$oldText)=@_; my @hist=@{$self->{undoUndoHistory}}; # print "hist is $hist\n"; # my @hist=@{$hist}; my $historyItem=Sty->addHistoryItem($self->num,$caretPos,$oldText); my $lastValid=Sty->getLastValidHistoryIndex; push @hist,$historyItem; while($hist[0] < $lastValid) { shift @hist; } $self->{undoUndoHistory}=\@hist; # print "undo history is ". (join ",",@hist)."\n"; } sub printundoshit() { my ($self)=@_; print "g=("; for (my $i=0;$i<=$#globalHistory;$i++) { print "'".$globalHistory[$i]."'"; if($i != $#globalHistory) { print ","; } } print ")\ni=("; #print STDOUT (join ",",@{$self->{undoHistory}}).") (".$self->{undoPosition}.")\nu=("; # print "argh fuck that\n"; #print STDOUT (join ",",@{$self->{undoUndoHistory}}).")\n"; # print "argh screw that that\n"; } sub undo() { my ($self)=@_; if(!$self->{undoing}) { $self->{undoing}=$true; my @hist=@{$self->{undoHistory}}; if(@hist) { my $pos=$self->{undoPosition}; $pos++; if($pos >= 0 && $pos <= $#hist) { # print "i=(" . join(" ",@hist).") (".$pos.")\n" # print "pos is $pos, returning item at index: -$pos\n"; my $item=$hist[$#hist-$pos]; # print "item from other end is " . $hist[$#hist-$pos]."\n"; # print "item number is $item\n"; my $undotext=Sty->getHistoryItem($item); # print "setting text to $undotext\n"; if($undotext =~ /^(\d+):(\d+):(.*)$/) { $undotext = $3; my $pignum=$1; my $caretpos=$2; if($pignum != $self->num) { return; } $self->{undoPosition}=$pos;#the +1 is because the previous line added a history item if(defined($undotext)) { #the next line adds an entry for this undo, so that later, the undos #can be undone themselves $self->addUndoUndoHistoryItem($self->{text}->get_position(),$self->getProp("text")); $self->setProp("text",$undotext); $self->putTextToWindow($caretpos); # $self->printundoshit(); return; } } } } $self->message("No more undos"); $self->{undoing}=$false; } } sub doredo() { my ($self)=@_; if(!$self->{undoing}) { $self->{undoing}=$true; my @hist=@{$self->{undoUndoHistory}}; if(@hist) { my $pos=$self->{undoPosition}; if($pos >= 0) { my $item=pop @hist; $self->{undoUndoHistory}=\@hist; my $undotext=Sty->getHistoryItem($item); if($undotext =~ /^(\d+):(\d+):(.*)$/) { $undotext = $3; my $pignum=$1; my $caretpos=$2; if($pignum != $self->num) { return; } $pos--; $self->{undoPosition}=$pos; $self->setProp("text",$undotext); $self->putTextToWindow($caretpos); # $self->printundoshit(); return; } } } $self->message("No more redos"); $self->{undoing}=$false; } } sub changed() { my ($self)=@_; #print "changed, for fucks sake\n"; if(! $self->{undoing}) { # print "changed:\n"; $self->{undoPosition}=-1; #here, any undos which the user has gone back by are now added to the undo list #so that the user can effectively undo from this point back to where they started #doing undos. my @hist=@{$self->{undoHistory}}; my @undos=@{$self->{undoUndoHistory}}; push (@hist, @undos); @undos=(); $self->{undoHistory}=\@hist; $self->{undoUndoHistory}=\@undos; # print "changed, printing undo history\n"; # print "fucking hell, undo history is ".join(",cunt,",$self->{undoHistory})."\n"; # print "and undo undo history\n"; # print "fucking hell, undo undo history is ".join(",cunt,",$self->{undoUndoHistory})."\n"; my $caretpos=$self->{text}->get_position; #print "caretpos is $caretpos\n"; $self->addHistoryItem($caretpos,$self->getProp("text")); $self->setTextFromWindow(); # $self->printundoshit(); } else { print "finished undo\n"; $self->{undoing}=$false; } } sub message() { my ($self,$text)=@_; $self->{label}->set_text($text); my $timernumber=$self->{messagetimer}; if($timernumber) { Gtk->timeout_remove($timernumber); } $timernumber=Gtk->timeout_add(500, sub { $self->{label}->set_text($self->getProp("title")); $self->{messagetimer}=undef; return $false; }); $self->{messagetimer}=$timernumber; } sub showtitle() { my ($self)=@_; my $event_box=$self->{titlebar}; if($event_box) { $event_box->show; } } sub hidetitle() { my ($self)=@_; my $event_box=$self->{titlebar}; if($event_box) { $event_box->hide; } } sub showscroll() { my ($self)=@_; my $sw=$self->{scrolled_win}; if($sw) { $sw->set_policy( 'automatic', 'automatic' ); } $self->setFlag("scrolled",$true); } sub hidescroll() { my ($self)=@_; my $sw=$self->{scrolled_win}; if($sw) { $sw->set_policy( 'never', 'never' ); } $self->setFlag("scrolled",$false); } sub shade() { my ($self)=@_; my $scrolled_win=$self->{scrolled_win}; my $event_box=$self->{event_box}; my $window=$self->{window}; if($scrolled_win && $scrolled_win->visible) { $self->{shading}=2;#moved_or_resized clears this $scrolled_win->hide(); my ($wxx,$wyy,$www,$whh,$wdd) = $window->window->get_geometry(); my ($exx,$eyy,$eww,$ehh,$edd) = (1,1,1,200,1); if($event_box->window) { ($exx,$eyy,$eww,$ehh,$edd) = $event_box->window->get_geometry(); } $self->{'unshaded-height'}=$whh; # print "unshaded height is $whh\n"; $window->window->resize($www,$ehh); } $self->setProp("shaded","true"); } sub unshade() { my ($self)=@_; my $scrolled_win=$self->{scrolled_win}; my $event_box=$self->{event_box}; my $window=$self->{window}; if($scrolled_win && !$scrolled_win->visible) { $self->{shading}=2;#moved_or_resized clears this $scrolled_win->show(); my ($wxx,$wyy,$www,$whh,$wdd) = $window->window->get_geometry(); my ($exx,$eyy,$eww,$ehh,$edd) = $event_box->window->get_geometry(); my $oldh=$self->{'unshaded-height'}; # $window->set_usize($www,$oldh); $window->window->resize($www,$oldh); } $self->setProp("shaded","false"); } sub toggleShade() { my ($self)=@_; if(!$self->window) { if($self->getFlag("shaded",$false)) { $self->shade(); } else { $self->unshade(); } } else { if($self->{scrolled_win}->visible) { $self->shade(); } else { $self->unshade(); } } } sub toggleWrap() { my ($self)=@_; my $oldval=$self->getFlag("wrap",$true); my $newval=!$oldval; $self->setFlag("wrap",$newval); if($self->{text}) { $self->{text}->line_wrap($newval); } } sub toggleTitle() { my ($self)=@_; my $oldval=$self->getFlag("titled",$true); my $newval=!$oldval; $self->setFlag("titled",$newval); if($newval) { $self->showtitle; } else { $self->hidetitle; } } sub toggleScroll() { my ($self)=@_; my $oldval=$self->getFlag("scrolled",$false); my $newval=!$oldval; $self->setFlag("scrolled",$newval); if($newval) { $self->showscroll(); } else { $self->hidescroll(); } } package GnomeSux; sub init() { my ($appName,$req)=@_; if($req =~ /applet/i)#/gui|applet|embedded/i) { if(!eval "require 'Gnome.pm'") { die "no gnome support"; } init Gnome $appName; if($req eq "applet") { if(!eval "require 'Gnome/Applet.pm'") { die "no gnome applet support"; } init Gnome::AppletWidget $appName; } } } package PigsGUI; my @default_icon_xpm_data = ( '18 18 17 1', ' c None', '. c #322A22', '+ c #7E6E56', '@ c #968666', '# c #B7A67E', '$ c #C3AE85', '% c #C3B69D', '& c #CAB286', '* c #C2B396', '= c #BBAD92', '- c #C6B285', '; c #C4B38F', '> c #BEB39A', ', c #A2967C', '_ c #CBB686', ') c #BDAD88', '! c #C5B38A', ' % ', ' >*%%!$&', ' __*%=)$&&--&', ' =;$-$$$$--&&_', ' %--$!))$$---&_', ' %$$)>%>=-$$&&-_', ' *;)>%>==)----&&_', ' =,#--$$$-&--_', ' >,.+$--&&-&&&_', ' >-#@#-----&---&', ' =-$-)$$&&&-&&&&&', ' =---$$-&&--&----_', '%_-&--$$$&&-&&&_&_', ' %&$)))-&-&&&&---&', ' > >$$&&-&$$$$)$-', ' >*!----$$))-&', ' >!!!#)))-', ' %#' ); sub applet() { my ($self)=@_; return $self->{applet}; } sub window() { my ($self)=@_; return $self->{window}; } sub checkIcon() { my ($self,$state)=@_; my $filename=Sty->getPref("options","icon_$state"); if(!$filename) { $filename=$defaultConfigLocation."icon-".$state.".png"; if(stat($filename)) { Sty->setPref("options","icon_$state",$filename); } else { $filename=undef; } } return $filename; } sub updatePixmap() { my ($self)=@_; $self->changePixmap($self->{currentIconState}); return 0; } sub unfocussed() { my ($self)=@_; $self->changePixmap("unfocussed"); return 0; } sub focussed() { my ($self)=@_; $self->changePixmap("focussed"); return 0; } sub getSize() { my ($self)=@_; if($self->{applet}) { my $psize= $self->{applet}->get_panel_pixel_size-4; # print "returning psize:$psize\n"; return ($psize,$psize); } else { return $self->{windowObj}->getSize; } } sub changePixmap() { my ($self,$state)=@_; $self->initPixmap(); my $psize=$self->getSize(); # print "state is $state\n"; my $ok=$false; my $filename=Sty->getPref("options","icon_".$state); # print STDERR ::Dumper([$self->{img}->widget->allocation]); my ($wxx,$wyy,$www,$whh)=@{$self->{img}->widget->allocation}; if((!defined($wxx)) || (!defined($whh)) || ($www == 1 && $whh == 1)) { $www=$whh=$psize; } if($filename && stat($filename)) { # $self->{img}->load_file_at_size($filename,$psize,$psize); $self->{img}->set_file($filename,$www,$whh); $ok=$true; } if(!$ok) { my $otherState= $state eq "focussed" ? "unfocussed" : "focussed"; my $otherIcon= Sty->getPref("options","icon_$otherState"); if($otherIcon && stat($otherIcon)) { $self->{img}->set_file($otherIcon,$www,$whh); $ok=$true; } } if(!$ok) { #load_xpm_d_at_size is FUCKED # $self->{img}->load_xpm_d_at_size($psize,$psize,@default_icon_xpm_data); ##this is the one # $self->{img}->load_xpm_d(@default_icon_xpm_data); } # $self->{button}->add(new Gtk::Button("!")); # $self->{button}->add($self->{img}->widget); $self->{currentIconState}=$state; } sub resized() { my ($self) =@_; $self->updatePixmap(); } sub style() { my ($self)=@_; if($self->{applet}) { return $self->{applet}->style; } else { return $self->{window}->style; } } sub backChanged() { my($self,$type,$pixmap,$color)=@_; $self->initPixmap(); if($pixmap) { print "hey, pixmap is defined in back_change for type $type\n"; } #print "type is $type, pixmap is $pixmap, color is $color\n"; my $newstyle= $self->style->copy(); if($type == 0) #none { #I don't really know what is suitable to do here... #It will really depend on the pixmap } elsif ($type == 1) #color { my $cm = $self->{button}->window->get_colormap; $cm->color_alloc($color); $newstyle->bg('normal',$color); } elsif ($type == 2) #pixmap { # print "pixmap arg is $pixmap\n"; # this is fucked, for some reason $pixmap seems to be always be null. } elsif ($type == 3) #translucent { # } # $self->{img}->widget->set_style($newstyle); $self->{button}->set_style($newstyle); } sub initPixmap() { my ($self)=@_; if(!$self->{img}) { my $style = $self->style; my $psize=$self->getSize; #new_from_xpm_d_at_size is FUCKED # my $img=Gnome::Pixmap->new_from_xpm_d_at_size($psize,$psize,@default_icon_xpm_data); #fuck off any requirement on gnome. # my $img=Gnome::Pixmap->new_from_xpm_d(@default_icon_xpm_data); init Gtk; my $img=$self->{img}=new csincock::Gtk::Pixmap('rgb',$true,8,15,15); $img->widget->set_style($style->copy); $self->changePixmap("unfocussed"); $self->{button}->remove($self->{padder}); $self->{button}->add($img->widget); $img->widget->show(); } } sub singleClicked() { my (@self)=@_; Sty->showNewNote($true,"",""); } sub doubleClicked() { my ($self)=@_; Sty->showNotDiscardedSummary(); } sub new() { my ($class,$mode)=@_; my $self=bless {}; #build the applet my $window=undef; $applet=undef; print "mode = $mode\n"; if($mode eq "applet") { print "starting in applet mode\n"; $applet=new Gnome::AppletWidget "$appName"; $self->{applet} = $applet; $window=$applet; } else { my $embeddedxid=""; if($mode =~ /embedded:(\d+)$/) { $embeddedxid=$1; } my $windowObj=new PigsWindow( sub { my ($prop)=@_; return Sty->getPref("options","mainwindow_".$prop); }, sub { my ($prop,$val)=@_; if($interfaceMode =~ /embedded/ && $prop =~ /width|height|x|y/) { #ignore these props if in embedded mode } else { Sty->setPref("options","mainwindow_".$prop,$val); } }, sub { }, sub { }); $self->{windowObj}=$windowObj; print "creating main window : embeddedxid = $embeddedxid\n"; $windowObj->createWindow($embeddedxid); $window = $windowObj->window; $window->set_title("$appName"); $pigsGUIMenu=new Gtk::Menu(); $window->signal_connect_after("delete_event",sub { print STDERR "delete_event in main window\n"; if($embeddedxid) { return 1; } }); $window->signal_connect("destroy",sub { print "Main window was destroyed!\n"; if($embeddedxid) { return 1; } print "Exiting\n"; Sty->quit(); }); $window->signal_connect("button_release_event", sub { my ($widg,$evt)=@_; # print "evt type: $evt->{type}\n"; # print "evt button: $evt->{button}\n"; if($evt->{type} eq "button_release" && $evt->{button} == 3) { $pigsGUIMenu->popup(undef,undef,1,$evt->{'time'}, undef); } return 1; }); $self->{window}=$window; } #an event box is used here rather than a button so that you #can get the background of the icon being the panel colour, #if the icon is transparent. my $button=new Gtk::EventBox(); #this label is needed to _make_sure_ the applet has #width > 0 already when it is first shown. #Otherwise, for some reason the bastard event box gets expanded #out to a ridiculous size, which pushes other things in the panel #out of the way. So This label is just a placeholder for when #we can add the pixmap (after the window is realized). my $label=new Gtk::Label("."); $self->{padder}=$label; $label->show; $button->add($label); $self->{button}=$button; $self->{currentIconState}="unfocussed"; $button->border_width(0); $window->border_width(0); #set up default icons if necessary $self->checkIcon("unfocussed"); $self->checkIcon("focussed"); $window->add($button); $window->show_all; if(!$applet) { $self->initPixmap(); } else { $window->signal_connect("realize", sub { $self->initPixmap(); }); } ########################################### #add signals, etc #it is FUCKING IMPOSSIBLE as far as I can tell to properly sit a transparent icon #in an applet in the gnome panel with gtk-perl (at least in v7.008) #as the back_change signal does not pass the pixmap object that it is supposed to #fucking pass. #also, the implementation of AppletWidget does not have the get_rgb_bg method which #means you can't use the send_draw method and the do_draw method to get an rgb #buffer for the applet and draw into that. #there goes 10 hours of my fucking life thanks dickwads. $button->signal_connect("enter_notify_event", sub { $self->focussed(); return 0; }); $button->signal_connect("leave_notify_event", sub { $self->unfocussed(); return 0; }); if($applet) { $applet->signal_connect("back_change", sub { shift; $self->backChanged(@_); }); $applet->signal_connect("change_pixel_size", sub { shift; $self->resized(shift); }); } $button->signal_connect("button_press_event",sub { my ($w,$evt)=@_; if(!$self->{clicktime}) { $self->{clicktime}=$evt->{time}; } if($evt->{button} && $evt->{button} == 1) { $self->{clicktime}=$evt->{time}; if($evt->{type} eq "2button_press") { $self->{dclick}=$true; return 1; } } return 0; }); $button->signal_connect("button_release_event", sub { my($w,$evt)=@_; if($evt->{button} && $evt->{button} == 1) { if($self->{dclick}) { $self->doubleClicked(); } else { my $diff=($evt->{time} - $self->{clicktime}); $self->{clicktime}=undef; if($diff > $doubleClickPeriod / 3) { return $false; } Gtk->timeout_add($doubleClickPeriod, sub { if($self->{dclick}) { $self->{dclick}=$false; #do nothing, the double-click should be handled by the second button release event } else { #the timeout has expired, after a single 'button_release' event, but without #a following 2button_press event, so it is just a single click. $self->singleClicked(); } return $false; }); } } return 0; }); if(!$applet) { #this is done after the other handlers so that dragging takes precedence $self->{windowObj}->setDragHandle($self->{button}); } my @callbacks=( "Settings", sub { Sty->showSettings(); } , "-Summary", sub { Sty-