/[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 1164 - (show annotations)
Thu Jun 12 12:41: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: 43781 byte(s)
(_has_gdal_support): New. Used to
        determine if the "Add Image Layer" menu option should be
        greyed out or not. Addresses RTbug #1877.

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