/[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 1126 - (show annotations)
Mon Jun 2 14:15:43 2003 UTC (21 years, 9 months ago) by bh
Original Path: trunk/thuban/Thuban/UI/mainwindow.py
File MIME type: text/x-python
File size: 42145 byte(s)
(layer_rename command, table_rename command):
New commands.
(main_menu): Add the new commands.
(MainWindow.TableRename): New. Implementation of the table_rename
command.
(MainWindow.RenameLayer): New. Implementation of the layer_rename
command.

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