/[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 2019 - (show annotations)
Wed Dec 3 18:56:23 2003 UTC (21 years, 3 months ago) by bh
Original Path: trunk/thuban/Thuban/UI/mainwindow.py
File MIME type: text/x-python
File size: 46896 byte(s)
(MainWindow.has_selected_shape_layer):
New. Like has_selected_layer but for shape layers only
(_has_selected_shape_layer): New. Like _has_selected_layer but for
shape layers only
(layer_show_table command, layer_jointable command): Use these
commands should only be available for shape layers

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26