package Tk::AppWindow::Ext::MDI; =head1 NAME Tk::AppWindow::Ext::MDI - multiple document interface =cut use strict; use warnings; use Carp; use vars qw($VERSION); $VERSION="0.17"; use base qw( Tk::AppWindow::BaseClasses::Extension ); use File::Basename; use File::Spec; use File::stat; use Time::localtime; require Tk::LabFrame; require Tk::YAMessage; require Tk::YANoteBook; use Config; my $mswin = 0; $mswin = 1 if $Config{'osname'} eq 'MSWin32'; =head1 SYNOPSIS my $app = new Tk::AppWindow(@options, -extensions => ['MDI'], ); $app->MainLoop; =head1 DESCRIPTION Provides a multi document interface to your application. When L is loaded it creates menu entries for creating, opening, saving and closing files. It also maintains a history of recently closed files. When L is loaded it creates toolbuttons for creating, opening, saving and closing files. It features deferred loading. If you open a document it will not load the document until there is a need to access it. This comes in handy when you want to open multiple documents at one time. You should define a content handler based on the abstract baseclass L. See also there. This extension will also load the extensions B and B. =head1 CONFIG VARIABLES =over 4 =item Switch: B<-contentmanagerclass> This one should always be specified and you should always define a content manager class inheriting L. This base class is a valid Tk widget. =item Switch: B<-contentmanageroptions> The possible options to pass on to the contentmanager. These will also become options to the main application. =item Switch: B<-diskmonitorinterval> Default value 100. This means every 100 cycles of the B extension. Specifies the interval for monitoring the disk status of documents. =item Switch: B<-filetypes> Default value is "All files|*" =item Switch: B<-historymenupath> Specifies the default location in the main menu of the history menu. Default value is File::Open recent. See also L. =item Switch: B<-maxhistory> Default value is 12. =item Switch: B<-maxtablength> Default value 16 Maximum size of the document tab in the document bar. =item Switch: B<-modifiedmonitorinterval> Default value 25. This means every 25 cycles of the B extension. Specifies the interval for monitoring the modified status of documents. =item Switch: B<-readonly> Default value 0. If set to 1 MDI will operate in read only mode. =back =head1 COMMANDS The following commands are defined. =over 4 =item B Takes a document name that is in deferred state as parameter and creates a new content handler for it. Returns a boolean indicating the succesfull load of its content. =item B Takes a document name as parameter and closes it. If no parameter is specified closes the current selected document. Returns a boolean for succes or failure. =item B Takes a document name as parameter and creates a new document. If no parameter is specified an Untitled document is created. Returns a boolean for succes or failure. =item B Takes a filename as parameter and opens it in deferred state. If no parameter is specified a file dialog is issued. Returns a boolean for succes or failure. =item B Takes two document names as parameters and renames the first one to the second one in the interface. =item B Takes a document name as parameter and saves it if it is modified. If no parameter is specified the current selected document is saved. Returns a boolean for succes or failure. =item B Takes a document name as parameter and issues a file dialog to rename it. If no parameter is specified the current selected document is initiated in the dialog. Returns a boolean for succes or failure. =item B Saves all open and modified documents. Returns a boolean for succes or failure. =item B Select an opened document. =item B Is called when the file menu is opened in the menubar. It populates the 'Open recent' menu with the current history. =item B Takes a document name as parameter and sets the main window title accordingly. =back =cut sub new { my $class = shift; my $self = $class->SUPER::new(@_); $self->Require( qw[ConfigFolder Daemons] ); $self->{CMOPTIONS} = {}; $self->{DEFERRED} = {}; $self->{DOCS} = {}; $self->{FORCECLOSE} = 0; $self->{HISTORY} = []; $self->{HISTORYDISABLED} = 0; $self->{INTERFACE} = undef; $self->{MONITOR} = {}; $self->{SELECTDISABLED} = 0; $self->{SELECTED} = undef; $self->{SILENTMODE} = 0; my $args = $self->GetArgsRef; my $cmo = delete $args->{'-contentmanageroptions'}; $cmo = [] unless defined $cmo; my @preconfig = (); for (@$cmo) { push @preconfig, $_ => ['PASSIVE', undef, undef, '']; } $self->addPreConfig(@preconfig, -contentmanagerclass => ['PASSIVE', undef, undef, 'Wx::Perl::FrameWorks::BaseClasses::ContentManager'], -contentmanageroptions => ['PASSIVE', undef, undef, $cmo], -diskmonitorinterval => ['PASSIVE', undef, undef, 100], #times 10 ms -maxhistory => ['PASSIVE', undef, undef, 12], -filetypes => ['PASSIVE', undef, undef, "All files|*"], -historymenupath => ['PASSIVE', undef, undef, 'File::Open recent'], -maxtablength => ['PASSIVE', undef, undef, 16], -modifiedmonitorinterval => ['PASSIVE', undef, undef, 25], #times 10 ms -readonly => ['PASSIVE', undef, undef, 0], ); $self->cmdConfig( deferred_open => ['deferredOpen', $self], doc_close => ['CmdDocClose', $self], doc_new => ['CmdDocNew', $self], doc_open => ['CmdDocOpen', $self], doc_rename => ['docRename', $self], doc_save => ['CmdDocSave', $self], doc_save_as => ['CmdDocSaveAs', $self], $self->CommandDocSaveAll, doc_select => ['docSelect', $self], set_title => ['setTitle', $self], pop_hist_menu => ['CmdPopulateHistoryMenu', $self], pop_hist_tool => ['CmdPopulateHistoryTool', $self], ); $self->addPostConfig('DoPostConfig', $self); $self->historyLoad; return $self; } =head1 METHODS =over 4 =cut sub CanQuit { my $self = shift; if ($self->docConfirmSaveAll) { $self->docForceClose(1); return 1 } return 0 } sub CmdDocClose { my ($self, $name) = @_; $name = $self->docSelected unless defined $name; return 1 unless defined $name; my $close = 1; my $fc = $self->docForceClose; if ($self->docForceClose or $self->docConfirmSave($name)) { my $geosave = $self->geometry; $close = $self->docClose($name); $self->interfaceRemove($name) if $close; $self->geometry($geosave); } $self->log("Closed '$name'") if $close; $self->logWarning("Failed closing '$name'") unless $close; $close = $name if $close; return $close } sub CmdDocNew { my ($self, $name) = @_; $name = $self->docUntitled unless defined $name; $self->deferredAssign($name); $self->interfaceAdd($name); $self->cmdExecute('doc_select', $name); return $name; } sub CmdDocOpen { my $self = shift; my @files = @_; unless (@files) { my @op = (); my $sel = $self->docSelected; push @op, -initialdir => dirname($sel) if defined $sel; @files = $self->docOpenDialog(@op); } my @o = (); my $silentmode = $self->silentMode; $self->silentMode(1) if @files > 1; for (@files) { my $file = $_; $file = File::Spec->rel2abs($file); next if -d $file; if ($self->docExists($file)) { # push @o, $file; } elsif ($self->cmdExecute('doc_new', $file)) { $self->historyRemove($file); $self->log("Opened '$file'"); push @o, $file; } } $self->silentMode($silentmode) if @files > 1; $self->cmdExecute('doc_select', $o[0]) if @o; return @o } sub CmdDocSave { my ($self, $name) = @_; return 1 if $self->configGet('-readonly'); $name = $self->docSelected unless defined $name; return 1 unless defined $name; return 1 unless $self->docModified($name); my $doc = $self->docGet($name); if (defined $doc) { unless ($name =~ /^Untitled/) { if ($doc->Save($name)) { $self->monitorUpdate($name); $self->log("Saved '$name'"); $self->Interface->indicator($name, 0); my $nav = $self->navigator; $nav->EntrySaved($name) if defined $nav; return $name } else { $self->logWarning("Failed saving '$name'"); return 0 } } else { return $self->CmdDocSaveAs($name); } } return 0 } sub CmdDocSaveAs { my ($self, $name) = @_; return 0 if $self->configGet('-readonly'); $name = $self->docSelected unless defined $name; return 0 unless defined $name; my $doc = $self->docGet($name); if (defined $doc) { my @op = ( -initialdir => dirname($name), -initialfile => basename($name), ); # push @op, -popover => 'mainwindow' unless $mswin; my ($file) = $self->pickFileSave(@op,); if (defined $file) { $file = File::Spec->rel2abs($file); if ($doc->Save($file)) { $self->log("Saved '$file'"); $self->cmdExecute('doc_rename', $name, $file); return $file } else { $self->logWarning("Failed saving '$file'"); return '' } } } return '' } sub CmdDocSaveAll { my $self = shift; my @list = $self->docList; my $succes = 1; for (@list) { $succes = '' unless $self->cmdExecute('doc_save', $_) } return $succes } sub CmdPopulateHistoryMenu { my $self = shift; my $mnu = $self->extGet('MenuBar'); if (defined $mnu) { my $path = $self->configGet('-historymenupath'); my ($menu, $index) = $mnu->FindMenuEntry($path); if (defined($menu)) { my $submenu = $menu->entrycget($index, '-menu'); $submenu->delete(1, 'last'); my $h = $self->{HISTORY}; for (@$h) { my $f = $_; $submenu->add('command', -label => $f, -command => sub { $self->cmdExecute('doc_open', $f) } ); } $submenu->add('separator'); $submenu->add('command', -label => 'Clear list', -command => sub { @$h = () }, ); } } } sub CmdPopulateHistoryTool { my $self = shift; my $tb = $self->extGet('ToolBar'); my $hist = $tb->GetItem('history'); for ($hist->children) { $_->destroy } my $lf = $hist->LabFrame( -label => 'Recent files', -labelside => 'acrosstop', )->pack(-fill => 'both'); my $f = $lf->Subwidget('frame'); my $h = $self->{HISTORY}; for (@$h) { my $file = $_; my $l = $f->Label( -anchor => 'w', -borderwidth => 1, -text => $file, )->pack(-fill => 'x'); $l->bind('', sub { $l->configure(-relief => 'raised') }); $l->bind('', sub { $l->configure(-relief => 'flat') }); $l->bind('', sub { $l->configure(-relief => 'sunken') }); $l->bind('', sub { $l->configure(-relief => 'flat'); $self->update; $tb->PopDown; $self->cmdExecute('doc_open', $file); }); } $f->Frame(-borderwidth => 1, -relief => 'sunken', -height => 2)->pack(-fill => 'x', -pady => 2); my $l = $f->Label( -anchor => 'w', -borderwidth => 1, -text => 'Clear list', )->pack(-fill => 'x'); $l->bind('', sub { $l->configure(-relief => 'raised') }); $l->bind('', sub { $l->configure(-relief => 'flat') }); $l->bind('', sub { $l->configure(-relief => 'sunken') }); $l->bind('', sub { $l->configure(-relief => 'flat'); $self->update; $tb->PopDown; @$h = (); }); my $width = 0; my $height = 0; for ($f->children) { my $w = $_->reqwidth; my $h = $_->reqheight; $width = $w if $w >$width; $height = $height + $h; } $hist->configure(-height => $height + 32, -width => $width + 12); } sub CommandDocSaveAll { my $self = shift; return doc_save_all => ['CmdDocSaveAll', $self], } =item BI<($name)> Pops a dialog with a warning that $name is unsaved. Asks for your action. Does not check if $name is modified or not. Returns the key you press, 'Yes', 'No', or cancel. Does not do any saving or checking whether a file has been modified. =cut sub ConfirmSaveDialog { my ($self, $name) = @_; croak 'Name not defined' unless defined $name; my $title = 'Warning, file modified'; my $text = "Closing " . basename($name) . ".\nDocument has been modified. Save it?"; my $icon = 'dialog-warning'; return $self->popDialog($title, $text, $icon, qw/Yes No Cancel/); } =item BI<($name)> Returns the page frame widget in the notebook belonging to $name. =cut sub ContentSpace { my ($self, $name) = @_; croak 'Name not defined' unless defined $name; return $self->Interface->getPage($name); } =item BI($name); Initiates a new content handler for $name. =cut sub CreateContentHandler { my ($self, $name) = @_; croak 'Name not defined' unless defined $name; my $page = $self->ContentSpace($name); my $cmclass = $self->configGet('-contentmanagerclass'); my $h = $page->$cmclass(-extension => $self)->pack(-expand => 1, -fill => 'both'); $self->{DOCS}->{$name} = $h; return $h; } =item B Creates a Tk::YANoteBook multiple document interface. =cut sub CreateInterface { my $self = shift; $self->{INTERFACE} = $self->WorkSpace->YANoteBook( -image => $self->getArt('document-multiple', 16), -indicatorimage => $self->getArt('document-save', 16), -selecttabcall => ['cmdExecute', $self, 'doc_select'], -closetabcall => ['cmdExecute', $self, 'doc_close'], )->pack(-expand => 1, -fill => 'both'); } =item BI<($name, ?$options?)> This method is called when you open a document. It adds document $name to the interface and stores $options in the deferred hash. $options is a reference to a hash. It's keys can be any option accepted by your content manager. =cut sub deferredAssign { my ($self, $name, $options) = @_; croak 'Name not defined' unless defined $name; $options = {} unless defined $options; $self->{DEFERRED}->{$name} = $options; } =item BI<($name)> Returns true if deferred entry $name exists. =cut sub deferredExists { my ($self, $name) = @_; croak 'Name not defined' unless defined $name; return exists $self->{DEFERRED}->{$name} } =item B Returns a list of deferred documents. =cut sub deferredList { my $self = shift; my $d = $self->{DEFERRED}; return keys %$d } =item BI<($name)> This method is called when you access the document for the first time. It creates the content manager with the deferred options and loads the file. =cut sub deferredOpen { my ($self, $name) = @_; croak 'Name not defined' unless defined $name; my $doc = $self->CreateContentHandler($name); my $flag = 1; $flag = '' unless (-e $name) and ($doc->Load($name)); my $options = $self->deferredOptions($name); $self->after(20, sub { for (keys %$options) { $doc->configure($_, $options->{$_}) } $self->monitorAdd($name); }); $self->deferredRemove($name); if ($flag) { $self->log("Loaded $name"); } else { $self->logWarning("Failed loading $name"); } $flag = $name if $flag; return $flag } =item BI<($name, ?$options?)> Sets and returns a reference to the hash containing the options for $name. =cut sub deferredOptions { my ($self, $name, $options) = @_; croak 'Name not defined' unless defined $name; my $def = $self->{DEFERRED}; $def->{$name} = $options if defined $options; return $def->{$name} } =item BI<($name)> Removes $name from the deferred hash. =cut sub deferredRemove { my ($self, $name) = @_; croak 'Name not defined' unless defined $name; delete $self->{DEFERRED}->{$name} } =item BI<($name)> Removes $name from the interface and destroys the content manager. Also adds $name to the history list. =cut sub docClose { my ($self, $name) = @_; croak 'Name not defined' unless defined $name; if ($self->deferredExists($name)) { $self->historyAdd($name); $self->deferredRemove($name); return $name } my $doc = $self->docGet($name); if ($doc->Close) { #Add to history $self->historyAdd($name); #delete from document hash delete $self->{DOCS}->{$name}; $self->monitorRemove($name); if ((defined $self->docSelected) and ($self->docSelected eq $name)) { $self->docSelected(undef); } $doc->destroy; return $name } return 0 } =item BI<($name)> Checks if $name is modified and asks confirmation for save. Saves the document if you press 'Yes'. Returns 1 unless you cancel the dialog, then it returns 0. =cut sub docConfirmSave { my ($self, $name) = @_; croak 'Name not defined' unless defined $name; if ($self->docModified($name)) { #confirm save dialog comes here my $answer = $self->ConfirmSaveDialog($name); if ($answer eq 'Yes') { return 0 unless $self->cmdExecute('doc_save', $name); } elsif ($answer eq 'No') { return 1 } else { return 0 } } else { return 1 } } =item B Calls docConfirmSave for all loaded documents. returns 0 if a 'Cancel' is detected. =cut sub docConfirmSaveAll { my $self = shift; my $close = 1; my @docs = $self->docList; for (@docs) { my $name = $_; $close = $self->docConfirmSave($name); last if $close eq 0; } return $close; } =item BI<($name)> Returns true if $name exists in either loaded or deferred state. =cut sub docExists { my ($self, $name) = @_; croak 'Name not defined' unless defined $name; return 1 if exists $self->{DOCS}->{$name}; return 1 if $self->deferredExists($name); return 0 } =item BI<(?$flag?)> If $flag is set ConfirmSave dialogs will be skipped, documents will be closed ruthlessly. Use with care and always reset it back to 0 when you're done. =cut sub docForceClose { my $self = shift; $self->{FORCECLOSE} = shift if @_; return $self->{FORCECLOSE} } =item B Returns a list of all documents, loaded and deferred. =cut sub docFullList { my $self = shift; return $self->docList, $self->deferredList; } =item BI<($name)> Returns the content manager object for $name. =cut sub docGet { my ($self, $name) = @_; croak 'Name not defined' unless defined $name; $self->cmdExecute('deferred_open', $name) if $self->deferredExists($name); return $self->{DOCS}->{$name} } =item B Returns a list of all loaded documents. =cut sub docList { my $self = shift; my $dochash = $self->{DOCS}; return keys %$dochash; } =item B Returns a list of documents currently visible in the tabs bar. =cut sub docListDisplayed { my $self = shift; my $interface = $self->extGet('CoditMDI')->Interface; my $disp = $interface->{DISPLAYED}; return @$disp } =item B Returns a list of documents currently not visible in the tabs bar. =cut sub docListUnDisplayed { my $self = shift; my $interface = $self->extGet('CoditMDI')->Interface; my $undisp = $interface->{UNDISPLAYED}; return @$undisp } =item BI<($name)> Returns true if $name is modified. =cut sub docModified { my ($self, $name) = @_; croak 'Name not defined' unless defined $name; return 0 if $self->deferredExists($name); return $self->docGet($name)->IsModified; } sub docOpenDialog { my $self = shift; return $self->pickFileOpenMulti(@_); } =item BI<($old, $new)> Renames a loaded document. =cut sub docRename { my ($self, $old, $new) = @_; croak 'Old not defined' unless defined $old; croak 'New not defined' unless defined $new; unless ($old eq $new) { my $doc = delete $self->{DOCS}->{$old}; $self->{DOCS}->{$new} = $doc; $self->interfaceRename($old, $new); $self->monitorRemove($old); $self->monitorAdd($new); if ($self->docSelected eq $old) { $self->cmdExecute('doc_select', $new) } } } =item BI<($name)> Selects $name. =cut sub docSelect { my ($self, $name) = @_; croak 'Name not defined' unless defined $name; return if $self->selectDisabled; $self->cmdExecute('deferred_open', $name) if $self->deferredExists($name); $self->docSelected($name); $self->interfaceSelect($name); $self->docGet($name)->doSelect; $self->cmdExecute('set_title', $name); } =item B Returns the name of the currently selected document. Returns undef if no document is selected. =cut sub docSelected { my $self = shift; $self->{SELECTED} = shift if @_; return $self->{SELECTED} } =item BI<($name)> Strips the path from $name for the title bar. =cut sub docTitle { my ($self, $name) = @_; return basename($name, ''); } =item B> Returns 'Untitled' plus a digit '(d)'. It checks how many untitled documents exists and adjusts the number. =cut sub docUntitled { my $self = shift; my $name = 'Untitled'; if ($self->docExists($name)) { my $num = 2; while ($self->docExists("$name ($num)")) { $num ++ } $name = "$name ($num)"; } return $name } sub DoPostConfig { my $self = shift; $self->CreateInterface; } =item BI<($name)> =cut sub historyAdd { my ($self, $filename) = @_; croak 'Name not defined' unless defined $filename; return if $self->historyDisabled; if (defined($filename) and (-e $filename)) { my $hist = $self->{HISTORY}; unshift @$hist, $filename; #Keep history size at or below maximum my $siz = @$hist; pop @$hist if ($siz > $self->configGet('-maxhistory')); } } =item BI<($name)> =cut sub historyDisabled { my $self = shift; $self->{HISTORYDISABLED} = shift if @_; return $self->{HISTORYDISABLED} } =item B Loads the history file in the config folder. =cut sub historyLoad { my $self = shift; my $folder = $self->configGet('-configfolder'); if (-e "$folder/history") { if (open(OFILE, "<", "$folder/history")) { my @history = (); while () { my $line = $_; chomp $line; push @history, $line; } close OFILE; $self->{HISTORY} = \@history; } } } =item BI<($name)> Removes $name from the history list. Called when a document is opened. =cut sub historyRemove { my ($self, $file) = @_; croak 'Name not defined' unless defined $file; return if $self->historyDisabled; my $h = $self->{HISTORY}; my ($index) = grep { $h->[$_] eq $file } (0 .. @$h-1); splice @$h, $index, 1 if defined $index; } =item B Saves the history list to the history file in the config folder. =cut sub historySave { my $self = shift; my $hist = $self->{HISTORY}; if (@$hist) { my $folder = $self->configGet('-configfolder'); if (open(OFILE, ">", "$folder/history")) { for (@$hist) { print OFILE "$_\n"; } close OFILE } else { warn "Cannot save document history" } } } =item B Returns a reference to the multiple document interface. =cut sub Interface { return $_[0]->{INTERFACE} } =item BI<($name)> Adds $name to the multiple document interface and to the Selector if the Selector extension is loaded. =cut sub interfaceAdd { my ($self, $name) = @_; croak 'Name not defined' unless defined $name; #add to document notebook my $if = $self->Interface; if (defined $if) { my @op = (); my $cti = $self->getArt('tab-close', 16); push @op, -closeimage => $cti if defined $cti; my $page = $if->addPage($name, @op, -title => $self->docTitle($name), -closebutton => 1, ); } #add to navigator my $navigator = $self->navigator; if (defined $navigator) { $navigator->Add($name) if defined $navigator; # $self->interfaceCollapse; } } =item B Collapses all folder trees in the document tree except the path of the selected entry, if extension Selector is loaded. =cut sub interfaceCollapse { my $self = shift; my $t = $self->Subwidget('NAVTREE'); $t->collapseAll if defined $t; } =item B Opens all folder trees in the document tree, if extension Selector is loaded. =cut sub interfaceExpand { my $self = shift; my $t = $self->Subwidget('NAVTREE'); $t->expandAll if defined $t; } sub interfaceGet { my $self = shift; my $navigator = $self->navigator; return $navigator } =item BI<($name, ?$flag?)> Removes $name from the multiple document interface and from the Selector if the Selector extension is loaded. =cut sub interfaceRemove { my ($self, $name, $flag) = @_; croak 'Name not defined' unless defined $name; $flag = 1 unless defined $flag; #remove from document notebook my $if = $self->Interface; $if->deletePage($name) if (defined $if) and $flag; #remove from navigator my $navigator = $self->interfaceGet; $navigator->Delete($name) if defined $navigator; } =item BI<($old, $new)> Renames the $old entry in the multiple document interface and the navigator. =cut sub interfaceRename { my ($self, $old, $new) = @_; croak 'Old not defined' unless defined $old; croak 'New not defined' unless defined $new; #rename in document notebook my $if = $self->Interface; if (defined $if) { $if->renamePage($old, $new); my $tab = $if->getTab($new); $tab->configure( -name => $new, -title => $self->docTitle($new), ); } #rename in navigator my $navigator = $self->interfaceGet; if (defined $navigator) { $navigator->Delete($old); $navigator->Add($new); } } =item BI<($name)> Is called when something else than the user selects a document. =cut sub interfaceSelect { my ($self, $name) = @_; croak 'Name not defined' unless defined $name; #select on document notebook my $if = $self->Interface; $if->selectPage($name) if defined $if; #select on navigator my $navigator = $self->interfaceGet; $navigator->SelectEntry($name) if defined $navigator; } =item BI<($name)> Makes I<$name> visible in the Selector. =cut sub interfaceShow { my ($self, $name) = @_; croak 'Name not defined' unless defined $name; my $t = $self->Subwidget('NAVTREE'); $t->entryShow($name) if defined $t; } =item B Returns the menu items for MDI. Called by extension B. =cut sub MenuSaveAll { my $self = shift; return [ 'menu_normal', 'File::', "Save ~all", 'doc_save_all', 'document-save', 'CTRL+L'], } sub MenuItems { my $self = shift; my $readonly = $self->configGet('-readonly'); my @items = ( # type menupath label cmd icon keyb [ 'menu', undef, "~File" ], ); push @items, [ 'menu_normal', 'File::', "~New", 'doc_new', 'document-new', 'CTRL+N' ], [ 'menu_separator', 'File::', 'f1'], unless $readonly; push @items, [ 'menu_normal', 'File::', "~Open", 'doc_open', 'document-open', 'CTRL+O' ], [ 'menu', 'File::', "Open ~recent", 'pop_hist_menu', ], ; push @items, [ 'menu_separator', 'File::', 'f2' ], [ 'menu_normal', 'File::', "~Save", 'doc_save', 'document-save', 'CTRL+S' ], [ 'menu_normal', 'File::', "S~ave as", 'doc_save_as', 'document-save-as',], $self->MenuSaveAll, unless $readonly; push @items, [ 'menu_separator', 'File::', 'f3' ], [ 'menu_normal', 'File::', "~Close", 'doc_close', 'document-close', 'CTRL+SHIFT+O' ], ; return @items } =item BI<($name)> Adds $name to the hash of monitored documents. It will check it's modified status. It willcollect its time stamp, if $name is an existing file. =cut sub monitorAdd { my ($self, $name) = @_; croak 'Name not defined' unless defined $name; my $dem = $self->extGet('Daemons'); my $di = $self->configGet('-diskmonitorinterval'); my $mi = $self->configGet('-modifiedmonitorinterval'); $dem->jobAdd("$name-disk", $di, 'monitorDisk', $self, $name); $dem->jobAdd("$name-modified", $mi, 'monitorModified', $self, $name); my $hash = $self->{MONITOR}; my $modified = $self->docModified($name); my $stamp; $stamp = ctime(stat($name)->mtime) if -e $name; $hash->{$name} = { modified => $modified, timestamp => $stamp, } } =item BI<($name)> Checks if $name is modified on disk after it was loaded. Launches a dialog for reload or ignore if so. =cut sub monitorDisk { my ($self, $name) = @_; croak 'Name not defined' unless defined $name; # print "monitorDisk $name\n"; return unless -e $name; my $stamp = $self->{MONITOR}->{$name}->{'timestamp'}; my $docstamp = ctime(stat($name)->mtime); if ($stamp ne $docstamp) { my $title = 'Warning, file modified on disk'; my $text = "$name\nHas been modified on disk."; my $icon = 'dialog-warning'; my $answer = $self->popDialog($title, $text, $icon, qw/Reload Ignore/); if ($answer eq 'Reload') { $self->docGet($name)->Load($name); } $self->{MONITOR}->{$name}->{'timestamp'} = $docstamp; } } =item BI<($name)> returns a list of monitored documents. =cut sub monitorList { my $self = shift; my $hash = $self->{MONITOR}; return sort keys %$hash; } =item BI<($name)> Checks if the modified status of the document has changed and updates the navigator. =cut sub monitorModified { my ($self, $name) = @_; croak 'Name not defined' unless defined $name; my $mod = $self->{MONITOR}->{$name}->{'modified'}; my $docmod = $self->docModified($name); my $nav = $self->navigator; if ($mod ne $docmod) { if ($docmod) { $self->Interface->indicator($name, 1); $nav->EntryModified($name) if defined $nav; } else { $self->Interface->indicator($name, 0); $nav->EntrySaved($name) if defined $nav; } $self->{MONITOR}->{$name}->{'modified'} = $docmod; } } =item BI<($name)> Removes $name from the hash of monitored documents. =cut sub monitorRemove { my ($self, $name) = @_; croak 'Name not defined' unless defined $name; my $dem = $self->extGet('Daemons'); $dem->jobRemove("$name-disk"); $dem->jobRemove("$name-modified"); delete $self->{MONITOR}->{$name}; } =item BI<($name)> Assigns a fresh time stamp to $name. Called when a document is saved. =cut sub monitorUpdate { my ($self, $name) = @_; croak 'Name not defined' unless defined $name; $self->{MONITOR}->{$name}->{'timestamp'} = ctime(stat($name)->mtime); } sub navigator { my $self = shift; my $nav = $self->extGet('Selector'); return $nav } sub ReConfigure { my $self = shift; my @docs = $self->docList; for (@docs) { $self->docGet($_)->ConfigureCM; } } sub Quit { my $self = shift; my @docs = $self->docList; # $self->docForceClose(1); for (@docs) { $self->CmdDocClose($_); } $self->historySave; } =item BI Sets and returns the selectdisabled flag. If this flag is set, no document can be selected. Use with care. =cut sub selectDisabled { my $self = shift; $self->{SELECTDISABLED} = shift if @_; return $self->{SELECTDISABLED} } sub setTitle { my ($self, $name) = @_; my $appname = $self->configGet('-appname'); $self->configPut(-title => "$name - $appname") if defined $name; $self->configPut(-title => $appname) unless defined $name; } =item BI<($flag)> Takes a boolean as parameter. When silentMode is on the document bar will not update, document history and select are disabled. This will speed up things when you open multiple documents at once. =cut sub silentMode { my ($self, $flag) = @_; unless (defined $flag) { return $self->{SILENTMODE}; } $self->{SILENTMODE} = $flag; my $if = $self->Interface; if ($flag) { $self->{'autoupdate'} = $if->autoupdate; $if->autoupdate(0); $self->{'historydisabled'} = $self->historyDisabled; $self->historyDisabled(1); $self->{'selectdisabled'} = $self->selectDisabled; $self->selectDisabled(1); } else { my $a = $self->{'autoupdate'}; $if->autoupdate($a) if defined $a; delete $self->{'autoupdate'}; # $if->UpdateTabs; my $h = $self->{'historydisabled'}; $self->historyDisabled($h) if defined $h; delete $self->{'historydisabled'}; my $d = $self->{'selectdisabled'}; $self->selectDisabled($d) if defined $d; delete $self->{'selectdisabled'}; } } sub ToolSaveAll { my $self = shift; return [ 'tool_button', 'Save all', 'doc_save_all', 'document-save', 'Save all open documents'], } =item B Returns the tool items for MDI. Called by extension B. =cut sub ToolItems { my $self = shift; my $readonly = $self->configGet('-readonly'); my @items = (); push @items, # type label cmd icon help [ 'tool_button', 'New', 'doc_new', 'document-new', 'Create a new document'], unless $readonly; push @items, [ 'tool_list', 'history', 'pop_hist_tool' ], [ 'tool_button', 'Open', 'doc_open', 'document-open', 'Open a document'], [ 'tool_list_end' ], ; push @items, [ 'tool_list' ], [ 'tool_button', 'Save', 'doc_save', 'document-save', 'Save current document'], [ 'tool_button', 'Save as ', 'doc_save_as', 'document-save-as', 'Rename and save current document'], $self->ToolSaveAll, [ 'tool_list_end' ], unless $readonly; push @items, [ 'tool_button', 'Close', 'doc_close', 'document-close', 'Close current document'], ; return @items } =back =head1 AUTHOR Hans Jeuken (hanje at cpan dot org) =head1 BUGS Unknown. If you find any, please contact the author. =head1 SEE ALSO =over 4 =item L =item L =item L =item L =item L =back =cut 1;