updated on Sat Jan 14 00:11:12 UTC 2012
[aur-mirror.git] / ifmapper / FXMapperWindow.rb
blob5095b0de56a66ccd55a740298b50128a49c24bee
3 begin
4   $rubygems = false
5   require 'rubygems'
6   $rubygems = true
7 rescue LoadError
8 end
10 def no_fox
11   $stderr.puts ERR_NO_FOX
12   if $rubygems
13     $stderr.puts ERR_HAS_GEMS
14   end
15   exit(1)
16 end
18 def get_fox
19   ##### ARRRGH!!!! Why does Lyle keep changing the fxruby name on each
20   ##### release!
21   foxes = [ 'fox16', 'fox14', 'fox12', 'fox' ]
22   foxes.each { |fox|
23     begin
24       require "#{fox}"
25       require "#{fox}/colors"
26       break
27     rescue LoadError
28       no_fox if fox == foxes[-1]
29     end
30   }
31   
32   # verify fxruby version
33   ver, rev, = Fox::fxrubyversion().split('.')
34   no_fox if ver.to_i < 1 or rev.to_i < 2
35 end
38 get_fox
39 include Fox
41 require 'IFMapper/FXMap'
42 require 'IFMapper/FXMapperSettings'
43 require 'IFMapper/FXWarningBox'
46 class FXMapperWindow < FXMainWindow
48   PROGRAM_NAME = "Interactive Fiction Mapper"
49   VERSION      = '1.0.5'
50   AUTHOR = "Gonzalo Garramuno"
52   @@copy_buffer = nil
53   @@default_options = FXMapperSettings.new
55   LANGUAGES = {
56     'English'  => 'en',
57     'Espanol' => 'es',
58     # todo1
59     'Deutsch'  => 'de',
60     'Italiano' => 'it',
61     'Francais' => 'fr',
62     # todo2
63     'Japanese' => 'ja',
64     'Chinese'  => 'ch',
65     'Korean'   => 'ko',
66     'Arabic'   => 'ar',
67   }
69   def open_map(file)
70     tmp = nil
71     begin
72       f = File.open(file, 'rb')
73       tmp = Marshal.load(f)
74       f.close
75       tmp.filename = file
76     rescue => e
77       tmp = e
78     end
79     return tmp
80   end
82   def open_ifm(file, map)
83     require 'IFMapper/IFMReader'
84     begin
85       IFMReader.new(file, map)
86     rescue => e
87       return "#{e} #{e.backtrace}"
88     end
89     return map
90   end
92   def open_tads(file, map)
93     require 'IFMapper/TADSReader'
94     begin
95       TADSReader.new(file, map)
96     rescue => e
97       return "#{e}"
98     end
99     return map
100   end
102   def open_inform(file, map)
103     require 'IFMapper/InformReader'
104     begin
105       InformReader.new(file, map)
106     rescue => e
107       return "#{e}"
108     end
109     return map
110   end
112   def open_guemap(file, map)
113     require 'IFMapper/GUEReader'
114     begin
115       GUEReader.new(file, map)
116     rescue => e
117       return "#{e}"
118     end
119     return map
120   end
122   #
123   # Start automapping from a transcript
124   #
125   def start_automap_cb(sender, sel, ptr)
126     map = current_map
127     return if not map
128     map.start_automap
129   end
131   #
132   # Properties of the automapper callback
133   #
134   def automap_properties_cb(sender, sel, ptr)
135     map = current_map
136     return if not map or not map.automap
137     map.automap.properties(true)
138   end
140   #
141   # Stop automapping from a transcript
142   #
143   def stop_automap_cb(sender, sel, ptr)
144     map = current_map
145     return if not map
146     map.stop_automap
147   end
150   #
151   # Callback to Open File
152   #
153   def open_cb(sender, sel, ptr)
154     require 'IFMapper/FXMapFileDialog'
155     file = FXMapFileDialog.new(self, MSG_LOAD_MAP).filename
156     return if file == ''
158     # First, make sure we don't have it loaded already...
159     @maps.each { |m|
160       if m.filename == file
161         @mdiclient.setActiveChild(m.window)
162         return
163       end
164     }
166     # Then, check if we have a single and empty map.
167     # If so, we can just use that one to load the file in.
168     # If not, we need to create a new map
169     make_new_map = false
170     if @maps.size == 1
171       @maps[0].sections.each { |p|
172         if p.rooms.size != 0
173           make_new_map = true
174           break
175         end
176       }
177     else
178       make_new_map = true
179     end
181     if make_new_map
182       map = new_map
183     else
184       map = @maps[0]
185     end
186     status "#{MSG_LOADING} '#{file}'..."
188     tmp = nil
189     if file =~ /\.ifm$/i
190       tmp = open_ifm(file, map)
191     elsif file =~ /\.inf$/i
192       tmp = open_inform(file, map)
193     elsif file =~ /\.t$/i or file =~ /\.t3m$/
194       tmp = open_tads(file, map)
195     elsif file =~ /\.gmp$/
196       tmp = open_guemap(file, map)
197     else
198       tmp = open_map(file)
199     end
201     if not tmp.kind_of?(Map) and not tmp.kind_of?(FXMap)
202       $stderr.puts tmp
203       w = FXWarningBox.new( self, 
204                            "#{tmp}")
205       w.execute
206       status "#{ERR_COULD_NOT_LOAD} '#{file}'." 
207       if make_new_map
208         if map.close_cb
209           @maps.delete(map)
210           GC.start
211         end
212       end
213       sleep 2
214       return
215     end
216     
217     map.copy(tmp)
218     map.options.replace( @@default_options.merge(map.options) )
219     map.verify_integrity
220     map.fit
221     map.window.create
222     map.modified = false
223     update_map
224     status "#{MSG_LOADED} '#{file}'."
225   end
227   #
228   # Write a message to the status bar
229   #
230   def status(msg)
231     @statusbar.statusLine.text = msg
232   end
234   #
235   # Returns current active map or nil if no maps
236   #
237   def current_map
238     window = @mdiclient.activeChild
239     return nil unless window
241     @maps.each { |m|
242       return m if m.window == window
243     }
244     return nil
245   end
247   #
248   # Callback for Save
249   #
250   def save_cb(sender, sel, ptr)
251     map = current_map
252     return unless map
253     map.save
254   end
256   #
257   # Callback for Save As
258   #
259   def save_as_cb(sender, sel, ptr)
260     map = current_map
261     return unless map
262     map.save_as
263   end
265   #
266   # Callback used to create new map
267   #
268   def new_map_cb(*args)
269     m = new_map
270     m.window.create
271   end
273   #
274   # Callback used to change language
275   #
276   def language_cb(sender, msg, opts)
277     @@default_options['Language'] = LANGUAGES[sender.text]
278     
279     require "IFMapper/locales/#{language}/Messages.rb"
280     recreate
281   end
283   #
284   # Create a new map
285   #
286   def new_map
287     mapname  = "#{MSG_EMPTY_MAP} \##{@maps.size+1}"
288     @maps.push( FXMap.new(mapname, @mdiclient, @@default_options.dup,
289                           @mdiicon, @mdimenu, MDI_NORMAL, 0, 0, 790, 500) )
290     map = @maps[-1]
291     map.window.connect(SEL_PAINT) {
292       map.draw
293     }
294     map.window.connect(SEL_CLOSE) {
295       if map.close_cb
296         @maps.delete(map)
297       end
298       if @maps[-1]
299         @maps[-1].update_roomlist
300       else
301         FXMap::no_maps
302       end
303     }
305     # Make it active
306     @mdiclient.setActiveChild(map.window)
307     map.update_roomlist
308     return map
309   end
311   #
312   # Load the named PNG icon from a file
313   #
314   def load_icon(filename)
315     begin
316       filename = File.join("icons", filename) + ".png"
317       icon = nil
318       File.open(filename, "rb") { |f|
319         icon = FXPNGIcon.new(getApp(), f.read)
320       }
321       icon
322     rescue
323       raise RuntimeError, "#{ERR_NO_ICON} #{filename}"
324     end
325   end
327   #
328   # Start a complex connection
329   #
330   def complex_connection_cb(sender, sel, msg)
331     map = current_map
332     return unless map
333     map.complex_connection
334   end
336   #
337   # Delete selected elements in map
338   #
339   def delete_selected_cb(sender, sel, msg)
340     map = current_map
341     return unless map
342     map.delete_selected
343   end
345   #
346   # Popup a printer dialog and return the settings or false if user cancels
347   #
348   def printer_dialog(title = MSG_PRINT_MAP)
349     map = current_map
350     dlg = FXPrintDialog.new(self, title + " for #{map.name}")
351     dlg.printer.flags |= PRINT_DEST_PAPER 
352     return dlg.printer if dlg.execute != 0
353     return false
354   end
356   #
357   # Print out all the locations in map
358   #
359   def print_locations_cb(sender, sel, msg)
360     map = current_map
361     return unless map
363     w = FXWarningBox.new( self, ERR_NO_PRINTING)
364     w.execute
365     return
367     printer = printer_dialog MSG_PRINT_LOC
368     map.print_locations( printer ) if printer
369   end
371   #
372   # Export current map as an IFM file
373   #
374   def ifm_export_cb(sender, sel, msg)
375     map = current_map
376     return unless map
378     require 'IFMapper/FXMapFileDialog'
379     d = FXMapFileDialog.new(self, MSG_SAVE_MAP_AS_IFM, 
380                             [
381                               FMT_IFM
382                             ])
383     map.export_ifm(d.filename) if d.filename != ''
384   end
386   #
387   # Export current map as an Inform source file
388   #
389   def inform_export_cb(sender, sel, msg)
390     map = current_map
391     return unless map
393     require 'IFMapper/FXMapFileDialog'
394     d = FXMapFileDialog.new(self, MSG_SAVE_MAP_AS_INFORM, 
395                             [
396                               FMT_INFORM,
397                             ])
398     map.export_inform( d.filename ) if d.filename != ''
399   end
402   #
403   # Export current map as a TADs source file
404   #
405   def tads_export_cb(sender, sel, msg)
406     map = current_map
407     return unless map
409     require 'IFMapper/TADSWriter'
410     require 'IFMapper/FXMapFileDialog'
411     d = FXMapFileDialog.new(self, MSG_SAVE_MAP_AS_TADS, 
412                             [
413                               FMT_TADS
414                             ])
415     map.export_tads( d.filename ) if d.filename != ''
416   end
419   #
420   # Export current map as Acrobat PDF
421   #
422   def pdf_export_cb(sender, sel, msg)
423     map = current_map
424     return unless map
426     begin
427       require 'IFMapper/PDFMapExporter'
428     rescue LoadError => e
429       w = FXWarningBox.new( self, "#{e}")
430       w.execute
431       return
432     end
433     
434     # PRE: Let's ask for a page size and orientation for the PDF
435     #      and whether the user wants to include location numbers
436     map.pdfpapersize = 0
437     map.pdflocationnos = 1
438     require 'IFMapper/FXPDFMapExporterOptionsDialogBox'
439     FXPDFMapExporterOptionsDialogBox.new(self, MSG_SAVE_MAP_AS_PDF, map).execute
441     require 'IFMapper/FXMapFileDialog'
442     d = FXMapFileDialog.new(self, MSG_SAVE_MAP_AS_PDF, 
443                             [
444                               FMT_PDF
445                             ])
446     if d.filename != ''
447       map.pdf_export(d.filename)
448     end
449   end
451   #
452   # Print current map graphically
453   #
454   def print_cb(sender, sel, msg)
455     map = current_map
456     return unless map
458     w = FXWarningBox.new( self, ERR_NO_PRINTING )
459     w.execute
460     return
461     require 'IFMapper/MapPrinting'
463     printer = printer_dialog
464     map.print( printer ) if printer
465   end
467   def update_map
468     map = current_map
469     return unless map
470     map.update_roomlist
471     update_section
472   end
474   #
475   # Creates the MDI (multi-document) client
476   #
477   def create_mdiclient
478     # MDI Client
479     @maps = []
481     @mdiclient = FXMDIClient.new(self, LAYOUT_FILL_X|LAYOUT_FILL_Y)
482     @mdiclient.connect(SEL_CHANGED) { 
483       update_map
484     }
486     # MDI buttons in menu:- note the message ID's!!!!!
487     # Normally, MDI commands are simply sensitized or desensitized;
488     # Under the @menubar, however, they're hidden if the MDI Client is
489     # not maximized.  To do this, they must have different ID's.
490     FXMDIWindowButton.new(@menubar, @mdimenu, @mdiclient,
491       FXMDIClient::ID_MDI_MENUWINDOW, LAYOUT_LEFT)
492     FXMDIDeleteButton.new(@menubar, @mdiclient,
493       FXMDIClient::ID_MDI_MENUCLOSE, FRAME_RAISED|LAYOUT_RIGHT)
494     FXMDIRestoreButton.new(@menubar, @mdiclient,
495       FXMDIClient::ID_MDI_MENURESTORE, FRAME_RAISED|LAYOUT_RIGHT)
496     FXMDIMinimizeButton.new(@menubar, @mdiclient,
497       FXMDIClient::ID_MDI_MENUMINIMIZE, FRAME_RAISED|LAYOUT_RIGHT)
499     # Icon for MDI Child
500     @mdiicon = load_icon("winapp")
502     # Make MDI Window Menu
503     @mdimenu = FXMDIMenu.new(self, @mdiclient)
504   end
506   #
507   # Return the copied elements 
508   #
509   def self.copy_buffer
510     return @@copy_buffer
511   end
513   #
514   # Copy selected elements
515   #
516   def self.copy_selected(map)
517     sect = map.sections[map.section]
519     # Get all selected rooms
520     rooms = sect.rooms.find_all { |r| r.selected }
521     return if rooms.size < 1
523     # Get all selected connections
524     links = sect.connections.find_all { |c| c.selected }
526     # Make sure we store only those connections for
527     # those rooms we selected
528     delete = []
529     links.each { |c|
530       if not rooms.include?(c.roomA) or 
531           (c.roomB and not rooms.include?(c.roomB))
532         delete << c
533       end
534     }
535     links -= delete
537     selection = [ rooms, links ]
539     @@copy_buffer = selection
540   end
542   #
543   # Cut selected elements
544   #
545   def self.cut_selected(map)
546     FXMapperWindow::copy_selected(map)
547     map.cut_selected
548   end
550   #
551   # Paste selected elements
552   #
553   def self.paste_selected(map)
554     return if not @@copy_buffer
555     return map.navigation_warning if map.navigation
557     sel = @@copy_buffer
558     pos = map.find_empty_area( sel[0] )
559     if not pos
560       w = FXWarningBox.new( map.window, ERR_NO_FREE_ROOM)
561       w.execute
562     else
563       map.clear_selection
565       # Add rooms
566       r_to_nr = {}  # orig room to new room hash
567       rooms = sel[0]
568       rooms.each { |r|
569         nr = map.new_room(r.x + pos[0], r.y + pos[1])
570         nr.selected = true
571         nr.copy(r) # copy the room data
572         r_to_nr[r] = nr
573       }
575       if rooms.empty?
576         # Add connections only (no rooms copied)
577         sel[1].each { |c|
578           exitA, exitB = c.dirs
579           roomA = c.roomA
580           roomB = c.roomB
581           sect  = map.sections[map.section]
582           if not sect.rooms.include?(roomA) or 
583               (roomB and not sect.rooms.include?(roomB))
584             next
585           end
586           begin
587             nc = map.new_connection(roomA, exitA, roomB, exitB)
588             nc.selected = true
589             nc.dir = c.dir
590             nc.type = c.type
591           rescue
592           end
593         }
594       else
595         # Add connections
596         sel[1].each { |c|
597           exitA, exitB = c.dirs
598           roomA = r_to_nr[c.roomA]
599           if c.roomB
600             roomB = r_to_nr[c.roomB]
601           else
602             roomB = nil
603           end
604           next if not roomA
605           begin
606             nc = map.new_connection(roomA, exitA, roomB, exitB)
607             nc.selected = true
608             nc.dir  = c.dir
609             nc.type = c.type
610           rescue Section::ConnectionError => e
611             puts c
612             puts e
613           end
614         }
615       end
617       map.create_pathmap
618       map.draw
619     end
620   end
622   def cut_selected_cb(*o)
623     map = current_map
624     return unless map
625     FXMapperWindow::cut_selected(map)
626   end
628   def copy_selected_cb(*o)
629     map = current_map
630     return unless map
631     FXMapperWindow::copy_selected(map)
632   end
634   def paste_selected_cb(*o)
635     map = current_map
636     return if not map or not @@copy_buffer
637     FXMapperWindow::paste_selected(map)
638   end
641   #
642   # Hilite matches after a search
643   #
644   def hilite_matches(map, matches, re, idx = 0 )
645     if matches.size == 0
646       status "#{ERR_NO_MATCHES} '#{re}'."
647       return
648     end
650     # sort matches by section
651     matches.sort_by { |a| a[0] }
653     # Jump to first section of match
654     map.section = matches[idx][0]
655     map.sections.each { |s|
656       s.rooms.each { |r| r.selected = false }
657     }
659     matches.each { |p, r| 
660       next if p != map.section
661       r.selected = true
662     }
664     num = matches.find_all { |p, r| p == matches[idx][0] }
665     room = matches[idx][1]
666     map.center_view_on_room( room )
667     update_section
669     status "'#{room.name}' #{MSG_MATCHES}. #{matches.size} #{MSG_MATCHES_IN_MAP}, #{num.size} #{MSG_MATCHES_IN_SECTION}."
670     map.draw
671   end
673   #
674   # Find location in map
675   #
676   def find_in_map(s, m, e)
677     map = current_map
678     return unless map
680     re = /#{s.text}/
681     matches = []
682     (0...map.sections.size).each { |p|
683       map.sections[p].rooms.each { |r|
684         next unless r.name =~ re
685         matches.push( [p, r] )
686       }
687     }
688     idx = @search.index
689     @search.index = matches.size-1 if idx >= matches.size
690     hilite_matches(map, matches, re, @search.index)
691   end
693   def find_in_map_cb(s, m, e)
694     map = current_map
695     return unless map
697     title = MSG_FIND_LOCATION_IN_MAP
698     if not @search
699       require 'IFMapper/FXSearchDialogBox'
700       @search = FXSearchDialogBox.new(self)
701     end
702     @search.proc  = method(:find_in_map)
703     @search.title = title
704     @search.text  = ''
705     @search.show
706   end
708   #
709   # Find location in section
710   #
711   def find_in_section(s, m, e)
712     map = current_map
713     return unless map
715     re = /#{s.text}/
716     matches = []
717     map.sections[map.section].rooms.each { |r|
718       next unless r.name =~ re
719       matches.push( [ map.section, r] )
720     }
721     hilite_matches(map, matches, re)
722   end
724   #
725   # Callback
726   #
727   def find_in_section_cb(s, m, e)
728     map = current_map
729     return unless map
731     title = MSG_FIND_LOCATION_IN_SECTION
732     if not @search
733       require 'IFMapper/FXSearchDialogBox'
734       @search = FXSearchDialogBox.new(self)
735     end
736     @search.proc  = method(:find_in_section)
737     @search.title = title
738     @search.text  = ''
739     @search.show
740   end
742   #
743   # Find object in map
744   #
745   def find_object_in_map(s, m, e)
746     map = current_map
747     return unless map
749     re = /#{s.text}/
750     matches = []
751     (0...map.sections.size).each { |p|
752       map.sections[p].rooms.each { |r|
753         next unless r.objects =~ re
754         matches.push( [p, r] )
755       }
756     }
757     idx = @search.index
758     @search.index = matches.size-1 if idx >= matches.size
759     hilite_matches(map, matches, re, @search.index)
760   end
762   #
763   # Find task in map
764   #
765   def find_task_in_map(s, m, e)
766     map = current_map
767     return unless map
769     re = /#{s.text}/
770     matches = []
771     (0...map.sections.size).each { |p|
772       map.sections[p].rooms.each { |r|
773         next unless r.tasks =~ re
774         matches.push( [p, r] )
775       }
776     }
777     idx = @search.index
778     @search.index = matches.size-1 if idx >= matches.size
779     hilite_matches(map, matches, re, @search.index)
780   end
782   #
783   # Find object in map
784   #
785   def find_object_in_map_cb(s, m, e)
786     map = current_map
787     return unless map
789     title = MSG_FIND_OBJECT_IN_MAP
790     if not @search
791       require 'IFMapper/FXSearchDialogBox'
792       @search = FXSearchDialogBox.new(self)
793     end
794     @search.proc  = method(:find_object_in_map)
795     @search.title = title
796     @search.text  = ''
797     @search.show
798   end
800   #
801   # Find task in map
802   #
803   def find_task_in_map_cb(s, m, e)
804     map = current_map
805     return unless map
807     title = MSG_FIND_TASK_IN_MAP
808     if not @search
809       require 'IFMapper/FXSearchDialogBox'
810       @search = FXSearchDialogBox.new(self)
811     end
812     @search.proc  = method(:find_task_in_map)
813     @search.title = title
814     @search.text  = ''
815     @search.show
816   end
818   #
819   # Find task in map
820   #
821   def find_desc_in_map(s, m, e)
822     map = current_map
823     return unless map
825     re = /#{s.text}/
826     matches = []
827     (0...map.sections.size).each { |p|
828       map.sections[p].rooms.each { |r|
829         next unless r.desc =~ re
830         matches.push( [p, r] )
831       }
832     }
833     idx = @search.index
834     @search.index = matches.size-1 if idx >= matches.size
835     hilite_matches(map, matches, re, @search.index)
836   end
838   #
839   # Find description in map
840   #
841   def find_desc_in_map_cb(s, m, e)
842     map = current_map
843     return unless map
845     title = MSG_FIND_DESCRIPTION_IN_MAP
846     if not @search
847       require 'IFMapper/FXSearchDialogBox'
848       @search = FXSearchDialogBox.new(self)
849     end
850     @search.proc  = method(:find_desc_in_map)
851     @search.title = title
852     @search.text  = ''
853     @search.show
854   end
857   #
858   # Pop-up color preferences
859   #
860   def colors_cb(sender, id, msg)
861     map = current_map
862     return if not map
864     if not @colors
865       require 'IFMapper/FXMapColorBox'
866       @colors = FXMapColorBox.new(self)
867     else
868       @colors.show
869     end
870     @colors.copy_from(map)
871   end
873   #
874   # Unselect all
875   #
876   def select_none_cb( sender, id, event )
877     map = current_map
878     return if not map
879     map.clear_selection
880   end
882   #
883   # Select all
884   #
885   def select_all_cb( sender, id, event )
886     map = current_map
887     return if not map
888     sect = map.sections[map.section]
889     sect.rooms.each { |r|
890       r.selected = true
891     }
892     sect.connections.each { |c|
893       c.selected = true
894     }
895   end
897   def roomlist(sender, sel, event)
898     map = current_map
899     return unless map
900     map.show_roomlist
901   end
904   def about_cb(sender, id, event )
905     require 'IFMapper/FXAboutDialogBox'
906     FXAboutDialogBox.new(self, MSG_ABOUT_SOFTWARE, 
907                          eval("\"#{MSG_ABOUT}\"")).execute
908   end
911   def create_menus
912     # Construct these icons
913     newdoc    = load_icon("filenew")
914     opendoc   = load_icon("fileopen")
915     savedoc   = load_icon("filesave")
916     saveasdoc = load_icon("filesaveas")
918     # File Menu
919     filemenu = FXMenuPane.new(self)
920     FXMenuTitle.new(@menubar, MENU_FILE, nil, filemenu)
921     cmd = FXMenuCommand.new(filemenu, MENU_NEW, newdoc)
922     cmd.connect(SEL_COMMAND, method(:new_map_cb))
924     cmd = FXMenuCommand.new(filemenu, MENU_OPEN, opendoc)
925     cmd.connect(SEL_COMMAND, method(:open_cb))
926     cmd = FXMenuCommand.new(filemenu, MENU_SAVE, savedoc)
927     cmd.connect(SEL_COMMAND, method(:save_cb))
928     cmd = FXMenuCommand.new(filemenu, MENU_SAVE_AS,
929                             saveasdoc)
930     cmd.connect(SEL_COMMAND, method(:save_as_cb))
932     # Export submenu
933     submenu = FXMenuPane.new(self)
935     cmd = FXMenuCommand.new(submenu, MENU_EXPORT_PDF, nil)
936     cmd.connect(SEL_COMMAND, method(:pdf_export_cb))
938     cmd = FXMenuCommand.new(submenu, MENU_EXPORT_IFM, nil)
939     cmd.connect(SEL_COMMAND, method(:ifm_export_cb))
941     cmd = FXMenuCommand.new(submenu, MENU_EXPORT_INFORM, nil)
942     cmd.connect(SEL_COMMAND, method(:inform_export_cb))
944     cmd = FXMenuCommand.new(submenu, MENU_EXPORT_TADS, nil)
945     cmd.connect(SEL_COMMAND, method(:tads_export_cb))
947     FXMenuCascade.new(filemenu, MENU_EXPORT, nil, submenu)
950     submenu = FXMenuPane.new(self)
951     cmd = FXMenuCommand.new(submenu, MENU_PRINT_MAP, nil)
952     cmd.connect(SEL_COMMAND, method(:print_cb))
954     cmd = FXMenuCommand.new(submenu, MENU_PRINT_LOCATIONS, nil)
955     cmd.connect(SEL_COMMAND, method(:print_locations_cb))
956     FXMenuCascade.new(filemenu, MENU_PRINT, nil, submenu)
958     cmd = FXMenuCommand.new(filemenu, MENU_QUIT, nil)
959     cmd.connect( SEL_COMMAND, method(:close_cb) )
961     # Edit Menu
962     editmenu = FXMenuPane.new(self)
963     FXMenuTitle.new(@menubar, MENU_EDIT, nil, editmenu)
964     cmd = FXMenuCommand.new(editmenu, MENU_COPY, nil)
965     cmd.connect(SEL_COMMAND, method(:copy_selected_cb))
966     cmd = FXMenuCommand.new(editmenu, MENU_CUT, nil)
967     cmd.connect(SEL_COMMAND, method(:cut_selected_cb))
968     cmd = FXMenuCommand.new(editmenu, MENU_PASTE, nil) 
969     cmd.connect(SEL_COMMAND, method(:paste_selected_cb))
971     # Select submenu
972     FXMenuSeparator.new(editmenu)
973     submenu = FXMenuPane.new(self)
974     cmd = FXMenuCommand.new( submenu, MENU_SELECT_ALL )
975     cmd.connect(SEL_COMMAND, method(:select_all_cb))
976     cmd = FXMenuCommand.new( submenu, MENU_SELECT_NONE )
977     cmd.connect(SEL_COMMAND, method(:select_none_cb))
978     FXMenuCascade.new( editmenu, MENU_SELECT, nil, submenu )
980     # Searching submenu
981     FXMenuSeparator.new(editmenu)
982     submenu = FXMenuPane.new(self)
983     cmd = FXMenuCommand.new(submenu, MENU_SEARCH_MAP)
984     cmd.connect(SEL_COMMAND, method(:find_in_map_cb))
985     cmd = FXMenuCommand.new(submenu, MENU_SEARCH_SECTION)
986     cmd.connect(SEL_COMMAND, method(:find_in_section_cb))
987     cmd = FXMenuCommand.new(submenu, MENU_SEARCH_OBJECT)
988     cmd.connect(SEL_COMMAND, method(:find_object_in_map_cb))
989     cmd = FXMenuCommand.new(submenu, MENU_SEARCH_TASK)
990     cmd.connect(SEL_COMMAND, method(:find_task_in_map_cb))
991     cmd = FXMenuCommand.new(submenu, MENU_SEARCH_DESCRIPTION)
992     cmd.connect(SEL_COMMAND, method(:find_desc_in_map_cb))
993     FXMenuCascade.new(editmenu, MENU_SEARCH, nil, submenu)
995     # Complex Connection
996     FXMenuSeparator.new(editmenu)
997     cmd = FXMenuCommand.new(editmenu, MENU_COMPLEX_CONNECTION, nil)
998     cmd.connect( SEL_COMMAND, method(:complex_connection_cb) )
1000     FXMenuSeparator.new(editmenu)
1001     cmd = FXMenuCommand.new(editmenu, MENU_DELETE, nil)
1002     cmd.connect( SEL_COMMAND, method(:delete_selected_cb) )
1004     # Map menu
1005     mapmenu = FXMenuPane.new(self)
1007     cmd = FXMenuCommand.new(mapmenu, MENU_MAP_INFO)
1008     cmd.connect(SEL_COMMAND) {  map_properties }
1010     cmd = FXMenuCommand.new(mapmenu, MENU_ROOM_LIST)
1011     cmd.connect(SEL_COMMAND, method(:roomlist) )
1013     # Automap submenu
1014     #
1015     submenu = FXMenuPane.new(self)
1016     cmd = FXMenuCommand.new(submenu, MENU_AUTOMAP_START)
1017     cmd.connect(SEL_COMMAND, method(:start_automap_cb))
1018     cmd = FXMenuCommand.new(submenu, MENU_AUTOMAP_STOP)
1019     cmd.connect(SEL_COMMAND, method(:stop_automap_cb))
1020     FXMenuSeparator.new(submenu)
1021     cmd = FXMenuCommand.new(submenu, MENU_AUTOMAP_PROPERTIES)
1022     cmd.connect(SEL_COMMAND, method(:automap_properties_cb))
1023     FXMenuCascade.new(mapmenu, MENU_AUTOMAP, nil, submenu)
1025     # Sections submenu
1026     submenu = FXMenuPane.new(self)
1027     cmd = FXMenuCommand.new(submenu, MENU_NEXT_SECTION)
1028     cmd.connect(SEL_COMMAND) { 
1029       next_section
1030     }
1031     cmd = FXMenuCommand.new(submenu, MENU_PREVIOUS_SECTION)
1032     cmd.connect(SEL_COMMAND) { 
1033       previous_section
1034     }
1035     FXMenuSeparator.new(submenu)
1036     cmd = FXMenuCommand.new(submenu, MENU_ADD_SECTION)
1037     cmd.connect(SEL_COMMAND) { 
1038       map = current_map
1039       if map
1040         map.new_section
1041         map.modified = true
1042         update_section
1043       end
1044     }
1045     cmd = FXMenuCommand.new(submenu, MENU_RENAME_SECTION)
1046     cmd.connect(SEL_COMMAND) { 
1047       map = current_map
1048       if map
1049         map.rename_section
1050         map.modified = true
1051       end
1052     }
1053     FXMenuSeparator.new(submenu)
1054     cmd = FXMenuCommand.new(submenu, MENU_DELETE_SECTION)
1055     cmd.connect(SEL_COMMAND) { 
1056       map = current_map
1057       if map
1058         map.delete_section
1059         map.modified = true
1060         update_section
1061       end
1062     }
1063     FXMenuCascade.new(mapmenu, MENU_SECTIONS, nil, submenu)
1064     
1065     #
1066     # Zoom submenu
1067     #
1068     submenu = FXMenuPane.new(self)
1069     [25, 50, 75, 100, 125].each { |v| 
1070       cmd = FXMenuCommand.new(submenu, eval("\"#{MENU_ZOOM_PERCENT}\""))
1071       cmd.connect(SEL_COMMAND) { 
1072         map = current_map
1073         if map
1074           map.zoom = v / 100.0
1075           map.draw
1076         end
1077       }
1078     }
1079     FXMenuCascade.new(mapmenu, MENU_ZOOM, nil, submenu)
1081     submenu = FXMenuPane.new(self)
1083     cmd = FXMenuCheck.new(submenu, MENU_EDIT_ON_CREATION)
1084     cmd.check = @@default_options['Edit on Creation']
1085     cmd.connect(SEL_COMMAND) { |s, m, e|
1086       map = current_map
1087       if map
1088         map.options['Edit on Creation'] = (s.check == true)
1089       end
1090     }
1091     cmd.connect(SEL_UPDATE) { |s, m, e|
1092       map = current_map
1093       s.check = map.options['Edit on Creation'] if map
1094     }
1096     cmd = FXMenuCheck.new(submenu, MENU_AUTOMATIC_CONNECTION)
1097     cmd.check = @@default_options['Automatic Connection']
1098     cmd.connect(SEL_COMMAND) { |s, m, e|
1099       map = current_map
1100       if map
1101         map.options['Automatic Connection'] = (s.check == true)
1102       end
1103     }
1104     cmd.connect(SEL_UPDATE) { |s, m, e|
1105       map = current_map
1106       s.check = map.options['Automatic Connection'] if map
1107     }
1109     cmd = FXMenuCheck.new(submenu, MENU_CREATE_ON_CONNECTION)
1110     cmd.check = @@default_options['Create on Connection']
1111     cmd.connect(SEL_COMMAND) { |s, m, e|
1112       map = current_map
1113       map.options['Create on Connection'] = s.check if map
1114     }
1115     cmd.connect(SEL_UPDATE) { |s, m, e|
1116       map = current_map
1117       s.check = map.options['Create on Connection'] if map
1118     }
1120     FXMenuCascade.new(mapmenu, MENU_OPTIONS, nil, submenu)
1122     ##########################
1123     # Display submenu
1124     #########################
1125     submenu = FXMenuPane.new(self)
1126     cmd = FXMenuCheck.new(submenu, MENU_USE_ROOM_CURSOR)
1127     cmd.check = @@default_options['Use Room Cursor']
1128     cmd.connect(SEL_COMMAND) { |s, m, e|
1129       map = current_map
1130       if map
1131         map.options['Use Room Cursor'] = (s.check == true)
1132         map.draw
1133       end
1134     }
1135     cmd.connect(SEL_UPDATE) { |s, m, e|
1136       map = current_map
1137       s.check = map.options['Use Room Cursor'] if map
1138     }
1140     cmd = FXMenuCheck.new(submenu, MENU_PATHS_AS_CURVES)
1141     cmd.check = @@default_options['Paths as Curves']
1142     cmd.connect(SEL_COMMAND) { |s, m, e|
1143       map = current_map
1144       if map
1145         map.options['Paths as Curves'] = (s.check == true)
1146         map.draw
1147       end
1148     }
1149     cmd.connect(SEL_UPDATE) { |s, m, e|
1150       map = current_map
1151       s.check = map.options['Paths as Curves'] if map
1152     }
1153     cmd = FXMenuCheck.new(submenu, MENU_LOCATION_NUMBERS)
1154     cmd.check = @@default_options['Location Numbers']
1155     cmd.connect(SEL_COMMAND) { |s, m, e|
1156       map = current_map
1157       if map
1158         map.options['Location Numbers'] = (s.check == true)
1159         map.draw
1160       end
1161     }
1162     cmd.connect(SEL_UPDATE) { |s, m, e|
1163       map = current_map
1164       s.check = map.options['Location Numbers'] if map
1165     }
1167     cmd = FXMenuCheck.new(submenu, MENU_LOCATION_TASKS)
1168     cmd.check = @@default_options['Location Tasks']
1169     cmd.connect(SEL_COMMAND) { |s, m, e|
1170       map = current_map
1171       if map
1172         map.options['Location Tasks'] = (s.check == true)
1173         map.draw
1174       end
1175     }
1176     cmd.connect(SEL_UPDATE) { |s, m, e|
1177       map = current_map
1178       s.check = map.options['Location Tasks'] if map
1179     }
1181     cmd = FXMenuCheck.new(submenu, MENU_LOCATION_DESC)
1182     cmd.check = @@default_options['Location Description']
1183     cmd.connect(SEL_COMMAND) { |s, m, e|
1184       map = current_map
1185       if map
1186         map.options['Location Description'] = (s.check == true)
1187         map.draw
1188       end
1189     }
1190     cmd.connect(SEL_UPDATE) { |s, m, e|
1191       map = current_map
1192       s.check = map.options['Location Description'] if map
1193     }
1195     cmd = FXMenuCheck.new(submenu, MENU_BOXES)
1196     cmd.check = @@default_options['Grid Boxes']
1197     cmd.connect(SEL_COMMAND) { |s, m, e|
1198       map = current_map
1199       if map
1200         map.options['Grid Boxes'] = (s.check == true)
1201         map.draw
1202       end
1203     }
1204     cmd.connect(SEL_UPDATE) { |s, m, e|
1205       map = current_map
1206       s.check = map.options['Grid Boxes'] if map
1207     }
1209     cmd = FXMenuCheck.new(submenu, MENU_STRAIGHT_CONN)
1210     cmd.check = @@default_options['Grid Straight Connections']
1211     cmd.connect(SEL_COMMAND) { |s, m, e|
1212       map = current_map
1213       if map
1214         map.options['Grid Straight Connections'] = (s.check == true)
1215         map.draw
1216       end
1217     }
1218     cmd.connect(SEL_UPDATE) { |s, m, e|
1219       map = current_map
1220       s.check = map.options['Grid Straight Connections'] if map
1221     }
1223     cmd = FXMenuCheck.new(submenu, MENU_DIAGONAL_CONN)
1224     cmd.check = @@default_options['Grid Diagonal Connections']
1225     cmd.connect(SEL_COMMAND) { |s, m, e|
1226       map = current_map
1227       if map
1228         map.options['Grid Diagonal Connections'] = s.check
1229         map.draw
1230       end
1231     }
1232     cmd.connect(SEL_UPDATE) { |s, m, e|
1233       map = current_map
1234       s.check = map.options['Grid Diagonal Connections'] if map
1235     }
1237     FXMenuCascade.new(mapmenu, MENU_DISPLAY, nil, submenu)
1239     submenu = FXMenuPane.new(self)
1241     cmd = FXMenuCommand.new(submenu, MENU_COLORS)
1242     cmd.connect(SEL_COMMAND, method(:colors_cb))
1244 #     langmenu = FXMenuPane.new(self)
1245 #     LANGUAGES.each { |k, v|
1246 #       next unless File.exists?( "lib/IFMapper/locales/#{v}/Messages.rb" )
1247 #       cmd = FXMenuCheck.new(langmenu, k)
1248 #       cmd.connect(SEL_COMMAND, method(:language_cb))
1249 #     }
1251 #     FXMenuCascade.new(mapmenu, MENU_LANGUAGE, nil, langmenu)
1252     
1254     FXMenuSeparator.new(submenu)
1255     cmd = FXMenuCommand.new(submenu, MENU_SAVE_PREFS)
1256     cmd.connect(SEL_COMMAND) { 
1257       map = current_map
1258       map.options.write if map
1259     }
1260     FXMenuCascade.new(mapmenu, MENU_PREFS, nil, submenu)
1262     FXMenuTitle.new(@menubar, MENU_MAP, nil, mapmenu)
1264     # Window menu
1265     windowmenu = FXMenuPane.new(self)
1266     FXMenuCommand.new(windowmenu, MENU_TILE_HORIZONTALLY, nil,
1267       @mdiclient, FXMDIClient::ID_MDI_TILEHORIZONTAL)
1268     FXMenuCommand.new(windowmenu, MENU_TILE_VERTICALLY, nil,
1269       @mdiclient, FXMDIClient::ID_MDI_TILEVERTICAL)
1270     FXMenuCommand.new(windowmenu, MENU_CASCADE, nil,
1271       @mdiclient, FXMDIClient::ID_MDI_CASCADE)
1272     FXMenuCommand.new(windowmenu, MENU_CLOSE, nil,
1273       @mdiclient, FXMDIClient::ID_MDI_CLOSE)
1274     sep1 = FXMenuSeparator.new(windowmenu)
1275     sep1.setTarget(@mdiclient)
1276     sep1.setSelector(FXMDIClient::ID_MDI_ANY)
1277     FXMenuCommand.new(windowmenu, nil, nil, @mdiclient, FXMDIClient::ID_MDI_1)
1278     FXMenuCommand.new(windowmenu, nil, nil, @mdiclient, FXMDIClient::ID_MDI_2)
1279     FXMenuCommand.new(windowmenu, nil, nil, @mdiclient, FXMDIClient::ID_MDI_3)
1280     FXMenuCommand.new(windowmenu, nil, nil, @mdiclient, FXMDIClient::ID_MDI_4)
1281     FXMenuCommand.new(windowmenu, MENU_OTHERS, nil, @mdiclient, 
1282                       FXMDIClient::ID_MDI_OVER_5)
1283     FXMenuTitle.new(@menubar, MENU_WINDOW, nil, windowmenu)
1285     # Help menu
1286     helpmenu = FXMenuPane.new(self)
1287     cmd = FXMenuCommand.new(helpmenu, MENU_HOTKEYS, nil)
1288     cmd.connect(SEL_COMMAND, method(:hotkeys))
1289     cmd = FXMenuCommand.new(helpmenu, MENU_INSTRUCTIONS, nil)
1290     cmd.connect(SEL_COMMAND, method(:docs))
1291     FXMenuSeparator.new(helpmenu)
1292     cmd = FXMenuCommand.new(helpmenu, MENU_ABOUT, nil)
1293     cmd.connect(SEL_COMMAND, method(:about_cb))
1295     cmd = FXMenuCommand.new(helpmenu, MENU_RESOURCE, nil)
1296     cmd.connect(SEL_COMMAND) {
1297       require 'IFMapper/FXMapFileDialog'
1298       file = FXMapFileDialog.new(self, "Resource a Ruby File", 
1299                                  ['Ruby File (*.rb)']).filename
1300       if file != ''
1301         begin
1302           Kernel.load file
1303         rescue => e
1304           p e
1305         end
1306       end
1307     }
1308     FXMenuTitle.new(@menubar, MENU_HELP, nil, helpmenu)
1309   end
1311   def language
1312     return @@default_options['Language']
1313   end
1315   def docs(*opts)
1316     browsers = [ 'firefox', 'opera', 'explorer' ]
1317     address  = 'docs/' + language + '/start.html'
1318     status "#{MSG_OPENING_WEB_PAGE} #{address}..."
1319     ok = false
1320     browsers.each { |cmd|
1321       if RUBY_PLATFORM =~ /mswin/
1322         ok = system("start #{cmd} #{address}")
1323       else
1324         ok = system("#{cmd} #{address} &")
1325       end
1326       break if ok
1327     }
1328     if not ok
1329       status ERR_COULD_NOT_OPEN_WEB_BROWSER
1330     end
1331   end
1334   def hotkeys(*opts)
1335     require 'IFMapper/FXAboutDialogBox'
1336     FXAboutDialogBox.new(self, BOX_HOTKEYS, MSG_HOTKEYS).show
1337   end
1339   def create_toolbar(toolbar)
1341     # Construct these icons
1342     newdoc = load_icon("filenew")
1343     opendoc = load_icon("fileopen")
1344     savedoc = load_icon("filesave")
1345     saveasdoc = load_icon("filesaveas")
1347     # File manipulation
1348     cmd = FXButton.new(toolbar, ICON_NEW, newdoc, nil, 0,
1349                        FRAME_THICK|FRAME_RAISED|LAYOUT_TOP|LAYOUT_LEFT)
1350     cmd.connect(SEL_COMMAND, method(:new_map_cb))
1352     cmd = FXButton.new(toolbar, ICON_OPEN, opendoc, nil, 0,
1353       FRAME_THICK|FRAME_RAISED|LAYOUT_TOP|LAYOUT_LEFT)
1354     cmd.connect(SEL_COMMAND, method(:open_cb))
1356     cmd = FXButton.new(toolbar, ICON_SAVE, savedoc, nil, 0,
1357       FRAME_THICK|FRAME_RAISED|LAYOUT_TOP|LAYOUT_LEFT)
1358     cmd.connect(SEL_COMMAND, method(:save_cb))
1359     cmd.connect(SEL_UPDATE) { |sender, sel, ptr|
1360       map = current_map
1361       message = map ? FXWindow::ID_ENABLE : FXWindow::ID_DISABLE
1362       sender.handle(self, MKUINT(message, SEL_COMMAND), nil)
1363     }
1364     cmd = FXButton.new(toolbar, ICON_SAVE_AS,
1365       saveasdoc, nil, 0, FRAME_THICK|FRAME_RAISED|LAYOUT_TOP|LAYOUT_LEFT)
1366     cmd.connect(SEL_COMMAND, method(:save_as_cb))
1367     cmd.connect(SEL_UPDATE) { |sender, sel, ptr|
1368       map = current_map
1369       message = map ? FXWindow::ID_ENABLE : FXWindow::ID_DISABLE
1370       sender.handle(self, MKUINT(message, SEL_COMMAND), nil)
1371     }
1373     # Print
1374     FXFrame.new(toolbar,
1375       LAYOUT_TOP|LAYOUT_LEFT|LAYOUT_FIX_WIDTH|LAYOUT_FIX_HEIGHT, 0, 0, 4, 20)
1376     cmd = FXButton.new(toolbar, ICON_PRINT,
1377                        load_icon("printicon"), @mdiclient, FXGLViewer::ID_PRINT_IMAGE,
1378                        BUTTON_AUTOGRAY|FRAME_THICK|FRAME_RAISED|LAYOUT_TOP|LAYOUT_LEFT)
1379     cmd.connect(SEL_COMMAND, method(:print_cb))
1380     cmd.connect(SEL_UPDATE) { |sender, sel, ptr|
1381       map = current_map
1382       message = map ? FXWindow::ID_ENABLE : FXWindow::ID_DISABLE
1383       sender.handle(self, MKUINT(message, SEL_COMMAND), nil)
1384     }
1385   
1386     # Editing
1387     FXFrame.new(toolbar,
1388       LAYOUT_TOP|LAYOUT_LEFT|LAYOUT_FIX_WIDTH|LAYOUT_FIX_HEIGHT, 0, 0, 4, 20)
1389     cmd = FXButton.new(toolbar, ICON_CUT, load_icon("cut"), @mdiclient,
1390       FXGLViewer::ID_CUT_SEL, (BUTTON_AUTOGRAY|FRAME_THICK|FRAME_RAISED|
1391       LAYOUT_TOP|LAYOUT_LEFT))
1392     cmd.connect(SEL_COMMAND, method(:cut_selected_cb))
1393     cmd.connect(SEL_UPDATE) { |sender, sel, ptr|
1394       map = current_map
1395       message = map ? FXWindow::ID_ENABLE : FXWindow::ID_DISABLE
1396       sender.handle(self, MKUINT(message, SEL_COMMAND), nil)
1397     }
1398     cmd = FXButton.new(toolbar, ICON_COPY, load_icon("copy"), @mdiclient,
1399       FXGLViewer::ID_COPY_SEL, (BUTTON_AUTOGRAY|FRAME_THICK|FRAME_RAISED|
1400       LAYOUT_TOP|LAYOUT_LEFT))
1401     cmd.connect(SEL_COMMAND, method(:copy_selected_cb))
1402     cmd.connect(SEL_UPDATE) { |sender, sel, ptr|
1403       map = current_map
1404       message = map ? FXWindow::ID_ENABLE : FXWindow::ID_DISABLE
1405       sender.handle(self, MKUINT(message, SEL_COMMAND), nil)
1406     }
1407     cmd = FXButton.new(toolbar, ICON_PASTE, load_icon("paste"), @mdiclient,
1408       FXGLViewer::ID_PASTE_SEL, (BUTTON_AUTOGRAY|FRAME_THICK|FRAME_RAISED|
1409       LAYOUT_TOP|LAYOUT_LEFT))
1410     cmd.connect(SEL_COMMAND, method(:paste_selected_cb))
1411     cmd.connect(SEL_UPDATE) { |sender, sel, ptr|
1412       map = current_map
1413       message = (map and @@copy_buffer) ? FXWindow::ID_ENABLE : FXWindow::ID_DISABLE
1414       sender.handle(self, MKUINT(message, SEL_COMMAND), nil)
1415     }
1417     # Zooming
1418     FXFrame.new(toolbar,
1419       LAYOUT_TOP|LAYOUT_LEFT|LAYOUT_FIX_WIDTH|LAYOUT_FIX_HEIGHT, 0, 0, 4, 20)
1420     cmd = FXButton.new(toolbar, ICON_ZOOM_IN, load_icon("zoom"), @mdiclient,
1421                        0, FRAME_THICK|FRAME_RAISED|LAYOUT_TOP|LAYOUT_LEFT)
1422     cmd.connect(SEL_COMMAND) { zoom_in }
1424     cmd = FXButton.new(toolbar, ICON_ZOOM_OUT, load_icon("zoom"), @mdiclient, 
1425                        0, FRAME_THICK|FRAME_RAISED|LAYOUT_TOP|LAYOUT_LEFT)
1426     cmd.connect(SEL_COMMAND) { zoom_out }
1429     # Section travel
1430     frame = FXHorizontalFrame.new(toolbar,
1431                                   LAYOUT_RIGHT|FRAME_THICK|FRAME_RAISED)
1432     cmd = FXButton.new(frame, ICON_PREV_SECTION, load_icon("prevpage"), 
1433                        @mdiclient,
1434                        0, FRAME_THICK|FRAME_RAISED|LAYOUT_TOP|LAYOUT_LEFT)
1435     cmd.connect(SEL_COMMAND) { previous_section }
1437     @section = FXTextField.new(frame, 5, nil, 0, 
1438                             TEXTFIELD_INTEGER|LAYOUT_FILL_ROW)
1439     @section.text = '1'
1440     @section.connect(SEL_COMMAND) { |s,m,e| 
1441       v = s.text.to_i
1442       map = current_map
1443       if map
1444         map.section = v - 1
1445         map.draw
1446         update_section
1447       end
1448     }
1449     @section.connect(SEL_UPDATE) {  |s,m,e| 
1450       v = s.text.to_i
1451       map = current_map
1452       update_section if map
1453     }
1455     cmd = FXButton.new(frame, ICON_NEXT_SECTION, load_icon("nextpage"), 
1456                        @mdiclient,
1457                        0, FRAME_THICK|FRAME_RAISED|LAYOUT_TOP|LAYOUT_LEFT)
1458     cmd.connect(SEL_COMMAND) { next_section }    
1459   end
1461   #
1462   # Update section # in toolbar widget
1463   #
1464   def update_section
1465     map = current_map
1466     return unless map
1467     @section.text = (map.section + 1).to_s
1468   end
1470   #
1471   # Go to next section in current map
1472   #
1473   def next_section
1474     map = current_map
1475     map.next_section if map
1476     update_section
1477   end
1479   #
1480   # Go to previous section in current map
1481   #
1482   def previous_section 
1483     map = current_map
1484     map.previous_section if map
1485     update_section
1486   end
1488   #
1489   # Zoom in into current map
1490   #
1491   def zoom_in
1492     map = current_map
1493     if map
1494       map.zoom_in
1495       map.draw
1496     end
1497   end
1499   #
1500   # Zoom out from current map
1501   #
1502   def zoom_out
1503     map = current_map
1504     if map
1505       map.zoom_out
1506       map.draw
1507     end
1508   end
1510   #
1511   # Bring up the map property requester for current map
1512   #
1513   def map_properties
1514     map = current_map
1515     map.properties if map
1516   end
1518   #
1519   # In case of crash or runtime error, autosave all maps, so user
1520   # does not loose any data.
1521   #
1522   def autosave
1523     @maps.each { |m|
1524       m.save
1525     }
1526   end
1529   def create_widgets
1530     # Menubar
1531     @menubar = FXMenuBar.new(self, LAYOUT_SIDE_TOP|LAYOUT_FILL_X)
1533     FXHorizontalSeparator.new(self,
1534                               LAYOUT_SIDE_TOP|SEPARATOR_GROOVE|LAYOUT_FILL_X)
1535     toolbar = FXToolBar.new(self, LAYOUT_SIDE_TOP|LAYOUT_FILL_X,
1536       0, 0, 0, 0, 4, 4, 0, 0, 0, 0)
1537   
1538     # Status bar
1539     @statusbar = FXStatusBar.new(self,
1540                                  LAYOUT_SIDE_BOTTOM|LAYOUT_FILL_X|
1541                                  STATUSBAR_WITH_DRAGCORNER)
1544     create_mdiclient
1545     create_menus
1546     create_toolbar(toolbar)
1547     new_map
1549     self.connect(SEL_CLOSE, method(:close_cb))
1550     show
1551   end
1553   def initialize(app)
1554     super(app, eval("\"#{TITLE}\""), nil, nil, DECOR_ALL, 0, 0, 800, 600)
1555   
1556     @colors = nil
1557     @mdimenu = nil
1559     create_widgets
1561     # Trap CTRL-C signals and exit nicely
1562     trap('SIGINT') {
1563       close_cb
1564       exit(0)
1565     }
1566   end
1568   def close_cb(*args)
1569       exit = true
1570       @maps.each { |m| 
1571         if not m.close_cb
1572           exit = false
1573           break
1574         else
1575           @maps.delete(m)
1576         end
1577       }
1578       self.close if exit
1579   end