/[thuban]/branches/WIP-pyshapelib-bramz/Thuban/UI/mainwindow.py
ViewVC logotype

Annotation of /branches/WIP-pyshapelib-bramz/Thuban/UI/mainwindow.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1293 - (hide annotations)
Mon Jun 23 13:05:58 2003 UTC (21 years, 8 months ago) by jonathan
Original Path: trunk/thuban/Thuban/UI/mainwindow.py
File MIME type: text/x-python
File size: 43836 byte(s)
Add blank line at the end because
        file was not being read correctly building the Windows
        distribution.

1 bh 535 # Copyright (C) 2001, 2002, 2003 by Intevation GmbH
2 bh 6 # Authors:
3     # Jan-Oliver Wagner <[email protected]>
4     # Bernhard Herzog <[email protected]>
5 frank 911 # Frank Koormann <[email protected]>
6 bh 6 #
7     # This program is free software under the GPL (>=v2)
8     # Read the file COPYING coming with Thuban for details.
9    
10     """
11     The main window
12     """
13    
14     __version__ = "$Revision$"
15    
16 jonathan 1287 __ThubanVersion__ = "0.8" #"$THUBAN_0_2$"
17 jonathan 517 #__BuildDate__ = "$Date$"
18    
19 bh 188 import os
20 bh 1094 import copy
21 bh 6
22     from wxPython.wx import *
23 frank 923 from wxPython.wx import __version__ as wxPython_version
24 bh 6
25     import Thuban
26 frank 923 import Thuban.version
27    
28 jan 374 from Thuban import _
29 bh 188 from Thuban.Model.session import create_empty_session
30 jonathan 937 from Thuban.Model.layer import Layer, RasterLayer
31 bh 6
32 jan 1155 # XXX: replace this by
33     # from wxPython.lib.dialogs import wxMultipleChoiceDialog
34     # when Thuban does not support wxPython 2.4.0 any more.
35     from Thuban.UI.multiplechoicedialog import wxMultipleChoiceDialog
36    
37 bh 6 import view
38     import tree
39     import tableview, identifyview
40 jonathan 633 from Thuban.UI.classifier import Classifier
41 jonathan 550 import legend
42 bh 188 from menu import Menu
43 bh 6
44 bh 222 from context import Context
45 bh 357 from command import registry, Command, ToolCommand
46 bh 704 from messages import LAYER_SELECTED, SHAPES_SELECTED, VIEW_POSITION
47 bh 6
48 bh 704 from Thuban.UI.dock import DockFrame
49 jonathan 879 from Thuban.UI.join import JoinDialog
50 bh 6
51 jonathan 653 import resource
52 jonathan 1164 import Thuban.Model.resource
53 jonathan 563
54 jonathan 713 import projdialog
55 jonathan 653
56 jonathan 573 class MainWindow(DockFrame):
57 bh 6
58 bh 535 # Some messages that can be subscribed/unsubscribed directly through
59     # the MapCanvas come in fact from other objects. This is a map to
60     # map those messages to the names of the instance variables they
61     # actually come from. This delegation is implemented in the
62     # Subscribe and unsubscribed methods
63     delegated_messages = {LAYER_SELECTED: "canvas",
64     SHAPES_SELECTED: "canvas"}
65    
66     # Methods delegated to some instance variables. The delegation is
67     # implemented in the __getattr__ method.
68     delegated_methods = {"SelectLayer": "canvas",
69     "SelectShapes": "canvas",
70 bh 1074 "SelectedLayer": "canvas",
71 jonathan 879 "SelectedShapes": "canvas",
72 bh 535 }
73    
74 bh 235 def __init__(self, parent, ID, title, application, interactor,
75 bh 238 initial_message = None, size = wxSize(-1, -1)):
76 jonathan 573 DockFrame.__init__(self, parent, ID, title, wxDefaultPosition, size)
77     #wxFrame.__init__(self, parent, ID, title, wxDefaultPosition, size)
78 bh 6
79 bh 227 self.application = application
80 bh 37
81 bh 6 self.CreateStatusBar()
82 bh 235 if initial_message:
83     self.SetStatusText(initial_message)
84 bh 6
85     self.identify_view = None
86    
87     self.init_ids()
88    
89 bh 191 # creat the menubar from the main_menu description
90 bh 188 self.SetMenuBar(self.build_menu_bar(main_menu))
91 bh 6
92 bh 191 # Similarly, create the toolbar from main_toolbar
93     toolbar = self.build_toolbar(main_toolbar)
94 bh 13 # call Realize to make sure that the tools appear.
95     toolbar.Realize()
96 bh 6
97 jonathan 563
98 bh 6 # Create the map canvas
99 bh 535 canvas = view.MapCanvas(self, -1)
100 bh 123 canvas.Subscribe(VIEW_POSITION, self.view_position_changed)
101 bh 535 canvas.Subscribe(SHAPES_SELECTED, self.identify_view_on_demand)
102 bh 6 self.canvas = canvas
103    
104 jonathan 573 self.SetMainWindow(self.canvas)
105    
106 jonathan 563 self.SetAutoLayout(True)
107    
108 bh 31 self.init_dialogs()
109    
110 jonathan 1233 self.ShowLegend()
111    
112 frank 951 EVT_CLOSE(self, self.OnClose)
113 bh 6
114 bh 535 def Subscribe(self, channel, *args):
115     """Subscribe a function to a message channel.
116    
117     If channel is one of the delegated messages call the appropriate
118     object's Subscribe method. Otherwise do nothing.
119     """
120     if channel in self.delegated_messages:
121     object = getattr(self, self.delegated_messages[channel])
122     object.Subscribe(channel, *args)
123     else:
124     print "Trying to subscribe to unsupported channel %s" % channel
125    
126     def Unsubscribe(self, channel, *args):
127     """Unsubscribe a function from a message channel.
128    
129     If channel is one of the delegated messages call the appropriate
130     object's Unsubscribe method. Otherwise do nothing.
131     """
132     if channel in self.delegated_messages:
133     object = getattr(self, self.delegated_messages[channel])
134     object.Unsubscribe(channel, *args)
135    
136     def __getattr__(self, attr):
137     """If attr is one of the delegated methods return that method
138    
139     Otherwise raise AttributeError.
140     """
141     if attr in self.delegated_methods:
142     return getattr(getattr(self, self.delegated_methods[attr]), attr)
143     raise AttributeError(attr)
144    
145 bh 6 def init_ids(self):
146     """Initialize the ids"""
147     self.current_id = 6000
148     self.id_to_name = {}
149     self.name_to_id = {}
150 bh 193 self.events_bound = {}
151 bh 6
152     def get_id(self, name):
153     """Return the wxWindows id for the command named name.
154    
155     Create a new one if there isn't one yet"""
156     ID = self.name_to_id.get(name)
157     if ID is None:
158     ID = self.current_id
159     self.current_id = self.current_id + 1
160     self.name_to_id[name] = ID
161     self.id_to_name[ID] = name
162     return ID
163 bh 188
164 bh 193 def bind_command_events(self, command, ID):
165     """Bind the necessary events for the given command and ID"""
166     if not self.events_bound.has_key(ID):
167     # the events haven't been bound yet
168     EVT_MENU(self, ID, self.invoke_command)
169     if command.IsDynamic():
170     EVT_UPDATE_UI(self, ID, self.update_command_ui)
171    
172 bh 188 def build_menu_bar(self, menudesc):
173     """Build and return the menu bar from the menu description"""
174     menu_bar = wxMenuBar()
175    
176     for item in menudesc.items:
177     # here the items must all be Menu instances themselves
178     menu_bar.Append(self.build_menu(item), item.title)
179    
180     return menu_bar
181    
182     def build_menu(self, menudesc):
183 bh 314 """Return a wxMenu built from the menu description menudesc"""
184 bh 188 wxmenu = wxMenu()
185     last = None
186     for item in menudesc.items:
187     if item is None:
188     # a separator. Only add one if the last item was not a
189     # separator
190     if last is not None:
191     wxmenu.AppendSeparator()
192     elif isinstance(item, Menu):
193     # a submenu
194     wxmenu.AppendMenu(wxNewId(), item.title, self.build_menu(item))
195     else:
196     # must the name the name of a command
197     self.add_menu_command(wxmenu, item)
198     last = item
199     return wxmenu
200    
201 bh 191 def build_toolbar(self, toolbardesc):
202     """Build and return the main toolbar window from a toolbar description
203    
204     The parameter should be an instance of the Menu class but it
205     should not contain submenus.
206     """
207     toolbar = self.CreateToolBar(wxTB_3DBUTTONS)
208    
209     # set the size of the tools' bitmaps. Not needed on wxGTK, but
210     # on Windows, although it doesn't work very well there. It seems
211     # that only 16x16 icons are really supported on windows.
212     # We probably shouldn't hardwire the bitmap size here.
213     toolbar.SetToolBitmapSize(wxSize(24, 24))
214    
215     for item in toolbardesc.items:
216     if item is None:
217     toolbar.AddSeparator()
218     else:
219     # assume it's a string.
220     self.add_toolbar_command(toolbar, item)
221    
222     return toolbar
223    
224 bh 6 def add_menu_command(self, menu, name):
225     """Add the command with name name to the menu menu.
226    
227     If name is None, add a separator.
228     """
229     if name is None:
230     menu.AppendSeparator()
231     else:
232     command = registry.Command(name)
233     if command is not None:
234     ID = self.get_id(name)
235     menu.Append(ID, command.Title(), command.HelpText(),
236     command.IsCheckCommand())
237 bh 193 self.bind_command_events(command, ID)
238 bh 6 else:
239 jan 374 print _("Unknown command %s") % name
240 bh 6
241     def add_toolbar_command(self, toolbar, name):
242     """Add the command with name name to the toolbar toolbar.
243    
244     If name is None, add a separator.
245     """
246     # Assume that all toolbar commands are also menu commmands so
247     # that we don't have to add the event handlers here
248     if name is None:
249     toolbar.AddSeparator()
250     else:
251     command = registry.Command(name)
252     if command is not None:
253     ID = self.get_id(name)
254 jonathan 653 bitmap = resource.GetBitmapResource(command.Icon(),
255     wxBITMAP_TYPE_XPM)
256 bh 6 toolbar.AddTool(ID, bitmap,
257     shortHelpString = command.HelpText(),
258     isToggle = command.IsCheckCommand())
259 bh 193 self.bind_command_events(command, ID)
260 bh 6 else:
261 jan 374 print _("Unknown command %s") % name
262 bh 6
263 bh 281 def Context(self):
264     """Return the context object for a command invoked from this window
265     """
266     return Context(self.application, self.application.Session(), self)
267    
268 bh 6 def invoke_command(self, event):
269     name = self.id_to_name.get(event.GetId())
270     if name is not None:
271     command = registry.Command(name)
272 bh 281 command.Execute(self.Context())
273 bh 6 else:
274 jan 374 print _("Unknown command ID %d") % event.GetId()
275 bh 6
276     def update_command_ui(self, event):
277     #print "update_command_ui", self.id_to_name[event.GetId()]
278 bh 281 context = self.Context()
279 bh 6 command = registry.Command(self.id_to_name[event.GetId()])
280     if command is not None:
281 bh 357 sensitive = command.Sensitive(context)
282     event.Enable(sensitive)
283     if command.IsTool() and not sensitive and command.Checked(context):
284     # When a checked tool command is disabled deselect all
285     # tools. Otherwise the tool would remain active but it
286     # might lead to errors if the tools stays active. This
287     # problem occurred in GREAT-ER and this fixes it, but
288     # it's not clear to me whether this is really the best
289     # way to do it (BH, 20021206).
290     self.canvas.SelectTool(None)
291 bh 222 event.SetText(command.DynText(context))
292 bh 13 if command.IsCheckCommand():
293 bh 357 event.Check(command.Checked(context))
294 bh 6
295 bh 20 def RunMessageBox(self, title, text, flags = wxOK | wxICON_INFORMATION):
296 bh 181 """Run a modal message box with the given text, title and flags
297 bh 20 and return the result"""
298     dlg = wxMessageDialog(self, text, title, flags)
299 bh 316 dlg.CenterOnParent()
300 bh 20 result = dlg.ShowModal()
301     dlg.Destroy()
302     return result
303    
304 bh 31 def init_dialogs(self):
305     """Initialize the dialog handling"""
306     # The mainwindow maintains a dict mapping names to open
307     # non-modal dialogs. The dialogs are put into this dict when
308     # they're created and removed when they're closed
309     self.dialogs = {}
310    
311     def add_dialog(self, name, dialog):
312     if self.dialogs.has_key(name):
313 jan 374 raise RuntimeError(_("The Dialog named %s is already open") % name)
314 bh 31 self.dialogs[name] = dialog
315    
316     def dialog_open(self, name):
317     return self.dialogs.has_key(name)
318    
319     def remove_dialog(self, name):
320     del self.dialogs[name]
321    
322     def get_open_dialog(self, name):
323     return self.dialogs.get(name)
324    
325 bh 123 def view_position_changed(self):
326     pos = self.canvas.CurrentPosition()
327     if pos is not None:
328     text = "(%10.10g, %10.10g)" % pos
329     else:
330     text = ""
331 bh 321 self.set_position_text(text)
332    
333     def set_position_text(self, text):
334     """Set the statusbar text showing the current position.
335    
336     By default the text is shown in field 0 of the status bar.
337     Override this method in derived classes to put it into a
338     different field of the statusbar.
339     """
340 bh 123 self.SetStatusText(text)
341    
342 bh 58 def save_modified_session(self, can_veto = 1):
343     """If the current session has been modified, ask the user
344     whether to save it and do so if requested. Return the outcome of
345     the dialog (either wxID_OK, wxID_CANCEL or wxID_NO). If the
346     dialog wasn't run return wxID_NO.
347    
348     If the can_veto parameter is true (default) the dialog includes
349     a cancel button, otherwise not.
350     """
351 bh 227 if self.application.session.WasModified():
352 bh 58 flags = wxYES_NO | wxICON_QUESTION
353     if can_veto:
354     flags = flags | wxCANCEL
355 jan 374 result = self.RunMessageBox(_("Exit"),
356     _("The session has been modified."
357 bh 58 " Do you want to save it?"),
358     flags)
359     if result == wxID_YES:
360     self.SaveSession()
361     else:
362     result = wxID_NO
363     return result
364    
365 jonathan 487 def prepare_new_session(self):
366     for d in self.dialogs.values():
367     if not isinstance(d, tree.SessionTreeView):
368 jonathan 502 d.Close()
369 jonathan 487
370 bh 6 def NewSession(self):
371 jonathan 937 if self.save_modified_session() != wxID_CANCEL:
372     self.prepare_new_session()
373     self.application.SetSession(create_empty_session())
374 bh 6
375     def OpenSession(self):
376 jonathan 937 if self.save_modified_session() != wxID_CANCEL:
377     dlg = wxFileDialog(self, _("Open Session"), ".", "",
378     "Thuban Session File (*.thuban)|*.thuban",
379     wxOPEN)
380     if dlg.ShowModal() == wxID_OK:
381     self.prepare_new_session()
382     self.application.OpenSession(dlg.GetPath())
383     dlg.Destroy()
384 bh 6
385     def SaveSession(self):
386 bh 227 if self.application.session.filename == None:
387 jan 102 self.SaveSessionAs()
388 jonathan 487 else:
389     self.application.SaveSession()
390 bh 6
391     def SaveSessionAs(self):
392 jonathan 431 dlg = wxFileDialog(self, _("Save Session As"), ".", "",
393 jonathan 879 "Thuban Session File (*.thuban)|*.thuban",
394     wxSAVE|wxOVERWRITE_PROMPT)
395 bh 6 if dlg.ShowModal() == wxID_OK:
396 bh 227 self.application.session.SetFilename(dlg.GetPath())
397     self.application.SaveSession()
398 bh 6 dlg.Destroy()
399    
400     def Exit(self):
401 jonathan 621 self.Close(False)
402 bh 6
403 frank 951 def OnClose(self, event):
404 bh 58 result = self.save_modified_session(can_veto = event.CanVeto())
405     if result == wxID_CANCEL:
406 bh 6 event.Veto()
407     else:
408 bh 307 # FIXME: it would be better to tie the unsubscription to
409     # wx's destroy event, but that isn't implemented for wxGTK
410     # yet.
411     self.canvas.Unsubscribe(VIEW_POSITION, self.view_position_changed)
412 jonathan 974 DockFrame.OnClose(self, event)
413 frank 1056 for dlg in self.dialogs.values():
414     dlg.Destroy()
415     self.canvas.Destroy()
416 bh 6 self.Destroy()
417    
418     def SetMap(self, map):
419     self.canvas.SetMap(map)
420 jonathan 653 self.__SetTitle(map.Title())
421 bh 6
422 jonathan 768 dialog = self.FindRegisteredDock("legend")
423     if dialog is not None:
424     dialog.GetPanel().SetMap(self.Map())
425    
426 bh 310 def Map(self):
427     """Return the map displayed by this mainwindow"""
428 jonathan 563
429 bh 310 return self.canvas.Map()
430    
431 bh 622 def ToggleSessionTree(self):
432     """If the session tree is shown close it otherwise create a new tree"""
433 bh 37 name = "session_tree"
434     dialog = self.get_open_dialog(name)
435     if dialog is None:
436 bh 227 dialog = tree.SessionTreeView(self, self.application, name)
437 bh 37 self.add_dialog(name, dialog)
438 jonathan 512 dialog.Show(True)
439 bh 37 else:
440 bh 622 dialog.Close()
441 bh 37
442 bh 622 def SessionTreeShown(self):
443     """Return true iff the session tree is currently shown"""
444     return self.get_open_dialog("session_tree") is not None
445 jonathan 517
446 bh 6 def About(self):
447 jan 374 self.RunMessageBox(_("About"),
448 jonathan 1287 _("Thuban %s\n\n"
449 jonathan 517 #"Build Date: %s\n"
450 jonathan 1287 "Currently using:\n"
451 frank 923 " %s\n"
452     " %s\n\n"
453 jonathan 517 "Thuban is a program for\n"
454 bh 20 "exploring geographic data.\n"
455 jan 374 "Copyright (C) 2001-2003 Intevation GmbH.\n"
456 jonathan 517 "Thuban is licensed under the GNU GPL"
457 frank 923 % (Thuban.version.longversion,
458     "wxPython %s" % wxPython_version,
459     "Python %d.%d.%d" % sys.version_info[:3]
460     )),
461     # % __ThubanVersion__), #__BuildDate__)),
462 bh 20 wxOK | wxICON_INFORMATION)
463 bh 6
464     def AddLayer(self):
465 jan 374 dlg = wxFileDialog(self, _("Select a data file"), ".", "", "*.*",
466 bh 6 wxOPEN)
467     if dlg.ShowModal() == wxID_OK:
468     filename = dlg.GetPath()
469     title = os.path.splitext(os.path.basename(filename))[0]
470 bh 18 map = self.canvas.Map()
471     has_layers = map.HasLayers()
472 bh 20 try:
473 jonathan 963 store = self.application.Session().OpenShapefile(filename)
474 bh 20 except IOError:
475     # the layer couldn't be opened
476 jan 374 self.RunMessageBox(_("Add Layer"),
477     _("Can't open the file '%s'.") % filename)
478 bh 20 else:
479 jonathan 963 layer = Layer(title, store)
480     map.AddLayer(layer)
481 bh 20 if not has_layers:
482 bh 535 # if we're adding a layer to an empty map, fit the
483 bh 20 # new map to the window
484     self.canvas.FitMapToWindow()
485 bh 6 dlg.Destroy()
486    
487 jonathan 937 def AddRasterLayer(self):
488     dlg = wxFileDialog(self, _("Select an image file"), ".", "", "*.*",
489     wxOPEN)
490     if dlg.ShowModal() == wxID_OK:
491     filename = dlg.GetPath()
492     title = os.path.splitext(os.path.basename(filename))[0]
493     map = self.canvas.Map()
494     has_layers = map.HasLayers()
495     try:
496 jonathan 963 layer = RasterLayer(title, filename)
497 jonathan 937 except IOError:
498     # the layer couldn't be opened
499     self.RunMessageBox(_("Add Image Layer"),
500     _("Can't open the file '%s'.") % filename)
501     else:
502 jonathan 963 map.AddLayer(layer)
503 jonathan 937 if not has_layers:
504     # if we're adding a layer to an empty map, fit the
505     # new map to the window
506     self.canvas.FitMapToWindow()
507     dlg.Destroy()
508    
509 bh 6 def RemoveLayer(self):
510     layer = self.current_layer()
511     if layer is not None:
512     self.canvas.Map().RemoveLayer(layer)
513    
514 bh 299 def CanRemoveLayer(self):
515     """Return true if the currently selected layer can be deleted.
516    
517 jonathan 621 If no layer is selected return False.
518 bh 299
519     The return value of this method determines whether the remove
520     layer command is sensitive in menu.
521     """
522     layer = self.current_layer()
523     if layer is not None:
524     return self.canvas.Map().CanRemoveLayer(layer)
525 jonathan 621 return False
526 bh 299
527 bh 6 def RaiseLayer(self):
528     layer = self.current_layer()
529     if layer is not None:
530     self.canvas.Map().RaiseLayer(layer)
531 bh 222
532 bh 6 def LowerLayer(self):
533     layer = self.current_layer()
534     if layer is not None:
535     self.canvas.Map().LowerLayer(layer)
536    
537     def current_layer(self):
538     """Return the currently selected layer.
539    
540     If no layer is selected, return None
541     """
542 bh 535 return self.canvas.SelectedLayer()
543 bh 6
544     def has_selected_layer(self):
545     """Return true if a layer is currently selected"""
546 bh 535 return self.canvas.HasSelectedLayer()
547 bh 6
548 jonathan 829 def has_selected_shapes(self):
549     """Return true if a shape is currently selected"""
550     return self.canvas.HasSelectedShapes()
551    
552 bh 6 def HideLayer(self):
553     layer = self.current_layer()
554     if layer is not None:
555     layer.SetVisible(0)
556 bh 1094
557 bh 6 def ShowLayer(self):
558     layer = self.current_layer()
559     if layer is not None:
560     layer.SetVisible(1)
561    
562 bh 1094 def DuplicateLayer(self):
563     """Ceate a new layer above the selected layer with the same shapestore
564     """
565     layer = self.current_layer()
566     if layer is not None and hasattr(layer, "ShapeStore"):
567     new_layer = Layer(_("Copy of `%s'") % layer.Title(),
568     layer.ShapeStore(),
569     projection = layer.GetProjection())
570     new_classification = copy.deepcopy(layer.GetClassification())
571     new_layer.SetClassification(new_classification)
572     self.Map().AddLayer(new_layer)
573    
574     def CanDuplicateLayer(self):
575     """Return whether the DuplicateLayer method can create a duplicate"""
576     layer = self.current_layer()
577     return layer is not None and hasattr(layer, "ShapeStore")
578    
579 bh 6 def LayerShowTable(self):
580     layer = self.current_layer()
581     if layer is not None:
582 bh 1219 table = layer.ShapeStore().Table()
583 bh 31 name = "table_view" + str(id(table))
584     dialog = self.get_open_dialog(name)
585     if dialog is None:
586 bh 535 dialog = tableview.LayerTableFrame(self, name,
587 jan 1023 _("Layer Table: %s") % layer.Title(),
588     layer, table)
589 bh 31 self.add_dialog(name, dialog)
590 jan 1035 dialog.Show(True)
591 bh 31 else:
592     # FIXME: bring dialog to front here
593     pass
594 bh 6
595 jonathan 729 def MapProjection(self):
596 bh 6
597 jonathan 729 name = "map_projection"
598 jonathan 713 dialog = self.get_open_dialog(name)
599    
600     if dialog is None:
601     map = self.canvas.Map()
602 jonathan 750 dialog = projdialog.ProjFrame(self, name,
603     _("Map Projection: %s") % map.Title(), map)
604 jonathan 713 self.add_dialog(name, dialog)
605     dialog.Show()
606     dialog.Raise()
607    
608 jonathan 729 def LayerProjection(self):
609    
610     layer = self.current_layer()
611    
612     name = "layer_projection" + str(id(layer))
613     dialog = self.get_open_dialog(name)
614    
615     if dialog is None:
616     map = self.canvas.Map()
617 jonathan 750 dialog = projdialog.ProjFrame(self, name,
618     _("Layer Projection: %s") % layer.Title(), layer)
619 jonathan 729 self.add_dialog(name, dialog)
620     dialog.Show()
621     dialog.Raise()
622    
623 jonathan 640 def LayerEditProperties(self):
624 jonathan 363
625 jonathan 487 #
626     # the menu option for this should only be available if there
627     # is a current layer, so we don't need to check if the
628     # current layer is None
629     #
630    
631     layer = self.current_layer()
632 jonathan 640 self.OpenLayerProperties(layer)
633 jonathan 550
634 jonathan 640 def OpenLayerProperties(self, layer, group = None):
635     name = "layer_properties" + str(id(layer))
636 jonathan 487 dialog = self.get_open_dialog(name)
637    
638     if dialog is None:
639 bh 1142 dialog = Classifier(self, name, self.Map(), layer, group)
640 jonathan 487 self.add_dialog(name, dialog)
641     dialog.Show()
642 jonathan 573 dialog.Raise()
643 jonathan 487
644 jonathan 879 def LayerJoinTable(self):
645 bh 1072 layer = self.canvas.SelectedLayer()
646     if layer is not None:
647     dlg = JoinDialog(self, _("Join Layer with Table"),
648     self.application.session,
649     layer = layer)
650     dlg.ShowModal()
651 jonathan 550
652 jonathan 879 def LayerUnjoinTable(self):
653 bh 1074 layer = self.canvas.SelectedLayer()
654     if layer is not None:
655     orig_store = layer.ShapeStore().OrigShapeStore()
656     if orig_store:
657     layer.SetShapeStore(orig_store)
658 jonathan 879
659 jonathan 621 def ShowLegend(self):
660 bh 622 if not self.LegendShown():
661     self.ToggleLegend()
662    
663     def ToggleLegend(self):
664     """Show the legend if it's not shown otherwise hide it again"""
665 jonathan 550 name = "legend"
666 jonathan 573 dialog = self.FindRegisteredDock(name)
667 jonathan 550
668     if dialog is None:
669 jonathan 640 dialog = self.CreateDock(name, -1, _("Legend"), wxLAYOUT_LEFT)
670 jonathan 573 legend.LegendPanel(dialog, None, self)
671 jonathan 580 dialog.Dock()
672 bh 622 dialog.GetPanel().SetMap(self.Map())
673     dialog.Show()
674     else:
675     dialog.Show(not dialog.IsShown())
676 jonathan 563
677 jonathan 1108 self.canvas.FitMapToWindow()
678    
679 bh 622 def LegendShown(self):
680     """Return true iff the legend is currently open"""
681     dialog = self.FindRegisteredDock("legend")
682     return dialog is not None and dialog.IsShown()
683 jonathan 563
684 jonathan 879 def TableOpen(self):
685     dlg = wxFileDialog(self, _("Open Table"), ".", "",
686 bh 1037 _("DBF Files (*.dbf)") + "|*.dbf|" +
687     #_("CSV Files (*.csv)") + "|*.csv|" +
688     _("All Files (*.*)") + "|*.*",
689 jonathan 879 wxOPEN)
690     if dlg.ShowModal() == wxID_OK:
691 bh 1054 filename = dlg.GetPath()
692     dlg.Destroy()
693     try:
694     table = self.application.session.OpenTableFile(filename)
695     except IOError:
696     # the layer couldn't be opened
697     self.RunMessageBox(_("Open Table"),
698     _("Can't open the file '%s'.") % filename)
699     else:
700     self.ShowTableView(table)
701 jonathan 879
702     def TableClose(self):
703 bh 1068 tables = self.application.session.UnreferencedTables()
704 jonathan 879
705 jan 1084 lst = [(t.Title(), t) for t in tables]
706     lst.sort()
707     titles = [i[0] for i in lst]
708 bh 1068 dlg = wxMultipleChoiceDialog(self, _("Pick the tables to close:"),
709 jan 1084 _("Close Table"), titles,
710     size = (400, 300),
711     style = wxDEFAULT_DIALOG_STYLE |
712     wxRESIZE_BORDER)
713 bh 1068 if dlg.ShowModal() == wxID_OK:
714     for i in dlg.GetValue():
715 jan 1084 self.application.session.RemoveTable(lst[i][1])
716 bh 1068
717    
718 jonathan 879 def TableShow(self):
719 jan 1014 """Offer a multi-selection dialog for tables to be displayed
720 bh 1054
721 jan 1014 The windows for the selected tables are opened or brought to
722     the front.
723     """
724     tables = self.application.session.Tables()
725 jonathan 879
726 jan 1084 lst = [(t.Title(), t) for t in tables]
727     lst.sort()
728     titles = [i[0] for i in lst]
729 jan 1014 dlg = wxMultipleChoiceDialog(self, _("Pick the table to show:"),
730 jan 1084 _("Show Table"), titles,
731 frank 1076 size = (400,300),
732     style = wxDEFAULT_DIALOG_STYLE |
733     wxRESIZE_BORDER)
734 jan 1014 if (dlg.ShowModal() == wxID_OK):
735     for i in dlg.GetValue():
736 jan 1023 # XXX: if the table belongs to a layer, open a
737     # LayerTableFrame instead of QueryTableFrame
738 jan 1084 self.ShowTableView(lst[i][1])
739 jan 1014
740 jonathan 879 def TableJoin(self):
741     dlg = JoinDialog(self, _("Join Tables"), self.application.session)
742 frank 1001 dlg.ShowModal()
743 jonathan 879
744 bh 1054 def ShowTableView(self, table):
745     """Open a table view for the table and optionally"""
746     name = "table_view%d" % id(table)
747     dialog = self.get_open_dialog(name)
748     if dialog is None:
749     dialog = tableview.QueryTableFrame(self, name,
750     _("Table: %s") % table.Title(),
751     table)
752     self.add_dialog(name, dialog)
753     dialog.Show(True)
754     # FIXME: else bring dialog to front
755    
756 bh 1126 def TableRename(self):
757     """Let the user rename a table"""
758    
759     # First, let the user select a table
760     tables = self.application.session.Tables()
761     lst = [(t.Title(), t) for t in tables]
762     lst.sort()
763     titles = [i[0] for i in lst]
764     dlg = wxMultipleChoiceDialog(self, _("Pick the table to rename:"),
765     _("Rename Table"), titles,
766     size = (400,300),
767     style = wxDEFAULT_DIALOG_STYLE |
768     wxRESIZE_BORDER)
769     if (dlg.ShowModal() == wxID_OK):
770     to_rename = [lst[i][1] for i in dlg.GetValue()]
771     dlg.Destroy()
772     else:
773     to_rename = []
774    
775     # Second, let the user rename the layers
776     for table in to_rename:
777     dlg = wxTextEntryDialog(self, "Table Title: ", "Rename Table",
778     table.Title())
779     try:
780     if dlg.ShowModal() == wxID_OK:
781     title = dlg.GetValue()
782     if title != "":
783     table.SetTitle(title)
784    
785     # Make sure the session is marked as modified.
786     # FIXME: This should be handled automatically,
787     # but that requires more changes to the tables
788     # than I have time for currently.
789     self.application.session.changed()
790     finally:
791     dlg.Destroy()
792    
793    
794 bh 6 def ZoomInTool(self):
795     self.canvas.ZoomInTool()
796    
797     def ZoomOutTool(self):
798     self.canvas.ZoomOutTool()
799    
800     def PanTool(self):
801     self.canvas.PanTool()
802    
803     def IdentifyTool(self):
804     self.canvas.IdentifyTool()
805 bh 49 self.identify_view_on_demand(None, None)
806 bh 6
807     def LabelTool(self):
808     self.canvas.LabelTool()
809    
810     def FullExtent(self):
811     self.canvas.FitMapToWindow()
812    
813 jonathan 821 def FullLayerExtent(self):
814     self.canvas.FitLayerToWindow(self.current_layer())
815    
816 jonathan 829 def FullSelectionExtent(self):
817     self.canvas.FitSelectedToWindow()
818    
819 frank 911 def ExportMap(self):
820     self.canvas.Export()
821    
822 bh 6 def PrintMap(self):
823     self.canvas.Print()
824    
825 jonathan 653 def RenameMap(self):
826     dlg = wxTextEntryDialog(self, "Map Title: ", "Rename Map",
827     self.Map().Title())
828     if dlg.ShowModal() == wxID_OK:
829     title = dlg.GetValue()
830     if title != "":
831     self.Map().SetTitle(title)
832     self.__SetTitle(title)
833    
834     dlg.Destroy()
835    
836 bh 1126 def RenameLayer(self):
837     """Let the user rename the currently selected layer"""
838     layer = self.current_layer()
839     if layer is not None:
840     dlg = wxTextEntryDialog(self, "Layer Title: ", "Rename Layer",
841     layer.Title())
842     try:
843     if dlg.ShowModal() == wxID_OK:
844     title = dlg.GetValue()
845     if title != "":
846     layer.SetTitle(title)
847     finally:
848     dlg.Destroy()
849    
850 bh 535 def identify_view_on_demand(self, layer, shapes):
851 bh 787 """Subscribed to the canvas' SHAPES_SELECTED message
852    
853     If the current tool is the identify tool, at least one shape is
854     selected and the identify dialog is not shown, show the dialog.
855     """
856     # If the selection has become empty we don't need to do
857     # anything. Otherwise it could happen that the dialog was popped
858     # up when the selection became empty, e.g. when a new selection
859     # is opened while the identify tool is active and dialog had
860     # been closed
861     if not shapes:
862     return
863    
864 bh 31 name = "identify_view"
865     if self.canvas.CurrentTool() == "IdentifyTool":
866     if not self.dialog_open(name):
867 bh 535 dialog = identifyview.IdentifyView(self, name)
868 bh 31 self.add_dialog(name, dialog)
869 jonathan 563 dialog.Show(True)
870 bh 31 else:
871 bh 33 # FIXME: bring dialog to front?
872 bh 31 pass
873 bh 6
874 jonathan 653 def __SetTitle(self, title):
875     self.SetTitle("Thuban - " + title)
876    
877 bh 6 #
878     # Define all the commands available in the main window
879     #
880    
881    
882     # Helper functions to define common command implementations
883     def call_method(context, methodname, *args):
884 bh 222 """Call the mainwindow's method methodname with args *args"""
885     apply(getattr(context.mainwindow, methodname), args)
886 bh 6
887 jan 110 def _method_command(name, title, method, helptext = "",
888 bh 622 icon = "", sensitive = None, checked = None):
889 bh 222 """Add a command implemented by a method of the mainwindow object"""
890 bh 6 registry.Add(Command(name, title, call_method, args=(method,),
891 jan 110 helptext = helptext, icon = icon,
892 bh 622 sensitive = sensitive, checked = checked))
893 jan 110
894 bh 270 def make_check_current_tool(toolname):
895     """Return a function that tests if the currently active tool is toolname
896    
897     The returned function can be called with the context and returns
898     true iff the currently active tool's name is toolname. It's directly
899     usable as the 'checked' callback of a command.
900     """
901     def check_current_tool(context, name=toolname):
902     return context.mainwindow.canvas.CurrentTool() == name
903     return check_current_tool
904    
905 bh 6 def _tool_command(name, title, method, toolname, helptext = "",
906 bh 310 icon = "", sensitive = None):
907 bh 6 """Add a tool command"""
908 bh 357 registry.Add(ToolCommand(name, title, call_method, args=(method,),
909     helptext = helptext, icon = icon,
910     checked = make_check_current_tool(toolname),
911     sensitive = sensitive))
912 bh 6
913     def _has_selected_layer(context):
914     """Return true if a layer is selected in the context"""
915 bh 222 return context.mainwindow.has_selected_layer()
916 bh 6
917 jonathan 829 def _has_selected_shapes(context):
918     """Return true if a layer is selected in the context"""
919     return context.mainwindow.has_selected_shapes()
920    
921 bh 299 def _can_remove_layer(context):
922     return context.mainwindow.CanRemoveLayer()
923    
924 jan 264 def _has_tree_window_shown(context):
925     """Return true if the tree window is shown"""
926 bh 622 return context.mainwindow.SessionTreeShown()
927 jan 264
928 bh 310 def _has_visible_map(context):
929     """Return true iff theres a visible map in the mainwindow.
930    
931     A visible map is a map with at least one visible layer."""
932     map = context.mainwindow.Map()
933     if map is not None:
934     for layer in map.Layers():
935     if layer.Visible():
936     return 1
937     return 0
938    
939 jonathan 550 def _has_legend_shown(context):
940     """Return true if the legend window is shown"""
941 bh 622 return context.mainwindow.LegendShown()
942 bh 310
943 jonathan 1164 def _has_gdal_support(context):
944     """Return True if the GDAL is available"""
945     return Thuban.Model.resource.has_gdal_support()
946 jonathan 550
947 bh 6 # File menu
948 jan 1140 _method_command("new_session", _("&New Session"), "NewSession",
949     helptext = _("Start a new session"))
950     _method_command("open_session", _("&Open Session..."), "OpenSession",
951     helptext = _("Open a session file"))
952     _method_command("save_session", _("&Save Session"), "SaveSession",
953     helptext =_("Save this session to the file it was opened from"))
954     _method_command("save_session_as", _("Save Session &As..."), "SaveSessionAs",
955     helptext = _("Save this session to a new file"))
956 bh 622 _method_command("toggle_session_tree", _("Session &Tree"), "ToggleSessionTree",
957 jan 1140 checked = _has_tree_window_shown,
958     helptext = _("Toggle on/off the session tree analysis window"))
959 bh 622 _method_command("toggle_legend", _("Legend"), "ToggleLegend",
960 jan 1140 checked = _has_legend_shown,
961     helptext = _("Toggle Legend on/off"))
962     _method_command("exit", _("E&xit"), "Exit",
963     helptext = _("Finish working with Thuban"))
964 bh 6
965     # Help menu
966 jan 1140 _method_command("help_about", _("&About..."), "About",
967     helptext = _("Info about Thuban authors, version and modules"))
968 bh 6
969    
970     # Map menu
971 jan 1140 _method_command("map_projection", _("Pro&jection..."), "MapProjection",
972     helptext = _("Set or change the map projection"))
973 bh 6
974 jan 374 _tool_command("map_zoom_in_tool", _("&Zoom in"), "ZoomInTool", "ZoomInTool",
975     helptext = _("Switch to map-mode 'zoom-in'"), icon = "zoom_in",
976 bh 310 sensitive = _has_visible_map)
977 jan 374 _tool_command("map_zoom_out_tool", _("Zoom &out"), "ZoomOutTool", "ZoomOutTool",
978     helptext = _("Switch to map-mode 'zoom-out'"), icon = "zoom_out",
979 bh 310 sensitive = _has_visible_map)
980 jan 374 _tool_command("map_pan_tool", _("&Pan"), "PanTool", "PanTool",
981     helptext = _("Switch to map-mode 'pan'"), icon = "pan",
982 bh 310 sensitive = _has_visible_map)
983 jan 374 _tool_command("map_identify_tool", _("&Identify"), "IdentifyTool",
984     "IdentifyTool",
985     helptext = _("Switch to map-mode 'identify'"), icon = "identify",
986 bh 310 sensitive = _has_visible_map)
987 jan 374 _tool_command("map_label_tool", _("&Label"), "LabelTool", "LabelTool",
988     helptext = _("Add/Remove labels"), icon = "label",
989 bh 310 sensitive = _has_visible_map)
990 jan 374 _method_command("map_full_extent", _("&Full extent"), "FullExtent",
991 jan 1140 helptext = _("Zoom to the full map extent"), icon = "fullextent",
992 bh 310 sensitive = _has_visible_map)
993 jonathan 821 _method_command("layer_full_extent", _("&Full layer extent"), "FullLayerExtent",
994 jan 1140 helptext = _("Zoom to the full layer extent"),
995     icon = "fulllayerextent", sensitive = _has_selected_layer)
996     _method_command("selected_full_extent", _("&Full selection extent"),
997     "FullSelectionExtent",
998     helptext = _("Zoom to the full selection extent"),
999     icon = "fullselextent", sensitive = _has_selected_shapes)
1000 frank 911 _method_command("map_export", _("E&xport"), "ExportMap",
1001 jan 1140 helptext = _("Export the map to file"))
1002 jan 374 _method_command("map_print", _("Prin&t"), "PrintMap",
1003     helptext = _("Print the map"))
1004 jonathan 815 _method_command("map_rename", _("&Rename..."), "RenameMap",
1005 jonathan 653 helptext = _("Rename the map"))
1006 jonathan 815 _method_command("layer_add", _("&Add Layer..."), "AddLayer",
1007 jan 1140 helptext = _("Add a new layer to the map"))
1008 jonathan 937 _method_command("rasterlayer_add", _("&Add Image Layer..."), "AddRasterLayer",
1009 jonathan 1164 helptext = _("Add a new image layer to the map"),
1010     sensitive = _has_gdal_support)
1011 jan 374 _method_command("layer_remove", _("&Remove Layer"), "RemoveLayer",
1012 jan 1140 helptext = _("Remove selected layer"),
1013 bh 299 sensitive = _can_remove_layer)
1014 jonathan 729
1015     # Layer menu
1016 jonathan 815 _method_command("layer_projection", _("Pro&jection..."), "LayerProjection",
1017 jan 1140 sensitive = _has_selected_layer,
1018     helptext = _("Specify projection for selected layer"))
1019 bh 1094 _method_command("layer_duplicate", _("&Duplicate"), "DuplicateLayer",
1020 jan 1140 helptext = _("Duplicate selected layer"),
1021 bh 1094 sensitive = lambda context: context.mainwindow.CanDuplicateLayer())
1022 bh 1126 _method_command("layer_rename", _("Re&name ..."), "RenameLayer",
1023     helptext = _("Rename selected layer"),
1024     sensitive = _has_selected_layer)
1025 jan 374 _method_command("layer_raise", _("&Raise"), "RaiseLayer",
1026 jan 1140 helptext = _("Raise selected layer"),
1027 bh 6 sensitive = _has_selected_layer)
1028 jan 374 _method_command("layer_lower", _("&Lower"), "LowerLayer",
1029 jan 1140 helptext = _("Lower selected layer"),
1030 bh 6 sensitive = _has_selected_layer)
1031 jan 374 _method_command("layer_show", _("&Show"), "ShowLayer",
1032 jan 1140 helptext = _("Make selected layer visible"),
1033 bh 6 sensitive = _has_selected_layer)
1034 jan 374 _method_command("layer_hide", _("&Hide"), "HideLayer",
1035 jan 1140 helptext = _("Make selected layer unvisible"),
1036 bh 6 sensitive = _has_selected_layer)
1037 jan 374 _method_command("layer_show_table", _("Show Ta&ble"), "LayerShowTable",
1038     helptext = _("Show the selected layer's table"),
1039 bh 6 sensitive = _has_selected_layer)
1040 jonathan 815 _method_command("layer_properties", _("&Properties..."), "LayerEditProperties",
1041 jan 1140 sensitive = _has_selected_layer,
1042     helptext = _("Edit the properties of the selected layer"))
1043 jonathan 879 _method_command("layer_jointable", _("&Join Table..."), "LayerJoinTable",
1044 jan 1140 sensitive = _has_selected_layer,
1045     helptext = _("Join and attach a table to the selected layer"))
1046 bh 1074
1047     def _can_unjoin(context):
1048 bh 1080 """Return whether the Layer/Unjoin command can be executed.
1049    
1050     This is the case if a layer is selected and that layer has a
1051     shapestore that has an original shapestore.
1052     """
1053 bh 1074 layer = context.mainwindow.SelectedLayer()
1054 bh 1080 if layer is None:
1055     return 0
1056     getstore = getattr(layer, "ShapeStore", None)
1057     if getstore is not None:
1058     return getstore().OrigShapeStore() is not None
1059     else:
1060     return 0
1061 jonathan 879 _method_command("layer_unjointable", _("&Unjoin Table..."), "LayerUnjoinTable",
1062 jan 1140 sensitive = _can_unjoin,
1063     helptext = _("Undo the last join operation"))
1064 bh 188
1065 bh 1126
1066     def _has_tables(context):
1067     return bool(context.session.Tables())
1068    
1069 jonathan 879 # Table menu
1070 jan 1140 _method_command("table_open", _("&Open..."), "TableOpen",
1071     helptext = _("Open a DBF-table from a file"))
1072     _method_command("table_close", _("&Close..."), "TableClose",
1073     sensitive = lambda context: bool(context.session.UnreferencedTables()),
1074     helptext = _("Close one or more tables from a list"))
1075 bh 1126 _method_command("table_rename", _("&Rename..."), "TableRename",
1076 jan 1140 sensitive = _has_tables,
1077     helptext = _("Rename one or more tables"))
1078     _method_command("table_show", _("&Show..."), "TableShow",
1079     sensitive = _has_tables,
1080     helptext = _("Show one or more tables in a dialog"))
1081 bh 1126 _method_command("table_join", _("&Join..."), "TableJoin",
1082 jan 1140 sensitive = _has_tables,
1083     helptext = _("Join two tables creating a new one"))
1084 jonathan 879
1085 frank 911 # Export only under Windows ...
1086 jonathan 1164 map_menu = ["layer_add", "rasterlayer_add", "layer_remove",
1087 bh 188 None,
1088 jonathan 1164 "map_rename",
1089 bh 188 "map_projection",
1090     None,
1091     "map_zoom_in_tool", "map_zoom_out_tool",
1092 jonathan 829 "map_pan_tool",
1093     "map_full_extent",
1094     "layer_full_extent",
1095     "selected_full_extent",
1096 bh 188 None,
1097 jonathan 815 "map_identify_tool", "map_label_tool",
1098 bh 188 None,
1099 bh 624 "toggle_legend",
1100 frank 911 None]
1101     if wxPlatform == '__WXMSW__':
1102     map_menu.append("map_export")
1103     map_menu.append("map_print")
1104    
1105     # the menu structure
1106     main_menu = Menu("<main>", "<main>",
1107     [Menu("file", _("&File"),
1108     ["new_session", "open_session", None,
1109     "save_session", "save_session_as", None,
1110     "toggle_session_tree", None,
1111     "exit"]),
1112     Menu("map", _("&Map"), map_menu),
1113 jan 374 Menu("layer", _("&Layer"),
1114 bh 1126 ["layer_rename", "layer_duplicate",
1115 bh 188 None,
1116 bh 1126 "layer_raise", "layer_lower",
1117     None,
1118 bh 188 "layer_show", "layer_hide",
1119     None,
1120 jonathan 879 "layer_projection",
1121     None,
1122 jonathan 363 "layer_show_table",
1123 jonathan 879 "layer_jointable",
1124     "layer_unjointable",
1125 jonathan 363 None,
1126 jonathan 815 "layer_properties"]),
1127 jonathan 879 Menu("table", _("&Table"),
1128 bh 1126 ["table_open", "table_close", "table_rename",
1129 jonathan 879 None,
1130 bh 1052 "table_show",
1131 jonathan 879 None,
1132     "table_join"]),
1133 jan 374 Menu("help", _("&Help"),
1134 bh 188 ["help_about"])])
1135 bh 191
1136     # the main toolbar
1137    
1138     main_toolbar = Menu("<toolbar>", "<toolbar>",
1139 frank 351 ["map_zoom_in_tool", "map_zoom_out_tool", "map_pan_tool",
1140 jonathan 829 "map_full_extent",
1141     "layer_full_extent",
1142     "selected_full_extent",
1143     None,
1144 frank 351 "map_identify_tool", "map_label_tool"])
1145 jonathan 1293

Properties

Name Value
svn:eol-style native
svn:keywords Author Date Id Revision

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26