/[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 2520 - (show annotations)
Tue Jan 11 16:52:40 2005 UTC (20 years, 1 month ago) by frank
Original Path: trunk/thuban/Thuban/UI/mainwindow.py
File MIME type: text/x-python
File size: 50178 byte(s)
* Thuban/UI/mainwindow.py (MainWindow.DuplicateLayer):
	Fix indention bug.

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26