/[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 1108 - (show annotations)
Fri May 30 06:31:21 2003 UTC (21 years, 9 months ago) by jonathan
Original Path: trunk/thuban/Thuban/UI/mainwindow.py
File MIME type: text/x-python
File size: 39616 byte(s)
(MainWindow.ToggleLegend): Apply a full
        extent to the map when the legend is toggled. Fixes RTBug #1881.

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, 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 ZoomInTool(self):
753 self.canvas.ZoomInTool()
754
755 def ZoomOutTool(self):
756 self.canvas.ZoomOutTool()
757
758 def PanTool(self):
759 self.canvas.PanTool()
760
761 def IdentifyTool(self):
762 self.canvas.IdentifyTool()
763 self.identify_view_on_demand(None, None)
764
765 def LabelTool(self):
766 self.canvas.LabelTool()
767
768 def FullExtent(self):
769 self.canvas.FitMapToWindow()
770
771 def FullLayerExtent(self):
772 self.canvas.FitLayerToWindow(self.current_layer())
773
774 def FullSelectionExtent(self):
775 self.canvas.FitSelectedToWindow()
776
777 def ExportMap(self):
778 self.canvas.Export()
779
780 def PrintMap(self):
781 self.canvas.Print()
782
783 def RenameMap(self):
784 dlg = wxTextEntryDialog(self, "Map Title: ", "Rename Map",
785 self.Map().Title())
786 if dlg.ShowModal() == wxID_OK:
787 title = dlg.GetValue()
788 if title != "":
789 self.Map().SetTitle(title)
790 self.__SetTitle(title)
791
792 dlg.Destroy()
793
794 def identify_view_on_demand(self, layer, shapes):
795 """Subscribed to the canvas' SHAPES_SELECTED message
796
797 If the current tool is the identify tool, at least one shape is
798 selected and the identify dialog is not shown, show the dialog.
799 """
800 # If the selection has become empty we don't need to do
801 # anything. Otherwise it could happen that the dialog was popped
802 # up when the selection became empty, e.g. when a new selection
803 # is opened while the identify tool is active and dialog had
804 # been closed
805 if not shapes:
806 return
807
808 name = "identify_view"
809 if self.canvas.CurrentTool() == "IdentifyTool":
810 if not self.dialog_open(name):
811 dialog = identifyview.IdentifyView(self, name)
812 self.add_dialog(name, dialog)
813 dialog.Show(True)
814 else:
815 # FIXME: bring dialog to front?
816 pass
817
818 def __SetTitle(self, title):
819 self.SetTitle("Thuban - " + title)
820
821 #
822 # Define all the commands available in the main window
823 #
824
825
826 # Helper functions to define common command implementations
827 def call_method(context, methodname, *args):
828 """Call the mainwindow's method methodname with args *args"""
829 apply(getattr(context.mainwindow, methodname), args)
830
831 def _method_command(name, title, method, helptext = "",
832 icon = "", sensitive = None, checked = None):
833 """Add a command implemented by a method of the mainwindow object"""
834 registry.Add(Command(name, title, call_method, args=(method,),
835 helptext = helptext, icon = icon,
836 sensitive = sensitive, checked = checked))
837
838 def make_check_current_tool(toolname):
839 """Return a function that tests if the currently active tool is toolname
840
841 The returned function can be called with the context and returns
842 true iff the currently active tool's name is toolname. It's directly
843 usable as the 'checked' callback of a command.
844 """
845 def check_current_tool(context, name=toolname):
846 return context.mainwindow.canvas.CurrentTool() == name
847 return check_current_tool
848
849 def _tool_command(name, title, method, toolname, helptext = "",
850 icon = "", sensitive = None):
851 """Add a tool command"""
852 registry.Add(ToolCommand(name, title, call_method, args=(method,),
853 helptext = helptext, icon = icon,
854 checked = make_check_current_tool(toolname),
855 sensitive = sensitive))
856
857 def _has_selected_layer(context):
858 """Return true if a layer is selected in the context"""
859 return context.mainwindow.has_selected_layer()
860
861 def _has_selected_shapes(context):
862 """Return true if a layer is selected in the context"""
863 return context.mainwindow.has_selected_shapes()
864
865 def _can_remove_layer(context):
866 return context.mainwindow.CanRemoveLayer()
867
868 def _has_tree_window_shown(context):
869 """Return true if the tree window is shown"""
870 return context.mainwindow.SessionTreeShown()
871
872 def _has_visible_map(context):
873 """Return true iff theres a visible map in the mainwindow.
874
875 A visible map is a map with at least one visible layer."""
876 map = context.mainwindow.Map()
877 if map is not None:
878 for layer in map.Layers():
879 if layer.Visible():
880 return 1
881 return 0
882
883 def _has_legend_shown(context):
884 """Return true if the legend window is shown"""
885 return context.mainwindow.LegendShown()
886
887
888 # File menu
889 _method_command("new_session", _("&New Session"), "NewSession")
890 _method_command("open_session", _("&Open Session..."), "OpenSession")
891 _method_command("save_session", _("&Save Session"), "SaveSession")
892 _method_command("save_session_as", _("Save Session &As..."), "SaveSessionAs")
893 _method_command("toggle_session_tree", _("Session &Tree"), "ToggleSessionTree",
894 checked = _has_tree_window_shown)
895 _method_command("toggle_legend", _("Legend"), "ToggleLegend",
896 checked = _has_legend_shown)
897 _method_command("exit", _("E&xit"), "Exit")
898
899 # Help menu
900 _method_command("help_about", _("&About..."), "About")
901
902
903 # Map menu
904 _method_command("map_projection", _("Pro&jection..."), "MapProjection")
905
906 _tool_command("map_zoom_in_tool", _("&Zoom in"), "ZoomInTool", "ZoomInTool",
907 helptext = _("Switch to map-mode 'zoom-in'"), icon = "zoom_in",
908 sensitive = _has_visible_map)
909 _tool_command("map_zoom_out_tool", _("Zoom &out"), "ZoomOutTool", "ZoomOutTool",
910 helptext = _("Switch to map-mode 'zoom-out'"), icon = "zoom_out",
911 sensitive = _has_visible_map)
912 _tool_command("map_pan_tool", _("&Pan"), "PanTool", "PanTool",
913 helptext = _("Switch to map-mode 'pan'"), icon = "pan",
914 sensitive = _has_visible_map)
915 _tool_command("map_identify_tool", _("&Identify"), "IdentifyTool",
916 "IdentifyTool",
917 helptext = _("Switch to map-mode 'identify'"), icon = "identify",
918 sensitive = _has_visible_map)
919 _tool_command("map_label_tool", _("&Label"), "LabelTool", "LabelTool",
920 helptext = _("Add/Remove labels"), icon = "label",
921 sensitive = _has_visible_map)
922 _method_command("map_full_extent", _("&Full extent"), "FullExtent",
923 helptext = _("Full Extent"), icon = "fullextent",
924 sensitive = _has_visible_map)
925 _method_command("layer_full_extent", _("&Full layer extent"), "FullLayerExtent",
926 helptext = _("Full Layer Extent"), icon = "fulllayerextent",
927 sensitive = _has_selected_layer)
928 _method_command("selected_full_extent", _("&Full selection extent"), "FullSelectionExtent",
929 helptext = _("Full Selection Extent"), icon = "fullselextent",
930 sensitive = _has_selected_shapes)
931 _method_command("map_export", _("E&xport"), "ExportMap",
932 helptext = _("Export the map to file"))
933 _method_command("map_print", _("Prin&t"), "PrintMap",
934 helptext = _("Print the map"))
935 _method_command("map_rename", _("&Rename..."), "RenameMap",
936 helptext = _("Rename the map"))
937 _method_command("layer_add", _("&Add Layer..."), "AddLayer",
938 helptext = _("Add a new layer to active map"))
939 _method_command("rasterlayer_add", _("&Add Image Layer..."), "AddRasterLayer",
940 helptext = _("Add a new image layer to active map"))
941 _method_command("layer_remove", _("&Remove Layer"), "RemoveLayer",
942 helptext = _("Remove selected layer(s)"),
943 sensitive = _can_remove_layer)
944
945 # Layer menu
946 _method_command("layer_projection", _("Pro&jection..."), "LayerProjection",
947 sensitive = _has_selected_layer)
948 _method_command("layer_duplicate", _("&Duplicate"), "DuplicateLayer",
949 helptext = _("Duplicate selected layer(s)"),
950 sensitive = lambda context: context.mainwindow.CanDuplicateLayer())
951 _method_command("layer_raise", _("&Raise"), "RaiseLayer",
952 helptext = _("Raise selected layer(s)"),
953 sensitive = _has_selected_layer)
954 _method_command("layer_lower", _("&Lower"), "LowerLayer",
955 helptext = _("Lower selected layer(s)"),
956 sensitive = _has_selected_layer)
957 _method_command("layer_show", _("&Show"), "ShowLayer",
958 helptext = _("Make selected layer(s) visible"),
959 sensitive = _has_selected_layer)
960 _method_command("layer_hide", _("&Hide"), "HideLayer",
961 helptext = _("Make selected layer(s) unvisible"),
962 sensitive = _has_selected_layer)
963 _method_command("layer_show_table", _("Show Ta&ble"), "LayerShowTable",
964 helptext = _("Show the selected layer's table"),
965 sensitive = _has_selected_layer)
966 _method_command("layer_properties", _("&Properties..."), "LayerEditProperties",
967 sensitive = _has_selected_layer)
968 _method_command("layer_jointable", _("&Join Table..."), "LayerJoinTable",
969 sensitive = _has_selected_layer)
970
971 def _can_unjoin(context):
972 """Return whether the Layer/Unjoin command can be executed.
973
974 This is the case if a layer is selected and that layer has a
975 shapestore that has an original shapestore.
976 """
977 layer = context.mainwindow.SelectedLayer()
978 if layer is None:
979 return 0
980 getstore = getattr(layer, "ShapeStore", None)
981 if getstore is not None:
982 return getstore().OrigShapeStore() is not None
983 else:
984 return 0
985 _method_command("layer_unjointable", _("&Unjoin Table..."), "LayerUnjoinTable",
986 sensitive = _can_unjoin)
987
988 # Table menu
989 _method_command("table_open", _("&Open..."), "TableOpen")
990 _method_command("table_close", _("&Close"), "TableClose",
991 sensitive = lambda context: bool(context.session.UnreferencedTables()))
992 _method_command("table_show", _("&Show"), "TableShow")
993 _method_command("table_join", _("&Join..."), "TableJoin")
994
995 # Export only under Windows ...
996 map_menu = ["layer_add", "rasterlayer_add", "layer_remove", "map_rename",
997 None,
998 "map_projection",
999 None,
1000 "map_zoom_in_tool", "map_zoom_out_tool",
1001 "map_pan_tool",
1002 "map_full_extent",
1003 "layer_full_extent",
1004 "selected_full_extent",
1005 None,
1006 "map_identify_tool", "map_label_tool",
1007 None,
1008 "toggle_legend",
1009 None]
1010 if wxPlatform == '__WXMSW__':
1011 map_menu.append("map_export")
1012 map_menu.append("map_print")
1013
1014 # the menu structure
1015 main_menu = Menu("<main>", "<main>",
1016 [Menu("file", _("&File"),
1017 ["new_session", "open_session", None,
1018 "save_session", "save_session_as", None,
1019 "toggle_session_tree", None,
1020 "exit"]),
1021 Menu("map", _("&Map"), map_menu),
1022 Menu("layer", _("&Layer"),
1023 ["layer_raise", "layer_lower",
1024 None,
1025 "layer_show", "layer_hide",
1026 None,
1027 "layer_duplicate",
1028 None,
1029 "layer_projection",
1030 None,
1031 "layer_show_table",
1032 "layer_jointable",
1033 "layer_unjointable",
1034 None,
1035 "layer_properties"]),
1036 Menu("table", _("&Table"),
1037 ["table_open", "table_close",
1038 None,
1039 "table_show",
1040 None,
1041 "table_join"]),
1042 Menu("help", _("&Help"),
1043 ["help_about"])])
1044
1045 # the main toolbar
1046
1047 main_toolbar = Menu("<toolbar>", "<toolbar>",
1048 ["map_zoom_in_tool", "map_zoom_out_tool", "map_pan_tool",
1049 "map_full_extent",
1050 "layer_full_extent",
1051 "selected_full_extent",
1052 None,
1053 "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