/[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 1622 - (show annotations)
Thu Aug 21 11:56:06 2003 UTC (21 years, 6 months ago) by jan
Original Path: trunk/thuban/Thuban/UI/mainwindow.py
File MIME type: text/x-python
File size: 45074 byte(s)
(MainWindow.AddLayer): Changed dialog settings to allow multiple files
to load into the map.  Also reduced selection to *.shp as a default.

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26