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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1142 - (show 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 # Copyright (C) 2001, 2002, 2003 by Intevation GmbH
2 # Authors:
3 # Jan-Oliver Wagner <[email protected]>
4 # Bernhard Herzog <[email protected]>
5 # Frank Koormann <[email protected]>
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 __ThubanVersion__ = "0.2" #"$THUBAN_0_2$"
17 #__BuildDate__ = "$Date$"
18
19 import os
20 import copy
21
22 from wxPython.wx import *
23 from wxPython.wx import __version__ as wxPython_version
24
25 from wxPython.lib.dialogs import wxMultipleChoiceDialog
26
27 import Thuban
28 import Thuban.version
29
30 from Thuban import _
31 from Thuban.Model.session import create_empty_session
32 from Thuban.Model.layer import Layer, RasterLayer
33
34 import view
35 import tree
36 import tableview, identifyview
37 from Thuban.UI.classifier import Classifier
38 import legend
39 from menu import Menu
40
41 from context import Context
42 from command import registry, Command, ToolCommand
43 from messages import LAYER_SELECTED, SHAPES_SELECTED, VIEW_POSITION
44
45 from Thuban.UI.dock import DockFrame
46 from Thuban.UI.join import JoinDialog
47
48 import resource
49
50 import projdialog
51
52
53
54 class MainWindow(DockFrame):
55
56 # 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 "SelectedLayer": "canvas",
69 "SelectedShapes": "canvas",
70 }
71
72 def __init__(self, parent, ID, title, application, interactor,
73 initial_message = None, size = wxSize(-1, -1)):
74 DockFrame.__init__(self, parent, ID, title, wxDefaultPosition, size)
75 #wxFrame.__init__(self, parent, ID, title, wxDefaultPosition, size)
76
77 self.application = application
78
79 self.CreateStatusBar()
80 if initial_message:
81 self.SetStatusText(initial_message)
82
83 self.identify_view = None
84
85 self.init_ids()
86
87 # creat the menubar from the main_menu description
88 self.SetMenuBar(self.build_menu_bar(main_menu))
89
90 # Similarly, create the toolbar from main_toolbar
91 toolbar = self.build_toolbar(main_toolbar)
92 # call Realize to make sure that the tools appear.
93 toolbar.Realize()
94
95
96 # Create the map canvas
97 canvas = view.MapCanvas(self, -1)
98 canvas.Subscribe(VIEW_POSITION, self.view_position_changed)
99 canvas.Subscribe(SHAPES_SELECTED, self.identify_view_on_demand)
100 self.canvas = canvas
101
102 self.SetMainWindow(self.canvas)
103
104 self.SetAutoLayout(True)
105
106 self.init_dialogs()
107
108 EVT_CLOSE(self, self.OnClose)
109
110 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 def init_ids(self):
142 """Initialize the ids"""
143 self.current_id = 6000
144 self.id_to_name = {}
145 self.name_to_id = {}
146 self.events_bound = {}
147
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
160 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 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 """Return a wxMenu built from the menu description menudesc"""
180 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 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 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 self.bind_command_events(command, ID)
234 else:
235 print _("Unknown command %s") % name
236
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 bitmap = resource.GetBitmapResource(command.Icon(),
251 wxBITMAP_TYPE_XPM)
252 toolbar.AddTool(ID, bitmap,
253 shortHelpString = command.HelpText(),
254 isToggle = command.IsCheckCommand())
255 self.bind_command_events(command, ID)
256 else:
257 print _("Unknown command %s") % name
258
259 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 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 command.Execute(self.Context())
269 else:
270 print _("Unknown command ID %d") % event.GetId()
271
272 def update_command_ui(self, event):
273 #print "update_command_ui", self.id_to_name[event.GetId()]
274 context = self.Context()
275 command = registry.Command(self.id_to_name[event.GetId()])
276 if command is not None:
277 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 event.SetText(command.DynText(context))
288 if command.IsCheckCommand():
289 event.Check(command.Checked(context))
290
291 def RunMessageBox(self, title, text, flags = wxOK | wxICON_INFORMATION):
292 """Run a modal message box with the given text, title and flags
293 and return the result"""
294 dlg = wxMessageDialog(self, text, title, flags)
295 dlg.CenterOnParent()
296 result = dlg.ShowModal()
297 dlg.Destroy()
298 return result
299
300 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 raise RuntimeError(_("The Dialog named %s is already open") % name)
310 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 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 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 self.SetStatusText(text)
337
338 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 if self.application.session.WasModified():
348 flags = wxYES_NO | wxICON_QUESTION
349 if can_veto:
350 flags = flags | wxCANCEL
351 result = self.RunMessageBox(_("Exit"),
352 _("The session has been modified."
353 " 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 def prepare_new_session(self):
362 for d in self.dialogs.values():
363 if not isinstance(d, tree.SessionTreeView):
364 d.Close()
365
366 def NewSession(self):
367 if self.save_modified_session() != wxID_CANCEL:
368 self.prepare_new_session()
369 self.application.SetSession(create_empty_session())
370
371 def OpenSession(self):
372 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
381 def SaveSession(self):
382 if self.application.session.filename == None:
383 self.SaveSessionAs()
384 else:
385 self.application.SaveSession()
386
387 def SaveSessionAs(self):
388 dlg = wxFileDialog(self, _("Save Session As"), ".", "",
389 "Thuban Session File (*.thuban)|*.thuban",
390 wxSAVE|wxOVERWRITE_PROMPT)
391 if dlg.ShowModal() == wxID_OK:
392 self.application.session.SetFilename(dlg.GetPath())
393 self.application.SaveSession()
394 dlg.Destroy()
395
396 def Exit(self):
397 self.Close(False)
398
399 def OnClose(self, event):
400 result = self.save_modified_session(can_veto = event.CanVeto())
401 if result == wxID_CANCEL:
402 event.Veto()
403 else:
404 # 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 DockFrame.OnClose(self, event)
409 for dlg in self.dialogs.values():
410 dlg.Destroy()
411 self.canvas.Destroy()
412 self.Destroy()
413
414 def SetMap(self, map):
415 self.canvas.SetMap(map)
416 self.__SetTitle(map.Title())
417
418 dialog = self.FindRegisteredDock("legend")
419 if dialog is not None:
420 dialog.GetPanel().SetMap(self.Map())
421
422 def Map(self):
423 """Return the map displayed by this mainwindow"""
424
425 return self.canvas.Map()
426
427 def ToggleSessionTree(self):
428 """If the session tree is shown close it otherwise create a new tree"""
429 name = "session_tree"
430 dialog = self.get_open_dialog(name)
431 if dialog is None:
432 dialog = tree.SessionTreeView(self, self.application, name)
433 self.add_dialog(name, dialog)
434 dialog.Show(True)
435 else:
436 dialog.Close()
437
438 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
442 def About(self):
443 self.RunMessageBox(_("About"),
444 _("Thuban %s\n"
445 #"Build Date: %s\n"
446 "using:\n"
447 " %s\n"
448 " %s\n\n"
449 "Thuban is a program for\n"
450 "exploring geographic data.\n"
451 "Copyright (C) 2001-2003 Intevation GmbH.\n"
452 "Thuban is licensed under the GNU GPL"
453 % (Thuban.version.longversion,
454 "wxPython %s" % wxPython_version,
455 "Python %d.%d.%d" % sys.version_info[:3]
456 )),
457 # % __ThubanVersion__), #__BuildDate__)),
458 wxOK | wxICON_INFORMATION)
459
460 def AddLayer(self):
461 dlg = wxFileDialog(self, _("Select a data file"), ".", "", "*.*",
462 wxOPEN)
463 if dlg.ShowModal() == wxID_OK:
464 filename = dlg.GetPath()
465 title = os.path.splitext(os.path.basename(filename))[0]
466 map = self.canvas.Map()
467 has_layers = map.HasLayers()
468 try:
469 store = self.application.Session().OpenShapefile(filename)
470 except IOError:
471 # the layer couldn't be opened
472 self.RunMessageBox(_("Add Layer"),
473 _("Can't open the file '%s'.") % filename)
474 else:
475 layer = Layer(title, store)
476 map.AddLayer(layer)
477 if not has_layers:
478 # if we're adding a layer to an empty map, fit the
479 # new map to the window
480 self.canvas.FitMapToWindow()
481 dlg.Destroy()
482
483 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 layer = RasterLayer(title, filename)
493 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 map.AddLayer(layer)
499 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 def RemoveLayer(self):
506 layer = self.current_layer()
507 if layer is not None:
508 self.canvas.Map().RemoveLayer(layer)
509
510 def CanRemoveLayer(self):
511 """Return true if the currently selected layer can be deleted.
512
513 If no layer is selected return False.
514
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 return False
522
523 def RaiseLayer(self):
524 layer = self.current_layer()
525 if layer is not None:
526 self.canvas.Map().RaiseLayer(layer)
527
528 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 return self.canvas.SelectedLayer()
539
540 def has_selected_layer(self):
541 """Return true if a layer is currently selected"""
542 return self.canvas.HasSelectedLayer()
543
544 def has_selected_shapes(self):
545 """Return true if a shape is currently selected"""
546 return self.canvas.HasSelectedShapes()
547
548 def HideLayer(self):
549 layer = self.current_layer()
550 if layer is not None:
551 layer.SetVisible(0)
552
553 def ShowLayer(self):
554 layer = self.current_layer()
555 if layer is not None:
556 layer.SetVisible(1)
557
558 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 def LayerShowTable(self):
576 layer = self.current_layer()
577 if layer is not None:
578 table = layer.table
579 name = "table_view" + str(id(table))
580 dialog = self.get_open_dialog(name)
581 if dialog is None:
582 dialog = tableview.LayerTableFrame(self, name,
583 _("Layer Table: %s") % layer.Title(),
584 layer, table)
585 self.add_dialog(name, dialog)
586 dialog.Show(True)
587 else:
588 # FIXME: bring dialog to front here
589 pass
590
591 def MapProjection(self):
592
593 name = "map_projection"
594 dialog = self.get_open_dialog(name)
595
596 if dialog is None:
597 map = self.canvas.Map()
598 dialog = projdialog.ProjFrame(self, name,
599 _("Map Projection: %s") % map.Title(), map)
600 self.add_dialog(name, dialog)
601 dialog.Show()
602 dialog.Raise()
603
604 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 dialog = projdialog.ProjFrame(self, name,
614 _("Layer Projection: %s") % layer.Title(), layer)
615 self.add_dialog(name, dialog)
616 dialog.Show()
617 dialog.Raise()
618
619 def LayerEditProperties(self):
620
621 #
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 self.OpenLayerProperties(layer)
629
630 def OpenLayerProperties(self, layer, group = None):
631 name = "layer_properties" + str(id(layer))
632 dialog = self.get_open_dialog(name)
633
634 if dialog is None:
635 dialog = Classifier(self, name, self.Map(), layer, group)
636 self.add_dialog(name, dialog)
637 dialog.Show()
638 dialog.Raise()
639
640 def LayerJoinTable(self):
641 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
648 def LayerUnjoinTable(self):
649 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
655 def ShowLegend(self):
656 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 name = "legend"
662 dialog = self.FindRegisteredDock(name)
663
664 if dialog is None:
665 dialog = self.CreateDock(name, -1, _("Legend"), wxLAYOUT_LEFT)
666 legend.LegendPanel(dialog, None, self)
667 dialog.Dock()
668 dialog.GetPanel().SetMap(self.Map())
669 dialog.Show()
670 else:
671 dialog.Show(not dialog.IsShown())
672
673 self.canvas.FitMapToWindow()
674
675 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
680 def TableOpen(self):
681 dlg = wxFileDialog(self, _("Open Table"), ".", "",
682 _("DBF Files (*.dbf)") + "|*.dbf|" +
683 #_("CSV Files (*.csv)") + "|*.csv|" +
684 _("All Files (*.*)") + "|*.*",
685 wxOPEN)
686 if dlg.ShowModal() == wxID_OK:
687 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
698 def TableClose(self):
699 tables = self.application.session.UnreferencedTables()
700
701 lst = [(t.Title(), t) for t in tables]
702 lst.sort()
703 titles = [i[0] for i in lst]
704 dlg = wxMultipleChoiceDialog(self, _("Pick the tables to close:"),
705 _("Close Table"), titles,
706 size = (400, 300),
707 style = wxDEFAULT_DIALOG_STYLE |
708 wxRESIZE_BORDER)
709 if dlg.ShowModal() == wxID_OK:
710 for i in dlg.GetValue():
711 self.application.session.RemoveTable(lst[i][1])
712
713
714 def TableShow(self):
715 """Offer a multi-selection dialog for tables to be displayed
716
717 The windows for the selected tables are opened or brought to
718 the front.
719 """
720 tables = self.application.session.Tables()
721
722 lst = [(t.Title(), t) for t in tables]
723 lst.sort()
724 titles = [i[0] for i in lst]
725 dlg = wxMultipleChoiceDialog(self, _("Pick the table to show:"),
726 _("Show Table"), titles,
727 size = (400,300),
728 style = wxDEFAULT_DIALOG_STYLE |
729 wxRESIZE_BORDER)
730 if (dlg.ShowModal() == wxID_OK):
731 for i in dlg.GetValue():
732 # XXX: if the table belongs to a layer, open a
733 # LayerTableFrame instead of QueryTableFrame
734 self.ShowTableView(lst[i][1])
735
736 def TableJoin(self):
737 dlg = JoinDialog(self, _("Join Tables"), self.application.session)
738 dlg.ShowModal()
739
740 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 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 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 self.identify_view_on_demand(None, None)
802
803 def LabelTool(self):
804 self.canvas.LabelTool()
805
806 def FullExtent(self):
807 self.canvas.FitMapToWindow()
808
809 def FullLayerExtent(self):
810 self.canvas.FitLayerToWindow(self.current_layer())
811
812 def FullSelectionExtent(self):
813 self.canvas.FitSelectedToWindow()
814
815 def ExportMap(self):
816 self.canvas.Export()
817
818 def PrintMap(self):
819 self.canvas.Print()
820
821 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 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 def identify_view_on_demand(self, layer, shapes):
847 """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 name = "identify_view"
861 if self.canvas.CurrentTool() == "IdentifyTool":
862 if not self.dialog_open(name):
863 dialog = identifyview.IdentifyView(self, name)
864 self.add_dialog(name, dialog)
865 dialog.Show(True)
866 else:
867 # FIXME: bring dialog to front?
868 pass
869
870 def __SetTitle(self, title):
871 self.SetTitle("Thuban - " + title)
872
873 #
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 """Call the mainwindow's method methodname with args *args"""
881 apply(getattr(context.mainwindow, methodname), args)
882
883 def _method_command(name, title, method, helptext = "",
884 icon = "", sensitive = None, checked = None):
885 """Add a command implemented by a method of the mainwindow object"""
886 registry.Add(Command(name, title, call_method, args=(method,),
887 helptext = helptext, icon = icon,
888 sensitive = sensitive, checked = checked))
889
890 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 def _tool_command(name, title, method, toolname, helptext = "",
902 icon = "", sensitive = None):
903 """Add a tool command"""
904 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
909 def _has_selected_layer(context):
910 """Return true if a layer is selected in the context"""
911 return context.mainwindow.has_selected_layer()
912
913 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 def _can_remove_layer(context):
918 return context.mainwindow.CanRemoveLayer()
919
920 def _has_tree_window_shown(context):
921 """Return true if the tree window is shown"""
922 return context.mainwindow.SessionTreeShown()
923
924 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 def _has_legend_shown(context):
936 """Return true if the legend window is shown"""
937 return context.mainwindow.LegendShown()
938
939
940 # File menu
941 _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 _method_command("toggle_session_tree", _("Session &Tree"), "ToggleSessionTree",
950 checked = _has_tree_window_shown,
951 helptext = _("Toggle on/off the session tree analysis window"))
952 _method_command("toggle_legend", _("Legend"), "ToggleLegend",
953 checked = _has_legend_shown,
954 helptext = _("Toggle Legend on/off"))
955 _method_command("exit", _("E&xit"), "Exit",
956 helptext = _("Finish working with Thuban"))
957
958 # Help menu
959 _method_command("help_about", _("&About..."), "About",
960 helptext = _("Info about Thuban authors, version and modules"))
961
962
963 # Map menu
964 _method_command("map_projection", _("Pro&jection..."), "MapProjection",
965 helptext = _("Set or change the map projection"))
966
967 _tool_command("map_zoom_in_tool", _("&Zoom in"), "ZoomInTool", "ZoomInTool",
968 helptext = _("Switch to map-mode 'zoom-in'"), icon = "zoom_in",
969 sensitive = _has_visible_map)
970 _tool_command("map_zoom_out_tool", _("Zoom &out"), "ZoomOutTool", "ZoomOutTool",
971 helptext = _("Switch to map-mode 'zoom-out'"), icon = "zoom_out",
972 sensitive = _has_visible_map)
973 _tool_command("map_pan_tool", _("&Pan"), "PanTool", "PanTool",
974 helptext = _("Switch to map-mode 'pan'"), icon = "pan",
975 sensitive = _has_visible_map)
976 _tool_command("map_identify_tool", _("&Identify"), "IdentifyTool",
977 "IdentifyTool",
978 helptext = _("Switch to map-mode 'identify'"), icon = "identify",
979 sensitive = _has_visible_map)
980 _tool_command("map_label_tool", _("&Label"), "LabelTool", "LabelTool",
981 helptext = _("Add/Remove labels"), icon = "label",
982 sensitive = _has_visible_map)
983 _method_command("map_full_extent", _("&Full extent"), "FullExtent",
984 helptext = _("Zoom to the full map extent"), icon = "fullextent",
985 sensitive = _has_visible_map)
986 _method_command("layer_full_extent", _("&Full layer extent"), "FullLayerExtent",
987 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 _method_command("map_export", _("E&xport"), "ExportMap",
994 helptext = _("Export the map to file"))
995 _method_command("map_print", _("Prin&t"), "PrintMap",
996 helptext = _("Print the map"))
997 _method_command("map_rename", _("&Rename..."), "RenameMap",
998 helptext = _("Rename the map"))
999 _method_command("layer_add", _("&Add Layer..."), "AddLayer",
1000 helptext = _("Add a new layer to the map"))
1001 _method_command("rasterlayer_add", _("&Add Image Layer..."), "AddRasterLayer",
1002 helptext = _("Add a new image layer to the map"))
1003 _method_command("layer_remove", _("&Remove Layer"), "RemoveLayer",
1004 helptext = _("Remove selected layer"),
1005 sensitive = _can_remove_layer)
1006
1007 # Layer menu
1008 _method_command("layer_projection", _("Pro&jection..."), "LayerProjection",
1009 sensitive = _has_selected_layer,
1010 helptext = _("Specify projection for selected layer"))
1011 _method_command("layer_duplicate", _("&Duplicate"), "DuplicateLayer",
1012 helptext = _("Duplicate selected layer"),
1013 sensitive = lambda context: context.mainwindow.CanDuplicateLayer())
1014 _method_command("layer_rename", _("Re&name ..."), "RenameLayer",
1015 helptext = _("Rename selected layer"),
1016 sensitive = _has_selected_layer)
1017 _method_command("layer_raise", _("&Raise"), "RaiseLayer",
1018 helptext = _("Raise selected layer"),
1019 sensitive = _has_selected_layer)
1020 _method_command("layer_lower", _("&Lower"), "LowerLayer",
1021 helptext = _("Lower selected layer"),
1022 sensitive = _has_selected_layer)
1023 _method_command("layer_show", _("&Show"), "ShowLayer",
1024 helptext = _("Make selected layer visible"),
1025 sensitive = _has_selected_layer)
1026 _method_command("layer_hide", _("&Hide"), "HideLayer",
1027 helptext = _("Make selected layer unvisible"),
1028 sensitive = _has_selected_layer)
1029 _method_command("layer_show_table", _("Show Ta&ble"), "LayerShowTable",
1030 helptext = _("Show the selected layer's table"),
1031 sensitive = _has_selected_layer)
1032 _method_command("layer_properties", _("&Properties..."), "LayerEditProperties",
1033 sensitive = _has_selected_layer,
1034 helptext = _("Edit the properties of the selected layer"))
1035 _method_command("layer_jointable", _("&Join Table..."), "LayerJoinTable",
1036 sensitive = _has_selected_layer,
1037 helptext = _("Join and attach a table to the selected layer"))
1038
1039 def _can_unjoin(context):
1040 """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 layer = context.mainwindow.SelectedLayer()
1046 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 _method_command("layer_unjointable", _("&Unjoin Table..."), "LayerUnjoinTable",
1054 sensitive = _can_unjoin,
1055 helptext = _("Undo the last join operation"))
1056
1057
1058 def _has_tables(context):
1059 return bool(context.session.Tables())
1060
1061 # Table menu
1062 _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 _method_command("table_rename", _("&Rename..."), "TableRename",
1068 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 _method_command("table_join", _("&Join..."), "TableJoin",
1074 sensitive = _has_tables,
1075 helptext = _("Join two tables creating a new one"))
1076
1077 # Export only under Windows ...
1078 map_menu = ["layer_add", "rasterlayer_add", "layer_remove", "map_rename",
1079 None,
1080 "map_projection",
1081 None,
1082 "map_zoom_in_tool", "map_zoom_out_tool",
1083 "map_pan_tool",
1084 "map_full_extent",
1085 "layer_full_extent",
1086 "selected_full_extent",
1087 None,
1088 "map_identify_tool", "map_label_tool",
1089 None,
1090 "toggle_legend",
1091 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 Menu("layer", _("&Layer"),
1105 ["layer_rename", "layer_duplicate",
1106 None,
1107 "layer_raise", "layer_lower",
1108 None,
1109 "layer_show", "layer_hide",
1110 None,
1111 "layer_projection",
1112 None,
1113 "layer_show_table",
1114 "layer_jointable",
1115 "layer_unjointable",
1116 None,
1117 "layer_properties"]),
1118 Menu("table", _("&Table"),
1119 ["table_open", "table_close", "table_rename",
1120 None,
1121 "table_show",
1122 None,
1123 "table_join"]),
1124 Menu("help", _("&Help"),
1125 ["help_about"])])
1126
1127 # the main toolbar
1128
1129 main_toolbar = Menu("<toolbar>", "<toolbar>",
1130 ["map_zoom_in_tool", "map_zoom_out_tool", "map_pan_tool",
1131 "map_full_extent",
1132 "layer_full_extent",
1133 "selected_full_extent",
1134 None,
1135 "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