/[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 1448 - (hide annotations)
Thu Jul 17 14:59:41 2003 UTC (21 years, 7 months ago) by jonathan
Original Path: trunk/thuban/Thuban/UI/mainwindow.py
File MIME type: text/x-python
File size: 43008 byte(s)
(MainWindow.ToggleLegend): Don't
        fit the map to the window as this changes any zoom level that
        the user may have set.

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