/[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 1648 - (show annotations)
Mon Aug 25 13:55:35 2003 UTC (21 years, 6 months ago) by bh
Original Path: trunk/thuban/Thuban/UI/mainwindow.py
File MIME type: text/x-python
File size: 45437 byte(s)
* Thuban/UI/dbdialog.py (DBDialog): Reimplement to make it look a
bit nucer and be more generic.
(DBFrame.OnAdd): Adapt to new DBDialog interface

* Thuban/UI/application.py (ThubanApplication.OpenSession): New
optional parameter db_connection_callback which is passed to
load_session.

* Thuban/UI/mainwindow.py (MainWindow.run_db_param_dialog): New.
Suitable as a db_connection_callback
(MainWindow.OpenSession): Use self.run_db_param_dialog as the
db_connection_callback of the application's OpenSession method

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26