/[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 1155 - (show annotations)
Thu Jun 12 12:17:11 2003 UTC (21 years, 8 months ago) by jan
Original Path: trunk/thuban/Thuban/UI/mainwindow.py
File MIME type: text/x-python
File size: 43551 byte(s)
import wxMultipleChoiceDialog from multiplechoicedialog.py rather than
from the wxPython library.

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

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26