/[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 1464 - (hide annotations)
Fri Jul 18 18:20:40 2003 UTC (21 years, 7 months ago) by bh
Original Path: trunk/thuban/Thuban/UI/mainwindow.py
File MIME type: text/x-python
File size: 42839 byte(s)
* Thuban/UI/messages.py (MAP_REPLACED): New message.

* Thuban/UI/viewport.py (ViewPort.SetMap): Issue MAP_REPLACED
after the new map has been assigned

* Thuban/UI/mainwindow.py (MainWindow.delegated_messages):
Delegate MAP_REPLACED to the canvas too
(MainWindow.prepare_new_session): Removed. Thanks to the new
MAP_REPLACED message it's no longer needed
(MainWindow.OpenSession, MainWindow.NewSession):
prepare_new_session has been removed.

* Thuban/UI/classifier.py (Classifier.__init__): Subscribe to
MAP_REPLACED so that we can close the dialog if a new map is set.
(Classifier.unsubscribe_messages): Unsubscribe from MAP_REPLACED
(Classifier.map_replaced): Handle MAP_REPLACED by closing the
dialog

* test/test_viewport.py (SimpleViewPortTest)
(SimpleViewPortTest.test_default_size): Add doc-strings
(ViewPortTest.setUp): Bind map to self.map so we can use it in
tests. Subscribe to MAP_REPLACED messages too.
(ViewPortTest.tearDown): No need to explicitly unsubscribe
(ViewPortTest.test_set_map): New test for the SetMap method.

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