/[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 1309 - (show annotations)
Thu Jun 26 17:00:44 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: 43067 byte(s)
(MainWindow.About): Call new About box in Thuban.UI.about.

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 self.canvas.FitMapToWindow()
667
668 def LegendShown(self):
669 """Return true iff the legend is currently open"""
670 dialog = self.FindRegisteredDock("legend")
671 return dialog is not None and dialog.IsShown()
672
673 def TableOpen(self):
674 dlg = wxFileDialog(self, _("Open Table"), ".", "",
675 _("DBF Files (*.dbf)") + "|*.dbf|" +
676 #_("CSV Files (*.csv)") + "|*.csv|" +
677 _("All Files (*.*)") + "|*.*",
678 wxOPEN)
679 if dlg.ShowModal() == wxID_OK:
680 filename = dlg.GetPath()
681 dlg.Destroy()
682 try:
683 table = self.application.session.OpenTableFile(filename)
684 except IOError:
685 # the layer couldn't be opened
686 self.RunMessageBox(_("Open Table"),
687 _("Can't open the file '%s'.") % filename)
688 else:
689 self.ShowTableView(table)
690
691 def TableClose(self):
692 tables = self.application.session.UnreferencedTables()
693
694 lst = [(t.Title(), t) for t in tables]
695 lst.sort()
696 titles = [i[0] for i in lst]
697 dlg = wxMultipleChoiceDialog(self, _("Pick the tables to close:"),
698 _("Close Table"), titles,
699 size = (400, 300),
700 style = wxDEFAULT_DIALOG_STYLE |
701 wxRESIZE_BORDER)
702 if dlg.ShowModal() == wxID_OK:
703 for i in dlg.GetValue():
704 self.application.session.RemoveTable(lst[i][1])
705
706
707 def TableShow(self):
708 """Offer a multi-selection dialog for tables to be displayed
709
710 The windows for the selected tables are opened or brought to
711 the front.
712 """
713 tables = self.application.session.Tables()
714
715 lst = [(t.Title(), t) for t in tables]
716 lst.sort()
717 titles = [i[0] for i in lst]
718 dlg = wxMultipleChoiceDialog(self, _("Pick the table to show:"),
719 _("Show Table"), titles,
720 size = (400,300),
721 style = wxDEFAULT_DIALOG_STYLE |
722 wxRESIZE_BORDER)
723 if (dlg.ShowModal() == wxID_OK):
724 for i in dlg.GetValue():
725 # XXX: if the table belongs to a layer, open a
726 # LayerTableFrame instead of QueryTableFrame
727 self.ShowTableView(lst[i][1])
728
729 def TableJoin(self):
730 dlg = JoinDialog(self, _("Join Tables"), self.application.session)
731 dlg.ShowModal()
732
733 def ShowTableView(self, table):
734 """Open a table view for the table and optionally"""
735 name = "table_view%d" % id(table)
736 dialog = self.get_open_dialog(name)
737 if dialog is None:
738 dialog = tableview.QueryTableFrame(self, name,
739 _("Table: %s") % table.Title(),
740 table)
741 self.add_dialog(name, dialog)
742 dialog.Show(True)
743 # FIXME: else bring dialog to front
744
745 def TableRename(self):
746 """Let the user rename a table"""
747
748 # First, let the user select a table
749 tables = self.application.session.Tables()
750 lst = [(t.Title(), t) for t in tables]
751 lst.sort()
752 titles = [i[0] for i in lst]
753 dlg = wxMultipleChoiceDialog(self, _("Pick the table to rename:"),
754 _("Rename Table"), titles,
755 size = (400,300),
756 style = wxDEFAULT_DIALOG_STYLE |
757 wxRESIZE_BORDER)
758 if (dlg.ShowModal() == wxID_OK):
759 to_rename = [lst[i][1] for i in dlg.GetValue()]
760 dlg.Destroy()
761 else:
762 to_rename = []
763
764 # Second, let the user rename the layers
765 for table in to_rename:
766 dlg = wxTextEntryDialog(self, "Table Title: ", "Rename Table",
767 table.Title())
768 try:
769 if dlg.ShowModal() == wxID_OK:
770 title = dlg.GetValue()
771 if title != "":
772 table.SetTitle(title)
773
774 # Make sure the session is marked as modified.
775 # FIXME: This should be handled automatically,
776 # but that requires more changes to the tables
777 # than I have time for currently.
778 self.application.session.changed()
779 finally:
780 dlg.Destroy()
781
782
783 def ZoomInTool(self):
784 self.canvas.ZoomInTool()
785
786 def ZoomOutTool(self):
787 self.canvas.ZoomOutTool()
788
789 def PanTool(self):
790 self.canvas.PanTool()
791
792 def IdentifyTool(self):
793 self.canvas.IdentifyTool()
794 self.identify_view_on_demand(None, None)
795
796 def LabelTool(self):
797 self.canvas.LabelTool()
798
799 def FullExtent(self):
800 self.canvas.FitMapToWindow()
801
802 def FullLayerExtent(self):
803 self.canvas.FitLayerToWindow(self.current_layer())
804
805 def FullSelectionExtent(self):
806 self.canvas.FitSelectedToWindow()
807
808 def ExportMap(self):
809 self.canvas.Export()
810
811 def PrintMap(self):
812 self.canvas.Print()
813
814 def RenameMap(self):
815 dlg = wxTextEntryDialog(self, "Map Title: ", "Rename Map",
816 self.Map().Title())
817 if dlg.ShowModal() == wxID_OK:
818 title = dlg.GetValue()
819 if title != "":
820 self.Map().SetTitle(title)
821 self.__SetTitle(title)
822
823 dlg.Destroy()
824
825 def RenameLayer(self):
826 """Let the user rename the currently selected layer"""
827 layer = self.current_layer()
828 if layer is not None:
829 dlg = wxTextEntryDialog(self, "Layer Title: ", "Rename Layer",
830 layer.Title())
831 try:
832 if dlg.ShowModal() == wxID_OK:
833 title = dlg.GetValue()
834 if title != "":
835 layer.SetTitle(title)
836 finally:
837 dlg.Destroy()
838
839 def identify_view_on_demand(self, layer, shapes):
840 """Subscribed to the canvas' SHAPES_SELECTED message
841
842 If the current tool is the identify tool, at least one shape is
843 selected and the identify dialog is not shown, show the dialog.
844 """
845 # If the selection has become empty we don't need to do
846 # anything. Otherwise it could happen that the dialog was popped
847 # up when the selection became empty, e.g. when a new selection
848 # is opened while the identify tool is active and dialog had
849 # been closed
850 if not shapes:
851 return
852
853 name = "identify_view"
854 if self.canvas.CurrentTool() == "IdentifyTool":
855 if not self.dialog_open(name):
856 dialog = identifyview.IdentifyView(self, name)
857 self.add_dialog(name, dialog)
858 dialog.Show(True)
859 else:
860 # FIXME: bring dialog to front?
861 pass
862
863 def __SetTitle(self, title):
864 self.SetTitle("Thuban - " + title)
865
866 #
867 # Define all the commands available in the main window
868 #
869
870
871 # Helper functions to define common command implementations
872 def call_method(context, methodname, *args):
873 """Call the mainwindow's method methodname with args *args"""
874 apply(getattr(context.mainwindow, methodname), args)
875
876 def _method_command(name, title, method, helptext = "",
877 icon = "", sensitive = None, checked = None):
878 """Add a command implemented by a method of the mainwindow object"""
879 registry.Add(Command(name, title, call_method, args=(method,),
880 helptext = helptext, icon = icon,
881 sensitive = sensitive, checked = checked))
882
883 def make_check_current_tool(toolname):
884 """Return a function that tests if the currently active tool is toolname
885
886 The returned function can be called with the context and returns
887 true iff the currently active tool's name is toolname. It's directly
888 usable as the 'checked' callback of a command.
889 """
890 def check_current_tool(context, name=toolname):
891 return context.mainwindow.canvas.CurrentTool() == name
892 return check_current_tool
893
894 def _tool_command(name, title, method, toolname, helptext = "",
895 icon = "", sensitive = None):
896 """Add a tool command"""
897 registry.Add(ToolCommand(name, title, call_method, args=(method,),
898 helptext = helptext, icon = icon,
899 checked = make_check_current_tool(toolname),
900 sensitive = sensitive))
901
902 def _has_selected_layer(context):
903 """Return true if a layer is selected in the context"""
904 return context.mainwindow.has_selected_layer()
905
906 def _has_selected_shapes(context):
907 """Return true if a layer is selected in the context"""
908 return context.mainwindow.has_selected_shapes()
909
910 def _can_remove_layer(context):
911 return context.mainwindow.CanRemoveLayer()
912
913 def _has_tree_window_shown(context):
914 """Return true if the tree window is shown"""
915 return context.mainwindow.SessionTreeShown()
916
917 def _has_visible_map(context):
918 """Return true iff theres a visible map in the mainwindow.
919
920 A visible map is a map with at least one visible layer."""
921 map = context.mainwindow.Map()
922 if map is not None:
923 for layer in map.Layers():
924 if layer.Visible():
925 return 1
926 return 0
927
928 def _has_legend_shown(context):
929 """Return true if the legend window is shown"""
930 return context.mainwindow.LegendShown()
931
932 def _has_gdal_support(context):
933 """Return True if the GDAL is available"""
934 return Thuban.Model.resource.has_gdal_support()
935
936 # File menu
937 _method_command("new_session", _("&New Session"), "NewSession",
938 helptext = _("Start a new session"))
939 _method_command("open_session", _("&Open Session..."), "OpenSession",
940 helptext = _("Open a session file"))
941 _method_command("save_session", _("&Save Session"), "SaveSession",
942 helptext =_("Save this session to the file it was opened from"))
943 _method_command("save_session_as", _("Save Session &As..."), "SaveSessionAs",
944 helptext = _("Save this session to a new file"))
945 _method_command("toggle_session_tree", _("Session &Tree"), "ToggleSessionTree",
946 checked = _has_tree_window_shown,
947 helptext = _("Toggle on/off the session tree analysis window"))
948 _method_command("toggle_legend", _("Legend"), "ToggleLegend",
949 checked = _has_legend_shown,
950 helptext = _("Toggle Legend on/off"))
951 _method_command("exit", _("E&xit"), "Exit",
952 helptext = _("Finish working with Thuban"))
953
954 # Help menu
955 _method_command("help_about", _("&About..."), "About",
956 helptext = _("Info about Thuban authors, version and modules"))
957
958
959 # Map menu
960 _method_command("map_projection", _("Pro&jection..."), "MapProjection",
961 helptext = _("Set or change the map projection"))
962
963 _tool_command("map_zoom_in_tool", _("&Zoom in"), "ZoomInTool", "ZoomInTool",
964 helptext = _("Switch to map-mode 'zoom-in'"), icon = "zoom_in",
965 sensitive = _has_visible_map)
966 _tool_command("map_zoom_out_tool", _("Zoom &out"), "ZoomOutTool", "ZoomOutTool",
967 helptext = _("Switch to map-mode 'zoom-out'"), icon = "zoom_out",
968 sensitive = _has_visible_map)
969 _tool_command("map_pan_tool", _("&Pan"), "PanTool", "PanTool",
970 helptext = _("Switch to map-mode 'pan'"), icon = "pan",
971 sensitive = _has_visible_map)
972 _tool_command("map_identify_tool", _("&Identify"), "IdentifyTool",
973 "IdentifyTool",
974 helptext = _("Switch to map-mode 'identify'"), icon = "identify",
975 sensitive = _has_visible_map)
976 _tool_command("map_label_tool", _("&Label"), "LabelTool", "LabelTool",
977 helptext = _("Add/Remove labels"), icon = "label",
978 sensitive = _has_visible_map)
979 _method_command("map_full_extent", _("&Full extent"), "FullExtent",
980 helptext = _("Zoom to the full map extent"), icon = "fullextent",
981 sensitive = _has_visible_map)
982 _method_command("layer_full_extent", _("&Full layer extent"), "FullLayerExtent",
983 helptext = _("Zoom to the full layer extent"),
984 icon = "fulllayerextent", sensitive = _has_selected_layer)
985 _method_command("selected_full_extent", _("&Full selection extent"),
986 "FullSelectionExtent",
987 helptext = _("Zoom to the full selection extent"),
988 icon = "fullselextent", sensitive = _has_selected_shapes)
989 _method_command("map_export", _("E&xport"), "ExportMap",
990 helptext = _("Export the map to file"))
991 _method_command("map_print", _("Prin&t"), "PrintMap",
992 helptext = _("Print the map"))
993 _method_command("map_rename", _("&Rename..."), "RenameMap",
994 helptext = _("Rename the map"))
995 _method_command("layer_add", _("&Add Layer..."), "AddLayer",
996 helptext = _("Add a new layer to the map"))
997 _method_command("rasterlayer_add", _("&Add Image Layer..."), "AddRasterLayer",
998 helptext = _("Add a new image layer to the map"),
999 sensitive = _has_gdal_support)
1000 _method_command("layer_remove", _("&Remove Layer"), "RemoveLayer",
1001 helptext = _("Remove selected layer"),
1002 sensitive = _can_remove_layer)
1003
1004 # Layer menu
1005 _method_command("layer_projection", _("Pro&jection..."), "LayerProjection",
1006 sensitive = _has_selected_layer,
1007 helptext = _("Specify projection for selected layer"))
1008 _method_command("layer_duplicate", _("&Duplicate"), "DuplicateLayer",
1009 helptext = _("Duplicate selected layer"),
1010 sensitive = lambda context: context.mainwindow.CanDuplicateLayer())
1011 _method_command("layer_rename", _("Re&name ..."), "RenameLayer",
1012 helptext = _("Rename selected layer"),
1013 sensitive = _has_selected_layer)
1014 _method_command("layer_raise", _("&Raise"), "RaiseLayer",
1015 helptext = _("Raise selected layer"),
1016 sensitive = _has_selected_layer)
1017 _method_command("layer_lower", _("&Lower"), "LowerLayer",
1018 helptext = _("Lower selected layer"),
1019 sensitive = _has_selected_layer)
1020 _method_command("layer_show", _("&Show"), "ShowLayer",
1021 helptext = _("Make selected layer visible"),
1022 sensitive = _has_selected_layer)
1023 _method_command("layer_hide", _("&Hide"), "HideLayer",
1024 helptext = _("Make selected layer unvisible"),
1025 sensitive = _has_selected_layer)
1026 _method_command("layer_show_table", _("Show Ta&ble"), "LayerShowTable",
1027 helptext = _("Show the selected layer's table"),
1028 sensitive = _has_selected_layer)
1029 _method_command("layer_properties", _("&Properties..."), "LayerEditProperties",
1030 sensitive = _has_selected_layer,
1031 helptext = _("Edit the properties of the selected layer"))
1032 _method_command("layer_jointable", _("&Join Table..."), "LayerJoinTable",
1033 sensitive = _has_selected_layer,
1034 helptext = _("Join and attach a table to the selected layer"))
1035
1036 def _can_unjoin(context):
1037 """Return whether the Layer/Unjoin command can be executed.
1038
1039 This is the case if a layer is selected and that layer has a
1040 shapestore that has an original shapestore.
1041 """
1042 layer = context.mainwindow.SelectedLayer()
1043 if layer is None:
1044 return 0
1045 getstore = getattr(layer, "ShapeStore", None)
1046 if getstore is not None:
1047 return getstore().OrigShapeStore() is not None
1048 else:
1049 return 0
1050 _method_command("layer_unjointable", _("&Unjoin Table..."), "LayerUnjoinTable",
1051 sensitive = _can_unjoin,
1052 helptext = _("Undo the last join operation"))
1053
1054
1055 def _has_tables(context):
1056 return bool(context.session.Tables())
1057
1058 # Table menu
1059 _method_command("table_open", _("&Open..."), "TableOpen",
1060 helptext = _("Open a DBF-table from a file"))
1061 _method_command("table_close", _("&Close..."), "TableClose",
1062 sensitive = lambda context: bool(context.session.UnreferencedTables()),
1063 helptext = _("Close one or more tables from a list"))
1064 _method_command("table_rename", _("&Rename..."), "TableRename",
1065 sensitive = _has_tables,
1066 helptext = _("Rename one or more tables"))
1067 _method_command("table_show", _("&Show..."), "TableShow",
1068 sensitive = _has_tables,
1069 helptext = _("Show one or more tables in a dialog"))
1070 _method_command("table_join", _("&Join..."), "TableJoin",
1071 sensitive = _has_tables,
1072 helptext = _("Join two tables creating a new one"))
1073
1074 # Export only under Windows ...
1075 map_menu = ["layer_add", "rasterlayer_add", "layer_remove",
1076 None,
1077 "map_rename",
1078 "map_projection",
1079 None,
1080 "map_zoom_in_tool", "map_zoom_out_tool",
1081 "map_pan_tool",
1082 "map_full_extent",
1083 "layer_full_extent",
1084 "selected_full_extent",
1085 None,
1086 "map_identify_tool", "map_label_tool",
1087 None,
1088 "toggle_legend",
1089 None]
1090 if wxPlatform == '__WXMSW__':
1091 map_menu.append("map_export")
1092 map_menu.append("map_print")
1093
1094 # the menu structure
1095 main_menu = Menu("<main>", "<main>",
1096 [Menu("file", _("&File"),
1097 ["new_session", "open_session", None,
1098 "save_session", "save_session_as", None,
1099 "toggle_session_tree", None,
1100 "exit"]),
1101 Menu("map", _("&Map"), map_menu),
1102 Menu("layer", _("&Layer"),
1103 ["layer_rename", "layer_duplicate",
1104 None,
1105 "layer_raise", "layer_lower",
1106 None,
1107 "layer_show", "layer_hide",
1108 None,
1109 "layer_projection",
1110 None,
1111 "layer_show_table",
1112 "layer_jointable",
1113 "layer_unjointable",
1114 None,
1115 "layer_properties"]),
1116 Menu("table", _("&Table"),
1117 ["table_open", "table_close", "table_rename",
1118 None,
1119 "table_show",
1120 None,
1121 "table_join"]),
1122 Menu("help", _("&Help"),
1123 ["help_about"])])
1124
1125 # the main toolbar
1126
1127 main_toolbar = Menu("<toolbar>", "<toolbar>",
1128 ["map_zoom_in_tool", "map_zoom_out_tool", "map_pan_tool",
1129 "map_full_extent",
1130 "layer_full_extent",
1131 "selected_full_extent",
1132 None,
1133 "map_identify_tool", "map_label_tool"])
1134

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26