/[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 1142 - (hide annotations)
Tue Jun 10 09:41:57 2003 UTC (21 years, 9 months ago) by bh
Original Path: trunk/thuban/Thuban/UI/mainwindow.py
File MIME type: text/x-python
File size: 43404 byte(s)
* Thuban/Model/messages.py (LAYER_SHAPESTORE_REPLACED): New
message.

* Thuban/Model/layer.py (Layer.SetShapeStore): Send
LAYER_SHAPESTORE_REPLACED when the shapestore changes. A
LAYER_CHANGED will still be sent if the classification changes.

* Thuban/UI/classifier.py (Classifier.__init__): Add the map as
parameter so we can subscribe to some of its messages
(Classifier.__init__): Subscribe to the map's MAP_LAYERS_REMOVED
and the layer's LAYER_SHAPESTORE_REPLACED
(Classifier.unsubscribe_messages): New. Unsubscribe from message
subscribed to in __init__
(Classifier.map_layers_removed)
(Classifier.layer_shapestore_replaced): receivers for the messages
subscribed to in __init__. Unsubscribe and close the dialog

* Thuban/UI/mainwindow.py (MainWindow.OpenLayerProperties): Pass
the map to the Classifier dialog

* test/test_layer.py (SetShapeStoreTests): Derive from
SubscriberMixin as well so we can test messages
(SetShapeStoreTests.setUp): Subscribe to some of the layer's
messages
(SetShapeStoreTests.tearDown): Clear the messages again
(SetShapeStoreTests.test_sanity): Expand the doc-string and check
for the modified flag too
(SetShapeStoreTests.test_set_shape_store_modified_flag): New test
to check whether SetShapeStore sets the modified flag
(SetShapeStoreTests.test_set_shape_store_different_field_name)
(SetShapeStoreTests.test_set_shape_store_same_field)
(SetShapeStoreTests.test_set_shape_store_same_field_different_type):
Add tests for the messages. This checks both the new
LAYER_SHAPESTORE_REPLACED and the older LAYER_CHANGED

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26