/[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 2186 - (show annotations)
Sun Apr 18 20:37:01 2004 UTC (20 years, 10 months ago) by jan
Original Path: trunk/thuban/Thuban/UI/mainwindow.py
File MIME type: text/x-python
File size: 49278 byte(s)
New method commands: layer_to_top, layer_to_bottom, layer_visibility
(MainWindow.LayerToTop): New. Put current layer to the top.
(MainWindow.LayerToBottom): New. Put current layer to bottom.
(MainWindow.HideLayer, MainWindow.ShowLayer, _has_visible_map):
Replace 1,0 by True, False.
(MainWindow.ToggleLayerVisibility): New. Toggle visibility of
current layer.
(MainWindow.LayerShowTable): Removed raising of dialog.
(_has_selected_layer_visible): New. Support function.

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26