/[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 1464 - (show annotations)
Fri Jul 18 18:20:40 2003 UTC (21 years, 7 months ago) by bh
Original Path: trunk/thuban/Thuban/UI/mainwindow.py
File MIME type: text/x-python
File size: 42839 byte(s)
* Thuban/UI/messages.py (MAP_REPLACED): New message.

* Thuban/UI/viewport.py (ViewPort.SetMap): Issue MAP_REPLACED
after the new map has been assigned

* Thuban/UI/mainwindow.py (MainWindow.delegated_messages):
Delegate MAP_REPLACED to the canvas too
(MainWindow.prepare_new_session): Removed. Thanks to the new
MAP_REPLACED message it's no longer needed
(MainWindow.OpenSession, MainWindow.NewSession):
prepare_new_session has been removed.

* Thuban/UI/classifier.py (Classifier.__init__): Subscribe to
MAP_REPLACED so that we can close the dialog if a new map is set.
(Classifier.unsubscribe_messages): Unsubscribe from MAP_REPLACED
(Classifier.map_replaced): Handle MAP_REPLACED by closing the
dialog

* test/test_viewport.py (SimpleViewPortTest)
(SimpleViewPortTest.test_default_size): Add doc-strings
(ViewPortTest.setUp): Bind map to self.map so we can use it in
tests. Subscribe to MAP_REPLACED messages too.
(ViewPortTest.tearDown): No need to explicitly unsubscribe
(ViewPortTest.test_set_map): New test for the SetMap method.

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26