/[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 1287 - (show annotations)
Mon Jun 23 10:47:11 2003 UTC (21 years, 8 months ago) by jonathan
Original Path: trunk/thuban/Thuban/UI/mainwindow.py
File MIME type: text/x-python
File size: 43835 byte(s)
(MainWindow.About): Fix text.

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