/[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 1667 - (show annotations)
Thu Aug 28 10:21:05 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: 45359 byte(s)
Remove some unused imports

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26