source: grass/trunk/gui/wxpython/lmgr/frame.py

Last change on this file was 74307, checked in by neteler, 5 years ago

i18N: cleanup gettext usage for Python code (fixes #3790) (contributed by pmav99)

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id
  • Property svn:mime-type set to text/x-python
File size: 98.5 KB
Line 
1# -*- coding: utf-8 -*-
2"""
3@package lmgr::frame
4
5@brief Layer Manager - main menu, layer management toolbar, notebook
6control for display management and access to command console.
7
8Classes:
9 - frame::GMFrame
10
11(C) 2006-2015 by the GRASS Development Team
12
13This program is free software under the GNU General Public License
14(>=v2). Read the file COPYING that comes with GRASS for details.
15
16@author Michael Barton (Arizona State University)
17@author Jachym Cepicky (Mendel University of Agriculture)
18@author Martin Landa <landa.martin gmail.com>
19@author Vaclav Petras <wenzeslaus gmail.com> (menu customization)
20"""
21
22import sys
23import os
24import tempfile
25import stat
26import platform
27import re
28try:
29 import xml.etree.ElementTree as etree
30except ImportError:
31 import elementtree.ElementTree as etree # Python <= 2.4
32
33from core import globalvar
34import wx
35import wx.aui
36try:
37 import wx.lib.agw.flatnotebook as FN
38except ImportError:
39 import wx.lib.flatnotebook as FN
40
41if os.path.join(globalvar.ETCDIR, "python") not in sys.path:
42 sys.path.append(os.path.join(globalvar.ETCDIR, "python"))
43
44from grass.script import core as grass
45from grass.script.utils import decode
46
47from core.gcmd import RunCommand, GError, GMessage, EncodeString
48from core.settings import UserSettings, GetDisplayVectSettings
49from core.utils import SetAddOnPath, GetLayerNameFromCmd, command2ltype
50from gui_core.preferences import MapsetAccess, PreferencesDialog
51from lmgr.layertree import LayerTree, LMIcons
52from lmgr.menudata import LayerManagerMenuData, LayerManagerModuleTree
53from gui_core.widgets import GNotebook, FormNotebook
54from core.workspace import ProcessWorkspaceFile, ProcessGrcFile, WriteWorkspaceFile
55from core.gconsole import GConsole, EVT_IGNORED_CMD_RUN
56from core.giface import Notification
57from gui_core.goutput import GConsoleWindow, GC_PROMPT
58from gui_core.dialogs import LocationDialog, MapsetDialog, CreateNewVector, GroupDialog, MapLayersDialog, QuitDialog
59from gui_core.menu import SearchModuleWindow
60from gui_core.menu import Menu as GMenu
61from core.debug import Debug
62from lmgr.toolbars import LMWorkspaceToolbar, LMDataToolbar, LMToolsToolbar
63from lmgr.toolbars import LMMiscToolbar, LMVectorToolbar, LMNvizToolbar
64from lmgr.pyshell import PyShellWindow
65from lmgr.giface import LayerManagerGrassInterface
66from datacatalog.catalog import DataCatalog
67from gui_core.forms import GUI
68from gui_core.wrap import Menu, TextEntryDialog
69
70
71class GMFrame(wx.Frame):
72 """Layer Manager frame with notebook widget for controlling GRASS
73 GIS. Includes command console page for typing GRASS (and other)
74 commands, tree widget page for managing map layers.
75 """
76
77 def __init__(
78 self, parent, id=wx.ID_ANY, title=None, workspace=None,
79 size=globalvar.GM_WINDOW_SIZE, style=wx.DEFAULT_FRAME_STYLE, **
80 kwargs):
81 self.parent = parent
82 if title:
83 self.baseTitle = title
84 else:
85 try:
86 grassVersion = grass.version()['version']
87 except KeyError:
88 sys.stderr.write(_("Unable to get GRASS version\n"))
89 grassVersion = "?"
90 self.baseTitle = _("GRASS GIS %s Layer Manager") % grassVersion
91
92 self.iconsize = (16, 16)
93
94 self.displayIndex = 0 # index value for map displays and layer trees
95 self.currentPage = None # currently selected page for layer tree notebook
96 self.currentPageNum = None # currently selected page number for layer tree notebook
97 self.workspaceFile = workspace # workspace file
98 self.workspaceChanged = False # track changes in workspace
99 # if we are currently loading workspace to ignore some events
100 self.loadingWorkspace = False
101 self.cwdPath = None # current working directory
102
103 wx.Frame.__init__(self, parent=parent, id=id, size=size,
104 style=style, **kwargs)
105 self._setTitle()
106 self.SetName("LayerManager")
107
108 self.SetIcon(
109 wx.Icon(
110 os.path.join(
111 globalvar.ICONDIR,
112 'grass.ico'),
113 wx.BITMAP_TYPE_ICO))
114
115 self._giface = LayerManagerGrassInterface(self)
116
117 menu_errors = []
118 def add_menu_error(message):
119 menu_errors.append(message)
120 def show_menu_errors(messages):
121 if messages:
122 self._gconsole.WriteError(
123 _("There were some issues when loading menu"
124 " or Modules tab:"))
125 for message in messages:
126 self._gconsole.WriteError(message)
127
128 # the main menu bar
129 self._menuTreeBuilder = LayerManagerMenuData(message_handler=add_menu_error)
130 # the search tree and command console
131 self._moduleTreeBuilder = LayerManagerModuleTree(message_handler=add_menu_error)
132 self._auimgr = wx.aui.AuiManager(self)
133
134 # list of open dialogs
135 self.dialogs = dict()
136 self.dialogs['preferences'] = None
137 self.dialogs['nvizPreferences'] = None
138 self.dialogs['atm'] = list()
139
140 # create widgets
141 self._createMenuBar()
142 self.statusbar = self.CreateStatusBar(number=1)
143 self.notebook = self._createNoteBook()
144 self.toolbars = {'workspace': LMWorkspaceToolbar(parent=self),
145 'data': LMDataToolbar(parent=self),
146 'tools': LMToolsToolbar(parent=self),
147 'misc': LMMiscToolbar(parent=self),
148 'vector': LMVectorToolbar(parent=self),
149 'nviz': LMNvizToolbar(parent=self)}
150 self._toolbarsData = {'workspace': ("toolbarWorkspace", # name
151 _("Workspace Toolbar"), # caption
152 1), # row
153 'data': ("toolbarData",
154 _("Data Toolbar"),
155 1),
156 'misc': ("toolbarMisc",
157 _("Misc Toolbar"),
158 2),
159 'tools': ("toolbarTools",
160 _("Tools Toolbar"),
161 2),
162 'vector': ("toolbarVector",
163 _("Vector Toolbar"),
164 2),
165 'nviz': ("toolbarNviz",
166 _("3D view Toolbar"),
167 2),
168 }
169 if sys.platform == 'win32':
170 self._toolbarsList = ('workspace', 'data',
171 'vector', 'tools', 'misc', 'nviz')
172 else:
173 self._toolbarsList = ('data', 'workspace',
174 'nviz', 'misc', 'tools', 'vector')
175 for toolbar in self._toolbarsList:
176 name, caption, row = self._toolbarsData[toolbar]
177 self._auimgr.AddPane(self.toolbars[toolbar],
178 wx.aui.AuiPaneInfo().
179 Name(name).Caption(caption).
180 ToolbarPane().Top().Row(row).
181 LeftDockable(False).RightDockable(False).
182 BottomDockable(False).TopDockable(True).
183 CloseButton(False).Layer(2).
184 BestSize((self.toolbars[toolbar].GetBestSize())))
185
186 self._auimgr.GetPane('toolbarNviz').Hide()
187 # bindings
188 self.Bind(wx.EVT_CLOSE, self.OnCloseWindowOrExit)
189 self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
190
191 self._giface.mapCreated.connect(self.OnMapCreated)
192 self._giface.updateMap.connect(self._updateCurrentMap)
193
194 # minimal frame size
195 self.SetMinSize(globalvar.GM_WINDOW_MIN_SIZE)
196
197 # AUI stuff
198 self._auimgr.AddPane(self.notebook, wx.aui.AuiPaneInfo().
199 Left().CentrePane().BestSize((-1, -1)).Dockable(False).
200 CloseButton(False).DestroyOnClose(True).Row(1).Layer(0))
201
202 self._auimgr.Update()
203
204 wx.CallAfter(self.notebook.SetSelectionByName, 'layers')
205
206 # use default window layout ?
207 if UserSettings.Get(
208 group='general', key='defWindowPos', subkey='enabled'):
209 dim = UserSettings.Get(
210 group='general',
211 key='defWindowPos',
212 subkey='dim')
213 try:
214 x, y = map(int, dim.split(',')[0:2])
215 w, h = map(int, dim.split(',')[2:4])
216 self.SetPosition((x, y))
217 self.SetSize((w, h))
218 except:
219 pass
220 else:
221 # does center (of screen) make sense for lmgr?
222 self.Centre()
223
224 self.Layout()
225 self.Show()
226
227 # load workspace file if requested
228 if self.workspaceFile:
229 # load given workspace file
230 if self.LoadWorkspaceFile(self.workspaceFile):
231 self._setTitle()
232 else:
233 self.workspaceFile = None
234 else:
235 # start default initial display
236 self.NewDisplay(show=False)
237
238 # show map display widnow
239 # -> OnSize() -> UpdateMap()
240 for mapdisp in self.GetMapDisplay(onlyCurrent=False):
241 mapdisp.Show()
242
243 # redirect stderr to log area
244 self._gconsole.Redirect()
245
246 # fix goutput's pane size (required for Mac OSX)`
247 self.goutput.SetSashPosition(int(self.GetSize()[1] * .8))
248
249 self.workspaceChanged = False
250
251 show_menu_errors(menu_errors)
252
253 # start with layer manager on top
254 if self.currentPage:
255 self.GetMapDisplay().Raise()
256 wx.CallAfter(self.Raise)
257
258 def _setTitle(self):
259 """Set frame title"""
260 if self.workspaceFile:
261 self.SetTitle(
262 self.baseTitle +
263 " - " +
264 os.path.splitext(
265 os.path.basename(
266 self.workspaceFile))[0])
267 else:
268 self.SetTitle(self.baseTitle)
269
270 def _createMenuBar(self):
271 """Creates menu bar"""
272 self.menubar = GMenu(
273 parent=self,
274 model=self._menuTreeBuilder.GetModel(
275 separators=True))
276 self.SetMenuBar(self.menubar)
277 self.menucmd = self.menubar.GetCmd()
278
279 def _createTabMenu(self):
280 """Creates context menu for display tabs.
281
282 Used to rename display.
283 """
284 menu = Menu()
285 item = wx.MenuItem(menu, id=wx.ID_ANY, text=_("Rename Map Display"))
286 menu.AppendItem(item)
287 self.Bind(wx.EVT_MENU, self.OnRenameDisplay, item)
288
289 return menu
290
291 def _setCopyingOfSelectedText(self):
292 copy = UserSettings.Get(
293 group='manager',
294 key='copySelectedTextToClipboard',
295 subkey='enabled')
296 self.goutput.SetCopyingOfSelectedText(copy)
297
298 def IsPaneShown(self, name):
299 """Check if pane (toolbar, ...) of given name is currently shown"""
300 if self._auimgr.GetPane(name).IsOk():
301 return self._auimgr.GetPane(name).IsShown()
302 return False
303
304 def _createNoteBook(self):
305 """Creates notebook widgets"""
306 if sys.platform == 'win32':
307 self.notebook = GNotebook(
308 parent=self, style=globalvar.FNPageDStyle)
309 else:
310 self.notebook = FormNotebook(parent=self, style=wx.NB_BOTTOM)
311 # create displays notebook widget and add it to main notebook page
312 cbStyle = globalvar.FNPageStyle
313 if globalvar.hasAgw:
314 self.notebookLayers = FN.FlatNotebook(
315 self.notebook, id=wx.ID_ANY, agwStyle=cbStyle)
316 else:
317 self.notebookLayers = FN.FlatNotebook(
318 self.notebook, id=wx.ID_ANY, style=cbStyle)
319 self.notebookLayers.SetTabAreaColour(globalvar.FNPageColor)
320 menu = self._createTabMenu()
321 self.notebookLayers.SetRightClickMenu(menu)
322 self.notebook.AddPage(
323 page=self.notebookLayers,
324 text=_("Layers"),
325 name='layers')
326
327 # create 'command output' text area
328 self._gconsole = GConsole(
329 guiparent=self, giface=self._giface,
330 ignoredCmdPattern='^d\..*|^r[3]?\.mapcalc$|^i.group$|^r.import$|'
331 '^r.external$|^r.external.out$|'
332 '^v.import$|^v.external$|^v.external.out$|'
333 '^cd$|^cd .*')
334 self.goutput = GConsoleWindow(
335 parent=self.notebook,
336 gconsole=self._gconsole,
337 menuModel=self._moduleTreeBuilder.GetModel(),
338 gcstyle=GC_PROMPT)
339 self.notebook.AddPage(
340 page=self.goutput,
341 text=_("Console"),
342 name='output')
343
344 self.goutput.showNotification.connect(
345 lambda message: self.SetStatusText(message))
346
347 self._gconsole.mapCreated.connect(self.OnMapCreated)
348 self.goutput.contentChanged.connect(
349 lambda notification: self._switchPage(notification))
350
351 self._gconsole.Bind(EVT_IGNORED_CMD_RUN,
352 lambda event: self.RunSpecialCmd(event.cmd))
353
354 self._setCopyingOfSelectedText()
355
356 # create 'search module' notebook page
357 if not UserSettings.Get(
358 group='manager', key='hideTabs', subkey='search'):
359 self.search = SearchModuleWindow(
360 parent=self.notebook, handlerObj=self,
361 giface=self._giface,
362 model=self._moduleTreeBuilder.GetModel())
363 self.search.showNotification.connect(
364 lambda message: self.SetStatusText(message))
365 self.notebook.AddPage(
366 page=self.search,
367 text=_("Modules"),
368 name='search')
369 else:
370 self.search = None
371
372 # create 'data catalog' notebook page
373 self.datacatalog = DataCatalog(
374 parent=self.notebook, giface=self._giface)
375 self.datacatalog.showNotification.connect(
376 lambda message: self.SetStatusText(message))
377 self.datacatalog.changeMapset.connect(lambda mapset: self.ChangeMapset(mapset))
378 self.datacatalog.changeLocation.connect(lambda mapset, location: self.ChangeLocation(location, mapset))
379 self.notebook.AddPage(
380 page=self.datacatalog,
381 text=_("Data"),
382 name='catalog')
383
384 # create 'python shell' notebook page
385 if not UserSettings.Get(
386 group='manager', key='hideTabs', subkey='pyshell'):
387 self.pyshell = PyShellWindow(
388 parent=self.notebook, giface=self._giface, simpleEditorHandler=self.OnSimpleEditor)
389 self.notebook.AddPage(
390 page=self.pyshell,
391 text=_("Python"),
392 name='pyshell')
393 else:
394 self.pyshell = None
395
396 # bindings
397 if sys.platform == 'win32':
398 self.notebook.Bind(
399 FN.EVT_FLATNOTEBOOK_PAGE_CHANGED,
400 self.OnPageChanged)
401 else:
402 self.notebook.Bind(
403 wx.EVT_NOTEBOOK_PAGE_CHANGED,
404 self.OnPageChanged)
405 self.notebookLayers.Bind(
406 FN.EVT_FLATNOTEBOOK_PAGE_CHANGED,
407 self.OnCBPageChanged)
408 self.notebookLayers.Bind(
409 FN.EVT_FLATNOTEBOOK_PAGE_CLOSING,
410 self.OnCBPageClosed)
411
412 return self.notebook
413
414 def AddNvizTools(self, firstTime):
415 """Add nviz notebook page
416
417 :param firstTime: if a mapdisplay is starting 3D mode for the
418 first time
419 """
420 Debug.msg(5, "GMFrame.AddNvizTools()")
421 from nviz.main import haveNviz
422 if not haveNviz:
423 return
424
425 from nviz.main import NvizToolWindow
426
427 # show toolbar
428 self._auimgr.GetPane('toolbarNviz').Show()
429 # reorder other toolbars
430 for pos, toolbar in enumerate(
431 ('toolbarVector', 'toolbarTools', 'toolbarMisc', 'toolbarNviz')):
432 self._auimgr.GetPane(toolbar).Row(2).Position(pos)
433 self._auimgr.Update()
434
435 # create nviz tools tab
436 self.nviz = NvizToolWindow(
437 parent=self.notebook,
438 tree=self.GetLayerTree(),
439 display=self.GetMapDisplay())
440 idx = self.notebook.GetPageIndexByName('layers')
441 self.notebook.InsertNBPage(
442 index=idx + 1,
443 page=self.nviz,
444 text=_("3D view"),
445 name='nviz')
446 self.notebook.SetSelectionByName('nviz')
447
448 # this is a bit strange here since a new window is created everytime
449 if not firstTime:
450 for page in ('view', 'light', 'fringe',
451 'constant', 'cplane', 'animation'):
452 self.nviz.UpdatePage(page)
453
454 def RemoveNvizTools(self):
455 """Remove nviz notebook page"""
456 # if more mapwindow3D were possible, check here if nb page should be
457 # removed
458 self.notebook.SetSelectionByName('layers')
459 self.notebook.DeleteNBPage('nviz')
460
461 # hide toolbar
462 self._auimgr.GetPane('toolbarNviz').Hide()
463 for pos, toolbar in enumerate(
464 ('toolbarVector', 'toolbarTools', 'toolbarMisc')):
465 self._auimgr.GetPane(toolbar).Row(2).Position(pos)
466 self._auimgr.Update()
467
468 def WorkspaceChanged(self):
469 """Update window title"""
470 if not self.workspaceChanged:
471 self.workspaceChanged = True
472
473 if self.workspaceFile:
474 self._setTitle()
475
476 def OnLocationWizard(self, event):
477 """Launch location wizard"""
478 from location_wizard.wizard import LocationWizard
479 from location_wizard.dialogs import RegionDef
480
481 gWizard = LocationWizard(parent=self,
482 grassdatabase=grass.gisenv()['GISDBASE'])
483 location = gWizard.location
484
485 if location is not None:
486 dlg = wx.MessageDialog(parent=self,
487 message=_('Location <%s> created.\n\n'
488 'Do you want to switch to the '
489 'new location?') % location,
490 caption=_("Switch to new location?"),
491 style=wx.YES_NO | wx.NO_DEFAULT |
492 wx.ICON_QUESTION | wx.CENTRE)
493
494 ret = dlg.ShowModal()
495 dlg.Destroy()
496 if ret == wx.ID_YES:
497 if RunCommand('g.mapset', parent=self,
498 location=location,
499 mapset='PERMANENT') != 0:
500 return
501
502 # close current workspace and create new one
503 self.OnWorkspaceClose()
504 self.OnWorkspaceNew()
505 GMessage(parent=self,
506 message=_("Current location is <%(loc)s>.\n"
507 "Current mapset is <%(mapset)s>.") %
508 {'loc': location, 'mapset': 'PERMANENT'})
509
510 # code duplication with gis_set.py
511 dlg = wx.MessageDialog(
512 parent=self,
513 message=_(
514 "Do you want to set the default "
515 "region extents and resolution now?"),
516 caption=_("Location <%s> created") %
517 location,
518 style=wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION)
519 dlg.CenterOnScreen()
520 if dlg.ShowModal() == wx.ID_YES:
521 dlg.Destroy()
522 defineRegion = RegionDef(self, location=location)
523 defineRegion.CenterOnScreen()
524 defineRegion.ShowModal()
525 defineRegion.Destroy()
526 else:
527 dlg.Destroy()
528
529 def OnSettingsChanged(self):
530 """Here can be functions which have to be called
531 after receiving settingsChanged signal.
532 Now only set copying of selected text to clipboard (in goutput).
533 """
534 # self._createMenuBar() # bug when menu is re-created on the fly
535 self._setCopyingOfSelectedText()
536
537 def OnGCPManager(self, event=None, cmd=None):
538 """Launch georectifier module. See OnIClass documentation"""
539 from gcp.manager import GCPWizard
540 GCPWizard(self, self._giface)
541
542 def OnGModeler(self, event=None, cmd=None):
543 """Launch Graphical Modeler. See OnIClass documentation"""
544 from gmodeler.frame import ModelFrame
545 win = ModelFrame(parent=self, giface=self._giface)
546 win.CentreOnScreen()
547 win.Show()
548
549 def OnPsMap(self, event=None, cmd=None):
550 """Launch Cartographic Composer. See OnIClass documentation"""
551 from psmap.frame import PsMapFrame
552 win = PsMapFrame(parent=self)
553 win.CentreOnScreen()
554 win.Show()
555
556 def OnMapSwipe(self, event=None, cmd=None):
557 """Launch Map Swipe. See OnIClass documentation"""
558 from mapswipe.frame import SwipeMapFrame
559
560 win = SwipeMapFrame(parent=self, giface=self._giface)
561
562 rasters = []
563 tree = self.GetLayerTree()
564 if tree:
565 for layer in tree.GetSelections():
566 if tree.GetLayerInfo(
567 layer, key='maplayer').GetType() != 'raster':
568 continue
569 rasters.append(
570 tree.GetLayerInfo(
571 layer, key='maplayer').GetName())
572
573 if len(rasters) >= 1:
574 win.SetFirstRaster(rasters[0])
575 if len(rasters) >= 2:
576 win.SetSecondRaster(rasters[1])
577 win.SetRasterNames()
578
579 win.CentreOnScreen()
580 win.Show()
581
582 def OnRLiSetup(self, event=None, cmd=None):
583 """Launch r.li setup. See OnIClass documentation"""
584 from rlisetup.frame import RLiSetupFrame
585 win = RLiSetupFrame(parent=self)
586 win.CentreOnScreen()
587 win.Show()
588
589 def OnDone(self, event):
590 """Command execution finished"""
591 if hasattr(self, "model"):
592 self.model.DeleteIntermediateData(log=self._gconsole)
593 del self.model
594 self.SetStatusText('')
595
596 def OnRunModel(self, event):
597 """Run model"""
598 filename = ''
599 dlg = wx.FileDialog(parent=self, message=_("Choose model to run"),
600 defaultDir=os.getcwd(),
601 wildcard=_("GRASS Model File (*.gxm)|*.gxm"))
602 if dlg.ShowModal() == wx.ID_OK:
603 filename = dlg.GetPath()
604
605 if not filename:
606 dlg.Destroy()
607 return
608
609 from gmodeler.model import Model
610 self.model = Model()
611 self.model.LoadModel(filename)
612 self.model.Run(
613 log=self.GetLogWindow(),
614 onDone=self.OnDone,
615 parent=self)
616 dlg.Destroy()
617
618 def OnMapsets(self, event):
619 """Launch mapset access dialog
620 """
621 dlg = MapsetAccess(parent=self, id=wx.ID_ANY)
622 dlg.CenterOnScreen()
623
624 if dlg.ShowModal() == wx.ID_OK:
625 ms = dlg.GetMapsets()
626 RunCommand('g.mapsets',
627 parent=self,
628 mapset='%s' % ','.join(ms),
629 operation='set')
630
631 def OnCBPageChanged(self, event):
632 """Page in notebook (display) changed"""
633 self.currentPage = self.notebookLayers.GetCurrentPage()
634 self.currentPageNum = self.notebookLayers.GetSelection()
635 try:
636 self.GetMapDisplay().SetFocus()
637 self.GetMapDisplay().Raise()
638 except:
639 pass
640
641 event.Skip()
642
643 def OnPageChanged(self, event):
644 """Page in notebook changed"""
645 page = event.GetSelection()
646 if page == self.notebook.GetPageIndexByName('output'):
647 wx.CallAfter(self.goutput.ResetFocus)
648 elif page == self.notebook.GetPageIndexByName('catalog'):
649 wx.CallAfter(self.datacatalog.LoadItems)
650 self.SetStatusText('', 0)
651
652 event.Skip()
653
654 def OnCBPageClosed(self, event):
655 """Page of notebook closed
656 Also close associated map display
657 """
658
659 # save changes in the workspace
660 maptree = self.GetLayerTree()
661 if self.workspaceChanged and UserSettings.Get(
662 group='manager', key='askOnQuit', subkey='enabled'):
663 if self.workspaceFile:
664 message = _("Do you want to save changes in the workspace?")
665 else:
666 message = _("Do you want to store current settings "
667 "to workspace file?")
668
669 # ask user to save current settings
670 if maptree.GetCount() > 0:
671 name = self.notebookLayers.GetPageText(self.currentPageNum)
672 dlg = wx.MessageDialog(self,
673 message=message,
674 caption=_("Close Map Display %s") % name,
675 style=wx.YES_NO | wx.YES_DEFAULT |
676 wx.CANCEL | wx.ICON_QUESTION | wx.CENTRE)
677 ret = dlg.ShowModal()
678 if ret == wx.ID_YES:
679 if not self.workspaceFile:
680 self.OnWorkspaceSaveAs()
681 else:
682 self.SaveToWorkspaceFile(self.workspaceFile)
683 elif ret == wx.ID_CANCEL:
684 event.Veto()
685 dlg.Destroy()
686 return
687 dlg.Destroy()
688
689 self.notebookLayers.GetPage(event.GetSelection()).maptree.Map.Clean()
690 self.notebookLayers.GetPage(event.GetSelection()).maptree.Close(True)
691
692 self.currentPage = None
693
694 event.Skip()
695
696 def _switchPageHandler(self, event, notification):
697 self._switchPage(notification=notification)
698 event.Skip()
699
700 def _switchPage(self, notification):
701 """Manages @c 'output' notebook page according to event notification."""
702 if notification == Notification.HIGHLIGHT:
703 self.notebook.HighlightPageByName('output')
704 if notification == Notification.MAKE_VISIBLE:
705 self.notebook.SetSelectionByName('output')
706 if notification == Notification.RAISE_WINDOW:
707 self.notebook.SetSelectionByName('output')
708 self.SetFocus()
709 self.Raise()
710
711 def RunSpecialCmd(self, command):
712 """Run command from command line, check for GUI wrappers"""
713 if re.compile('^d\..*').search(command[0]):
714 self.RunDisplayCmd(command)
715 elif re.compile('r[3]?\.mapcalc').search(command[0]):
716 self.OnMapCalculator(event=None, cmd=command)
717 elif command[0] == 'i.group':
718 self.OnEditImageryGroups(event=None, cmd=command)
719 elif command[0] == 'r.import':
720 self.OnImportGdalLayers(event=None, cmd=command)
721 elif command[0] == 'r.external':
722 self.OnLinkGdalLayers(event=None, cmd=command)
723 elif command[0] == 'r.external.out':
724 self.OnRasterOutputFormat(event=None)
725 elif command[0] == 'v.import':
726 self.OnImportOgrLayers(event=None, cmd=command)
727 elif command[0] == 'v.external':
728 self.OnLinkOgrLayers(event=None, cmd=command)
729 elif command[0] == 'v.external.out':
730 self.OnVectorOutputFormat(event=None)
731 elif command[0] == 'cd':
732 self.OnChangeCWD(event=None, cmd=command)
733 else:
734 raise ValueError('Layer Manager special command (%s)'
735 ' not supported.' % ' '.join(command))
736
737 def RunDisplayCmd(self, command):
738 """Handles display commands.
739
740 :param command: command in a list
741 """
742 if not self.currentPage:
743 self.NewDisplay(show=True)
744 # here should be the d.* commands which are not layers
745 if command[0] == 'd.erase':
746 # rest of d.erase is ignored
747 self.GetLayerTree().DeleteAllLayers()
748 return
749 try:
750 # display GRASS commands
751 layertype = command2ltype[command[0]]
752 except KeyError:
753 GMessage(
754 parent=self, message=_(
755 "Command '%s' not yet implemented in the WxGUI. "
756 "Try adding it as a command layer instead.") %
757 command[0])
758 return
759
760 if layertype == 'barscale':
761 if len(command) > 1:
762 self.GetMapDisplay().AddBarscale(cmd=command)
763 else:
764 self.GetMapDisplay().AddBarscale()
765 elif layertype == 'rastleg':
766 if len(command) > 1:
767 self.GetMapDisplay().AddLegendRast(cmd=command)
768 else:
769 self.GetMapDisplay().AddLegendRast()
770 elif layertype == 'vectleg':
771 if len(command) > 1:
772 self.GetMapDisplay().AddLegendVect(cmd=command, showDialog=False)
773 else:
774 self.GetMapDisplay().AddLegendVect(showDialog=True)
775 elif layertype == 'northarrow':
776 if len(command) > 1:
777 self.GetMapDisplay().AddArrow(cmd=command)
778 else:
779 self.GetMapDisplay().AddArrow()
780 elif layertype == 'text':
781 if len(command) > 1:
782 self.GetMapDisplay().AddDtext(cmd=command)
783 else:
784 self.GetMapDisplay().AddDtext()
785 elif layertype == 'redraw':
786 self.GetMapDisplay().OnRender(None)
787 elif layertype == 'export':
788 GUI(parent=self, show=False).ParseCommand(
789 command, completed=(self.GetMapDisplay().DOutFileOptData, '', ''))
790 elif layertype == 'torast':
791 if len(command) <= 1:
792 task = GUI(
793 parent=self, show=True).ParseCommand(
794 command, completed=(
795 self.GetMapDisplay().DToRastOptData, '', ''))
796 else:
797 task = GUI(
798 parent=self, show=None).ParseCommand(
799 command, completed=(
800 self.GetMapDisplay().DToRastOptData, '', ''))
801 self.GetMapDisplay().DToRast(command=task.get_cmd())
802 else:
803 # add layer into layer tree
804 lname, found = GetLayerNameFromCmd(command, fullyQualified=True,
805 layerType=layertype)
806 self.GetLayerTree().AddLayer(ltype=layertype, lchecked=True if lname else None,
807 lname=lname, lcmd=command)
808
809 def GetLayerNotebook(self):
810 """Get Layers Notebook"""
811 return self.notebookLayers
812
813 def GetLayerTree(self):
814 """Get current layer tree
815
816 :return: LayerTree instance
817 :return: None no layer tree selected
818 """
819 if self.currentPage:
820 return self.currentPage.maptree
821 return None
822
823 def GetMapDisplay(self, onlyCurrent=True):
824 """Get current map display
825
826 :param bool onlyCurrent: True to return only active mapdisplay
827 False for list of all mapdisplays
828
829 :return: MapFrame instance (or list)
830 :return: None no mapdisplay selected
831 """
832 if onlyCurrent:
833 if self.currentPage:
834 return self.GetLayerTree().GetMapDisplay()
835 else:
836 return None
837 else: # -> return list of all mapdisplays
838 mlist = list()
839 for idx in range(0, self.notebookLayers.GetPageCount()):
840 mlist.append(self.notebookLayers.GetPage(
841 idx).maptree.GetMapDisplay())
842
843 return mlist
844
845 def GetAllMapDisplays(self):
846 """Get all (open) map displays"""
847 return self.GetMapDisplay(onlyCurrent=False)
848
849 def GetLogWindow(self):
850 """Gets console for command output and messages"""
851 return self._gconsole
852
853 def GetToolbar(self, name):
854 """Returns toolbar if exists else None"""
855 if name in self.toolbars:
856 return self.toolbars[name]
857
858 return None
859
860 def GetMenuCmd(self, event):
861 """Get GRASS command from menu item
862
863 :return: command as a list"""
864 layer = None
865 if event:
866 cmd = self.menucmd[event.GetId()]
867 else:
868 cmd = ''
869
870 try:
871 cmdlist = cmd.split(' ')
872 except: # already list?
873 cmdlist = cmd
874
875 # check list of dummy commands for GUI modules that do not have GRASS
876 # bin modules or scripts.
877 if cmd in ['vcolors', 'r.mapcalc', 'r3.mapcalc']:
878 return cmdlist
879
880 try:
881 layer = self.GetLayerTree().layer_selected
882 name = self.GetLayerTree().GetLayerInfo(layer, key='maplayer').name
883 type = self.GetLayerTree().GetLayerInfo(layer, key='type')
884 except:
885 layer = None
886
887 if layer and len(cmdlist) == 1: # only if no parameters given
888 if (type == 'raster' and cmdlist[0][0] == 'r' and cmdlist[0][
889 1] != '3') or (type == 'vector' and cmdlist[0][0] == 'v'):
890 input = GUI().GetCommandInputMapParamKey(cmdlist[0])
891 if input:
892 cmdlist.append("%s=%s" % (input, name))
893
894 return cmdlist
895
896 def RunMenuCmd(self, event=None, cmd=[]):
897 """Run command selected from menu"""
898 if event:
899 cmd = self.GetMenuCmd(event)
900 self._gconsole.RunCmd(cmd)
901
902 def OnMenuCmd(self, event=None, cmd=[]):
903 """Parse command selected from menu"""
904 if event:
905 cmd = self.GetMenuCmd(event)
906 GUI(parent=self, giface=self._giface).ParseCommand(cmd)
907
908 def OnVNet(self, event):
909 """Vector network analysis tool"""
910 if self.GetMapDisplay():
911 self.GetMapDisplay().OnVNet(event)
912 else:
913 self.NewDisplay(show=True).OnVNet(event)
914
915 def OnVDigit(self, event):
916 """Start vector digitizer
917 """
918 if not self.currentPage:
919 self.MsgNoLayerSelected()
920 return
921
922 tree = self.GetLayerTree()
923 layer = tree.layer_selected
924 # no map layer selected
925 if not layer:
926 self.MsgNoLayerSelected()
927 return
928
929 # available only for vector map layers
930 try:
931 mapLayer = tree.GetLayerInfo(layer, key='maplayer')
932 except:
933 mapLayer = None
934
935 if not mapLayer or mapLayer.GetType() != 'vector':
936 GMessage(parent=self,
937 message=_("Selected map layer is not vector."))
938 return
939
940 if mapLayer.GetMapset() != grass.gisenv()['MAPSET']:
941 GMessage(
942 parent=self, message=_(
943 "Editing is allowed only for vector maps from the "
944 "current mapset."))
945 return
946
947 if not tree.GetLayerInfo(layer):
948 return
949 dcmd = tree.GetLayerInfo(layer, key='cmd')
950 if not dcmd:
951 return
952
953 digitToolbar = self.GetMapDisplay().GetToolbar('vdigit')
954 if digitToolbar:
955 stopOnly = False
956 if mapLayer is digitToolbar.GetLayer():
957 stopOnly = True
958 tree.OnStopEditing(None) # TODO: change to signal
959 if stopOnly:
960 return
961
962 tree.OnStartEditing(None) # TODO: change to signal
963
964 def OnRunScript(self, event):
965 """Run user-defined script"""
966 # open dialog and choose script file
967 dlg = wx.FileDialog(
968 parent=self,
969 message=_("Choose script file to run"),
970 defaultDir=os.getcwd(),
971 wildcard=_("Python script (*.py)|*.py|Bash script (*.sh)|*.sh"))
972
973 filename = None
974 if dlg.ShowModal() == wx.ID_OK:
975 filename = dlg.GetPath()
976
977 if not filename:
978 return False
979 try:
980 filename_encoded = EncodeString(filename)
981 except UnicodeEncodeError:
982 GError(
983 parent=self, message=_(
984 "Due to the limitations of your operating system, "
985 "the script path cannot contain certain non-ascii characters. "
986 "Please rename the script or move it to a different location."))
987 return
988
989 if not os.path.exists(filename):
990 GError(parent=self,
991 message=_("Script file '%s' doesn't exist. "
992 "Operation canceled.") % filename)
993 return
994
995 # check permission
996 if not os.access(filename, os.X_OK):
997 dlg = wx.MessageDialog(
998 self,
999 message=_(
1000 "Script <%s> is not executable. "
1001 "Do you want to set the permissions "
1002 "that allows you to run this script "
1003 "(note that you must be the owner of the file)?" %
1004 os.path.basename(filename)),
1005 caption=_("Set permission?"),
1006 style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION)
1007 if dlg.ShowModal() != wx.ID_YES:
1008 return
1009 dlg.Destroy()
1010 try:
1011 mode = stat.S_IMODE(os.lstat(filename)[stat.ST_MODE])
1012 os.chmod(filename, mode | stat.S_IXUSR)
1013 except OSError:
1014 GError(
1015 _("Unable to set permission. Operation canceled."),
1016 parent=self)
1017 return
1018
1019 # check GRASS_ADDON_PATH
1020 addonPath = os.getenv('GRASS_ADDON_PATH', [])
1021 if addonPath:
1022 addonPath = addonPath.split(os.pathsep)
1023 dirName = os.path.dirname(filename_encoded)
1024 if dirName not in addonPath:
1025 addonPath.append(dirName)
1026 dlg = wx.MessageDialog(
1027 self,
1028 message=_(
1029 "Directory '%s' is not defined in GRASS_ADDON_PATH. "
1030 "Do you want add this directory to GRASS_ADDON_PATH?") %
1031 dirName, caption=_("Update Addons path?"),
1032 style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION)
1033 if dlg.ShowModal() == wx.ID_YES:
1034 SetAddOnPath(os.pathsep.join(addonPath), key='PATH')
1035 dlg.Destroy()
1036
1037 self._gconsole.WriteCmdLog(_("Launching script '%s'...") % filename)
1038 self._gconsole.RunCmd([filename])
1039
1040 def OnChangeLocation(self, event):
1041 """Change current location"""
1042 dlg = LocationDialog(parent=self)
1043 if dlg.ShowModal() == wx.ID_OK:
1044 location, mapset = dlg.GetValues()
1045 dlg.Destroy()
1046
1047 if not location or not mapset:
1048 GError(
1049 parent=self,
1050 message=_(
1051 "No location/mapset provided. Operation canceled."))
1052 return # this should not happen
1053 self.ChangeLocation(location, mapset)
1054
1055 def ChangeLocation(self, location, mapset):
1056 if RunCommand('g.mapset', parent=self,
1057 location=location,
1058 mapset=mapset) != 0:
1059 return # error reported
1060
1061 # close current workspace and create new one
1062 self.OnWorkspaceClose()
1063 self.OnWorkspaceNew()
1064 GMessage(parent=self,
1065 message=_("Current location is <%(loc)s>.\n"
1066 "Current mapset is <%(mapset)s>.") %
1067 {'loc': location, 'mapset': mapset})
1068
1069 def OnCreateMapset(self, event):
1070 """Create new mapset"""
1071 dlg = wx.TextEntryDialog(parent=self,
1072 message=_('Enter name for new mapset:'),
1073 caption=_('Create new mapset'))
1074
1075 if dlg.ShowModal() == wx.ID_OK:
1076 mapset = dlg.GetValue()
1077 if not mapset:
1078 GError(parent=self,
1079 message=_("No mapset provided. Operation canceled."))
1080 return
1081
1082 ret = RunCommand('g.mapset',
1083 parent=self,
1084 flags='c',
1085 mapset=mapset)
1086 # ensure that DB connection is defined
1087 ret += RunCommand('db.connect',
1088 parent=self,
1089 flags='c')
1090 if ret == 0:
1091 GMessage(parent=self,
1092 message=_("Current mapset is <%s>.") % mapset)
1093
1094 def OnChangeMapset(self, event):
1095 """Change current mapset"""
1096 dlg = MapsetDialog(parent=self)
1097
1098 if dlg.ShowModal() == wx.ID_OK:
1099 mapset = dlg.GetMapset()
1100 dlg.Destroy()
1101
1102 if not mapset:
1103 GError(parent=self,
1104 message=_("No mapset provided. Operation canceled."))
1105 return
1106 self.ChangeMapset(mapset)
1107
1108 def ChangeMapset(self, mapset):
1109 """Change current mapset and update map display title"""
1110 if RunCommand('g.mapset',
1111 parent=self,
1112 mapset=mapset) == 0:
1113 GMessage(parent=self,
1114 message=_("Current mapset is <%s>.") % mapset)
1115
1116 # TODO: this does not use the actual names if they were
1117 # renamed (it just uses the numbers)
1118 dispId = 1
1119 for display in self.GetMapDisplay(onlyCurrent=False):
1120 display.SetTitleWithName(str(dispId)) # TODO: signal ?
1121 dispId += 1
1122
1123 def OnChangeCWD(self, event=None, cmd=None):
1124 """Change current working directory
1125
1126 :param event: to be able to serve as a handler of wx event
1127 :param cmd: command as a list (must start with 'cd')
1128 """
1129 # local functions
1130 def write_beginning(parameter=None, command=None):
1131 if parameter:
1132 self._giface.WriteCmdLog('cd "' + parameter + '"')
1133 else:
1134 # naive concat but will be enough most of the time
1135 self._giface.WriteCmdLog(' '.join(command))
1136
1137 def write_changed():
1138 self._giface.WriteLog(_("Working directory changed to:\n\"%s\"")
1139 % os.getcwd())
1140
1141 def write_end():
1142 self._giface.WriteCmdLog(' ')
1143
1144 def write_help():
1145 self._giface.WriteLog(_("Changes current working directory"
1146 " for this GUI."))
1147 self._giface.WriteLog(_("Usage: cd [directory]"))
1148 self._giface.WriteLog(_("Without parameters it opens a dialog."))
1149 # TODO: the following is longer then 80 chars
1150 # but this should be solved by the function not caller
1151 # also because of translations
1152 self._giface.WriteLog(_("If ~ (tilde) is present as the first"
1153 " directory on the path, it is replaced"
1154 " by user's home directory."))
1155
1156 # check correctness of cmd
1157 if cmd and cmd[0] != 'cd':
1158 # this is programmer's error
1159 # can be relaxed in future
1160 # but keep it strict unless needed otherwise
1161 raise ValueError("OnChangeCWD cmd parameter must be list of"
1162 " length 1 or 2 and 'cd' as a first item")
1163 if cmd and len(cmd) > 2:
1164 # this might be a user error
1165 write_beginning(command=cmd)
1166 self._giface.WriteError(_("More than one parameter provided."))
1167 write_help()
1168 write_end()
1169 return
1170 # use chdir or dialog
1171 if cmd and len(cmd) == 2:
1172 write_beginning(parameter=cmd[1])
1173 if cmd[1] in ['-h', '--h', '--help', 'help']:
1174 write_help()
1175 write_end()
1176 return
1177 try:
1178 path = os.path.expanduser(cmd[1])
1179 os.chdir(path)
1180 write_changed()
1181 except OSError as error:
1182 self._giface.WriteError(str(error))
1183 write_end()
1184 else:
1185 dlg = wx.DirDialog(parent=self,
1186 message=_("Choose a working directory"),
1187 defaultPath=os.getcwd())
1188
1189 if dlg.ShowModal() == wx.ID_OK:
1190 self.cwdPath = dlg.GetPath() # is saved in the workspace
1191 write_beginning(parameter=self.cwdPath)
1192 os.chdir(self.cwdPath)
1193 write_changed()
1194 write_end()
1195
1196 def GetCwdPath(self):
1197 """Get current working directory or None"""
1198 return self.cwdPath
1199
1200 def OnNewVector(self, event):
1201 """Create new vector map layer"""
1202 dlg = CreateNewVector(self, giface=self._giface,
1203 cmd=(('v.edit',
1204 {'tool': 'create'},
1205 'map')))
1206
1207 if not dlg:
1208 return
1209
1210 name = dlg.GetName(full=True)
1211 if name and dlg.IsChecked('add'):
1212 # add layer to map layer tree
1213 self.GetLayerTree().AddLayer(ltype='vector',
1214 lname=name, lchecked=True,
1215 lcmd=['d.vect', 'map=%s' % name])
1216 dlg.Destroy()
1217
1218 def OnSystemInfo(self, event):
1219 """Print system information"""
1220 vInfo = grass.version()
1221 if not vInfo:
1222 sys.stderr.write(_("Unable to get GRASS version\n"))
1223
1224 # check also OSGeo4W on MS Windows
1225 if sys.platform == 'win32' and not os.path.exists(
1226 os.path.join(os.getenv("GISBASE"), "WinGRASS-README.url")):
1227 osgeo4w = ' (OSGeo4W)'
1228 else:
1229 osgeo4w = ''
1230
1231 self._gconsole.WriteCmdLog(_("System Info"))
1232 # platform decoding was added because of the Fedora 19 release
1233 # which has the name "Schrödinger’s cat" (umlaut and special ' character)
1234 # which appears in the platform.platform() string
1235 platform_ = decode(platform.platform())
1236 self._gconsole.WriteLog("%s: %s\n"
1237 "%s: %s\n"
1238 "%s: %s\n"
1239 "%s: %s\n"
1240 # "%s: %s (%s)\n"
1241 "GDAL: %s\n"
1242 "PROJ.4: %s\n"
1243 "GEOS: %s\n"
1244 "SQLite: %s\n"
1245 "Python: %s\n"
1246 "wxPython: %s\n"
1247 "%s: %s%s\n" % (_("GRASS version"), vInfo.get('version', _('unknown version')),
1248 _("GRASS SVN revision"), vInfo.get(
1249 'revision', '?'),
1250 _("Build date"), vInfo.get(
1251 'build_date', '?'),
1252 _("Build platform"), vInfo.get(
1253 'build_platform', '?'),
1254 # _("GIS Library Revision"),
1255 # vInfo.get('libgis_revision'],
1256 # vInfo.get('libgis_date'].split('
1257 # ', 1)[0],
1258 vInfo.get(
1259 'gdal', '?'), vInfo.get(
1260 'proj4', '?'), vInfo.get(
1261 'geos', '?'), vInfo.get(
1262 'sqlite', '?'),
1263 platform.python_version(),
1264 wx.__version__,
1265 _("Platform"), platform_, osgeo4w),
1266 notification=Notification.MAKE_VISIBLE)
1267 self._gconsole.WriteCmdLog(' ')
1268
1269 def OnAboutGRASS(self, event):
1270 """Display 'About GRASS' dialog"""
1271 from gui_core.ghelp import AboutWindow
1272 win = AboutWindow(self)
1273 win.CentreOnScreen()
1274 win.Show(True)
1275
1276 def _popupMenu(self, data):
1277 """Create popup menu
1278 """
1279 menu = Menu()
1280
1281 for key, handler in data:
1282 if key is None:
1283 menu.AppendSeparator()
1284 continue
1285 item = wx.MenuItem(menu, wx.ID_ANY, LMIcons[key].GetLabel())
1286 item.SetBitmap(LMIcons[key].GetBitmap(self.iconsize))
1287 menu.AppendItem(item)
1288 self.Bind(wx.EVT_MENU, handler, item)
1289
1290 # create menu
1291 self.PopupMenu(menu)
1292 menu.Destroy()
1293
1294 def OnImportMenu(self, event):
1295 """Import maps menu (import, link)
1296 """
1297 self._popupMenu((('rastImport', self.OnImportGdalLayers),
1298 ('vectImport', self.OnImportOgrLayers),
1299 (None, None),
1300 ('rastUnpack', self.OnUnpackRaster),
1301 ('vectUnpack', self.OnUnpackVector),
1302 (None, None),
1303 ('rastLink', self.OnLinkGdalLayers),
1304 ('vectLink', self.OnLinkOgrLayers),
1305 ('rastOut', self.OnRasterOutputFormat),
1306 ('vectOut', self.OnVectorOutputFormat)))
1307
1308 def OnWorkspaceNew(self, event=None):
1309 """Create new workspace file
1310
1311 Erase current workspace settings first
1312 """
1313 Debug.msg(4, "GMFrame.OnWorkspaceNew():")
1314
1315 # start new map display if no display is available
1316 if not self.currentPage:
1317 self.NewDisplay()
1318
1319 maptrees = [self.notebookLayers.GetPage(i).maptree for i in range(self.notebookLayers.GetPageCount())]
1320
1321 # ask user to save current settings
1322 if self.workspaceFile and self.workspaceChanged:
1323 self.OnWorkspaceSave()
1324 elif self.workspaceFile is None and any(tree.GetCount() for tree in maptrees):
1325 dlg = wx.MessageDialog(
1326 self,
1327 message=_(
1328 "Current workspace is not empty. "
1329 "Do you want to store current settings "
1330 "to workspace file?"),
1331 caption=_("Create new workspace?"),
1332 style=wx.YES_NO | wx.YES_DEFAULT | wx.CANCEL | wx.ICON_QUESTION)
1333 ret = dlg.ShowModal()
1334 if ret == wx.ID_YES:
1335 self.OnWorkspaceSaveAs()
1336 elif ret == wx.ID_CANCEL:
1337 dlg.Destroy()
1338 return
1339
1340 dlg.Destroy()
1341
1342 # delete all layers in map displays
1343 for maptree in maptrees:
1344 maptree.DeleteAllLayers()
1345
1346 # delete all decorations
1347 for display in self.GetAllMapDisplays():
1348 for overlayId in display.decorations.keys():
1349 display.RemoveOverlay(overlayId)
1350
1351 # no workspace file loaded
1352 self.workspaceFile = None
1353 self.workspaceChanged = False
1354 self._setTitle()
1355
1356 def OnWorkspaceOpen(self, event=None):
1357 """Open file with workspace definition"""
1358 dlg = wx.FileDialog(
1359 parent=self,
1360 message=_("Choose workspace file"),
1361 defaultDir=os.getcwd(),
1362 wildcard=_("GRASS Workspace File (*.gxw)|*.gxw"))
1363
1364 filename = ''
1365 if dlg.ShowModal() == wx.ID_OK:
1366 filename = dlg.GetPath()
1367
1368 if filename == '':
1369 return
1370
1371 Debug.msg(4, "GMFrame.OnWorkspaceOpen(): filename=%s" % filename)
1372
1373 # delete current layer tree content
1374 self.OnWorkspaceClose()
1375 self.loadingWorkspace = True
1376 self.LoadWorkspaceFile(filename)
1377 self.loadingWorkspace = False
1378
1379 self.workspaceFile = filename
1380 self._setTitle()
1381
1382 def _tryToSwitchMapsetFromWorkspaceFile(self, gxwXml):
1383 returncode, errors = RunCommand('g.mapset',
1384 dbase=gxwXml.database,
1385 location=gxwXml.location,
1386 mapset=gxwXml.mapset,
1387 getErrorMsg=True,
1388 )
1389 if returncode != 0:
1390 # TODO: use the function from grass.py
1391 reason = _("Most likely the database, location or mapset"
1392 " does not exist")
1393 details = errors
1394 message = _("Unable to change to location and mapset"
1395 " specified in the workspace.\n"
1396 "Reason: {reason}\nDetails: {details}\n\n"
1397 "Do you want to proceed with opening"
1398 " the workspace anyway?"
1399 ).format(**locals())
1400 dlg = wx.MessageDialog(
1401 parent=self, message=message, caption=_(
1402 "Proceed with opening of the workspace?"),
1403 style=wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION)
1404 dlg.CenterOnParent()
1405 if dlg.ShowModal() in [wx.ID_NO, wx.ID_CANCEL]:
1406 return False
1407 else:
1408 # TODO: copy from ChangeLocation function
1409 GMessage(
1410 parent=self,
1411 message=_("Current location is <%(loc)s>.\n"
1412 "Current mapset is <%(mapset)s>.") %
1413 {'loc': gxwXml.location,
1414 'mapset': gxwXml.mapset})
1415 return True
1416
1417 def LoadWorkspaceFile(self, filename):
1418 """Load layer tree definition stored in GRASS Workspace XML file (gxw)
1419
1420 .. todo::
1421 Validate against DTD
1422
1423 :return: True on success
1424 :return: False on error
1425 """
1426 # parse workspace file
1427 try:
1428 gxwXml = ProcessWorkspaceFile(etree.parse(filename))
1429 except Exception as e:
1430 GError(
1431 parent=self, message=_(
1432 "Reading workspace file <%s> failed.\n"
1433 "Invalid file, unable to parse XML document.") %
1434 filename)
1435 return False
1436
1437 if gxwXml.database and gxwXml.location and gxwXml.mapset:
1438 if not self._tryToSwitchMapsetFromWorkspaceFile(gxwXml):
1439 return False
1440
1441 # the really busy part starts here (mapset change is fast)
1442 busy = wx.BusyInfo(_("Please wait, loading workspace..."),
1443 parent=self)
1444 wx.Yield()
1445
1446 #
1447 # load layer manager window properties
1448 #
1449 if UserSettings.Get(group='general', key='workspace', subkey=[
1450 'posManager', 'enabled']) is False:
1451 if gxwXml.layerManager['pos']:
1452 self.SetPosition(gxwXml.layerManager['pos'])
1453 if gxwXml.layerManager['size']:
1454 self.SetSize(gxwXml.layerManager['size'])
1455 if gxwXml.layerManager['cwd']:
1456 self.cwdPath = gxwXml.layerManager['cwd']
1457 if os.path.isdir(self.cwdPath):
1458 os.chdir(self.cwdPath)
1459
1460 #
1461 # start map displays first (list of layers can be empty)
1462 #
1463 displayId = 0
1464 mapdisplay = list()
1465 for display in gxwXml.displays:
1466 mapdisp = self.NewDisplay(name=display['name'], show=False)
1467 mapdisplay.append(mapdisp)
1468 maptree = self.notebookLayers.GetPage(displayId).maptree
1469
1470 # set windows properties
1471 mapdisp.SetProperties(render=display['render'],
1472 mode=display['mode'],
1473 showCompExtent=display['showCompExtent'],
1474 alignExtent=display['alignExtent'],
1475 constrainRes=display['constrainRes'],
1476 projection=display['projection']['enabled'])
1477
1478 if display['projection']['enabled']:
1479 if display['projection']['epsg']:
1480 UserSettings.Set(
1481 group='display',
1482 key='projection',
1483 subkey='epsg',
1484 value=display['projection']['epsg'])
1485 if display['projection']['proj']:
1486 UserSettings.Set(
1487 group='display',
1488 key='projection',
1489 subkey='proj4',
1490 value=display['projection']['proj'])
1491
1492 # set position and size of map display
1493 if not UserSettings.Get(
1494 group='general', key='workspace',
1495 subkey=['posDisplay', 'enabled']):
1496 if display['pos']:
1497 mapdisp.SetPosition(display['pos'])
1498 if display['size']:
1499 mapdisp.SetSize(display['size'])
1500
1501 # set extent if defined
1502 if display['extent']:
1503 w, s, e, n, b, t = display['extent']
1504 region = maptree.Map.region = maptree.Map.GetRegion(
1505 w=w, s=s, e=e, n=n)
1506 mapdisp.GetWindow().ResetZoomHistory()
1507 mapdisp.GetWindow().ZoomHistory(region['n'],
1508 region['s'],
1509 region['e'],
1510 region['w'])
1511 if 'showStatusbar' in display and not display['showStatusbar']:
1512 mapdisp.statusbarManager.Show(False)
1513 if 'showToolbars' in display and not display['showToolbars']:
1514 for toolbar in mapdisp.GetToolbarNames():
1515 mapdisp.RemoveToolbar(toolbar)
1516
1517 displayId += 1
1518 mapdisp.Show() # show mapdisplay
1519 # set render property to False to speed up loading layers
1520 mapdisp.mapWindowProperties.autoRender = False
1521
1522 maptree = None
1523 selectList = [] # list of selected layers
1524 #
1525 # load list of map layers
1526 #
1527 for layer in gxwXml.layers:
1528 display = layer['display']
1529 maptree = self.notebookLayers.GetPage(display).maptree
1530 newItem = maptree.AddLayer(ltype=layer['type'],
1531 lname=layer['name'],
1532 lchecked=layer['checked'],
1533 lopacity=layer['opacity'],
1534 lcmd=layer['cmd'],
1535 lgroup=layer['group'],
1536 lnviz=layer['nviz'],
1537 lvdigit=layer['vdigit'],
1538 loadWorkspace=True)
1539
1540 if 'selected' in layer:
1541 selectList.append((maptree, newItem, layer['selected']))
1542
1543 for maptree, layer, selected in selectList:
1544 if selected:
1545 if not layer.IsSelected():
1546 maptree.SelectItem(layer, select=True)
1547 else:
1548 maptree.SelectItem(layer, select=False)
1549
1550 del busy
1551
1552 # set render property again when all layers are loaded
1553 for i, display in enumerate(gxwXml.displays):
1554 mapdisplay[i].mapWindowProperties.autoRender = display['render']
1555
1556 for overlay in gxwXml.overlays:
1557 # overlay["cmd"][0] name of command e.g. d.barscale, d.legend
1558 # overlay["cmd"][1:] parameters and flags
1559 if overlay['display'] == i:
1560 if overlay['cmd'][0] == "d.legend.vect":
1561 mapdisplay[i].AddLegendVect(overlay['cmd'])
1562 if overlay['cmd'][0] == "d.legend":
1563 mapdisplay[i].AddLegendRast(overlay['cmd'])
1564 if overlay['cmd'][0] == "d.barscale":
1565 mapdisplay[i].AddBarscale(overlay['cmd'])
1566 if overlay['cmd'][0] == "d.northarrow":
1567 mapdisplay[i].AddArrow(overlay['cmd'])
1568 if overlay['cmd'][0] == "d.text":
1569 mapdisplay[i].AddDtext(overlay['cmd'])
1570
1571 # avoid double-rendering when loading workspace
1572 # mdisp.MapWindow2D.UpdateMap()
1573 # nviz
1574 if gxwXml.displays[i]['viewMode'] == '3d':
1575 mapdisplay[i].AddNviz()
1576 self.nviz.UpdateState(view=gxwXml.nviz_state['view'],
1577 iview=gxwXml.nviz_state['iview'],
1578 light=gxwXml.nviz_state['light'])
1579 mapdisplay[i].MapWindow3D.constants = gxwXml.nviz_state['constants']
1580 for idx, constant in enumerate(mapdisplay[i].MapWindow3D.constants):
1581 mapdisplay[i].MapWindow3D.AddConstant(constant, i + 1)
1582 for page in ('view', 'light', 'fringe', 'constant', 'cplane'):
1583 self.nviz.UpdatePage(page)
1584 self.nviz.UpdateSettings()
1585 mapdisplay[i].toolbars['map'].combo.SetSelection(1)
1586
1587 return True
1588
1589 def OnWorkspaceLoadGrcFile(self, event):
1590 """Load map layers from GRC file (Tcl/Tk GUI) into map layer tree"""
1591 dlg = wx.FileDialog(
1592 parent=self,
1593 message=_("Choose GRC file to load"),
1594 defaultDir=os.getcwd(),
1595 wildcard=_("Old GRASS Workspace File (*.grc)|*.grc"))
1596
1597 filename = ''
1598 if dlg.ShowModal() == wx.ID_OK:
1599 filename = dlg.GetPath()
1600
1601 if filename == '':
1602 return
1603
1604 Debug.msg(
1605 4,
1606 "GMFrame.OnWorkspaceLoadGrcFile(): filename=%s" %
1607 filename)
1608
1609 # start new map display if no display is available
1610 if not self.currentPage:
1611 self.NewDisplay()
1612
1613 busy = wx.BusyInfo(_("Please wait, loading workspace..."),
1614 parent=self)
1615 wx.Yield()
1616
1617 maptree = None
1618 for layer in ProcessGrcFile(filename).read(self):
1619 maptree = self.notebookLayers.GetPage(layer['display']).maptree
1620 newItem = maptree.AddLayer(ltype=layer['type'],
1621 lname=layer['name'],
1622 lchecked=layer['checked'],
1623 lopacity=layer['opacity'],
1624 lcmd=layer['cmd'],
1625 lgroup=layer['group'])
1626
1627 del busy
1628
1629 if maptree:
1630 # reverse list of map layers
1631 maptree.Map.ReverseListOfLayers()
1632
1633 def OnWorkspaceSaveAs(self, event=None):
1634 """Save workspace definition to selected file"""
1635 dlg = wx.FileDialog(
1636 parent=self,
1637 message=_("Choose file to save current workspace"),
1638 defaultDir=os.getcwd(),
1639 wildcard=_("GRASS Workspace File (*.gxw)|*.gxw"),
1640 style=wx.FD_SAVE)
1641
1642 filename = ''
1643 if dlg.ShowModal() == wx.ID_OK:
1644 filename = dlg.GetPath()
1645
1646 if filename == '':
1647 return False
1648
1649 # check for extension
1650 if filename[-4:] != ".gxw":
1651 filename += ".gxw"
1652
1653 if os.path.exists(filename):
1654 dlg = wx.MessageDialog(
1655 self,
1656 message=_(
1657 "Workspace file <%s> already exists. "
1658 "Do you want to overwrite this file?") %
1659 filename,
1660 caption=_("Save workspace"),
1661 style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION)
1662 if dlg.ShowModal() != wx.ID_YES:
1663 dlg.Destroy()
1664 return False
1665
1666 Debug.msg(4, "GMFrame.OnWorkspaceSaveAs(): filename=%s" % filename)
1667
1668 self.SaveToWorkspaceFile(filename)
1669 self.workspaceFile = filename
1670 self._setTitle()
1671
1672 def OnWorkspaceSave(self, event=None):
1673 """Save file with workspace definition"""
1674 if self.workspaceFile:
1675 dlg = wx.MessageDialog(
1676 self,
1677 message=_(
1678 "Workspace file <%s> already exists. "
1679 "Do you want to overwrite this file?") %
1680 self.workspaceFile,
1681 caption=_("Save workspace"),
1682 style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION)
1683 if dlg.ShowModal() == wx.ID_NO:
1684 dlg.Destroy()
1685 else:
1686 Debug.msg(
1687 4, "GMFrame.OnWorkspaceSave(): filename=%s" %
1688 self.workspaceFile)
1689 self.SaveToWorkspaceFile(self.workspaceFile)
1690 self._setTitle()
1691 self.workspaceChanged = False
1692 else:
1693 self.OnWorkspaceSaveAs()
1694
1695 def SaveToWorkspaceFile(self, filename):
1696 """Save layer tree layout to workspace file
1697
1698 :return: True on success, False on error
1699 """
1700 tmpfile = tempfile.TemporaryFile(mode='w+b')
1701 try:
1702 WriteWorkspaceFile(lmgr=self, file=tmpfile)
1703 except Exception as e:
1704 GError(parent=self,
1705 message=_("Writing current settings to workspace file "
1706 "failed."))
1707 return False
1708
1709 try:
1710 mfile = open(filename, "wb")
1711 tmpfile.seek(0)
1712 for line in tmpfile.readlines():
1713 mfile.write(line)
1714 except IOError:
1715 GError(
1716 parent=self,
1717 message=_("Unable to open file <%s> for writing.") %
1718 filename)
1719 return False
1720
1721 mfile.close()
1722
1723 return True
1724
1725 def OnWorkspaceClose(self, event=None):
1726 """Close file with workspace definition
1727
1728 If workspace has been modified ask user to save the changes.
1729 """
1730 Debug.msg(
1731 4, "GMFrame.OnWorkspaceClose(): file=%s" %
1732 self.workspaceFile)
1733
1734 self.OnDisplayCloseAll()
1735 self.workspaceFile = None
1736 self.workspaceChanged = False
1737 self._setTitle()
1738 self.displayIndex = 0
1739 self.currentPage = None
1740
1741 def OnDisplayClose(self, event=None):
1742 """Close current map display window
1743 """
1744 if self.currentPage and self.GetMapDisplay():
1745 self.GetMapDisplay().OnCloseWindow(event)
1746
1747 def OnDisplayCloseAll(self, event=None):
1748 """Close all open map display windows
1749 """
1750 for display in self.GetMapDisplay(onlyCurrent=False):
1751 display.OnCloseWindow(event)
1752
1753 def OnRenderAllMapDisplays(self, event=None):
1754 for display in self.GetAllMapDisplays():
1755 display.OnRender(None)
1756
1757 def OnRenameDisplay(self, event):
1758 """Change Map Display name"""
1759 name = self.notebookLayers.GetPageText(self.currentPageNum)
1760 dlg = TextEntryDialog(
1761 self, message=_("Enter new name:"),
1762 caption=_("Rename Map Display"),
1763 value=name)
1764 if dlg.ShowModal() == wx.ID_OK:
1765 name = dlg.GetValue()
1766 self.notebookLayers.SetPageText(
1767 page=self.currentPageNum, text=name)
1768 mapdisplay = self.GetMapDisplay()
1769 # There is a slight inconsistency: When creating the display
1770 # we use just the number, but when user renames it,
1771 # we use the full name. Both cases make sense and each
1772 # separately gives expected result, so we keep this
1773 # behavior.
1774 mapdisplay.SetTitleWithName(name)
1775 dlg.Destroy()
1776
1777 def OnRasterRules(self, event):
1778 """Launches dialog for raster color rules"""
1779 from modules.colorrules import RasterColorTable
1780 ctable = RasterColorTable(self, layerTree=self.GetLayerTree())
1781 ctable.Show()
1782 ctable.CentreOnScreen()
1783
1784 def OnVectorRules(self, event):
1785 """Launches dialog for vector color rules"""
1786 from modules.colorrules import VectorColorTable
1787 ctable = VectorColorTable(self, layerTree=self.GetLayerTree(),
1788 attributeType='color')
1789 ctable.Show()
1790 ctable.CentreOnScreen()
1791
1792 def OnEditImageryGroups(self, event, cmd=None):
1793 """Show dialog for creating and editing groups.
1794 """
1795 dlg = GroupDialog(self)
1796 dlg.CentreOnScreen()
1797 dlg.Show()
1798
1799 def OnInstallExtension(self, event):
1800 """Install extension from GRASS Addons SVN repository"""
1801 from modules.extensions import InstallExtensionWindow
1802 win = InstallExtensionWindow(
1803 self, giface=self._giface, size=(650, 550))
1804 win.CentreOnScreen()
1805 win.Show()
1806
1807 def OnManageExtension(self, event):
1808 """Manage or uninstall extensions"""
1809 from modules.extensions import ManageExtensionWindow
1810 win = ManageExtensionWindow(self, size=(650, 300))
1811 win.CentreOnScreen()
1812 win.Show()
1813
1814 def OnPreferences(self, event):
1815 """General GUI preferences/settings
1816 """
1817 if not self.dialogs['preferences']:
1818 dlg = PreferencesDialog(parent=self, giface=self._giface)
1819 self.dialogs['preferences'] = dlg
1820 self.dialogs['preferences'].CenterOnParent()
1821
1822 dlg.settingsChanged.connect(self.OnSettingsChanged)
1823 self.Bind(
1824 wx.EVT_CLOSE,
1825 lambda evt: self.dialogs.update(
1826 preferences=None),
1827 dlg)
1828
1829 self.dialogs['preferences'].Show()
1830
1831 def OnNvizPreferences(self, event):
1832 """Show nviz preferences"""
1833 if not self.dialogs['nvizPreferences']:
1834 from nviz.preferences import NvizPreferencesDialog
1835 dlg = NvizPreferencesDialog(parent=self, giface=self._giface)
1836 self.dialogs['nvizPreferences'] = dlg
1837 self.dialogs['nvizPreferences'].CenterOnScreen()
1838 self.dialogs['nvizPreferences'].Show()
1839
1840 def OnHelp(self, event):
1841 """Show help
1842 """
1843 self._gconsole.RunCmd(['g.manual', '-i'])
1844
1845 def OnIClass(self, event=None, cmd=None):
1846 """Start wxIClass tool
1847
1848 The parameters of all handlers which are associated with module
1849 and contained in menu/toolboxes must be event and cmd.
1850 When called from menu event is always None and cmd is the
1851 associated command (list containing a module name and parameters).
1852
1853 .. todo::
1854 This documentation is actually documentation of some
1855 component related to gui_core/menu.py file.
1856 """
1857 from iclass.frame import IClassMapFrame, haveIClass, errMsg
1858 if not haveIClass:
1859 GError(_('Unable to launch "Supervised Classification Tool".\n\n'
1860 'Reason: %s') % errMsg)
1861 return
1862
1863 win = IClassMapFrame(parent=self, giface=self._giface)
1864 win.CentreOnScreen()
1865
1866 win.Show()
1867
1868 def OnAnimationTool(self, event=None, cmd=None):
1869 """Launch Animation tool. See OnIClass documentation.
1870 """
1871 from animation.frame import AnimationFrame
1872
1873 frame = AnimationFrame(parent=self, giface=self._giface)
1874 frame.CentreOnScreen()
1875 frame.Show()
1876
1877 tree = self.GetLayerTree()
1878 if tree:
1879 rasters = []
1880 for layer in tree.GetSelectedLayers(checkedOnly=False):
1881 if tree.GetLayerInfo(layer, key='type') == 'raster':
1882 rasters.append(
1883 tree.GetLayerInfo(
1884 layer, key='maplayer').GetName())
1885 if len(rasters) >= 2:
1886 from core.layerlist import LayerList
1887 from animation.data import AnimLayer
1888 layerList = LayerList()
1889 layer = AnimLayer()
1890 layer.mapType = 'raster'
1891 layer.name = ','.join(rasters)
1892 layer.cmd = ['d.rast', 'map=']
1893 layerList.AddLayer(layer)
1894 frame.SetAnimations([layerList, None, None, None])
1895
1896 def OnTimelineTool(self, event=None, cmd=None):
1897 """Launch Timeline Tool"""
1898 try:
1899 from timeline.frame import TimelineFrame
1900 except ImportError:
1901 GError(parent=self, message=_("Unable to start Timeline Tool."))
1902 return
1903 frame = TimelineFrame(None)
1904 frame.Show()
1905
1906 def OnTplotTool(self, event=None, cmd=None):
1907 """Launch Temporal Plot Tool"""
1908 try:
1909 from tplot.frame import TplotFrame
1910 except ImportError:
1911 GError(parent=self, message=_(
1912 "Unable to start Temporal Plot Tool."))
1913 return
1914 frame = TplotFrame(parent=self, giface=self._giface)
1915 frame.Show()
1916
1917 def OnHistogram(self, event):
1918 """Init histogram display canvas and tools
1919 """
1920 from modules.histogram import HistogramFrame
1921 win = HistogramFrame(self, giface=self._giface)
1922
1923 win.CentreOnScreen()
1924 win.Show()
1925 win.Refresh()
1926 win.Update()
1927
1928 def OnMapCalculator(self, event, cmd=''):
1929 """Init map calculator for interactive creation of mapcalc statements
1930 """
1931 from modules.mcalc_builder import MapCalcFrame
1932 if event:
1933 try:
1934 cmd = self.GetMenuCmd(event)
1935 except KeyError:
1936 cmd = ['r.mapcalc']
1937 win = MapCalcFrame(parent=self,
1938 giface=self._giface,
1939 cmd=cmd[0])
1940 win.CentreOnScreen()
1941 win.Show()
1942
1943 def OnVectorCleaning(self, event, cmd=''):
1944 """Init interactive vector cleaning
1945 """
1946 from modules.vclean import VectorCleaningFrame
1947 win = VectorCleaningFrame(parent=self)
1948 win.CentreOnScreen()
1949 win.Show()
1950
1951 def OnRasterOutputFormat(self, event):
1952 """Set raster output format handler"""
1953 self.OnMenuCmd(cmd=['r.external.out'])
1954
1955 def OnVectorOutputFormat(self, event):
1956 """Set vector output format handler"""
1957 from modules.import_export import GdalOutputDialog
1958 dlg = GdalOutputDialog(parent=self, ogr=True)
1959 dlg.CentreOnScreen()
1960 dlg.Show()
1961
1962 def OnUnpackRaster(self, event):
1963 """Unpack raster map handler"""
1964 self.OnMenuCmd(cmd=['r.unpack'])
1965
1966 def OnUnpackVector(self, event):
1967 """Unpack vector map handler"""
1968 self.OnMenuCmd(cmd=['v.unpack'])
1969
1970 def OnImportDxfFile(self, event, cmd=None):
1971 """Convert multiple DXF layers to GRASS vector map layers"""
1972 from modules.import_export import DxfImportDialog
1973 dlg = DxfImportDialog(parent=self, giface=self._giface)
1974 dlg.CentreOnScreen()
1975 dlg.Show()
1976
1977 def OnImportGdalLayers(self, event, cmd=None):
1978 """Convert multiple GDAL layers to GRASS raster map layers"""
1979 from modules.import_export import GdalImportDialog
1980 dlg = GdalImportDialog(parent=self, giface=self._giface)
1981 dlg.CentreOnScreen()
1982 dlg.Show()
1983
1984 def OnLinkGdalLayers(self, event, cmd=None):
1985 """Link multiple GDAL layers to GRASS raster map layers"""
1986 from modules.import_export import GdalImportDialog
1987 dlg = GdalImportDialog(parent=self, giface=self._giface, link=True)
1988 dlg.CentreOnScreen()
1989 dlg.Show()
1990
1991 def OnImportOgrLayers(self, event, cmd=None):
1992 """Convert multiple OGR layers to GRASS vector map layers"""
1993 from modules.import_export import OgrImportDialog
1994 dlg = OgrImportDialog(parent=self, giface=self._giface)
1995 dlg.CentreOnScreen()
1996 dlg.Show()
1997
1998 def OnLinkOgrLayers(self, event, cmd=None):
1999 """Links multiple OGR layers to GRASS vector map layers"""
2000 from modules.import_export import OgrImportDialog
2001 dlg = OgrImportDialog(parent=self, giface=self._giface, link=True)
2002 dlg.CentreOnScreen()
2003 dlg.Show()
2004
2005 def OnAddWS(self, event, cmd=None):
2006 """Add web services layer"""
2007 from web_services.dialogs import AddWSDialog
2008 dlg = AddWSDialog(parent=self, giface=self._giface)
2009 dlg.CentreOnScreen()
2010 x, y = dlg.GetPosition()
2011 dlg.SetPosition((x, y - 200))
2012 dlg.Show()
2013
2014 def OnSimpleEditor(self, event):
2015 # import on demand
2016 from gui_core.pyedit import PyEditFrame
2017
2018 # we don't keep track of them and we don't care about open files
2019 # there when closing the main GUI
2020 simpleEditor = PyEditFrame(parent=self, giface=self._giface)
2021 simpleEditor.SetSize(self.GetSize())
2022 simpleEditor.CenterOnScreen()
2023 simpleEditor.Show()
2024
2025 def OnShowAttributeTable(self, event, selection=None):
2026 """Show attribute table of the given vector map layer
2027 """
2028 if not self.currentPage:
2029 self.MsgNoLayerSelected()
2030 return
2031
2032 tree = self.GetLayerTree()
2033 layer = tree.layer_selected
2034 # no map layer selected
2035 if not layer:
2036 self.MsgNoLayerSelected()
2037 return
2038
2039 # available only for vector map layers
2040 try:
2041 maptype = tree.GetLayerInfo(layer, key='maplayer').type
2042 except:
2043 maptype = None
2044
2045 if not maptype or maptype != 'vector':
2046 GMessage(parent=self,
2047 message=_("Selected map layer is not vector."))
2048 return
2049
2050 if not tree.GetLayerInfo(layer):
2051 return
2052 dcmd = tree.GetLayerInfo(layer, key='cmd')
2053 if not dcmd:
2054 return
2055
2056 from dbmgr.manager import AttributeManager
2057 dbmanager = AttributeManager(parent=self, id=wx.ID_ANY,
2058 size=wx.Size(500, 300),
2059 item=layer, log=self._gconsole,
2060 selection=selection)
2061 # register ATM dialog
2062 self.dialogs['atm'].append(dbmanager)
2063 # show ATM window
2064 dbmanager.Show()
2065
2066 def OnNewDisplay(self, event=None):
2067 """Create new layer tree and map display instance"""
2068 self.NewDisplay()
2069
2070 def NewDisplay(self, name=None, show=True):
2071 """Create new layer tree, which will
2072 create an associated map display frame
2073
2074 :param name: name of new map display
2075 :param show: show map display window if True
2076
2077 :return: reference to mapdisplay intance
2078 """
2079 Debug.msg(1, "GMFrame.NewDisplay(): idx=%d" % self.displayIndex)
2080
2081 # make a new page in the bookcontrol for the layer tree (on page 0 of
2082 # the notebook)
2083 self.pg_panel = wx.Panel(
2084 self.notebookLayers,
2085 id=wx.ID_ANY,
2086 style=wx.EXPAND)
2087 if name:
2088 dispName = name
2089 else:
2090 dispName = "Display " + str(self.displayIndex + 1)
2091 self.notebookLayers.AddPage(self.pg_panel, text=dispName, select=True)
2092 self.currentPage = self.notebookLayers.GetCurrentPage()
2093
2094 # create layer tree (tree control for managing GIS layers) and put on
2095 # new notebook page
2096 self.currentPage.maptree = LayerTree(
2097 self.currentPage, giface=self._giface, id=wx.ID_ANY,
2098 pos=wx.DefaultPosition, size=wx.DefaultSize,
2099 style=wx.TR_HAS_BUTTONS | wx.TR_LINES_AT_ROOT | wx.TR_HIDE_ROOT | wx.
2100 TR_DEFAULT_STYLE | wx.NO_BORDER | wx.FULL_REPAINT_ON_RESIZE,
2101 idx=self.displayIndex, lmgr=self, notebook=self.notebookLayers,
2102 showMapDisplay=show)
2103
2104 # layout for controls
2105 cb_boxsizer = wx.BoxSizer(wx.VERTICAL)
2106 cb_boxsizer.Add(
2107 self.GetLayerTree(),
2108 proportion=1,
2109 flag=wx.EXPAND,
2110 border=1)
2111 self.currentPage.SetSizer(cb_boxsizer)
2112 cb_boxsizer.Fit(self.GetLayerTree())
2113 self.currentPage.Layout()
2114 self.GetLayerTree().Layout()
2115
2116 mapdisplay = self.currentPage.maptree.mapdisplay
2117 mapdisplay.Bind(wx.EVT_ACTIVATE,
2118 lambda event, page=self.currentPage:
2119 self._onMapDisplayFocus(page))
2120 mapdisplay.starting3dMode.connect(
2121 lambda firstTime, mapDisplayPage=self.currentPage:
2122 self._onMapDisplayStarting3dMode(mapDisplayPage))
2123 mapdisplay.starting3dMode.connect(self.AddNvizTools)
2124 mapdisplay.ending3dMode.connect(self.RemoveNvizTools)
2125
2126 # use default window layout
2127 if UserSettings.Get(
2128 group='general', key='defWindowPos', subkey='enabled'):
2129 dim = UserSettings.Get(
2130 group='general',
2131 key='defWindowPos',
2132 subkey='dim')
2133 idx = 4 + self.displayIndex * 4
2134 try:
2135 x, y = map(int, dim.split(',')[idx:idx + 2])
2136 w, h = map(int, dim.split(',')[idx + 2:idx + 4])
2137 self.GetMapDisplay().SetPosition((x, y))
2138 self.GetMapDisplay().SetSize((w, h))
2139 except:
2140 pass
2141
2142 # set default properties
2143 mapdisplay.SetProperties(render=UserSettings.Get(
2144 group='display', key='autoRendering', subkey='enabled'),
2145 mode=UserSettings.Get(
2146 group='display', key='statusbarMode', subkey='selection'),
2147 alignExtent=UserSettings.Get(
2148 group='display', key='alignExtent', subkey='enabled'),
2149 constrainRes=UserSettings.Get(
2150 group='display', key='compResolution', subkey='enabled'),
2151 showCompExtent=UserSettings.Get(
2152 group='display', key='showCompExtent', subkey='enabled'))
2153
2154 self.displayIndex += 1
2155
2156 return self.GetMapDisplay()
2157
2158 def _onMapDisplayFocus(self, notebookLayerPage):
2159 """Changes bookcontrol page to page associated with display."""
2160 # moved from mapdisp/frame.py
2161 # TODO: why it is called 3 times when getting focus?
2162 # and one times when loosing focus?
2163 if self.loadingWorkspace:
2164 return
2165 pgnum = self.notebookLayers.GetPageIndex(notebookLayerPage)
2166 if pgnum > -1:
2167 self.notebookLayers.SetSelection(pgnum)
2168 self.currentPage = self.notebookLayers.GetCurrentPage()
2169
2170 def _onMapDisplayStarting3dMode(self, mapDisplayPage):
2171 """Disables 3D mode for all map displays except for @p mapDisplay"""
2172 # TODO: it should be disabled also for newly created map windows
2173 # moreover mapdisp.Disable3dMode() does not work properly
2174 for page in range(0, self.GetLayerNotebook().GetPageCount()):
2175 mapdisp = self.GetLayerNotebook().GetPage(page).maptree.GetMapDisplay()
2176 if self.GetLayerNotebook().GetPage(page) != mapDisplayPage:
2177 mapdisp.Disable3dMode()
2178
2179 def OnAddMaps(self, event=None):
2180 """Add selected map layers into layer tree"""
2181 dialog = MapLayersDialog(parent=self, title=_(
2182 "Add selected map layers into layer tree"))
2183 dialog.applyAddingMapLayers.connect(self.AddMaps)
2184 val = dialog.ShowModal()
2185
2186 if val == wx.ID_OK:
2187 self.AddMaps(dialog.GetMapLayers(), dialog.GetLayerType(cmd=True))
2188 dialog.Destroy()
2189
2190 def AddMaps(self, mapLayers, ltype, check=False):
2191 """Add map layers to layer tree.
2192
2193 :param list mapLayers: list of map names
2194 :param str ltype: layer type ('raster', 'raster_3d', 'vector')
2195 :param bool check: True if new layers should be checked in
2196 layer tree False otherwise
2197 """
2198 # start new map display if no display is available
2199 if not self.currentPage:
2200 self.NewDisplay()
2201
2202 maptree = self.GetLayerTree()
2203
2204 for layerName in mapLayers:
2205 if ltype == 'raster':
2206 cmd = ['d.rast', 'map=%s' % layerName]
2207 elif ltype == 'raster_3d':
2208 cmd = ['d.rast3d', 'map=%s' % layerName]
2209 elif ltype == 'vector':
2210 cmd = [
2211 'd.vect', 'map=%s' %
2212 layerName] + GetDisplayVectSettings()
2213 else:
2214 GError(parent=self,
2215 message=_("Unsupported map layer type <%s>.") % ltype)
2216 return
2217
2218 newItem = maptree.AddLayer(ltype=ltype,
2219 lname=layerName,
2220 lchecked=check,
2221 lopacity=1.0,
2222 lcmd=cmd,
2223 lgroup=None)
2224
2225 def _updateCurrentMap(self, **kwargs):
2226 """Updates map of the current map window."""
2227 if 'delay' in kwargs:
2228 self.GetMapDisplay().GetWindow().UpdateMap(delay=kwargs['delay'])
2229 else:
2230 self.GetMapDisplay().GetWindow().UpdateMap()
2231
2232 def OnMapCreated(self, name, ltype, add=None):
2233 """Decides wheter the map should be added to layer tree."""
2234 if add is None:
2235 # add new map into layer if globally enabled
2236 if UserSettings.Get(group='cmd',
2237 key='addNewLayer', subkey='enabled'):
2238 self.AddOrUpdateMap(name, ltype)
2239 elif add:
2240 # add new map into layer tree
2241 self.AddOrUpdateMap(name, ltype)
2242 else:
2243 # update the map
2244 display = self.GetMapDisplay()
2245 display.GetWindow().UpdateMap(render=True)
2246
2247 def AddOrUpdateMap(self, mapName, ltype):
2248 """Add map layer or update"""
2249 # start new map display if no display is available
2250 if ltype not in ['raster', 'raster_3d', 'vector']:
2251 GError(parent=self,
2252 message=_("Unsupported map layer type <%s>.") % ltype)
2253 return
2254
2255 if not self.currentPage:
2256 self.AddMaps([mapName], ltype, check=True)
2257 else:
2258 display = self.GetMapDisplay()
2259 mapLayers = map(lambda x: x.GetName(),
2260 display.GetMap().GetListOfLayers(ltype=ltype))
2261 if mapName in mapLayers:
2262 display.GetWindow().UpdateMap(render=True)
2263 else:
2264 self.AddMaps([mapName], ltype, check=True)
2265
2266 def OnAddRaster(self, event):
2267 """Add raster map layer"""
2268 # start new map display if no display is available
2269 if not self.currentPage:
2270 self.NewDisplay(show=True)
2271
2272 self.notebook.SetSelectionByName('layers')
2273 self.GetLayerTree().AddLayer('raster')
2274
2275 def OnAddRasterMisc(self, event):
2276 """Create misc raster popup-menu"""
2277 # start new map display if no display is available
2278 if not self.currentPage:
2279 self.NewDisplay(show=True)
2280
2281 self._popupMenu((('layerRaster_3d', self.OnAddRaster3D),
2282 (None, None),
2283 ('layerRgb', self.OnAddRasterRGB),
2284 ('layerHis', self.OnAddRasterHIS),
2285 (None, None),
2286 ('layerShaded', self.OnAddRasterShaded),
2287 (None, None),
2288 ('layerRastarrow', self.OnAddRasterArrow),
2289 ('layerRastnum', self.OnAddRasterNum)))
2290
2291 # show map display
2292 self.GetMapDisplay().Show()
2293
2294 def OnAddVector(self, event):
2295 """Add vector map to the current layer tree"""
2296 # start new map display if no display is available
2297 if not self.currentPage:
2298 self.NewDisplay(show=True)
2299
2300 self.notebook.SetSelectionByName('layers')
2301 self.GetLayerTree().AddLayer('vector')
2302
2303 def OnAddVectorMisc(self, event):
2304 """Create misc vector popup-menu"""
2305 # start new map display if no display is available
2306 if not self.currentPage:
2307 self.NewDisplay(show=True)
2308
2309 self._popupMenu((('layerThememap', self.OnAddVectorTheme),
2310 ('layerThemechart', self.OnAddVectorChart)))
2311
2312 # show map display
2313 self.GetMapDisplay().Show()
2314
2315 def OnAddVectorTheme(self, event):
2316 """Add thematic vector map to the current layer tree"""
2317 self.notebook.SetSelectionByName('layers')
2318 self.GetLayerTree().AddLayer('thememap')
2319
2320 def OnAddVectorChart(self, event):
2321 """Add chart vector map to the current layer tree"""
2322 self.notebook.SetSelectionByName('layers')
2323 self.GetLayerTree().AddLayer('themechart')
2324
2325 def OnAddOverlay(self, event):
2326 """Create decoration overlay menu"""
2327 # start new map display if no display is available
2328 if not self.currentPage:
2329 self.NewDisplay(show=True)
2330
2331 self._popupMenu((('layerGrid', self.OnAddGrid),
2332 ('layerLabels', self.OnAddLabels),
2333 ('layerGeodesic', self.OnAddGeodesic),
2334 ('layerRhumb', self.OnAddRhumb),
2335 (None, None),
2336 ('layerCmd', self.OnAddCommand)))
2337
2338 # show map display
2339 self.GetMapDisplay().Show()
2340
2341 def OnAddRaster3D(self, event):
2342 """Add 3D raster map to the current layer tree"""
2343 self.notebook.SetSelectionByName('layers')
2344 self.GetLayerTree().AddLayer('raster_3d')
2345
2346 def OnAddRasterRGB(self, event):
2347 """Add RGB raster map to the current layer tree"""
2348 self.notebook.SetSelectionByName('layers')
2349 self.GetLayerTree().AddLayer('rgb')
2350
2351 def OnAddRasterHIS(self, event):
2352 """Add HIS raster map to the current layer tree"""
2353 self.notebook.SetSelectionByName('layers')
2354 self.GetLayerTree().AddLayer('his')
2355
2356 def OnAddRasterShaded(self, event):
2357 """Add shaded relief raster map to the current layer tree"""
2358 self.notebook.SetSelectionByName('layers')
2359 self.GetLayerTree().AddLayer('shaded')
2360
2361 def OnAddRasterArrow(self, event):
2362 """Add flow arrows raster map to the current layer tree"""
2363 self.notebook.SetSelectionByName('layers')
2364 # here it seems that it should be retrieved from the mapwindow
2365 mapdisplay = self.GetMapDisplay()
2366 resolution = mapdisplay.mapWindowProperties.resolution
2367 if not resolution:
2368 dlg = self.MsgDisplayResolution()
2369 if dlg.ShowModal() == wx.ID_YES:
2370 mapdisplay.mapWindowProperties.resolution = True
2371 dlg.Destroy()
2372
2373 self.GetLayerTree().AddLayer('rastarrow')
2374
2375 def OnAddRasterNum(self, event):
2376 """Add cell number raster map to the current layer tree"""
2377 self.notebook.SetSelectionByName('layers')
2378 mapdisplay = self.GetMapDisplay()
2379 resolution = mapdisplay.mapWindowProperties.resolution
2380 if not resolution:
2381 limitText = _("Note that cell values can only be displayed for "
2382 "regions of less than 10,000 cells.")
2383 dlg = self.MsgDisplayResolution(limitText)
2384 if dlg.ShowModal() == wx.ID_YES:
2385 mapdisplay.mapWindowProperties.resolution = True
2386 dlg.Destroy()
2387
2388 # region = tree.GetMap().GetCurrentRegion()
2389 # if region['cells'] > 10000:
2390 # GMessage(message = "Cell values can only be displayed for regions of < 10,000 cells.", parent = self)
2391 self.GetLayerTree().AddLayer('rastnum')
2392
2393 def OnAddCommand(self, event):
2394 """Add command line map layer to the current layer tree"""
2395 # start new map display if no display is available
2396 if not self.currentPage:
2397 self.NewDisplay(show=True)
2398
2399 self.notebook.SetSelectionByName('layers')
2400 self.GetLayerTree().AddLayer('command')
2401
2402 # show map display
2403 self.GetMapDisplay().Show()
2404
2405 def OnAddGroup(self, event):
2406 """Add layer group"""
2407 # start new map display if no display is available
2408 if not self.currentPage:
2409 self.NewDisplay(show=True)
2410
2411 self.notebook.SetSelectionByName('layers')
2412 self.GetLayerTree().AddLayer('group')
2413
2414 # show map display
2415 self.GetMapDisplay().Show()
2416
2417 def OnAddGrid(self, event):
2418 """Add grid map layer to the current layer tree"""
2419 self.notebook.SetSelectionByName('layers')
2420 self.GetLayerTree().AddLayer('grid')
2421
2422 def OnAddGeodesic(self, event):
2423 """Add geodesic line map layer to the current layer tree"""
2424 self.notebook.SetSelectionByName('layers')
2425 self.GetLayerTree().AddLayer('geodesic')
2426
2427 def OnAddRhumb(self, event):
2428 """Add rhumb map layer to the current layer tree"""
2429 self.notebook.SetSelectionByName('layers')
2430 self.GetLayerTree().AddLayer('rhumb')
2431
2432 def OnAddLabels(self, event):
2433 """Add vector labels map layer to the current layer tree"""
2434 # start new map display if no display is available
2435 if not self.currentPage:
2436 self.NewDisplay(show=True)
2437
2438 self.notebook.SetSelectionByName('layers')
2439 self.GetLayerTree().AddLayer('labels')
2440
2441 # show map display
2442 self.GetMapDisplay().Show()
2443
2444 def OnShowRegionExtent(self, event):
2445 """Add vector labels map layer to the current layer tree"""
2446 # start new map display if no display is available
2447 if not self.currentPage:
2448 self.NewDisplay(show=True)
2449 # get current map display
2450 mapdisp = self.GetMapDisplay()
2451 # change the property
2452 mapdisp.mapWindowProperties.showRegion = True
2453 # show map display (user said show so make sure it is visible)
2454 mapdisp.Show()
2455 # redraw map if auto-rendering is enabled
2456 # seems little too low level for this place
2457 # no redraw when Render is unchecked
2458 if mapdisp.IsAutoRendered():
2459 mapdisp.GetMapWindow().UpdateMap(render=False)
2460
2461 def OnDeleteLayer(self, event):
2462 """Remove selected map layer from the current layer Tree
2463 """
2464 if not self.currentPage or not self.GetLayerTree().layer_selected:
2465 self.MsgNoLayerSelected()
2466 return
2467
2468 if UserSettings.Get(
2469 group='manager', key='askOnRemoveLayer', subkey='enabled'):
2470 layerName = ''
2471 for item in self.GetLayerTree().GetSelections():
2472 name = self.GetLayerTree().GetItemText(item)
2473 idx = name.find('(' + _('opacity:'))
2474 if idx > -1:
2475 layerName += '<' + name[:idx].strip(' ') + '>,\n'
2476 else:
2477 layerName += '<' + name + '>,\n'
2478 layerName = layerName.rstrip(',\n')
2479
2480 if len(layerName) > 2: # <>
2481 message = _("Do you want to remove map layer(s)\n%s\n"
2482 "from layer tree?") % layerName
2483 else:
2484 message = _("Do you want to remove selected map layer(s) "
2485 "from layer tree?")
2486
2487 dlg = wx.MessageDialog(
2488 parent=self,
2489 message=message,
2490 caption=_("Remove map layer"),
2491 style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION)
2492
2493 if dlg.ShowModal() != wx.ID_YES:
2494 dlg.Destroy()
2495 return
2496
2497 dlg.Destroy()
2498
2499 for layer in self.GetLayerTree().GetSelections():
2500 if self.GetLayerTree().GetLayerInfo(layer, key='type') == 'group':
2501 self.GetLayerTree().DeleteChildren(layer)
2502 self.GetLayerTree().Delete(layer)
2503
2504 def OnKeyDown(self, event):
2505 """Key pressed"""
2506 kc = event.GetKeyCode()
2507
2508 if event.ControlDown():
2509 if kc == wx.WXK_TAB:
2510 # switch layer list / command output
2511 if self.notebook.GetSelection() == self.notebook.GetPageIndexByName('layers'):
2512 self.notebook.SetSelectionByName('output')
2513 else:
2514 self.notebook.SetSelectionByName('layers')
2515
2516 try:
2517 ckc = chr(kc)
2518 except ValueError:
2519 event.Skip()
2520 return
2521
2522 if event.CtrlDown():
2523 if kc == 'R':
2524 self.OnAddRaster(None)
2525 elif kc == 'V':
2526 self.OnAddVector(None)
2527
2528 event.Skip()
2529
2530 def OnCloseWindow(self, event):
2531 """Cleanup when wxGUI is quitted"""
2532 self._closeWindow(event)
2533
2534 def OnCloseWindowOrExit(self, event):
2535 """Cleanup when wxGUI is quitted
2536
2537 Ask user also to quit GRASS including terminal
2538 """
2539 dlg = QuitDialog(self)
2540 ret = dlg.ShowModal()
2541 dlg.Destroy()
2542 if ret != wx.ID_CANCEL:
2543 self._closeWindow(event)
2544 if ret == wx.ID_YES:
2545 self._quitGRASS()
2546
2547 def _closeWindow(self, event):
2548 """Close wxGUI"""
2549 # save command protocol if actived
2550 if self.goutput.btnCmdProtocol.GetValue():
2551 self.goutput.CmdProtocolSave()
2552
2553 if not self.currentPage:
2554 self._auimgr.UnInit()
2555 self.Destroy()
2556 return
2557
2558 if UserSettings.Get(group='manager', key='askOnQuit',
2559 subkey='enabled') and self.workspaceChanged:
2560 maptree = self.GetLayerTree()
2561
2562 if self.workspaceFile:
2563 message = _("Do you want to save changes in the workspace?")
2564 else:
2565 message = _("Do you want to store current settings "
2566 "to workspace file?")
2567
2568 # ask user to save current settings
2569 if maptree.GetCount() > 0:
2570 dlg = wx.MessageDialog(self,
2571 message=message,
2572 caption=_("Quit GRASS GUI"),
2573 style=wx.YES_NO | wx.YES_DEFAULT |
2574 wx.CANCEL | wx.ICON_QUESTION | wx.CENTRE)
2575 ret = dlg.ShowModal()
2576 dlg.Destroy()
2577 if ret == wx.ID_YES:
2578 if not self.workspaceFile:
2579 self.OnWorkspaceSaveAs()
2580 else:
2581 self.SaveToWorkspaceFile(self.workspaceFile)
2582 elif ret == wx.ID_CANCEL:
2583 # when called from menu, it gets CommandEvent and not
2584 # CloseEvent
2585 if hasattr(event, 'Veto'):
2586 event.Veto()
2587 return
2588
2589 # don't ask any more...
2590 UserSettings.Set(group='manager', key='askOnQuit', subkey='enabled',
2591 value=False)
2592
2593 self.OnDisplayCloseAll()
2594
2595 self.notebookLayers.DeleteAllPages()
2596 self._auimgr.UnInit()
2597 self.Destroy()
2598
2599 def _quitGRASS(self):
2600 """Quit GRASS terminal"""
2601 try:
2602 shellPid = int(grass.gisenv()['PID'])
2603 except:
2604 grass.warning(_("Unable to exit GRASS shell: unknown PID"))
2605 return
2606
2607 Debug.msg(1, "Exiting shell with pid={0}".format(shellPid))
2608 import signal
2609 os.kill(shellPid, signal.SIGTERM)
2610
2611 def MsgNoLayerSelected(self):
2612 """Show dialog message 'No layer selected'"""
2613 wx.MessageBox(parent=self,
2614 message=_("No map layer selected. Operation canceled."),
2615 caption=_("Message"),
2616 style=wx.OK | wx.ICON_INFORMATION | wx.CENTRE)
2617
2618 def MsgDisplayResolution(self, limitText=None):
2619 """Returns dialog for d.rast.num, d.rast.arrow
2620 when display resolution is not constrained
2621
2622 :param limitText: adds a note about cell limit
2623 """
2624 message = _("Display resolution is currently not constrained to "
2625 "computational settings. "
2626 "It's suggested to constrain map to region geometry. "
2627 "Do you want to constrain "
2628 "the resolution?")
2629 if limitText:
2630 message += "\n\n%s" % _(limitText)
2631 dlg = wx.MessageDialog(
2632 parent=self, message=message,
2633 caption=_("Constrain map to region geometry?"),
2634 style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION | wx.CENTRE)
2635 return dlg
Note: See TracBrowser for help on using the repository browser.