/[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 2364 - (show annotations)
Fri Oct 1 18:18:49 2004 UTC (20 years, 5 months ago) by joey
Original Path: trunk/thuban/Thuban/UI/mainwindow.py
File MIME type: text/x-python
File size: 49934 byte(s)
Move the logic for checking whether a dialog is already opened (and
raising it to the users attention) and creating a new dialog into a
function of its own

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 OpenOrRaiseDialog(self, name, dialog_class, *args, **kw):
353 """
354 Open or raise a dialog.
355
356 If a dialog with the denoted name does already exist it is
357 raised. Otherwise a new dialog, an instance of dialog_class,
358 is created, inserted into the main list and displayed.
359 """
360 dialog = self.get_open_dialog(name)
361
362 if dialog is None:
363 dialog = dialog_class(self, name, *args, **kw)
364 self.add_dialog(name, dialog)
365 dialog.Show(True)
366 else:
367 dialog.Raise()
368
369 def save_modified_session(self, can_veto = 1):
370 """If the current session has been modified, ask the user
371 whether to save it and do so if requested. Return the outcome of
372 the dialog (either wxID_OK, wxID_CANCEL or wxID_NO). If the
373 dialog wasn't run return wxID_NO.
374
375 If the can_veto parameter is true (default) the dialog includes
376 a cancel button, otherwise not.
377 """
378 if self.application.session.WasModified():
379 flags = wxYES_NO | wxICON_QUESTION
380 if can_veto:
381 flags = flags | wxCANCEL
382 result = self.RunMessageBox(_("Exit"),
383 _("The session has been modified."
384 " Do you want to save it?"),
385 flags)
386 if result == wxID_YES:
387 self.SaveSession()
388 else:
389 result = wxID_NO
390 return result
391
392 def NewSession(self):
393 if self.save_modified_session() != wxID_CANCEL:
394 self.application.SetSession(create_empty_session())
395
396 def OpenSession(self):
397 if self.save_modified_session() != wxID_CANCEL:
398 dlg = wxFileDialog(self, _("Open Session"),
399 self.application.Path("data"), "",
400 "Thuban Session File (*.thuban)|*.thuban",
401 wxOPEN)
402 if dlg.ShowModal() == wxID_OK:
403 self.application.OpenSession(dlg.GetPath(),
404 self.run_db_param_dialog)
405 self.application.SetPath("data", dlg.GetPath())
406 dlg.Destroy()
407
408 def run_db_param_dialog(self, parameters, message):
409 dlg = DBDialog(self, _("DB Connection Parameters"), parameters,
410 message)
411 return dlg.RunDialog()
412
413 def SaveSession(self):
414 if self.application.session.filename == None:
415 self.SaveSessionAs()
416 else:
417 self.application.SaveSession()
418
419 def SaveSessionAs(self):
420 dlg = wxFileDialog(self, _("Save Session As"),
421 self.application.Path("data"), "",
422 "Thuban Session File (*.thuban)|*.thuban",
423 wxSAVE|wxOVERWRITE_PROMPT)
424 if dlg.ShowModal() == wxID_OK:
425 self.application.session.SetFilename(dlg.GetPath())
426 self.application.SaveSession()
427 self.application.SetPath("data",dlg.GetPath())
428 dlg.Destroy()
429
430 def Exit(self):
431 self.Close(False)
432
433 def OnClose(self, event):
434 result = self.save_modified_session(can_veto = event.CanVeto())
435 if result == wxID_CANCEL:
436 event.Veto()
437 else:
438 # FIXME: it would be better to tie the unsubscription to
439 # wx's destroy event, but that isn't implemented for wxGTK
440 # yet.
441 self.canvas.Unsubscribe(VIEW_POSITION, self.view_position_changed)
442 DockFrame.OnClose(self, event)
443 for dlg in self.dialogs.values():
444 dlg.Destroy()
445 self.canvas.Destroy()
446 self.Destroy()
447
448 def SetMap(self, map):
449 self.canvas.SetMap(map)
450 self.update_title()
451
452 dialog = self.FindRegisteredDock("legend")
453 if dialog is not None:
454 dialog.GetPanel().SetMap(self.Map())
455
456 def Map(self):
457 """Return the map displayed by this mainwindow"""
458
459 return self.canvas.Map()
460
461 def ToggleSessionTree(self):
462 """If the session tree is shown close it otherwise create a new tree"""
463 name = "session_tree"
464 dialog = self.get_open_dialog(name)
465 if dialog is None:
466 dialog = tree.SessionTreeView(self, self.application, name)
467 self.add_dialog(name, dialog)
468 dialog.Show(True)
469 else:
470 dialog.Close()
471
472 def SessionTreeShown(self):
473 """Return true iff the session tree is currently shown"""
474 return self.get_open_dialog("session_tree") is not None
475
476 def About(self):
477 dlg = About(self)
478 dlg.ShowModal()
479 dlg.Destroy()
480
481 def DatabaseManagement(self):
482 name = "dbmanagement"
483 dialog = self.get_open_dialog(name)
484 if dialog is None:
485 map = self.canvas.Map()
486 dialog = DBFrame(self, name, self.application.Session())
487 self.add_dialog(name, dialog)
488 dialog.Show()
489 dialog.Raise()
490
491 def AddLayer(self):
492 dlg = wxFileDialog(self, _("Select one or more data files"),
493 self.application.Path("data"), "",
494 _("Shapefiles (*.shp)") + "|*.shp;*.SHP|" +
495 _("All Files (*.*)") + "|*.*",
496 wxOPEN | wxMULTIPLE)
497 if dlg.ShowModal() == wxID_OK:
498 filenames = dlg.GetPaths()
499 for filename in filenames:
500 title = os.path.splitext(os.path.basename(filename))[0]
501 map = self.canvas.Map()
502 has_layers = map.HasLayers()
503 try:
504 store = self.application.Session().OpenShapefile(filename)
505 except IOError:
506 # the layer couldn't be opened
507 self.RunMessageBox(_("Add Layer"),
508 _("Can't open the file '%s'.")%filename)
509 else:
510 layer = Layer(title, store)
511 map.AddLayer(layer)
512 if not has_layers:
513 # if we're adding a layer to an empty map, fit the
514 # new map to the window
515 self.canvas.FitMapToWindow()
516 self.application.SetPath("data",filename)
517 dlg.Destroy()
518
519 def AddRasterLayer(self):
520 dlg = wxFileDialog(self, _("Select an image file"),
521 self.application.Path("data"), "", "*.*",
522 wxOPEN)
523 if dlg.ShowModal() == wxID_OK:
524 filename = dlg.GetPath()
525 title = os.path.splitext(os.path.basename(filename))[0]
526 map = self.canvas.Map()
527 has_layers = map.HasLayers()
528 try:
529 layer = RasterLayer(title, filename)
530 except IOError:
531 # the layer couldn't be opened
532 self.RunMessageBox(_("Add Image Layer"),
533 _("Can't open the file '%s'.") % filename)
534 else:
535 map.AddLayer(layer)
536 if not has_layers:
537 # if we're adding a layer to an empty map, fit the
538 # new map to the window
539 self.canvas.FitMapToWindow()
540 self.application.SetPath("data", filename)
541 dlg.Destroy()
542
543 def AddDBLayer(self):
544 """Add a layer read from a database"""
545 session = self.application.Session()
546 dlg = ChooseDBTableDialog(self, self.application.Session())
547
548 if dlg.ShowModal() == wxID_OK:
549 dbconn, dbtable, id_column, geo_column = dlg.GetTable()
550 try:
551 title = str(dbtable)
552
553 # Chose the correct Interface for the database type
554 store = session.OpenDBShapeStore(dbconn, dbtable,
555 id_column = id_column,
556 geometry_column = geo_column)
557 layer = Layer(title, store)
558 except:
559 # Some error occured while initializing the layer
560 self.RunMessageBox(_("Add Layer from database"),
561 _("Can't open the database table '%s'")
562 % dbtable)
563 return
564
565 map = self.canvas.Map()
566
567 has_layers = map.HasLayers()
568 map.AddLayer(layer)
569 if not has_layers:
570 self.canvas.FitMapToWindow()
571
572 dlg.Destroy()
573
574 def RemoveLayer(self):
575 layer = self.current_layer()
576 if layer is not None:
577 self.canvas.Map().RemoveLayer(layer)
578
579 def CanRemoveLayer(self):
580 """Return true if the currently selected layer can be deleted.
581
582 If no layer is selected return False.
583
584 The return value of this method determines whether the remove
585 layer command is sensitive in menu.
586 """
587 layer = self.current_layer()
588 if layer is not None:
589 return self.canvas.Map().CanRemoveLayer(layer)
590 return False
591
592 def LayerToTop(self):
593 layer = self.current_layer()
594 if layer is not None:
595 self.canvas.Map().MoveLayerToTop(layer)
596
597 def RaiseLayer(self):
598 layer = self.current_layer()
599 if layer is not None:
600 self.canvas.Map().RaiseLayer(layer)
601
602 def LowerLayer(self):
603 layer = self.current_layer()
604 if layer is not None:
605 self.canvas.Map().LowerLayer(layer)
606
607 def LayerToBottom(self):
608 layer = self.current_layer()
609 if layer is not None:
610 self.canvas.Map().MoveLayerToBottom(layer)
611
612 def current_layer(self):
613 """Return the currently selected layer.
614
615 If no layer is selected, return None
616 """
617 return self.canvas.SelectedLayer()
618
619 def has_selected_layer(self):
620 """Return true if a layer is currently selected"""
621 return self.canvas.HasSelectedLayer()
622
623 def has_selected_shape_layer(self):
624 """Return true if a shape layer is currently selected"""
625 return isinstance(self.current_layer(), Layer)
626
627 def has_selected_shapes(self):
628 """Return true if a shape is currently selected"""
629 return self.canvas.HasSelectedShapes()
630
631 def HideLayer(self):
632 layer = self.current_layer()
633 if layer is not None:
634 layer.SetVisible(False)
635
636 def ShowLayer(self):
637 layer = self.current_layer()
638 if layer is not None:
639 layer.SetVisible(True)
640
641 def ToggleLayerVisibility(self):
642 layer = self.current_layer()
643 layer.SetVisible(not layer.Visible())
644
645 def DuplicateLayer(self):
646 """Ceate a new layer above the selected layer with the same shapestore
647 """
648 layer = self.current_layer()
649 if layer is not None and hasattr(layer, "ShapeStore"):
650 new_layer = Layer(_("Copy of `%s'") % layer.Title(),
651 layer.ShapeStore(),
652 projection = layer.GetProjection())
653 new_classification = copy.deepcopy(layer.GetClassification())
654 new_layer.SetClassification(new_classification)
655 self.Map().AddLayer(new_layer)
656
657 def CanDuplicateLayer(self):
658 """Return whether the DuplicateLayer method can create a duplicate"""
659 layer = self.current_layer()
660 return layer is not None and hasattr(layer, "ShapeStore")
661
662 def LayerShowTable(self):
663 """
664 Present a TableView Window for the current layer.
665 In case the window is already open, bring it to the front.
666 In case, there is no active layer, do nothing.
667 In case, the layer has no ShapeStore, do nothing.
668 """
669 layer = self.current_layer()
670 if layer is not None:
671 if not hasattr(layer, "ShapeStore"):
672 return
673 table = layer.ShapeStore().Table()
674 name = "table_view" + str(id(table))
675 dialog = self.get_open_dialog(name)
676 if dialog is None:
677 dialog = tableview.LayerTableFrame(self, name,
678 _("Layer Table: %s") % layer.Title(),
679 layer, table)
680 self.add_dialog(name, dialog)
681 dialog.Show(True)
682 else:
683 dialog.Raise()
684
685 def MapProjection(self):
686
687 name = "map_projection"
688 dialog = self.get_open_dialog(name)
689
690 if dialog is None:
691 map = self.canvas.Map()
692 dialog = projdialog.ProjFrame(self, name,
693 _("Map Projection: %s") % map.Title(), map)
694 self.add_dialog(name, dialog)
695 dialog.Show()
696 dialog.Raise()
697
698 def LayerProjection(self):
699
700 layer = self.current_layer()
701
702 name = "layer_projection" + str(id(layer))
703 dialog = self.get_open_dialog(name)
704
705 if dialog is None:
706 map = self.canvas.Map()
707 dialog = projdialog.ProjFrame(self, name,
708 _("Layer Projection: %s") % layer.Title(), layer)
709 self.add_dialog(name, dialog)
710 dialog.Show()
711 dialog.Raise()
712
713 def LayerEditProperties(self):
714
715 #
716 # the menu option for this should only be available if there
717 # is a current layer, so we don't need to check if the
718 # current layer is None
719 #
720
721 layer = self.current_layer()
722 self.OpenLayerProperties(layer)
723
724 def OpenLayerProperties(self, layer, group = None):
725 """
726 Open or raise the properties dialog.
727
728 This method opens or raises the properties dialog for the
729 currently selected layer if one is defined for this layer
730 type.
731 """
732 name = "layer_properties" + str(id(layer))
733 self.OpenOrRaiseDialog(name, Classifier, layer, group = group)
734
735 def LayerJoinTable(self):
736 layer = self.canvas.SelectedLayer()
737 if layer is not None:
738 dlg = JoinDialog(self, _("Join Layer with Table"),
739 self.application.session,
740 layer = layer)
741 dlg.ShowModal()
742
743 def LayerUnjoinTable(self):
744 layer = self.canvas.SelectedLayer()
745 if layer is not None:
746 orig_store = layer.ShapeStore().OrigShapeStore()
747 if orig_store:
748 layer.SetShapeStore(orig_store)
749
750 def ShowLegend(self):
751 if not self.LegendShown():
752 self.ToggleLegend()
753
754 def ToggleLegend(self):
755 """Show the legend if it's not shown otherwise hide it again"""
756 name = "legend"
757 dialog = self.FindRegisteredDock(name)
758
759 if dialog is None:
760 dialog = self.CreateDock(name, -1, _("Legend"), wxLAYOUT_LEFT)
761 legend.LegendPanel(dialog, None, self)
762 dialog.Dock()
763 dialog.GetPanel().SetMap(self.Map())
764 dialog.Show()
765 else:
766 dialog.Show(not dialog.IsShown())
767
768 def LegendShown(self):
769 """Return true iff the legend is currently open"""
770 dialog = self.FindRegisteredDock("legend")
771 return dialog is not None and dialog.IsShown()
772
773 def TableOpen(self):
774 dlg = wxFileDialog(self, _("Open Table"),
775 self.application.Path("data"), "",
776 _("DBF Files (*.dbf)") + "|*.dbf|" +
777 #_("CSV Files (*.csv)") + "|*.csv|" +
778 _("All Files (*.*)") + "|*.*",
779 wxOPEN)
780 if dlg.ShowModal() == wxID_OK:
781 filename = dlg.GetPath()
782 dlg.Destroy()
783 try:
784 table = self.application.session.OpenTableFile(filename)
785 except IOError:
786 # the layer couldn't be opened
787 self.RunMessageBox(_("Open Table"),
788 _("Can't open the file '%s'.") % filename)
789 else:
790 self.ShowTableView(table)
791 self.application.SetPath("data",filename)
792
793 def TableClose(self):
794 tables = self.application.session.UnreferencedTables()
795
796 lst = [(t.Title(), t) for t in tables]
797 lst.sort()
798 titles = [i[0] for i in lst]
799 dlg = wxMultipleChoiceDialog(self, _("Pick the tables to close:"),
800 _("Close Table"), titles,
801 size = (400, 300),
802 style = wxDEFAULT_DIALOG_STYLE |
803 wxRESIZE_BORDER)
804 if dlg.ShowModal() == wxID_OK:
805 for i in dlg.GetValue():
806 self.application.session.RemoveTable(lst[i][1])
807
808
809 def TableShow(self):
810 """Offer a multi-selection dialog for tables to be displayed
811
812 The windows for the selected tables are opened or brought to
813 the front.
814 """
815 tables = self.application.session.Tables()
816
817 lst = [(t.Title(), t) for t in tables]
818 lst.sort()
819 titles = [i[0] for i in lst]
820 dlg = wxMultipleChoiceDialog(self, _("Pick the table to show:"),
821 _("Show Table"), titles,
822 size = (400,300),
823 style = wxDEFAULT_DIALOG_STYLE |
824 wxRESIZE_BORDER)
825 if (dlg.ShowModal() == wxID_OK):
826 for i in dlg.GetValue():
827 # XXX: if the table belongs to a layer, open a
828 # LayerTableFrame instead of QueryTableFrame
829 self.ShowTableView(lst[i][1])
830
831 def TableJoin(self):
832 dlg = JoinDialog(self, _("Join Tables"), self.application.session)
833 dlg.ShowModal()
834
835 def ShowTableView(self, table):
836 """Open a table view for the table and optionally"""
837 name = "table_view%d" % id(table)
838 dialog = self.get_open_dialog(name)
839 if dialog is None:
840 dialog = tableview.QueryTableFrame(self, name,
841 _("Table: %s") % table.Title(),
842 table)
843 self.add_dialog(name, dialog)
844 dialog.Show(True)
845 dialog.Raise()
846
847 def TableRename(self):
848 """Let the user rename a table"""
849
850 # First, let the user select a table
851 tables = self.application.session.Tables()
852 lst = [(t.Title(), t) for t in tables]
853 lst.sort()
854 titles = [i[0] for i in lst]
855 dlg = wxMultipleChoiceDialog(self, _("Pick the table to rename:"),
856 _("Rename Table"), titles,
857 size = (400,300),
858 style = wxDEFAULT_DIALOG_STYLE |
859 wxRESIZE_BORDER)
860 if (dlg.ShowModal() == wxID_OK):
861 to_rename = [lst[i][1] for i in dlg.GetValue()]
862 dlg.Destroy()
863 else:
864 to_rename = []
865
866 # Second, let the user rename the layers
867 for table in to_rename:
868 dlg = wxTextEntryDialog(self, _("Table Title:"), _("Rename Table"),
869 table.Title())
870 try:
871 if dlg.ShowModal() == wxID_OK:
872 title = dlg.GetValue()
873 if title != "":
874 table.SetTitle(title)
875
876 # Make sure the session is marked as modified.
877 # FIXME: This should be handled automatically,
878 # but that requires more changes to the tables
879 # than I have time for currently.
880 self.application.session.changed()
881 finally:
882 dlg.Destroy()
883
884
885 def ZoomInTool(self):
886 self.canvas.ZoomInTool()
887
888 def ZoomOutTool(self):
889 self.canvas.ZoomOutTool()
890
891 def PanTool(self):
892 self.canvas.PanTool()
893
894 def IdentifyTool(self):
895 self.canvas.IdentifyTool()
896 self.identify_view_on_demand(None, None)
897
898 def LabelTool(self):
899 self.canvas.LabelTool()
900
901 def FullExtent(self):
902 self.canvas.FitMapToWindow()
903
904 def FullLayerExtent(self):
905 self.canvas.FitLayerToWindow(self.current_layer())
906
907 def FullSelectionExtent(self):
908 self.canvas.FitSelectedToWindow()
909
910 def ExportMap(self):
911 self.canvas.Export()
912
913 def PrintMap(self):
914 self.canvas.Print()
915
916 def RenameMap(self):
917 dlg = wxTextEntryDialog(self, _("Map Title:"), _("Rename Map"),
918 self.Map().Title())
919 if dlg.ShowModal() == wxID_OK:
920 title = dlg.GetValue()
921 if title != "":
922 self.Map().SetTitle(title)
923
924 dlg.Destroy()
925
926 def RenameLayer(self):
927 """Let the user rename the currently selected layer"""
928 layer = self.current_layer()
929 if layer is not None:
930 dlg = wxTextEntryDialog(self, _("Layer Title:"), _("Rename Layer"),
931 layer.Title())
932 try:
933 if dlg.ShowModal() == wxID_OK:
934 title = dlg.GetValue()
935 if title != "":
936 layer.SetTitle(title)
937 finally:
938 dlg.Destroy()
939
940 def identify_view_on_demand(self, layer, shapes):
941 """Subscribed to the canvas' SHAPES_SELECTED message
942
943 If the current tool is the identify tool, at least one shape is
944 selected and the identify dialog is not shown, show the dialog.
945 """
946 # If the selection has become empty we don't need to do
947 # anything. Otherwise it could happen that the dialog was popped
948 # up when the selection became empty, e.g. when a new selection
949 # is opened while the identify tool is active and dialog had
950 # been closed
951 if not shapes:
952 return
953
954 name = "identify_view"
955 if self.canvas.CurrentTool() == "IdentifyTool":
956 if not self.dialog_open(name):
957 dialog = identifyview.IdentifyView(self, name)
958 self.add_dialog(name, dialog)
959 dialog.Show(True)
960 else:
961 # FIXME: bring dialog to front?
962 pass
963
964 def title_changed(self, map):
965 """Subscribed to the canvas' TITLE_CHANGED messages"""
966 self.update_title()
967
968 def update_title(self):
969 """Update the window's title according to it's current state.
970
971 In this default implementation the title is 'Thuban - ' followed
972 by the map's title or simply 'Thuban' if there is not map.
973 Derived classes should override this method to get different
974 titles.
975
976 This method is called automatically by other methods when the
977 title may have to change. For the methods implemented in this
978 class this usually only means that a different map has been set
979 or the current map's title has changed.
980 """
981 map = self.Map()
982 if map is not None:
983 title = _("Thuban - %s") % (map.Title(),)
984 else:
985 title = _("Thuban")
986 self.SetTitle(title)
987
988
989 #
990 # Define all the commands available in the main window
991 #
992
993
994 # Helper functions to define common command implementations
995 def call_method(context, methodname, *args):
996 """Call the mainwindow's method methodname with args *args"""
997 apply(getattr(context.mainwindow, methodname), args)
998
999 def _method_command(name, title, method, helptext = "",
1000 icon = "", sensitive = None, checked = None):
1001 """Add a command implemented by a method of the mainwindow object"""
1002 registry.Add(Command(name, title, call_method, args=(method,),
1003 helptext = helptext, icon = icon,
1004 sensitive = sensitive, checked = checked))
1005
1006 def make_check_current_tool(toolname):
1007 """Return a function that tests if the currently active tool is toolname
1008
1009 The returned function can be called with the context and returns
1010 true iff the currently active tool's name is toolname. It's directly
1011 usable as the 'checked' callback of a command.
1012 """
1013 def check_current_tool(context, name=toolname):
1014 return context.mainwindow.canvas.CurrentTool() == name
1015 return check_current_tool
1016
1017 def _tool_command(name, title, method, toolname, helptext = "",
1018 icon = "", sensitive = None):
1019 """Add a tool command"""
1020 registry.Add(ToolCommand(name, title, call_method, args=(method,),
1021 helptext = helptext, icon = icon,
1022 checked = make_check_current_tool(toolname),
1023 sensitive = sensitive))
1024
1025 def _has_selected_layer(context):
1026 """Return true if a layer is selected in the context"""
1027 return context.mainwindow.has_selected_layer()
1028
1029 def _has_selected_layer_visible(context):
1030 """Return true if a layer is selected in the context which is
1031 visible."""
1032 if context.mainwindow.has_selected_layer():
1033 layer = context.mainwindow.current_layer()
1034 if layer.Visible(): return True
1035 return False
1036
1037 def _has_selected_shape_layer(context):
1038 """Return true if a shape layer is selected in the context"""
1039 return context.mainwindow.has_selected_shape_layer()
1040
1041 def _has_selected_shapes(context):
1042 """Return true if a layer is selected in the context"""
1043 return context.mainwindow.has_selected_shapes()
1044
1045 def _can_remove_layer(context):
1046 return context.mainwindow.CanRemoveLayer()
1047
1048 def _has_tree_window_shown(context):
1049 """Return true if the tree window is shown"""
1050 return context.mainwindow.SessionTreeShown()
1051
1052 def _has_visible_map(context):
1053 """Return true iff theres a visible map in the mainwindow.
1054
1055 A visible map is a map with at least one visible layer."""
1056 map = context.mainwindow.Map()
1057 if map is not None:
1058 for layer in map.Layers():
1059 if layer.Visible():
1060 return True
1061 return False
1062
1063 def _has_legend_shown(context):
1064 """Return true if the legend window is shown"""
1065 return context.mainwindow.LegendShown()
1066
1067 def _has_gdal_support(context):
1068 """Return True if the GDAL is available"""
1069 return Thuban.Model.resource.has_gdal_support()
1070
1071 def _has_dbconnections(context):
1072 """Return whether the the session has database connections"""
1073 return context.session.HasDBConnections()
1074
1075 def _has_postgis_support(context):
1076 return has_postgis_support()
1077
1078
1079 # File menu
1080 _method_command("new_session", _("&New Session"), "NewSession",
1081 helptext = _("Start a new session"))
1082 _method_command("open_session", _("&Open Session..."), "OpenSession",
1083 helptext = _("Open a session file"))
1084 _method_command("save_session", _("&Save Session"), "SaveSession",
1085 helptext =_("Save this session to the file it was opened from"))
1086 _method_command("save_session_as", _("Save Session &As..."), "SaveSessionAs",
1087 helptext = _("Save this session to a new file"))
1088 _method_command("toggle_session_tree", _("Session &Tree"), "ToggleSessionTree",
1089 checked = _has_tree_window_shown,
1090 helptext = _("Toggle on/off the session tree analysis window"))
1091 _method_command("toggle_legend", _("Legend"), "ToggleLegend",
1092 checked = _has_legend_shown,
1093 helptext = _("Toggle Legend on/off"))
1094 _method_command("database_management", _("&Database Connections..."),
1095 "DatabaseManagement",
1096 sensitive = _has_postgis_support)
1097 _method_command("exit", _("E&xit"), "Exit",
1098 helptext = _("Finish working with Thuban"))
1099
1100 # Help menu
1101 _method_command("help_about", _("&About..."), "About",
1102 helptext = _("Info about Thuban authors, version and modules"))
1103
1104
1105 # Map menu
1106 _method_command("map_projection", _("Pro&jection..."), "MapProjection",
1107 helptext = _("Set or change the map projection"))
1108
1109 _tool_command("map_zoom_in_tool", _("&Zoom in"), "ZoomInTool", "ZoomInTool",
1110 helptext = _("Switch to map-mode 'zoom-in'"), icon = "zoom_in",
1111 sensitive = _has_visible_map)
1112 _tool_command("map_zoom_out_tool", _("Zoom &out"), "ZoomOutTool", "ZoomOutTool",
1113 helptext = _("Switch to map-mode 'zoom-out'"), icon = "zoom_out",
1114 sensitive = _has_visible_map)
1115 _tool_command("map_pan_tool", _("&Pan"), "PanTool", "PanTool",
1116 helptext = _("Switch to map-mode 'pan'"), icon = "pan",
1117 sensitive = _has_visible_map)
1118 _tool_command("map_identify_tool", _("&Identify"), "IdentifyTool",
1119 "IdentifyTool",
1120 helptext = _("Switch to map-mode 'identify'"), icon = "identify",
1121 sensitive = _has_visible_map)
1122 _tool_command("map_label_tool", _("&Label"), "LabelTool", "LabelTool",
1123 helptext = _("Add/Remove labels"), icon = "label",
1124 sensitive = _has_visible_map)
1125 _method_command("map_full_extent", _("&Full extent"), "FullExtent",
1126 helptext = _("Zoom to the full map extent"), icon = "fullextent",
1127 sensitive = _has_visible_map)
1128 _method_command("layer_full_extent", _("&Full layer extent"), "FullLayerExtent",
1129 helptext = _("Zoom to the full layer extent"),
1130 icon = "fulllayerextent", sensitive = _has_selected_layer)
1131 _method_command("selected_full_extent", _("&Full selection extent"),
1132 "FullSelectionExtent",
1133 helptext = _("Zoom to the full selection extent"),
1134 icon = "fullselextent", sensitive = _has_selected_shapes)
1135 _method_command("map_export", _("E&xport"), "ExportMap",
1136 helptext = _("Export the map to file"))
1137 _method_command("map_print", _("Prin&t"), "PrintMap",
1138 helptext = _("Print the map"))
1139 _method_command("map_rename", _("&Rename..."), "RenameMap",
1140 helptext = _("Rename the map"))
1141 _method_command("layer_add", _("&Add Layer..."), "AddLayer",
1142 helptext = _("Add a new layer to the map"))
1143 _method_command("rasterlayer_add", _("&Add Image Layer..."), "AddRasterLayer",
1144 helptext = _("Add a new image layer to the map"),
1145 sensitive = _has_gdal_support)
1146 _method_command("layer_add_db", _("Add &Database Layer..."), "AddDBLayer",
1147 helptext = _("Add a new database layer to active map"),
1148 sensitive = _has_dbconnections)
1149 _method_command("layer_remove", _("&Remove Layer"), "RemoveLayer",
1150 helptext = _("Remove selected layer"),
1151 sensitive = _can_remove_layer)
1152
1153 # Layer menu
1154 _method_command("layer_projection", _("Pro&jection..."), "LayerProjection",
1155 sensitive = _has_selected_layer,
1156 helptext = _("Specify projection for selected layer"))
1157 _method_command("layer_duplicate", _("&Duplicate"), "DuplicateLayer",
1158 helptext = _("Duplicate selected layer"),
1159 sensitive = lambda context: context.mainwindow.CanDuplicateLayer())
1160 _method_command("layer_rename", _("Re&name ..."), "RenameLayer",
1161 helptext = _("Rename selected layer"),
1162 sensitive = _has_selected_layer)
1163 _method_command("layer_raise", _("&Raise"), "RaiseLayer",
1164 helptext = _("Raise selected layer"),
1165 sensitive = _has_selected_layer)
1166 _method_command("layer_lower", _("&Lower"), "LowerLayer",
1167 helptext = _("Lower selected layer"),
1168 sensitive = _has_selected_layer)
1169 _method_command("layer_show", _("&Show"), "ShowLayer",
1170 helptext = _("Make selected layer visible"),
1171 sensitive = _has_selected_layer)
1172 _method_command("layer_hide", _("&Hide"), "HideLayer",
1173 helptext = _("Make selected layer unvisible"),
1174 sensitive = _has_selected_layer)
1175 _method_command("layer_show_table", _("Show Ta&ble"), "LayerShowTable",
1176 helptext = _("Show the selected layer's table"),
1177 sensitive = _has_selected_shape_layer)
1178 _method_command("layer_properties", _("&Properties..."), "LayerEditProperties",
1179 sensitive = _has_selected_layer,
1180 helptext = _("Edit the properties of the selected layer"))
1181 _method_command("layer_jointable", _("&Join Table..."), "LayerJoinTable",
1182 sensitive = _has_selected_shape_layer,
1183 helptext = _("Join and attach a table to the selected layer"))
1184
1185 # further layer methods:
1186 _method_command("layer_to_top", _("&Top"), "LayerToTop",
1187 helptext = _("Put selected layer to the top"),
1188 sensitive = _has_selected_layer)
1189 _method_command("layer_to_bottom", _("&Bottom"), "LayerToBottom",
1190 helptext = _("Put selected layer to the bottom"),
1191 sensitive = _has_selected_layer)
1192 _method_command("layer_visibility", _("&Visible"), "ToggleLayerVisibility",
1193 checked = _has_selected_layer_visible,
1194 helptext = _("Toggle visibility of selected layer"),
1195 sensitive = _has_selected_layer)
1196
1197 def _can_unjoin(context):
1198 """Return whether the Layer/Unjoin command can be executed.
1199
1200 This is the case if a layer is selected and that layer has a
1201 shapestore that has an original shapestore.
1202 """
1203 layer = context.mainwindow.SelectedLayer()
1204 if layer is None:
1205 return 0
1206 getstore = getattr(layer, "ShapeStore", None)
1207 if getstore is not None:
1208 return getstore().OrigShapeStore() is not None
1209 else:
1210 return 0
1211 _method_command("layer_unjointable", _("&Unjoin Table..."), "LayerUnjoinTable",
1212 sensitive = _can_unjoin,
1213 helptext = _("Undo the last join operation"))
1214
1215
1216 def _has_tables(context):
1217 return bool(context.session.Tables())
1218
1219 # Table menu
1220 _method_command("table_open", _("&Open..."), "TableOpen",
1221 helptext = _("Open a DBF-table from a file"))
1222 _method_command("table_close", _("&Close..."), "TableClose",
1223 sensitive = lambda context: bool(context.session.UnreferencedTables()),
1224 helptext = _("Close one or more tables from a list"))
1225 _method_command("table_rename", _("&Rename..."), "TableRename",
1226 sensitive = _has_tables,
1227 helptext = _("Rename one or more tables"))
1228 _method_command("table_show", _("&Show..."), "TableShow",
1229 sensitive = _has_tables,
1230 helptext = _("Show one or more tables in a dialog"))
1231 _method_command("table_join", _("&Join..."), "TableJoin",
1232 sensitive = _has_tables,
1233 helptext = _("Join two tables creating a new one"))
1234
1235 # Export only under Windows ...
1236 map_menu = ["layer_add", "layer_add_db", "rasterlayer_add", "layer_remove",
1237 None,
1238 "map_rename",
1239 "map_projection",
1240 None,
1241 "map_zoom_in_tool", "map_zoom_out_tool",
1242 "map_pan_tool",
1243 "map_full_extent",
1244 "layer_full_extent",
1245 "selected_full_extent",
1246 None,
1247 "map_identify_tool", "map_label_tool",
1248 None,
1249 "toggle_legend",
1250 None]
1251 if wxPlatform == '__WXMSW__':
1252 map_menu.append("map_export")
1253 map_menu.append("map_print")
1254
1255 # the menu structure
1256 main_menu = Menu("<main>", "<main>",
1257 [Menu("file", _("&File"),
1258 ["new_session", "open_session", None,
1259 "save_session", "save_session_as", None,
1260 "database_management", None,
1261 "toggle_session_tree", None,
1262 "exit"]),
1263 Menu("map", _("&Map"), map_menu),
1264 Menu("layer", _("&Layer"),
1265 ["layer_rename", "layer_duplicate",
1266 None,
1267 "layer_raise", "layer_lower",
1268 None,
1269 "layer_show", "layer_hide",
1270 None,
1271 "layer_projection",
1272 None,
1273 "layer_show_table",
1274 "layer_jointable",
1275 "layer_unjointable",
1276 None,
1277 "layer_properties"]),
1278 Menu("table", _("&Table"),
1279 ["table_open", "table_close", "table_rename",
1280 None,
1281 "table_show",
1282 None,
1283 "table_join"]),
1284 Menu("help", _("&Help"),
1285 ["help_about"])])
1286
1287 # the main toolbar
1288
1289 main_toolbar = Menu("<toolbar>", "<toolbar>",
1290 ["map_zoom_in_tool", "map_zoom_out_tool", "map_pan_tool",
1291 "map_full_extent",
1292 "layer_full_extent",
1293 "selected_full_extent",
1294 None,
1295 "map_identify_tool", "map_label_tool"])
1296

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26