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

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26