GRASS Programmer's Manual  6.4.3(2013)-r
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Macros Pages
psmap/frame.py
Go to the documentation of this file.
1 """!
2 @package psmap.frame
3 
4 @brief GUI for ps.map
5 
6 Classes:
7  - frame::PsMapFrame
8  - frame::PsMapBufferedWindow
9 
10 (C) 2011-2012 by Anna Kratochvilova, and the GRASS Development Team
11 This program is free software under the GNU General Public License
12 (>=v2). Read the file COPYING that comes with GRASS for details.
13 
14 @author Anna Kratochvilova <kratochanna gmail.com> (bachelor's project)
15 @author Martin Landa <landa.martin gmail.com> (mentor)
16 """
17 
18 import os
19 import sys
20 import textwrap
21 import Queue
22 from math import sin, cos, pi, sqrt
23 
24 if __name__ == "__main__":
25  sys.path.append(os.path.join(os.getenv('GISBASE'), 'etc', 'gui', 'wxpython'))
26 from core import globalvar
27 import wx
28 
29 try:
30  import wx.lib.agw.flatnotebook as fnb
31 except ImportError:
32  import wx.lib.flatnotebook as fnb
33 
34 import grass.script as grass
35 
36 from gui_core.menu import Menu
37 from gui_core.goutput import CmdThread, EVT_CMD_DONE
38 from psmap.toolbars import PsMapToolbar
39 from core.gcmd import RunCommand, GError, GMessage
40 from core.settings import UserSettings
41 from gui_core.forms import GUI
42 from gui_core.dialogs import HyperlinkDialog
43 from psmap.menudata import PsMapData
44 
45 from psmap.dialogs import *
46 from psmap.instructions import *
47 from psmap.utils import *
48 
49 class PsMapFrame(wx.Frame):
50  def __init__(self, parent = None, id = wx.ID_ANY,
51  title = _("GRASS GIS Cartographic Composer (experimental prototype)"), **kwargs):
52  """!Main window of ps.map GUI
53 
54  @param parent parent window
55  @param id window id
56  @param title window title
57 
58  @param kwargs wx.Frames' arguments
59  """
60  self.parent = parent
61 
62  wx.Frame.__init__(self, parent = parent, id = id, title = title, name = "PsMap", **kwargs)
63  self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
64  #menubar
65  self.menubar = Menu(parent = self, data = PsMapData())
66  self.SetMenuBar(self.menubar)
67  #toolbar
68 
69  self.toolbar = PsMapToolbar(parent = self)
70  self.SetToolBar(self.toolbar)
71 
72  self.actionOld = self.toolbar.action['id']
73  self.iconsize = (16, 16)
74  #satusbar
75  self.statusbar = self.CreateStatusBar(number = 1)
76 
77  # mouse attributes -- position on the screen, begin and end of
78  # dragging, and type of drawing
79  self.mouse = {
80  'begin': [0, 0], # screen coordinates
81  'end' : [0, 0],
82  'use' : "pointer",
83  }
84  # available cursors
85  self.cursors = {
86  "default" : wx.StockCursor(wx.CURSOR_ARROW),
87  "cross" : wx.StockCursor(wx.CURSOR_CROSS),
88  "hand" : wx.StockCursor(wx.CURSOR_HAND),
89  "sizenwse": wx.StockCursor(wx.CURSOR_SIZENWSE)
90  }
91  # pen and brush
92  self.pen = {
93  'paper': wx.Pen(colour = "BLACK", width = 1),
94  'margins': wx.Pen(colour = "GREY", width = 1),
95  'map': wx.Pen(colour = wx.Color(86, 122, 17), width = 2),
96  'rasterLegend': wx.Pen(colour = wx.Color(219, 216, 4), width = 2),
97  'vectorLegend': wx.Pen(colour = wx.Color(219, 216, 4), width = 2),
98  'mapinfo': wx.Pen(colour = wx.Color(5, 184, 249), width = 2),
99  'scalebar': wx.Pen(colour = wx.Color(150, 150, 150), width = 2),
100  'image': wx.Pen(colour = wx.Color(255, 150, 50), width = 2),
101  'northArrow': wx.Pen(colour = wx.Color(200, 200, 200), width = 2),
102  'point': wx.Pen(colour = wx.Color(100, 100, 100), width = 2),
103  'line': wx.Pen(colour = wx.Color(0, 0, 0), width = 2),
104  'box': wx.Pen(colour = 'RED', width = 2, style = wx.SHORT_DASH),
105  'select': wx.Pen(colour = 'BLACK', width = 1, style = wx.SHORT_DASH),
106  'resize': wx.Pen(colour = 'BLACK', width = 1)
107  }
108  self.brush = {
109  'paper': wx.WHITE_BRUSH,
110  'margins': wx.TRANSPARENT_BRUSH,
111  'map': wx.Brush(wx.Color(151, 214, 90)),
112  'rasterLegend': wx.Brush(wx.Color(250, 247, 112)),
113  'vectorLegend': wx.Brush(wx.Color(250, 247, 112)),
114  'mapinfo': wx.Brush(wx.Color(127, 222, 252)),
115  'scalebar': wx.Brush(wx.Color(200, 200, 200)),
116  'image': wx.Brush(wx.Color(255, 200, 50)),
117  'northArrow': wx.Brush(wx.Color(255, 255, 255)),
118  'point': wx.Brush(wx.Color(200, 200, 200)),
119  'line': wx.TRANSPARENT_BRUSH,
120  'box': wx.TRANSPARENT_BRUSH,
121  'select':wx.TRANSPARENT_BRUSH,
122  'resize': wx.BLACK_BRUSH
123  }
124 
125 
126  # list of objects to draw
127  self.objectId = []
128 
129  # instructions
130  self.instruction = Instruction(parent = self, objectsToDraw = self.objectId)
131  # open dialogs
132  self.openDialogs = dict()
133 
134  self.pageId = wx.NewId()
135  #current page of flatnotebook
136  self.currentPage = 0
137  #canvas for draft mode
138  self.canvas = PsMapBufferedWindow(parent = self, mouse = self.mouse, pen = self.pen,
139  brush = self.brush, cursors = self.cursors,
140  instruction = self.instruction, openDialogs = self.openDialogs,
141  pageId = self.pageId, objectId = self.objectId,
142  preview = False)
143 
144  self.canvas.SetCursor(self.cursors["default"])
145  self.getInitMap()
146 
147 
148  # image path
149  env = grass.gisenv()
150  self.imgName = grass.tempfile()
151 
152  #canvas for preview
153  self.previewCanvas = PsMapBufferedWindow(parent = self, mouse = self.mouse, cursors = self.cursors,
154  pen = self.pen, brush = self.brush, preview = True)
155 
156  # set WIND_OVERRIDE
157  grass.use_temp_region()
158 
159  # create queues
160  self.requestQ = Queue.Queue()
161  self.resultQ = Queue.Queue()
162  # thread
163  self.cmdThread = CmdThread(self, self.requestQ, self.resultQ)
164 
165  self._layout()
166  self.SetMinSize(wx.Size(750, 600))
167 
168  self.Bind(fnb.EVT_FLATNOTEBOOK_PAGE_CHANGING, self.OnPageChanging)
169  self.Bind(fnb.EVT_FLATNOTEBOOK_PAGE_CHANGED, self.OnPageChanged)
170  self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
171  self.Bind(EVT_CMD_DONE, self.OnCmdDone)
172 
173  if not havePILImage:
174  wx.CallAfter(self._showErrMsg)
175 
176  def _showErrMsg(self):
177  """!Show error message (missing preview)
178  """
179  GError(parent = self,
180  message = _("Python Imaging Library is not available.\n"
181  "'Preview' functionality won't work."),
182  showTraceback = False)
183 
184  def _layout(self):
185  """!Do layout
186  """
187  mainSizer = wx.BoxSizer(wx.VERTICAL)
188  if globalvar.hasAgw:
189  self.book = fnb.FlatNotebook(parent = self, id = wx.ID_ANY,
190  agwStyle = fnb.FNB_FANCY_TABS | fnb.FNB_BOTTOM |
191  fnb.FNB_NO_NAV_BUTTONS | fnb.FNB_NO_X_BUTTON)
192  else:
193  self.book = fnb.FlatNotebook(parent = self, id = wx.ID_ANY,
194  style = fnb.FNB_FANCY_TABS | fnb.FNB_BOTTOM |
195  fnb.FNB_NO_NAV_BUTTONS | fnb.FNB_NO_X_BUTTON)
196  #self.book = fnb.FlatNotebook(self, wx.ID_ANY, style = fnb.FNB_BOTTOM)
197  self.book.AddPage(self.canvas, "Draft mode")
198  self.book.AddPage(self.previewCanvas, "Preview")
199  self.book.SetSelection(0)
200 
201  mainSizer.Add(self.book,1, wx.EXPAND)
202 
203  self.SetSizer(mainSizer)
204  mainSizer.Fit(self)
205 
206 
207  def InstructionFile(self):
208  """!Creates mapping instructions"""
209 
210  return str(self.instruction)
211 
212  def OnPSFile(self, event):
213  """!Generate PostScript"""
214  filename = self.getFile(wildcard = "PostScript (*.ps)|*.ps|Encapsulated PostScript (*.eps)|*.eps")
215  if filename:
216  self.PSFile(filename)
217 
218  def OnPsMapDialog(self, event):
219  """!Launch ps.map dialog
220  """
221  GUI(parent = self).ParseCommand(cmd = ['ps.map'])
222 
223  def OnPDFFile(self, event):
224  """!Generate PDF from PS with ps2pdf if available"""
225  if not sys.platform == 'win32':
226  try:
227  p = grass.Popen(["ps2pdf"], stderr = grass.PIPE)
228  p.stderr.close()
229 
230  except OSError:
231  GMessage(parent = self,
232  message = _("Program ps2pdf is not available. Please install it first to create PDF."))
233  return
234 
235  filename = self.getFile(wildcard = "PDF (*.pdf)|*.pdf")
236  if filename:
237  self.PSFile(filename, pdf = True)
238 
239  def OnPreview(self, event):
240  """!Run ps.map and show result"""
241  self.PSFile()
242 
243  def PSFile(self, filename = None, pdf = False):
244  """!Create temporary instructions file and run ps.map with output = filename"""
245  instrFile = grass.tempfile()
246  instrFileFd = open(instrFile, mode = 'w')
247  instrFileFd.write(self.InstructionFile())
248  instrFileFd.flush()
249  instrFileFd.close()
250 
251  temp = False
252  regOld = grass.region()
253 
254  if pdf:
255  pdfname = filename
256  else:
257  pdfname = None
258  #preview or pdf
259  if not filename or (filename and pdf):
260  temp = True
261  filename = grass.tempfile()
262  if not pdf: # lower resolution for preview
263  if self.instruction.FindInstructionByType('map'):
264  mapId = self.instruction.FindInstructionByType('map').id
265  SetResolution(dpi = 100, width = self.instruction[mapId]['rect'][2],
266  height = self.instruction[mapId]['rect'][3])
267 
268  cmd = ['ps.map', '--overwrite']
269  if os.path.splitext(filename)[1] == '.eps':
270  cmd.append('-e')
271  if self.instruction[self.pageId]['Orientation'] == 'Landscape':
272  cmd.append('-r')
273  cmd.append('input=%s' % instrFile)
274  cmd.append('output=%s' % filename)
275  if pdf:
276  self.SetStatusText(_('Generating PDF...'), 0)
277  elif not temp:
278  self.SetStatusText(_('Generating PostScript...'), 0)
279  else:
280  self.SetStatusText(_('Generating preview...'), 0)
281 
282  self.cmdThread.RunCmd(cmd, userData = {'instrFile' : instrFile, 'filename' : filename,
283  'pdfname' : pdfname, 'temp' : temp, 'regionOld' : regOld})
284 
285  def OnCmdDone(self, event):
286  """!ps.map process finished"""
287 
288  if event.returncode != 0:
289  GMessage(parent = self,
290  message = _("Ps.map exited with return code %s") % event.returncode)
291 
292  grass.try_remove(event.userData['instrFile'])
293  if event.userData['temp']:
294  grass.try_remove(event.userData['filename'])
295  return
296 
297  if event.userData['pdfname']:
298  if sys.platform == 'win32':
299  command = ['gswin32c',
300  '-P-', '-dSAFER',
301  '-dCompatibilityLevel=1.4',
302  '-q', '-P-',
303  '-dNOPAUSE', '-dBATCH',
304  '-sDEVICE=pdfwrite',
305  '-dPDFSETTINGS=/prepress', '-r1200',
306  '-sstdout=%stderr',
307  '-sOutputFile=%s' % event.userData['pdfname'],
308  '-P-', '-dSAFER',
309  '-dCompatibilityLevel=1.4',
310  '-c', '.setpdfwrite', '-f',
311  event.userData['filename']]
312  else:
313  command = ['ps2pdf', '-dPDFSETTINGS=/prepress', '-r1200',
314  event.userData['filename'], event.userData['pdfname']]
315  try:
316  proc = grass.Popen(command)
317  ret = proc.wait()
318  if ret > 0:
319  GMessage(parent = self,
320  message = _("%(prg)s exited with return code %(code)s") % {'prg': command[0],
321  'code': ret})
322  else:
323  self.SetStatusText(_('PDF generated'), 0)
324  except OSError, e:
325  GError(parent = self,
326  message = _("Program ps2pdf is not available. Please install it to create PDF.\n\n %s") % e)
327 
328  elif not event.userData['temp']:
329  self.SetStatusText(_('PostScript file generated'), 0)
330 
331  # show preview only when user doesn't want to create ps or pdf
332  if havePILImage and event.userData['temp'] and not event.userData['pdfname']:
333  RunCommand('g.region', cols = event.userData['regionOld']['cols'], rows = event.userData['regionOld']['rows'])
334 
335  busy = wx.BusyInfo(message = _("Generating preview, wait please"), parent = self)
336  wx.Yield()
337  try:
338  im = PILImage.open(event.userData['filename'])
339  if self.instruction[self.pageId]['Orientation'] == 'Landscape':
340  im = im.rotate(270)
341 
342  # hack for Windows, change method for loading EPS
343  if sys.platform == 'win32':
344  import types
345  im.load = types.MethodType(loadPSForWindows, im)
346  im.save(self.imgName, format = 'PNG')
347  except IOError, e:
348  del busy
349  dlg = HyperlinkDialog(self, title=_("Preview not available"),
350  message=_("Preview is not available probably due to missing Ghostscript."),
351  hyperlink='http://trac.osgeo.org/grass/wiki/CompileOnWindows#Ghostscript',
352  hyperlinkLabel=_("Please follow instructions on GRASS Trac Wiki."))
353  dlg.ShowModal()
354  dlg.Destroy()
355  return
356 
357 
358  rect = self.previewCanvas.ImageRect()
359  self.previewCanvas.image = wx.Image(self.imgName, wx.BITMAP_TYPE_PNG)
360  self.previewCanvas.DrawImage(rect = rect)
361 
362  del busy
363  self.SetStatusText(_('Preview generated'), 0)
364  self.book.SetSelection(1)
365  self.currentPage = 1
366 
367  grass.try_remove(event.userData['instrFile'])
368  if event.userData['temp']:
369  grass.try_remove(event.userData['filename'])
370 
371  self.delayedCall = wx.CallLater(4000, lambda: self.SetStatusText("", 0))
372 
373  def getFile(self, wildcard):
374  suffix = []
375  for filter in wildcard.split('|')[1::2]:
376  s = filter.strip('*').split('.')[1]
377  if s:
378  s = '.' + s
379  suffix.append(s)
380  raster = self.instruction.FindInstructionByType('raster')
381  if raster:
382  rasterId = raster.id
383  else:
384  rasterId = None
385 
386 
387  if rasterId and self.instruction[rasterId]['raster']:
388  mapName = self.instruction[rasterId]['raster'].split('@')[0] + suffix[0]
389  else:
390  mapName = ''
391 
392  filename = ''
393  dlg = wx.FileDialog(self, message = _("Save file as"), defaultDir = "",
394  defaultFile = mapName, wildcard = wildcard,
395  style = wx.CHANGE_DIR | wx.SAVE | wx.OVERWRITE_PROMPT)
396  if dlg.ShowModal() == wx.ID_OK:
397  filename = dlg.GetPath()
398  suffix = suffix[dlg.GetFilterIndex()]
399  if not os.path.splitext(filename)[1]:
400  filename = filename + suffix
401  elif os.path.splitext(filename)[1] != suffix and suffix != '':
402  filename = os.path.splitext(filename)[0] + suffix
403 
404  dlg.Destroy()
405  return filename
406 
407  def OnInstructionFile(self, event):
408  filename = self.getFile(wildcard = "*.psmap|*.psmap|Text file(*.txt)|*.txt|All files(*.*)|*.*")
409  if filename:
410  instrFile = open(filename, "w")
411  instrFile.write(self.InstructionFile())
412  instrFile.close()
413 
414  def OnLoadFile(self, event):
415  """!Load file and read instructions"""
416  #find file
417  filename = ''
418  dlg = wx.FileDialog(self, message = "Find instructions file", defaultDir = "",
419  defaultFile = '', wildcard = "All files (*.*)|*.*",
420  style = wx.CHANGE_DIR|wx.OPEN)
421  if dlg.ShowModal() == wx.ID_OK:
422  filename = dlg.GetPath()
423  dlg.Destroy()
424  if not filename:
425  return
426  # load instructions
427  readObjectId = []
428  readInstruction = Instruction(parent = self, objectsToDraw = readObjectId)
429  ok = readInstruction.Read(filename)
430  if not ok:
431  GMessage(_("Failed to read file %s.") % filename)
432  else:
433  self.instruction = self.canvas.instruction = readInstruction
434  self.objectId = self.canvas.objectId = readObjectId
435  self.pageId = self.canvas.pageId = self.instruction.FindInstructionByType('page').id
436  self.canvas.UpdateMapLabel()
437  self.canvas.dragId = -1
438  self.canvas.Clear()
439  self.canvas.SetPage()
440  #self.canvas.ZoomAll()
441 
442  self.DialogDataChanged(self.objectId)
443 
444  def OnPageSetup(self, event = None):
445  """!Specify paper size, margins and orientation"""
446  id = self.instruction.FindInstructionByType('page').id
447  dlg = PageSetupDialog(self, id = id, settings = self.instruction)
448  dlg.CenterOnScreen()
449  val = dlg.ShowModal()
450  if val == wx.ID_OK:
451  self.canvas.SetPage()
452  self.getInitMap()
453  self.canvas.RecalculatePosition(ids = self.objectId)
454  dlg.Destroy()
455 
456  def OnPointer(self, event):
457  self.toolbar.OnTool(event)
458  self.mouse["use"] = "pointer"
459  self.canvas.SetCursor(self.cursors["default"])
460  self.previewCanvas.SetCursor(self.cursors["default"])
461 
462  def OnPan(self, event):
463  self.toolbar.OnTool(event)
464  self.mouse["use"] = "pan"
465  self.canvas.SetCursor(self.cursors["hand"])
466  self.previewCanvas.SetCursor(self.cursors["hand"])
467 
468  def OnZoomIn(self, event):
469  self.toolbar.OnTool(event)
470  self.mouse["use"] = "zoomin"
471  self.canvas.SetCursor(self.cursors["cross"])
472  self.previewCanvas.SetCursor(self.cursors["cross"])
473 
474  def OnZoomOut(self, event):
475  self.toolbar.OnTool(event)
476  self.mouse["use"] = "zoomout"
477  self.canvas.SetCursor(self.cursors["cross"])
478  self.previewCanvas.SetCursor(self.cursors["cross"])
479 
480  def OnZoomAll(self, event):
481  self.mouseOld = self.mouse['use']
482  if self.currentPage == 0:
483  self.cursorOld = self.canvas.GetCursor()
484  else:
485  self.cursorOld = self.previewCanvas.GetCursor()
486  self.previewCanvas.GetCursor()
487  self.mouse["use"] = "zoomin"
488  if self.currentPage == 0:
489  self.canvas.ZoomAll()
490  else:
491  self.previewCanvas.ZoomAll()
492  self.mouse["use"] = self.mouseOld
493  if self.currentPage == 0:
494  self.canvas.SetCursor(self.cursorOld)
495  else:
496  self.previewCanvas.SetCursor(self.cursorOld)
497 
498 
499  def OnAddMap(self, event, notebook = False):
500  """!Add or edit map frame"""
501  if event is not None:
502  if event.GetId() != self.toolbar.action['id']:
503  self.actionOld = self.toolbar.action['id']
504  self.mouseOld = self.mouse['use']
505  self.cursorOld = self.canvas.GetCursor()
506  self.toolbar.OnTool(event)
507 
508  if self.instruction.FindInstructionByType('map'):
509  mapId = self.instruction.FindInstructionByType('map').id
510  else: mapId = None
511  id = [mapId, None, None]
512 
513  if notebook:
514  if self.instruction.FindInstructionByType('vector'):
515  vectorId = self.instruction.FindInstructionByType('vector').id
516  else: vectorId = None
517  if self.instruction.FindInstructionByType('raster'):
518  rasterId = self.instruction.FindInstructionByType('raster').id
519  else: rasterId = None
520  id[1] = rasterId
521  id[2] = vectorId
522 
523 
524  if mapId: # map exists
525 
526  self.toolbar.ToggleTool(self.actionOld, True)
527  self.toolbar.ToggleTool(self.toolbar.action['id'], False)
528  self.toolbar.action['id'] = self.actionOld
529  try:
530  self.canvas.SetCursor(self.cursorOld)
531  except AttributeError:
532  pass
533 
534 ## dlg = MapDialog(parent = self, id = id, settings = self.instruction,
535 ## notebook = notebook)
536 ## dlg.ShowModal()
537  if notebook:
538  #check map, raster, vector and save, destroy them
539  if 'map' in self.openDialogs:
540  self.openDialogs['map'].OnOK(event = None)
541  if 'raster' in self.openDialogs:
542  self.openDialogs['raster'].OnOK(event = None)
543  if 'vector' in self.openDialogs:
544  self.openDialogs['vector'].OnOK(event = None)
545 
546  if 'mapNotebook' not in self.openDialogs:
547  dlg = MapDialog(parent = self, id = id, settings = self.instruction,
548  notebook = notebook)
549  self.openDialogs['mapNotebook'] = dlg
550  self.openDialogs['mapNotebook'].Show()
551  else:
552  if 'mapNotebook' in self.openDialogs:
553  self.openDialogs['mapNotebook'].notebook.ChangeSelection(0)
554  else:
555  if 'map' not in self.openDialogs:
556  dlg = MapDialog(parent = self, id = id, settings = self.instruction,
557  notebook = notebook)
558  self.openDialogs['map'] = dlg
559  self.openDialogs['map'].Show()
560 
561 
562  else: # sofar no map
563  self.mouse["use"] = "addMap"
564  self.canvas.SetCursor(self.cursors["cross"])
565  if self.currentPage == 1:
566  self.book.SetSelection(0)
567  self.currentPage = 0
568 
569  def OnAddRaster(self, event):
570  """!Add raster map"""
571  if self.instruction.FindInstructionByType('raster'):
572  id = self.instruction.FindInstructionByType('raster').id
573  else: id = None
574  if self.instruction.FindInstructionByType('map'):
575  mapId = self.instruction.FindInstructionByType('map').id
576  else: mapId = None
577 
578  if not id:
579  if not mapId:
580  GMessage(message = _("Please, create map frame first."))
581  return
582 
583 ## dlg = RasterDialog(self, id = id, settings = self.instruction)
584 ## dlg.ShowModal()
585  if 'mapNotebook' in self.openDialogs:
586  self.openDialogs['mapNotebook'].notebook.ChangeSelection(1)
587  else:
588  if 'raster' not in self.openDialogs:
589  dlg = RasterDialog(self, id = id, settings = self.instruction)
590  self.openDialogs['raster'] = dlg
591  self.openDialogs['raster'].Show()
592 
593  def OnAddVect(self, event):
594  """!Add vector map"""
595  if self.instruction.FindInstructionByType('vector'):
596  id = self.instruction.FindInstructionByType('vector').id
597  else: id = None
598  if self.instruction.FindInstructionByType('map'):
599  mapId = self.instruction.FindInstructionByType('map').id
600  else: mapId = None
601  if not id:
602  if not mapId:
603  GMessage(message = _("Please, create map frame first."))
604  return
605 
606 ## dlg = MainVectorDialog(self, id = id, settings = self.instruction)
607 ## dlg.ShowModal()
608  if 'mapNotebook' in self.openDialogs:
609  self.openDialogs['mapNotebook'].notebook.ChangeSelection(2)
610  else:
611  if 'vector' not in self.openDialogs:
612  dlg = MainVectorDialog(self, id = id, settings = self.instruction)
613  self.openDialogs['vector'] = dlg
614  self.openDialogs['vector'].Show()
615 
616  def OnAddScalebar(self, event):
617  """!Add scalebar"""
618  if projInfo()['proj'] == 'll':
619  GMessage(message = _("Scalebar is not appropriate for this projection"))
620  return
621  if self.instruction.FindInstructionByType('scalebar'):
622  id = self.instruction.FindInstructionByType('scalebar').id
623  else: id = None
624 
625  if 'scalebar' not in self.openDialogs:
626  dlg = ScalebarDialog(self, id = id, settings = self.instruction)
627  self.openDialogs['scalebar'] = dlg
628  self.openDialogs['scalebar'].Show()
629 
630  def OnAddLegend(self, event, page = 0):
631  """!Add raster or vector legend"""
632  if self.instruction.FindInstructionByType('rasterLegend'):
633  idR = self.instruction.FindInstructionByType('rasterLegend').id
634  else: idR = None
635  if self.instruction.FindInstructionByType('vectorLegend'):
636  idV = self.instruction.FindInstructionByType('vectorLegend').id
637  else: idV = None
638 
639  if 'rasterLegend' not in self.openDialogs:
640  dlg = LegendDialog(self, id = [idR, idV], settings = self.instruction, page = page)
641  self.openDialogs['rasterLegend'] = dlg
642  self.openDialogs['vectorLegend'] = dlg
643  self.openDialogs['rasterLegend'].notebook.ChangeSelection(page)
644  self.openDialogs['rasterLegend'].Show()
645 
646  def OnAddMapinfo(self, event):
647  if self.instruction.FindInstructionByType('mapinfo'):
648  id = self.instruction.FindInstructionByType('mapinfo').id
649  else: id = None
650 
651  if 'mapinfo' not in self.openDialogs:
652  dlg = MapinfoDialog(self, id = id, settings = self.instruction)
653  self.openDialogs['mapinfo'] = dlg
654  self.openDialogs['mapinfo'].Show()
655 
656  def OnAddImage(self, event, id = None):
657  """!Show dialog for image adding and editing"""
658  position = None
659  if 'image' in self.openDialogs:
660  position = self.openDialogs['image'].GetPosition()
661  self.openDialogs['image'].OnApply(event = None)
662  self.openDialogs['image'].Destroy()
663  dlg = ImageDialog(self, id = id, settings = self.instruction)
664  self.openDialogs['image'] = dlg
665  if position:
666  dlg.SetPosition(position)
667  dlg.Show()
668 
669  def OnAddNorthArrow(self, event, id = None):
670  """!Show dialog for north arrow adding and editing"""
671  if self.instruction.FindInstructionByType('northArrow'):
672  id = self.instruction.FindInstructionByType('northArrow').id
673  else: id = None
674 
675  if 'northArrow' not in self.openDialogs:
676  dlg = NorthArrowDialog(self, id = id, settings = self.instruction)
677  self.openDialogs['northArrow'] = dlg
678  self.openDialogs['northArrow'].Show()
679 
680  def OnAddText(self, event, id = None):
681  """!Show dialog for text adding and editing"""
682  position = None
683  if 'text' in self.openDialogs:
684  position = self.openDialogs['text'].GetPosition()
685  self.openDialogs['text'].OnApply(event = None)
686  self.openDialogs['text'].Destroy()
687  dlg = TextDialog(self, id = id, settings = self.instruction)
688  self.openDialogs['text'] = dlg
689  if position:
690  dlg.SetPosition(position)
691  dlg.Show()
692 
693  def OnAddPoint(self, event):
694  """!Add point action selected"""
695  self.mouse["use"] = "addPoint"
696  self.canvas.SetCursor(self.cursors["cross"])
697 
698  def AddPoint(self, id = None, coordinates = None):
699  """!Add point and open property dialog.
700 
701  @param id id point id (None if creating new point)
702  @param coordinates coordinates of new point
703  """
704  position = None
705  if 'point' in self.openDialogs:
706  position = self.openDialogs['point'].GetPosition()
707  self.openDialogs['point'].OnApply(event = None)
708  self.openDialogs['point'].Destroy()
709  dlg = PointDialog(self, id = id, settings = self.instruction,
710  coordinates = coordinates)
711  self.openDialogs['point'] = dlg
712  if position:
713  dlg.SetPosition(position)
714  if coordinates:
715  dlg.OnApply(event = None)
716  dlg.Show()
717 
718  def OnAddLine(self, event):
719  """!Add line action selected"""
720  self.mouse["use"] = "addLine"
721  self.canvas.SetCursor(self.cursors["cross"])
722 
723  def AddLine(self, id = None, coordinates = None):
724  """!Add line and open property dialog.
725 
726  @param id id line id (None if creating new line)
727  @param coordinates coordinates of new line
728  """
729  position = None
730  if 'line' in self.openDialogs:
731  position = self.openDialogs['line'].GetPosition()
732  self.openDialogs['line'].OnApply(event = None)
733  self.openDialogs['line'].Destroy()
734  dlg = RectangleDialog(self, id = id, settings = self.instruction,
735  type = 'line', coordinates = coordinates)
736  self.openDialogs['line'] = dlg
737  if position:
738  dlg.SetPosition(position)
739  if coordinates:
740  dlg.OnApply(event = None)
741  dlg.Show()
742 
743  def OnAddRectangle(self, event):
744  """!Add rectangle action selected"""
745  self.mouse["use"] = "addRectangle"
746  self.canvas.SetCursor(self.cursors["cross"])
747 
748  def AddRectangle(self, id = None, coordinates = None):
749  """!Add rectangle and open property dialog.
750 
751  @param id id rectangle id (None if creating new rectangle)
752  @param coordinates coordinates of new rectangle
753  """
754  position = None
755  if 'rectangle' in self.openDialogs:
756  position = self.openDialogs['rectangle'].GetPosition()
757  self.openDialogs['rectangle'].OnApply(event = None)
758  self.openDialogs['rectangle'].Destroy()
759  dlg = RectangleDialog(self, id = id, settings = self.instruction,
760  type = 'rectangle', coordinates = coordinates)
761  self.openDialogs['rectangle'] = dlg
762  if position:
763  dlg.SetPosition(position)
764  if coordinates:
765  dlg.OnApply(event = None)
766  dlg.Show()
767 
768  def getModifiedTextBounds(self, x, y, textExtent, rotation):
769  """!computes bounding box of rotated text, not very precisely"""
770  w, h = textExtent
771  rotation = float(rotation)/180*pi
772  H = float(w) * sin(rotation)
773  W = float(w) * cos(rotation)
774  X, Y = x, y
775  if pi/2 < rotation <= 3*pi/2:
776  X = x + W
777  if 0 < rotation < pi:
778  Y = y - H
779  if rotation == 0:
780  return wx.Rect(x,y, *textExtent)
781  else:
782  return wx.Rect(X, Y, abs(W), abs(H)).Inflate(h,h)
783 
784  def makePSFont(self, textDict):
785  """!creates a wx.Font object from selected postscript font. To be
786  used for estimating bounding rectangle of text"""
787 
788  fontsize = textDict['fontsize'] * self.canvas.currScale
789  fontface = textDict['font'].split('-')[0]
790  try:
791  fontstyle = textDict['font'].split('-')[1]
792  except IndexError:
793  fontstyle = ''
794 
795  if fontface == "Times":
796  family = wx.FONTFAMILY_ROMAN
797  face = "times"
798  elif fontface == "Helvetica":
799  family = wx.FONTFAMILY_SWISS
800  face = 'helvetica'
801  elif fontface == "Courier":
802  family = wx.FONTFAMILY_TELETYPE
803  face = 'courier'
804  else:
805  family = wx.FONTFAMILY_DEFAULT
806  face = ''
807 
808  style = wx.FONTSTYLE_NORMAL
809  weight = wx.FONTWEIGHT_NORMAL
810 
811  if 'Oblique' in fontstyle:
812  style = wx.FONTSTYLE_SLANT
813 
814  if 'Italic' in fontstyle:
815  style = wx.FONTSTYLE_ITALIC
816 
817  if 'Bold' in fontstyle:
818  weight = wx.FONTWEIGHT_BOLD
819 
820  try:
821  fn = wx.Font(pointSize = fontsize, family = family, style = style,
822  weight = weight, face = face)
823  except:
824  fn = wx.Font(pointSize = fontsize, family = wx.FONTFAMILY_DEFAULT,
825  style = wx.FONTSTYLE_NORMAL, weight = wx.FONTWEIGHT_NORMAL)
826 
827  return fn
828 
829 
830  def getTextExtent(self, textDict):
831  """!Estimates bounding rectangle of text"""
832  #fontsize = str(fontsize if fontsize >= 4 else 4)
833  dc = wx.ClientDC(self) # dc created because of method GetTextExtent, which pseudoDC lacks
834 
835  fn = self.makePSFont(textDict)
836 
837  try:
838  dc.SetFont(fn)
839  w,h,lh = dc.GetMultiLineTextExtent(textDict['text'])
840  return (w,h)
841  except:
842  return (0,0)
843 
844  def getInitMap(self):
845  """!Create default map frame when no map is selected, needed for coordinates in map units"""
846  instrFile = grass.tempfile()
847  instrFileFd = open(instrFile, mode = 'w')
848  instrFileFd.write(self.InstructionFile())
849  instrFileFd.flush()
850  instrFileFd.close()
851 
852  page = self.instruction.FindInstructionByType('page')
853  mapInitRect = GetMapBounds(instrFile, portrait = (page['Orientation'] == 'Portrait'))
854  grass.try_remove(instrFile)
855 
856  region = grass.region()
857  units = UnitConversion(self)
858  realWidth = units.convert(value = abs(region['w'] - region['e']), fromUnit = 'meter', toUnit = 'inch')
859  scale = mapInitRect.Get()[2]/realWidth
860 
861  initMap = self.instruction.FindInstructionByType('initMap')
862  if initMap:
863  id = initMap.id
864  else:
865  id = None
866 
867 
868  if not id:
869  id = wx.NewId()
870  initMap = InitMap(id)
871  self.instruction.AddInstruction(initMap)
872  self.instruction[id].SetInstruction(dict(rect = mapInitRect, scale = scale))
873 
874  def OnDelete(self, event):
875  if self.canvas.dragId != -1 and self.currentPage == 0:
876  if self.instruction[self.canvas.dragId].type == 'map':
877  self.deleteObject(self.canvas.dragId)
878  self.getInitMap()
879  self.canvas.RecalculateEN()
880  else:
881  self.deleteObject(self.canvas.dragId)
882 
883  def deleteObject(self, id):
884  """!Deletes object, his id and redraws"""
885  #delete from canvas
886  self.canvas.pdcObj.RemoveId(id)
887  if id == self.canvas.dragId:
888  self.canvas.pdcTmp.RemoveAll()
889  self.canvas.dragId = -1
890  self.canvas.Refresh()
891 
892  # delete from instructions
893  del self.instruction[id]
894 
895  def DialogDataChanged(self, id):
896  ids = id
897  if type(id) == int:
898  ids = [id]
899  for id in ids:
900  itype = self.instruction[id].type
901 
902  if itype in ('scalebar', 'mapinfo', 'image'):
903  drawRectangle = self.canvas.CanvasPaperCoordinates(rect = self.instruction[id]['rect'], canvasToPaper = False)
904  self.canvas.UpdateLabel(itype = itype, id = id)
905  self.canvas.Draw(pen = self.pen[itype], brush = self.brush[itype],
906  pdc = self.canvas.pdcObj, drawid = id, pdctype = 'rectText', bb = drawRectangle)
907  self.canvas.RedrawSelectBox(id)
908  if itype == 'northArrow':
909  self.canvas.UpdateLabel(itype = itype, id = id)
910  drawRectangle = self.canvas.CanvasPaperCoordinates(rect = self.instruction[id]['rect'], canvasToPaper = False)
911  self.canvas.Draw(pen = self.pen[itype], brush = self.brush[itype],
912  pdc = self.canvas.pdcObj, drawid = id, pdctype = 'bitmap', bb = drawRectangle)
913  self.canvas.RedrawSelectBox(id)
914 
915  if itype in ('point', 'line', 'rectangle'):
916  drawRectangle = self.canvas.CanvasPaperCoordinates(rect = self.instruction[id]['rect'], canvasToPaper = False)
917  # coords only for line
918  coords = None
919  if itype == 'line':
920  point1 = self.instruction[id]['where'][0]
921  point2 = self.instruction[id]['where'][1]
922  point1Coords = self.canvas.CanvasPaperCoordinates(rect = Rect2DPS(point1, (0, 0)), canvasToPaper = False)[:2]
923  point2Coords = self.canvas.CanvasPaperCoordinates(rect = Rect2DPS(point2, (0, 0)), canvasToPaper = False)[:2]
924  coords = (point1Coords, point2Coords)
925 
926  # fill color is not in line
927  fcolor = None
928  if 'fcolor' in self.instruction[id].GetInstruction():
929  fcolor = self.instruction[id]['fcolor']
930  # width is not in point
931  width = None
932  if 'width' in self.instruction[id].GetInstruction():
933  width = self.instruction[id]['width']
934 
935  self.canvas.DrawGraphics(drawid = id, color = self.instruction[id]['color'], shape = itype,
936  fcolor = fcolor, width = width, bb = drawRectangle, lineCoords = coords)
937 
938  self.canvas.RedrawSelectBox(id)
939 
940  if itype == 'text':
941 
942  if self.instruction[id]['rotate']:
943  rot = float(self.instruction[id]['rotate'])
944  else:
945  rot = 0
946 
947  extent = self.getTextExtent(textDict = self.instruction[id].GetInstruction())
948  rect = Rect2DPS(self.instruction[id]['where'], (0, 0))
949  self.instruction[id]['coords'] = list(self.canvas.CanvasPaperCoordinates(rect = rect, canvasToPaper = False)[:2])
950 
951  #computes text coordinates according to reference point, not precisely
952  if self.instruction[id]['ref'].split()[0] == 'lower':
953  self.instruction[id]['coords'][1] -= extent[1]
954  elif self.instruction[id]['ref'].split()[0] == 'center':
955  self.instruction[id]['coords'][1] -= extent[1]/2
956  if self.instruction[id]['ref'].split()[1] == 'right':
957  self.instruction[id]['coords'][0] -= extent[0] * cos(rot/180*pi)
958  self.instruction[id]['coords'][1] += extent[0] * sin(rot/180*pi)
959  elif self.instruction[id]['ref'].split()[1] == 'center':
960  self.instruction[id]['coords'][0] -= extent[0]/2 * cos(rot/180*pi)
961  self.instruction[id]['coords'][1] += extent[0]/2 * sin(rot/180*pi)
962 
963  self.instruction[id]['coords'][0] += self.instruction[id]['xoffset']
964  self.instruction[id]['coords'][1] -= self.instruction[id]['yoffset']
965  coords = self.instruction[id]['coords']
966  self.instruction[id]['rect'] = bounds = self.getModifiedTextBounds(coords[0], coords[1], extent, rot)
967  self.canvas.DrawRotText(pdc = self.canvas.pdcObj, drawId = id,
968  textDict = self.instruction[id].GetInstruction(),
969  coords = coords, bounds = bounds)
970  self.canvas.RedrawSelectBox(id)
971 
972  if itype in ('map', 'vector', 'raster'):
973 
974  if itype == 'raster':#set resolution
975  info = grass.raster_info(self.instruction[id]['raster'])
976  RunCommand('g.region', nsres = info['nsres'], ewres = info['ewres'])
977  # change current raster in raster legend
978 
979  if 'rasterLegend' in self.openDialogs:
980  self.openDialogs['rasterLegend'].updateDialog()
981  id = self.instruction.FindInstructionByType('map').id
982 
983  #check resolution
984  if itype == 'raster':
985  SetResolution(dpi = self.instruction[id]['resolution'],
986  width = self.instruction[id]['rect'].width,
987  height = self.instruction[id]['rect'].height)
988  rectCanvas = self.canvas.CanvasPaperCoordinates(rect = self.instruction[id]['rect'],
989  canvasToPaper = False)
990  self.canvas.RecalculateEN()
991  self.canvas.UpdateMapLabel()
992 
993  self.canvas.Draw(pen = self.pen['map'], brush = self.brush['map'],
994  pdc = self.canvas.pdcObj, drawid = id, pdctype = 'rectText', bb = rectCanvas)
995  # redraw select box
996  self.canvas.RedrawSelectBox(id)
997  self.canvas.pdcTmp.RemoveId(self.canvas.idZoomBoxTmp)
998  # redraw to get map to the bottom layer
999  #self.canvas.Zoom(zoomFactor = 1, view = (0, 0))
1000 
1001  if itype == 'rasterLegend':
1002  if self.instruction[id]['rLegend']:
1003  self.canvas.UpdateLabel(itype = itype, id = id)
1004  drawRectangle = self.canvas.CanvasPaperCoordinates(rect = self.instruction[id]['rect'], canvasToPaper = False)
1005  self.canvas.Draw(pen = self.pen[itype], brush = self.brush[itype],
1006  pdc = self.canvas.pdcObj, drawid = id, pdctype = 'rectText', bb = drawRectangle)
1007  self.canvas.RedrawSelectBox(id)
1008  else:
1009  self.deleteObject(id)
1010 
1011  if itype == 'vectorLegend':
1012  if not self.instruction.FindInstructionByType('vector'):
1013  self.deleteObject(id)
1014  elif self.instruction[id]['vLegend']:
1015  self.canvas.UpdateLabel(itype = itype, id = id)
1016  drawRectangle = self.canvas.CanvasPaperCoordinates(rect = self.instruction[id]['rect'], canvasToPaper = False)
1017  self.canvas.Draw(pen = self.pen[itype], brush = self.brush[itype],
1018  pdc = self.canvas.pdcObj, drawid = id, pdctype = 'rectText', bb = drawRectangle)
1019  self.canvas.RedrawSelectBox(id)
1020 
1021  else:
1022  self.deleteObject(id)
1023 
1024  def OnPageChanged(self, event):
1025  """!Flatnotebook page has changed"""
1026  self.currentPage = self.book.GetPageIndex(self.book.GetCurrentPage())
1027  if self.currentPage == 1:
1028  self.SetStatusText(_("Press button with green triangle icon to generate preview."))
1029  else:
1030  self.SetStatusText('')
1031 
1032 
1033 
1034  def OnPageChanging(self, event):
1035  """!Flatnotebook page is changing"""
1036  if self.currentPage == 0 and self.mouse['use'] == 'addMap':
1037  event.Veto()
1038 
1039  def OnHelp(self, event):
1040  """!Show help"""
1041  if self.parent and self.parent.GetName() == 'LayerManager':
1042  log = self.parent.GetLogWindow()
1043  log.RunCmd(['g.manual',
1044  'entry=wxGUI.PsMap'])
1045  else:
1046  RunCommand('g.manual',
1047  quiet = True,
1048  entry = 'wxGUI.PsMap')
1049 
1050  def OnAbout(self, event):
1051  """!Display About window"""
1052  info = wx.AboutDialogInfo()
1053 
1054  info.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
1055  info.SetName(_('wxGUI Cartographic Composer'))
1056  info.SetWebSite('http://grass.osgeo.org')
1057  info.SetDescription(_('(C) 2011 by the GRASS Development Team\n\n') +
1058  '\n'.join(textwrap.wrap(_('This program is free software under the GNU General Public License'
1059  '(>=v2). Read the file COPYING that comes with GRASS for details.'), 75)))
1060 
1061  wx.AboutBox(info)
1062 
1063  def OnCloseWindow(self, event):
1064  """!Close window"""
1065  try:
1066  os.remove(self.imgName)
1067  except OSError:
1068  pass
1069  grass.set_raise_on_error(False)
1070  if hasattr(self, 'delayedCall') and self.delayedCall.IsRunning():
1071  self.delayedCall.Stop()
1072  self.Destroy()
1073 
1074 
1075 
1076 class PsMapBufferedWindow(wx.Window):
1077  """!A buffered window class.
1078 
1079  @param parent parent window
1080  @param kwargs other wx.Window parameters
1081  """
1082  def __init__(self, parent, id = wx.ID_ANY,
1083  style = wx.NO_FULL_REPAINT_ON_RESIZE,
1084  **kwargs):
1085  wx.Window.__init__(self, parent, id = id, style = style)
1086  self.parent = parent
1087 
1088  self.FitInside()
1089 
1090  # store an off screen empty bitmap for saving to file
1091  self._buffer = None
1092  # indicates whether or not a resize event has taken place
1093  self.resize = False
1094 
1095  self.mouse = kwargs['mouse']
1096  self.cursors = kwargs['cursors']
1097  self.preview = kwargs['preview']
1098  self.pen = kwargs['pen']
1099  self.brush = kwargs['brush']
1100 
1101  if kwargs.has_key('instruction'):
1102  self.instruction = kwargs['instruction']
1103  if kwargs.has_key('openDialogs'):
1104  self.openDialogs = kwargs['openDialogs']
1105  if kwargs.has_key('pageId'):
1106  self.pageId = kwargs['pageId']
1107  if kwargs.has_key('objectId'):
1108  self.objectId = kwargs['objectId']
1109 
1110 
1111  #labels
1112  self.itemLabelsDict = { 'map': 'MAP FRAME',
1113  'rasterLegend': 'RASTER LEGEND',
1114  'vectorLegend': 'VECTOR LEGEND',
1115  'mapinfo': 'MAP INFO',
1116  'scalebar': 'SCALE BAR',
1117  'image': 'IMAGE',
1118  'northArrow': 'NORTH ARROW'}
1119  self.itemLabels = {}
1120 
1121  # define PseudoDC
1122  self.pdc = wx.PseudoDC()
1123  self.pdcObj = wx.PseudoDC()
1124  self.pdcPaper = wx.PseudoDC()
1125  self.pdcTmp = wx.PseudoDC()
1126  self.pdcImage = wx.PseudoDC()
1127  dc = wx.ClientDC(self)
1128  self.font = dc.GetFont()
1129 
1130  self.SetClientSize((700,510))#?
1131  self._buffer = wx.EmptyBitmap(*self.GetClientSize())
1132 
1133  self.idBoxTmp = wx.NewId()
1134  self.idZoomBoxTmp = wx.NewId()
1135  self.idResizeBoxTmp = wx.NewId()
1136  self.idLinePointsTmp = (wx.NewId(), wx.NewId()) # ids of marks for moving line vertices
1137 
1138  self.resizeBoxSize = wx.Size(8, 8)
1139  self.showResizeHelp = False # helper for correctly working statusbar
1140 
1141 
1142 
1143  self.dragId = -1
1144 
1145  if self.preview:
1146  self.image = None
1147  self.imageId = 2000
1148  self.imgName = self.parent.imgName
1149 
1150 
1151 
1152  self.currScale = None
1153 
1154  self.Clear()
1155 
1156  self.Bind(wx.EVT_ERASE_BACKGROUND, lambda x: None)
1157 
1158  self.Bind(wx.EVT_PAINT, self.OnPaint)
1159  self.Bind(wx.EVT_SIZE, self.OnSize)
1160  self.Bind(wx.EVT_IDLE, self.OnIdle)
1161  # self.Bind(wx.EVT_MOUSE_EVENTS, self.OnMouse)
1162  self.Bind(wx.EVT_MOUSE_EVENTS, self.MouseActions)
1163 
1164 
1165  def Clear(self):
1166  """!Clear canvas and set paper
1167  """
1168  bg = wx.LIGHT_GREY_BRUSH
1169  self.pdcPaper.BeginDrawing()
1170  self.pdcPaper.SetBackground(bg)
1171  self.pdcPaper.Clear()
1172  self.pdcPaper.EndDrawing()
1173 
1174  self.pdcObj.RemoveAll()
1175  self.pdcTmp.RemoveAll()
1176 
1177 
1178 
1179  if not self.preview:
1180  self.SetPage()
1181 
1182 
1183  def CanvasPaperCoordinates(self, rect, canvasToPaper = True):
1184  """!Converts canvas (pixel) -> paper (inch) coordinates and size and vice versa"""
1185 
1186  units = UnitConversion(self)
1187 
1188  fromU = 'pixel'
1189  toU = 'inch'
1190  pRect = self.pdcPaper.GetIdBounds(self.pageId)
1191  pRectx, pRecty = pRect.x, pRect.y
1192  scale = 1/self.currScale
1193  if not canvasToPaper: # paper -> canvas
1194  fromU = 'inch'
1195  toU = 'pixel'
1196  scale = self.currScale
1197  pRectx = units.convert(value = - pRect.x, fromUnit = 'pixel', toUnit = 'inch' ) /scale #inch, real, negative
1198  pRecty = units.convert(value = - pRect.y, fromUnit = 'pixel', toUnit = 'inch' ) /scale
1199  Width = units.convert(value = rect.GetWidth(), fromUnit = fromU, toUnit = toU) * scale
1200  Height = units.convert(value = rect.GetHeight(), fromUnit = fromU, toUnit = toU) * scale
1201  X = units.convert(value = (rect.GetX() - pRectx), fromUnit = fromU, toUnit = toU) * scale
1202  Y = units.convert(value = (rect.GetY() - pRecty), fromUnit = fromU, toUnit = toU) * scale
1203 
1204  return Rect2D(X, Y, Width, Height)
1205 
1206 
1207 
1208  def SetPage(self):
1209  """!Sets and changes page, redraws paper"""
1210 
1211  page = self.instruction[self.pageId]
1212  if not page:
1213  page = PageSetup(id = self.pageId)
1214  self.instruction.AddInstruction(page)
1215 
1216  ppi = wx.ClientDC(self).GetPPI()
1217  cW, cH = self.GetClientSize()
1218  pW, pH = page['Width']*ppi[0], page['Height']*ppi[1]
1219 
1220  if self.currScale is None:
1221  self.currScale = min(cW/pW, cH/pH)
1222  pW = pW * self.currScale
1223  pH = pH * self.currScale
1224 
1225  x = cW/2 - pW/2
1226  y = cH/2 - pH/2
1227  self.DrawPaper(wx.Rect(x, y, pW, pH))
1228 
1229 
1230  def modifyRectangle(self, r):
1231  """! Recalculates rectangle not to have negative size"""
1232  if r.GetWidth() < 0:
1233  r.SetX(r.GetX() + r.GetWidth())
1234  if r.GetHeight() < 0:
1235  r.SetY(r.GetY() + r.GetHeight())
1236  r.SetWidth(abs(r.GetWidth()))
1237  r.SetHeight(abs(r.GetHeight()))
1238  return r
1239 
1240  def RecalculateEN(self):
1241  """!Recalculate east and north for texts (eps, points) after their or map's movement"""
1242  try:
1243  mapId = self.instruction.FindInstructionByType('map').id
1244  except AttributeError:
1245  mapId = self.instruction.FindInstructionByType('initMap').id
1246 
1247  for itemType in ('text', 'image', 'northArrow', 'point', 'line', 'rectangle'):
1248  items = self.instruction.FindInstructionByType(itemType, list = True)
1249  for item in items:
1250  instr = self.instruction[item.id]
1251  if itemType in ('line', 'rectangle'):
1252  if itemType == 'line':
1253  e1, n1 = PaperMapCoordinates(mapInstr = self.instruction[mapId], x = instr['where'][0][0],
1254  y = instr['where'][0][1], paperToMap = True)
1255  e2, n2 = PaperMapCoordinates(mapInstr = self.instruction[mapId], x = instr['where'][1][0],
1256  y = instr['where'][1][1], paperToMap = True)
1257  else:
1258  e1, n1 = PaperMapCoordinates(mapInstr = self.instruction[mapId], x = instr['rect'].GetLeft(),
1259  y = instr['rect'].GetTop(), paperToMap = True)
1260  e2, n2 = PaperMapCoordinates(mapInstr = self.instruction[mapId], x = instr['rect'].GetRight(),
1261  y = instr['rect'].GetBottom(), paperToMap = True)
1262  instr['east1'] = e1
1263  instr['north1'] = n1
1264  instr['east2'] = e2
1265  instr['north2'] = n2
1266  else:
1267  e, n = PaperMapCoordinates(mapInstr = self.instruction[mapId], x = instr['where'][0],
1268  y = instr['where'][1], paperToMap = True)
1269  instr['east'], instr['north'] = e, n
1270 
1271  def OnPaint(self, event):
1272  """!Draw pseudo DC to buffer
1273  """
1274  if not self._buffer:
1275  return
1276  dc = wx.BufferedPaintDC(self, self._buffer)
1277  # use PrepareDC to set position correctly
1278  self.PrepareDC(dc)
1279 
1280  dc.SetBackground(wx.LIGHT_GREY_BRUSH)
1281  dc.Clear()
1282 
1283  # draw paper
1284  if not self.preview:
1285  self.pdcPaper.DrawToDC(dc)
1286  # draw to the DC using the calculated clipping rect
1287 
1288  rgn = self.GetUpdateRegion()
1289 
1290  if not self.preview:
1291  self.pdcObj.DrawToDCClipped(dc, rgn.GetBox())
1292  else:
1293  self.pdcImage.DrawToDCClipped(dc, rgn.GetBox())
1294  self.pdcTmp.DrawToDCClipped(dc, rgn.GetBox())
1295 
1296  def MouseActions(self, event):
1297  """!Mouse motion and button click notifier
1298  """
1299  # zoom with mouse wheel
1300  if event.GetWheelRotation() != 0:
1301  self.OnMouseWheel(event)
1302 
1303  # left mouse button pressed
1304  elif event.LeftDown():
1305  self.OnLeftDown(event)
1306 
1307  # left mouse button released
1308  elif event.LeftUp():
1309  self.OnLeftUp(event)
1310 
1311  # dragging
1312  elif event.Dragging():
1313  self.OnDragging(event)
1314 
1315  # double click
1316  elif event.ButtonDClick():
1317  self.OnButtonDClick(event)
1318 
1319  # middle mouse button pressed
1320  elif event.MiddleDown():
1321  self.OnMiddleDown(event)
1322 
1323  elif event.Moving():
1324  self.OnMouseMoving(event)
1325 
1326  def OnMouseWheel(self, event):
1327  """!Mouse wheel scrolled.
1328 
1329  Changes zoom."""
1330  if UserSettings.Get(group = 'display',
1331  key = 'mouseWheelZoom',
1332  subkey = 'selection') == 2:
1333  event.Skip()
1334  return
1335 
1336  zoom = event.GetWheelRotation()
1337  oldUse = self.mouse['use']
1338  self.mouse['begin'] = event.GetPosition()
1339 
1340  if UserSettings.Get(group = 'display',
1341  key = 'scrollDirection',
1342  subkey = 'selection'):
1343  zoom *= -1
1344 
1345  if zoom > 0:
1346  self.mouse['use'] = 'zoomin'
1347  else:
1348  self.mouse['use'] = 'zoomout'
1349 
1350  zoomFactor, view = self.ComputeZoom(wx.Rect(0, 0, 0, 0))
1351  self.Zoom(zoomFactor, view)
1352  self.mouse['use'] = oldUse
1353 
1354  def OnMouseMoving(self, event):
1355  """!Mouse cursor moving.
1356 
1357  Change cursor when moving over resize marker.
1358  """
1359  if self.preview:
1360  return
1361 
1362  if self.mouse['use'] in ('pointer', 'resize'):
1363  pos = event.GetPosition()
1364  foundResize = self.pdcTmp.FindObjects(pos[0], pos[1])
1365  if foundResize and foundResize[0] in (self.idResizeBoxTmp,) + self.idLinePointsTmp:
1366  self.SetCursor(self.cursors["sizenwse"])
1367  self.parent.SetStatusText(_('Click and drag to resize object'), 0)
1368  self.showResizeHelp = True
1369  else:
1370  if self.showResizeHelp:
1371  self.parent.SetStatusText('', 0)
1372  self.SetCursor(self.cursors["default"])
1373  self.showResizeHelp = False
1374 
1375  def OnLeftDown(self, event):
1376  """!Left mouse button pressed.
1377 
1378  Select objects, redraw, prepare for moving/resizing.
1379  """
1380  self.mouse['begin'] = event.GetPosition()
1381  self.begin = self.mouse['begin']
1382 
1383  # select
1384  if self.mouse['use'] == 'pointer':
1385  found = self.pdcObj.FindObjects(self.mouse['begin'][0], self.mouse['begin'][1])
1386  foundResize = self.pdcTmp.FindObjects(self.mouse['begin'][0], self.mouse['begin'][1])
1387 
1388  if foundResize and foundResize[0] in (self.idResizeBoxTmp,) + self.idLinePointsTmp:
1389  self.mouse['use'] = 'resize'
1390 
1391  # when resizing, proportions match region
1392  if self.instruction[self.dragId].type == 'map':
1393  self.constraint = False
1394  self.mapBounds = self.pdcObj.GetIdBounds(self.dragId)
1395  if self.instruction[self.dragId]['scaleType'] in (0, 1, 2):
1396  self.constraint = True
1397  self.mapBounds = self.pdcObj.GetIdBounds(self.dragId)
1398 
1399  if self.instruction[self.dragId].type == 'line':
1400  self.currentLinePoint = self.idLinePointsTmp.index(foundResize[0])
1401 
1402  elif found:
1403  self.dragId = found[0]
1404  self.RedrawSelectBox(self.dragId)
1405  if self.instruction[self.dragId].type not in ('map', 'rectangle'):
1406  self.pdcTmp.RemoveId(self.idResizeBoxTmp)
1407  self.Refresh()
1408  if self.instruction[self.dragId].type != 'line':
1409  for id in self.idLinePointsTmp:
1410  self.pdcTmp.RemoveId(id)
1411  self.Refresh()
1412 
1413  else:
1414  self.dragId = -1
1415  self.pdcTmp.RemoveId(self.idBoxTmp)
1416  self.pdcTmp.RemoveId(self.idResizeBoxTmp)
1417  for id in self.idLinePointsTmp:
1418  self.pdcTmp.RemoveId(id)
1419  self.Refresh()
1420 
1421  def OnLeftUp(self, event):
1422  """!Left mouse button released.
1423 
1424  Recalculate zooming/resizing/moving and redraw.
1425  """
1426  # zoom in, zoom out
1427  if self.mouse['use'] in ('zoomin','zoomout'):
1428  zoomR = self.pdcTmp.GetIdBounds(self.idZoomBoxTmp)
1429  self.pdcTmp.RemoveId(self.idZoomBoxTmp)
1430  self.Refresh()
1431  zoomFactor, view = self.ComputeZoom(zoomR)
1432  self.Zoom(zoomFactor, view)
1433 
1434  # draw map frame
1435  if self.mouse['use'] == 'addMap':
1436  rectTmp = self.pdcTmp.GetIdBounds(self.idZoomBoxTmp)
1437  # too small rectangle, it's usually some mistake
1438  if rectTmp.GetWidth() < 20 or rectTmp.GetHeight() < 20:
1439  self.pdcTmp.RemoveId(self.idZoomBoxTmp)
1440  self.Refresh()
1441  return
1442  rectPaper = self.CanvasPaperCoordinates(rect = rectTmp, canvasToPaper = True)
1443 
1444  dlg = MapDialog(parent = self.parent, id = [None, None, None], settings = self.instruction,
1445  rect = rectPaper)
1446  self.openDialogs['map'] = dlg
1447  self.openDialogs['map'].Show()
1448 
1449  self.mouse['use'] = self.parent.mouseOld
1450 
1451  self.SetCursor(self.parent.cursorOld)
1452  self.parent.toolbar.ToggleTool(self.parent.actionOld, True)
1453  self.parent.toolbar.ToggleTool(self.parent.toolbar.action['id'], False)
1454  self.parent.toolbar.action['id'] = self.parent.actionOld
1455  return
1456 
1457  # resize resizable objects (map, line, rectangle)
1458  if self.mouse['use'] == 'resize':
1459  mapObj = self.instruction.FindInstructionByType('map')
1460  if not mapObj:
1461  mapObj = self.instruction.FindInstructionByType('initMap')
1462  mapId = mapObj.id
1463 
1464  if self.dragId == mapId:
1465  # necessary to change either map frame (scaleType 0,1,2) or region (scaletype 3)
1466  newRectCanvas = self.pdcObj.GetIdBounds(mapId)
1467  newRectPaper = self.CanvasPaperCoordinates(rect = newRectCanvas, canvasToPaper = True)
1468  self.instruction[mapId]['rect'] = newRectPaper
1469 
1470  if self.instruction[mapId]['scaleType'] in (0, 1, 2):
1471  if self.instruction[mapId]['scaleType'] == 0:
1472 
1473  scale, foo, rect = AutoAdjust(self, scaleType = 0,
1474  map = self.instruction[mapId]['map'],
1475  mapType = self.instruction[mapId]['mapType'],
1476  rect = self.instruction[mapId]['rect'])
1477 
1478  elif self.instruction[mapId]['scaleType'] == 1:
1479  scale, foo, rect = AutoAdjust(self, scaleType = 1,
1480  region = self.instruction[mapId]['region'],
1481  rect = self.instruction[mapId]['rect'])
1482  else:
1483  scale, foo, rect = AutoAdjust(self, scaleType = 2,
1484  rect = self.instruction[mapId]['rect'])
1485  self.instruction[mapId]['rect'] = rect
1486  self.instruction[mapId]['scale'] = scale
1487 
1488  rectCanvas = self.CanvasPaperCoordinates(rect = rect, canvasToPaper = False)
1489  self.Draw(pen = self.pen['map'], brush = self.brush['map'],
1490  pdc = self.pdcObj, drawid = mapId, pdctype = 'rectText', bb = rectCanvas)
1491 
1492  elif self.instruction[mapId]['scaleType'] == 3:
1493  ComputeSetRegion(self, mapDict = self.instruction[mapId].GetInstruction())
1494  #check resolution
1495  SetResolution(dpi = self.instruction[mapId]['resolution'],
1496  width = self.instruction[mapId]['rect'].width,
1497  height = self.instruction[mapId]['rect'].height)
1498 
1499  self.RedrawSelectBox(mapId)
1500  self.Zoom(zoomFactor = 1, view = (0, 0))
1501 
1502  elif self.instruction[self.dragId].type == 'line':
1503  points = self.instruction[self.dragId]['where']
1504  self.instruction[self.dragId]['rect'] = Rect2DPP(points[0], points[1])
1505  self.RecalculatePosition(ids = [self.dragId])
1506 
1507  elif self.instruction[self.dragId].type == 'rectangle':
1508  self.RecalculatePosition(ids = [self.dragId])
1509 
1510  self.mouse['use'] = 'pointer'
1511 
1512  # recalculate the position of objects after dragging
1513  if self.mouse['use'] in ('pointer', 'resize') and self.dragId != -1:
1514  if self.mouse['begin'] != event.GetPosition(): #for double click
1515 
1516  self.RecalculatePosition(ids = [self.dragId])
1517  if self.instruction[self.dragId].type in self.openDialogs:
1518  self.openDialogs[self.instruction[self.dragId].type].updateDialog()
1519 
1520  elif self.mouse['use'] in ('addPoint', 'addLine', 'addRectangle'):
1521  endCoordinates = self.CanvasPaperCoordinates(rect = wx.Rect(event.GetX(), event.GetY(), 0, 0),
1522  canvasToPaper = True)[:2]
1523 
1524  diffX = event.GetX() - self.mouse['begin'][0]
1525  diffY = event.GetY() - self.mouse['begin'][1]
1526 
1527  if self.mouse['use'] == 'addPoint':
1528  self.parent.AddPoint(coordinates = endCoordinates)
1529  elif self.mouse['use'] in ('addLine', 'addRectangle'):
1530  # not too small lines/rectangles
1531  if sqrt(diffX * diffX + diffY * diffY) < 5:
1532  self.pdcTmp.RemoveId(self.idZoomBoxTmp)
1533  self.Refresh()
1534  return
1535 
1536  beginCoordinates = self.CanvasPaperCoordinates(rect = wx.Rect(self.mouse['begin'][0],
1537  self.mouse['begin'][1], 0, 0),
1538  canvasToPaper = True)[:2]
1539  if self.mouse['use'] == 'addLine':
1540  self.parent.AddLine(coordinates = [beginCoordinates, endCoordinates])
1541  else:
1542  self.parent.AddRectangle(coordinates = [beginCoordinates, endCoordinates])
1543  self.pdcTmp.RemoveId(self.idZoomBoxTmp)
1544  self.Refresh()
1545 
1546  def OnButtonDClick(self, event):
1547  """!Open object dialog for editing."""
1548  if self.mouse['use'] == 'pointer' and self.dragId != -1:
1549  itemCall = {'text':self.parent.OnAddText,
1550  'mapinfo': self.parent.OnAddMapinfo,
1551  'scalebar': self.parent.OnAddScalebar,
1552  'image': self.parent.OnAddImage,
1553  'northArrow' : self.parent.OnAddNorthArrow,
1554  'point': self.parent.AddPoint,
1555  'line': self.parent.AddLine,
1556  'rectangle': self.parent.AddRectangle,
1557  'rasterLegend': self.parent.OnAddLegend,
1558  'vectorLegend': self.parent.OnAddLegend,
1559  'map': self.parent.OnAddMap}
1560 
1561  itemArg = { 'text': dict(event = None, id = self.dragId),
1562  'mapinfo': dict(event = None),
1563  'scalebar': dict(event = None),
1564  'image': dict(event = None, id = self.dragId),
1565  'northArrow': dict(event = None, id = self.dragId),
1566  'point': dict(id = self.dragId),
1567  'line': dict(id = self.dragId),
1568  'rectangle': dict(id = self.dragId),
1569  'rasterLegend': dict(event = None),
1570  'vectorLegend': dict(event = None, page = 1),
1571  'map': dict(event = None, notebook = True)}
1572 
1573  type = self.instruction[self.dragId].type
1574  itemCall[type](**itemArg[type])
1575 
1576  def OnDragging(self, event):
1577  """!Process panning/resizing/drawing/moving."""
1578  if event.MiddleIsDown():
1579  # panning
1580  self.mouse['end'] = event.GetPosition()
1581  self.Pan(begin = self.mouse['begin'], end = self.mouse['end'])
1582  self.mouse['begin'] = event.GetPosition()
1583 
1584  elif event.LeftIsDown():
1585  # draw box when zooming, creating map
1586  if self.mouse['use'] in ('zoomin', 'zoomout', 'addMap', 'addLine', 'addRectangle'):
1587  self.mouse['end'] = event.GetPosition()
1588  r = wx.Rect(self.mouse['begin'][0], self.mouse['begin'][1],
1589  self.mouse['end'][0]-self.mouse['begin'][0], self.mouse['end'][1]-self.mouse['begin'][1])
1590  r = self.modifyRectangle(r)
1591 
1592  if self.mouse['use'] in ('addLine', 'addRectangle'):
1593  if self.mouse['use'] == 'addLine':
1594  pdcType = 'line'
1595  lineCoords = (self.mouse['begin'], self.mouse['end'])
1596  else:
1597  pdcType = 'rect'
1598  lineCoords = None
1599  if r[2] < 2 or r[3] < 2:
1600  # to avoid strange behavoiur
1601  return
1602 
1603  self.Draw(pen = self.pen['line'], brush = self.brush['line'],
1604  pdc = self.pdcTmp, drawid = self.idZoomBoxTmp,
1605  pdctype = pdcType, bb = r, lineCoords = lineCoords)
1606 
1607  else:
1608  self.Draw(pen = self.pen['box'], brush = self.brush['box'],
1609  pdc = self.pdcTmp, drawid = self.idZoomBoxTmp,
1610  pdctype = 'rect', bb = r)
1611 
1612  # panning
1613  if self.mouse["use"] == 'pan':
1614  self.mouse['end'] = event.GetPosition()
1615  self.Pan(begin = self.mouse['begin'], end = self.mouse['end'])
1616  self.mouse['begin'] = event.GetPosition()
1617 
1618  # move object
1619  if self.mouse['use'] == 'pointer' and self.dragId != -1:
1620  self.mouse['end'] = event.GetPosition()
1621  dx, dy = self.mouse['end'][0] - self.begin[0], self.mouse['end'][1] - self.begin[1]
1622  self.pdcObj.TranslateId(self.dragId, dx, dy)
1623  self.pdcTmp.TranslateId(self.idBoxTmp, dx, dy)
1624  self.pdcTmp.TranslateId(self.idResizeBoxTmp, dx, dy)
1625  for id in self.idLinePointsTmp:
1626  self.pdcTmp.TranslateId(id, dx, dy)
1627  if self.instruction[self.dragId].type == 'text':
1628  self.instruction[self.dragId]['coords'] = self.instruction[self.dragId]['coords'][0] + dx,\
1629  self.instruction[self.dragId]['coords'][1] + dy
1630  self.begin = event.GetPosition()
1631  self.Refresh()
1632 
1633  # resize object
1634  if self.mouse['use'] == 'resize':
1635  pos = event.GetPosition()
1636  diffX = pos[0] - self.mouse['begin'][0]
1637  diffY = pos[1] - self.mouse['begin'][1]
1638  if self.instruction[self.dragId].type == 'map':
1639  x, y = self.mapBounds.GetX(), self.mapBounds.GetY()
1640  width, height = self.mapBounds.GetWidth(), self.mapBounds.GetHeight()
1641  # match given region
1642  if self.constraint:
1643  if width > height:
1644  newWidth = width + diffX
1645  newHeight = height + diffX * (float(height) / width)
1646  else:
1647  newWidth = width + diffY * (float(width) / height)
1648  newHeight = height + diffY
1649  else:
1650  newWidth = width + diffX
1651  newHeight = height + diffY
1652 
1653  if newWidth < 10 or newHeight < 10:
1654  return
1655 
1656  bounds = wx.Rect(x, y, newWidth, newHeight)
1657  self.Draw(pen = self.pen['map'], brush = self.brush['map'], pdc = self.pdcObj, drawid = self.dragId,
1658  pdctype = 'rectText', bb = bounds)
1659 
1660  elif self.instruction[self.dragId].type == 'rectangle':
1661  instr = self.instruction[self.dragId]
1662  rect = self.CanvasPaperCoordinates(rect = instr['rect'], canvasToPaper = False)
1663  rect.SetWidth(rect.GetWidth() + diffX)
1664  rect.SetHeight(rect.GetHeight() + diffY)
1665 
1666  if rect.GetWidth() < 5 or rect.GetHeight() < 5:
1667  return
1668 
1669  self.DrawGraphics(drawid = self.dragId, shape = 'rectangle', color = instr['color'],
1670  fcolor = instr['fcolor'], width = instr['width'], bb = rect)
1671 
1672  elif self.instruction[self.dragId].type == 'line':
1673  instr = self.instruction[self.dragId]
1674  points = instr['where']
1675  # moving point
1676  if self.currentLinePoint == 0:
1677  pPaper = points[1]
1678  else:
1679  pPaper = points[0]
1680  pCanvas = self.CanvasPaperCoordinates(rect = Rect2DPS(pPaper, (0, 0)),
1681  canvasToPaper = False)[:2]
1682  bounds = wx.RectPP(pCanvas, pos)
1683  self.DrawGraphics(drawid = self.dragId, shape = 'line', color = instr['color'],
1684  width = instr['width'], bb = bounds, lineCoords = (pos, pCanvas))
1685 
1686  # update paper coordinates
1687  points[self.currentLinePoint] = self.CanvasPaperCoordinates(rect = wx.RectPS(pos, (0, 0)),
1688  canvasToPaper = True)[:2]
1689 
1690  self.RedrawSelectBox(self.dragId)
1691 
1692  def OnMiddleDown(self, event):
1693  """!Middle mouse button pressed."""
1694  self.mouse['begin'] = event.GetPosition()
1695 
1696  def Pan(self, begin, end):
1697  """!Move canvas while dragging.
1698 
1699  @param begin x,y coordinates of first point
1700  @param end x,y coordinates of second point
1701  """
1702  view = begin[0] - end[0], begin[1] - end[1]
1703  zoomFactor = 1
1704  self.Zoom(zoomFactor, view)
1705 
1706  def RecalculatePosition(self, ids):
1707  for id in ids:
1708  itype = self.instruction[id].type
1709  if itype in ('map', 'rectangle'):
1710  self.instruction[id]['rect'] = self.CanvasPaperCoordinates(rect = self.pdcObj.GetIdBounds(id),
1711  canvasToPaper = True)
1712  self.RecalculateEN()
1713 
1714  elif itype in ('mapinfo' ,'rasterLegend', 'vectorLegend', 'image', 'northArrow'):
1715  self.instruction[id]['rect'] = self.CanvasPaperCoordinates(rect = self.pdcObj.GetIdBounds(id),
1716  canvasToPaper = True)
1717  self.instruction[id]['where'] = self.CanvasPaperCoordinates(rect = self.pdcObj.GetIdBounds(id),
1718  canvasToPaper = True)[:2]
1719  if itype in ('image', 'northArrow'):
1720  self.RecalculateEN()
1721 
1722  elif itype == 'point':
1723  rect = self.pdcObj.GetIdBounds(id)
1724  self.instruction[id]['rect'] = self.CanvasPaperCoordinates(rect = rect,
1725  canvasToPaper = True)
1726  rect.OffsetXY(rect.GetWidth()/2, rect.GetHeight()/2)
1727  self.instruction[id]['where'] = self.CanvasPaperCoordinates(rect = rect,
1728  canvasToPaper = True)[:2]
1729  self.RecalculateEN()
1730 
1731  elif itype == 'line':
1732  rect = self.pdcObj.GetIdBounds(id)
1733  oldRect = self.instruction[id]['rect']
1734  newRect = self.CanvasPaperCoordinates(rect = rect, canvasToPaper = True)
1735  xDiff = newRect[0] - oldRect[0]
1736  yDiff = newRect[1] - oldRect[1]
1737  self.instruction[id]['rect'] = newRect
1738 
1739  point1 = wx.Point2D(xDiff, yDiff) + self.instruction[id]['where'][0]
1740  point2 = wx.Point2D(xDiff, yDiff) + self.instruction[id]['where'][1]
1741  self.instruction[id]['where'] = [point1, point2]
1742 
1743  self.RecalculateEN()
1744 
1745  elif itype == 'scalebar':
1746  self.instruction[id]['rect'] = self.CanvasPaperCoordinates(rect = self.pdcObj.GetIdBounds(id),
1747  canvasToPaper = True)
1748 
1749 
1750  self.instruction[id]['where'] = self.instruction[id]['rect'].GetCentre()
1751 
1752  elif itype == 'text':
1753  x, y = self.instruction[id]['coords'][0] - self.instruction[id]['xoffset'],\
1754  self.instruction[id]['coords'][1] + self.instruction[id]['yoffset']
1755  extent = self.parent.getTextExtent(textDict = self.instruction[id])
1756  if self.instruction[id]['rotate'] is not None:
1757  rot = float(self.instruction[id]['rotate'])/180*pi
1758  else:
1759  rot = 0
1760 
1761  if self.instruction[id]['ref'].split()[0] == 'lower':
1762  y += extent[1]
1763  elif self.instruction[id]['ref'].split()[0] == 'center':
1764  y += extent[1]/2
1765  if self.instruction[id]['ref'].split()[1] == 'right':
1766  x += extent[0] * cos(rot)
1767  y -= extent[0] * sin(rot)
1768  elif self.instruction[id]['ref'].split()[1] == 'center':
1769  x += extent[0]/2 * cos(rot)
1770  y -= extent[0]/2 * sin(rot)
1771 
1772  self.instruction[id]['where'] = self.CanvasPaperCoordinates(rect = Rect2D(x, y, 0, 0),
1773  canvasToPaper = True)[:2]
1774  self.RecalculateEN()
1775 
1776  def ComputeZoom(self, rect):
1777  """!Computes zoom factor and scroll view"""
1778  zoomFactor = 1
1779  cW, cH = self.GetClientSize()
1780  cW = float(cW)
1781  if rect.IsEmpty(): # clicked on canvas
1782  zoomFactor = 1.5
1783  if self.mouse['use'] == 'zoomout':
1784  zoomFactor = 1./zoomFactor
1785  x,y = self.mouse['begin']
1786  xView = x - x/zoomFactor#x - cW/(zoomFactor * 2)
1787  yView = y - y/zoomFactor#y - cH/(zoomFactor * 2)
1788 
1789  else: #dragging
1790  rW, rH = float(rect.GetWidth()), float(rect.GetHeight())
1791  try:
1792  zoomFactor = 1/max(rW/cW, rH/cH)
1793  except ZeroDivisionError:
1794  zoomFactor = 1
1795  # when zooming to full extent, in some cases, there was zoom 1.01..., which causes problem
1796  if abs(zoomFactor - 1) > 0.01:
1797  zoomFactor = zoomFactor
1798  else:
1799  zoomFactor = 1.
1800 
1801 
1802  if self.mouse['use'] == 'zoomout':
1803  zoomFactor = min(rW/cW, rH/cH)
1804  try:
1805  if rW/rH > cW/cH:
1806  yView = rect.GetY() - (rW*(cH/cW) - rH)/2
1807  xView = rect.GetX()
1808 
1809  if self.mouse['use'] == 'zoomout':
1810  x,y = rect.GetX() + (rW-(cW/cH)*rH)/2, rect.GetY()
1811  xView, yView = -x, -y
1812  else:
1813  xView = rect.GetX() - (rH*(cW/cH) - rW)/2
1814  yView = rect.GetY()
1815  if self.mouse['use'] == 'zoomout':
1816  x,y = rect.GetX(), rect.GetY() + (rH-(cH/cW)*rW)/2
1817  xView, yView = -x, -y
1818  except ZeroDivisionError:
1819  xView, yView = rect.GetX(), rect.GetY()
1820  return zoomFactor, (int(xView), int(yView))
1821 
1822 
1823  def Zoom(self, zoomFactor, view):
1824  """! Zoom to specified region, scroll view, redraw"""
1825  if not self.currScale:
1826  return
1827  self.currScale = self.currScale*zoomFactor
1828 
1829  if self.currScale > 10 or self.currScale < 0.1:
1830  self.currScale = self.currScale/zoomFactor
1831  return
1832  if not self.preview:
1833  # redraw paper
1834  pRect = self.pdcPaper.GetIdBounds(self.pageId)
1835  pRect.OffsetXY(-view[0], -view[1])
1836  pRect = self.ScaleRect(rect = pRect, scale = zoomFactor)
1837  self.DrawPaper(pRect)
1838 
1839  #redraw objects
1840  for id in self.objectId:
1841  oRect = self.CanvasPaperCoordinates(
1842  rect = self.instruction[id]['rect'], canvasToPaper = False)
1843 
1844  type = self.instruction[id].type
1845  if type == 'text':
1846  coords = self.instruction[id]['coords']# recalculate coordinates, they are not equal to BB
1847  self.instruction[id]['coords'] = coords = [(int(coord) - view[i]) * zoomFactor
1848  for i, coord in enumerate(coords)]
1849  extent = self.parent.getTextExtent(textDict = self.instruction[id])
1850  if self.instruction[id]['rotate']:
1851  rot = float(self.instruction[id]['rotate'])
1852  else:
1853  rot = 0
1854  self.instruction[id]['rect'] = bounds = self.parent.getModifiedTextBounds(coords[0], coords[1], extent, rot)
1855  self.DrawRotText(pdc = self.pdcObj, drawId = id, textDict = self.instruction[id],
1856  coords = coords, bounds = bounds )
1857 
1858  self.pdcObj.SetIdBounds(id, bounds)
1859 
1860  elif type == 'northArrow':
1861  self.Draw(pen = self.pen[type], brush = self.brush[type], pdc = self.pdcObj,
1862  drawid = id, pdctype = 'bitmap', bb = oRect)
1863 
1864  elif type in ('point', 'line', 'rectangle'):
1865  instr = self.instruction[id]
1866  color = self.instruction[id]['color']
1867  width = fcolor = coords = None
1868 
1869  if type in ('point', 'rectangle'):
1870  fcolor = self.instruction[id]['fcolor']
1871  if type in ('line', 'rectangle'):
1872  width = self.instruction[id]['width']
1873  if type in ('line'):
1874  point1, point2 = instr['where'][0], instr['where'][1]
1875  point1 = self.CanvasPaperCoordinates(rect = Rect2DPS(point1, (0, 0)),
1876  canvasToPaper = False)[:2]
1877  point2 = self.CanvasPaperCoordinates(rect = Rect2DPS(point2, (0, 0)),
1878  canvasToPaper = False)[:2]
1879  coords = (point1, point2)
1880 
1881  self.DrawGraphics(drawid = id, shape = type, bb = oRect, lineCoords = coords,
1882  color = color, fcolor = fcolor, width = width)
1883 
1884  else:
1885  self.Draw(pen = self.pen[type], brush = self.brush[type], pdc = self.pdcObj,
1886  drawid = id, pdctype = 'rectText', bb = oRect)
1887  #redraw tmp objects
1888  if self.dragId != -1:
1889  self.RedrawSelectBox(self.dragId)
1890 
1891  #redraw preview
1892  else: # preview mode
1893  imageRect = self.pdcImage.GetIdBounds(self.imageId)
1894  imageRect.OffsetXY(-view[0], -view[1])
1895  imageRect = self.ScaleRect(rect = imageRect, scale = zoomFactor)
1896  self.DrawImage(imageRect)
1897 
1898  def ZoomAll(self):
1899  """! Zoom to full extent"""
1900  if not self.preview:
1901  bounds = self.pdcPaper.GetIdBounds(self.pageId)
1902  else:
1903  bounds = self.pdcImage.GetIdBounds(self.imageId)
1904  zoomP = bounds.Inflate(bounds.width/20, bounds.height/20)
1905  zoomFactor, view = self.ComputeZoom(zoomP)
1906  self.Zoom(zoomFactor, view)
1907 
1908  def Draw(self, pen, brush, pdc, drawid = None, pdctype = 'rect', bb = wx.Rect(0,0,0,0), lineCoords = None):
1909  """! Draw object with given pen and brush.
1910 
1911  @param pdc PseudoDC
1912  @param pdctype 'bitmap'/'rectText'/'rect'/'point'/'line'
1913  @param bb bounding box
1914  @param lineCoords coordinates of line start, end points (wx.Point, wx.Point)
1915  """
1916  if drawid is None:
1917  drawid = wx.NewId()
1918  bb = bb.Get()
1919  pdc.BeginDrawing()
1920  pdc.RemoveId(drawid)
1921  pdc.SetId(drawid)
1922  pdc.SetPen(pen)
1923  pdc.SetBrush(brush)
1924 
1925  if pdctype == 'bitmap':
1926  if havePILImage:
1927  file = self.instruction[drawid]['epsfile']
1928  rotation = self.instruction[drawid]['rotate']
1929  self.DrawBitmap(pdc = pdc, filePath = file, rotation = rotation, bbox = bb)
1930  else: # draw only rectangle with label
1931  pdctype = 'rectText'
1932 
1933  if pdctype in ('rect', 'rectText'):
1934  pdc.DrawRectangle(*bb)
1935 
1936  if pdctype == 'rectText':
1937  dc = wx.ClientDC(self) # dc created because of method GetTextExtent, which pseudoDC lacks
1938  font = self.font
1939  size = 10
1940  font.SetPointSize(size)
1941  font.SetStyle(wx.ITALIC)
1942  dc.SetFont(font)
1943  pdc.SetFont(font)
1944  text = '\n'.join(self.itemLabels[drawid])
1945  w,h,lh = dc.GetMultiLineTextExtent(text)
1946  textExtent = (w,h)
1947  textRect = wx.Rect(0, 0, *textExtent).CenterIn(bb)
1948  r = map(int, bb)
1949  while not wx.Rect(*r).ContainsRect(textRect) and size >= 8:
1950  size -= 2
1951  font.SetPointSize(size)
1952  dc.SetFont(font)
1953  pdc.SetFont(font)
1954  textExtent = dc.GetTextExtent(text)
1955  textRect = wx.Rect(0, 0, *textExtent).CenterIn(bb)
1956  pdc.SetTextForeground(wx.Color(100,100,100,200))
1957  pdc.SetBackgroundMode(wx.TRANSPARENT)
1958  pdc.DrawLabel(text = text, rect = textRect)
1959 
1960  elif pdctype == 'point':
1961  pdc.DrawCircle(x = bb[0] + bb[2] / 2,
1962  y = bb[1] + bb[3] / 2,
1963  radius = bb[2] / 2)
1964 
1965  elif pdctype == 'line':
1966  pdc.DrawLinePoint(lineCoords[0], lineCoords[1])
1967 
1968  pdc.SetIdBounds(drawid, bb)
1969  pdc.EndDrawing()
1970  self.Refresh()
1971 
1972  return drawid
1973 
1974  def DrawGraphics(self, drawid, shape, color, bb, width = None, fcolor = None, lineCoords = None):
1975  """!Draw point/line/rectangle with given color and width
1976 
1977  @param drawid id of drawn object
1978  @param shape drawn shape: 'point'/'line'/'rectangle'
1979  @param color pen outline color ('RRR:GGG:BBB')
1980  @param fcolor brush fill color, if meaningful ('RRR:GGG:BBB')
1981  @param width pen width
1982  @param bb bounding box
1983  @param lineCoords line coordinates (for line only)
1984  """
1985  pdctype = {'point' : 'point',
1986  'line' : 'line',
1987  'rectangle' : 'rect'}
1988 
1989  if color == 'none':
1990  pen = wx.TRANSPARENT_PEN
1991  else:
1992  if width is not None:
1993  units = UnitConversion(self)
1994  width = int(units.convert(value = width, fromUnit = 'point', toUnit = 'pixel') * self.currScale)
1995  else:
1996  width = 2
1997  pen = wx.Pen(colour = convertRGB(color), width = width)
1998  pen.SetCap(wx.CAP_BUTT) # this is how ps.map draws
1999 
2000  brush = wx.TRANSPARENT_BRUSH
2001  if fcolor and fcolor != 'none':
2002  brush = wx.Brush(colour = convertRGB(fcolor))
2003 
2004  self.Draw(pen = pen, brush = brush, pdc = self.pdcObj, pdctype = pdctype[shape],
2005  drawid = drawid, bb = bb, lineCoords = lineCoords)
2006 
2007  def DrawBitmap(self, pdc, filePath, rotation, bbox):
2008  """!Draw bitmap using PIL"""
2009  pImg = PILImage.open(filePath)
2010  if sys.platform == 'win32' and \
2011  'eps' in os.path.splitext(filePath)[1].lower():
2012  import types
2013  pImg.load = types.MethodType(loadPSForWindows, pImg)
2014 
2015  if rotation:
2016  # get rid of black background
2017  pImg = pImg.convert("RGBA")
2018  rot = pImg.rotate(rotation, expand = 1)
2019  new = PILImage.new('RGBA', rot.size, (255,) * 4)
2020  pImg = PILImage.composite(rot, new, rot)
2021  pImg = pImg.resize((int(bbox[2]), int(bbox[3])), resample = PILImage.BICUBIC)
2022  img = PilImageToWxImage(pImg)
2023  bitmap = img.ConvertToBitmap()
2024  mask = wx.Mask(bitmap, wx.WHITE)
2025  bitmap.SetMask(mask)
2026  pdc.DrawBitmap(bitmap, bbox[0], bbox[1], useMask = True)
2027 
2028  def DrawRotText(self, pdc, drawId, textDict, coords, bounds):
2029  if textDict['rotate']:
2030  rot = float(textDict['rotate'])
2031  else:
2032  rot = 0
2033 
2034  if textDict['background'] != 'none':
2035  background = textDict['background']
2036  else:
2037  background = None
2038 
2039  pdc.RemoveId(drawId)
2040  pdc.SetId(drawId)
2041  pdc.BeginDrawing()
2042 
2043  # border is not redrawn when zoom changes, why?
2044 ## if textDict['border'] != 'none' and not rot:
2045 ## units = UnitConversion(self)
2046 ## borderWidth = units.convert(value = textDict['width'],
2047 ## fromUnit = 'point', toUnit = 'pixel' ) * self.currScale
2048 ## pdc.SetPen(wx.Pen(colour = convertRGB(textDict['border']), width = borderWidth))
2049 ## pdc.DrawRectangle(*bounds)
2050 
2051  if background:
2052  pdc.SetTextBackground(convertRGB(background))
2053  pdc.SetBackgroundMode(wx.SOLID)
2054  else:
2055  pdc.SetBackgroundMode(wx.TRANSPARENT)
2056 
2057  fn = self.parent.makePSFont(textDict)
2058 
2059  pdc.SetFont(fn)
2060  pdc.SetTextForeground(convertRGB(textDict['color']))
2061  if rot == 0:
2062  pdc.DrawLabel(text=textDict['text'], rect=bounds)
2063  else:
2064  pdc.DrawRotatedText(textDict['text'], coords[0], coords[1], rot)
2065 
2066  pdc.SetIdBounds(drawId, wx.Rect(*bounds))
2067  self.Refresh()
2068  pdc.EndDrawing()
2069 
2070  def DrawImage(self, rect):
2071  """!Draw preview image to pseudoDC"""
2072  self.pdcImage.ClearId(self.imageId)
2073  self.pdcImage.SetId(self.imageId)
2074  img = self.image
2075 
2076 
2077  if img.GetWidth() != rect.width or img.GetHeight() != rect.height:
2078  img = img.Scale(rect.width, rect.height)
2079  bitmap = img.ConvertToBitmap()
2080 
2081  self.pdcImage.BeginDrawing()
2082  self.pdcImage.DrawBitmap(bitmap, rect.x, rect.y)
2083  self.pdcImage.SetIdBounds(self.imageId, rect)
2084  self.pdcImage.EndDrawing()
2085  self.Refresh()
2086 
2087  def DrawPaper(self, rect):
2088  """!Draw paper and margins"""
2089  page = self.instruction[self.pageId]
2090  scale = page['Width'] / rect.GetWidth()
2091  w = (page['Width'] - page['Right'] - page['Left']) / scale
2092  h = (page['Height'] - page['Top'] - page['Bottom']) / scale
2093  x = page['Left'] / scale + rect.GetX()
2094  y = page['Top'] / scale + rect.GetY()
2095 
2096  self.pdcPaper.BeginDrawing()
2097  self.pdcPaper.RemoveId(self.pageId)
2098  self.pdcPaper.SetId(self.pageId)
2099  self.pdcPaper.SetPen(self.pen['paper'])
2100  self.pdcPaper.SetBrush(self.brush['paper'])
2101  self.pdcPaper.DrawRectangleRect(rect)
2102 
2103  self.pdcPaper.SetPen(self.pen['margins'])
2104  self.pdcPaper.SetBrush(self.brush['margins'])
2105  self.pdcPaper.DrawRectangle(x, y, w, h)
2106 
2107  self.pdcPaper.SetIdBounds(self.pageId, rect)
2108  self.pdcPaper.EndDrawing()
2109  self.Refresh()
2110 
2111 
2112  def ImageRect(self):
2113  """!Returns image centered in canvas, computes scale"""
2114  img = wx.Image(self.imgName, wx.BITMAP_TYPE_PNG)
2115  cW, cH = self.GetClientSize()
2116  iW, iH = img.GetWidth(), img.GetHeight()
2117 
2118  self.currScale = min(float(cW)/iW, float(cH)/iH)
2119  iW = iW * self.currScale
2120  iH = iH * self.currScale
2121  x = cW/2 - iW/2
2122  y = cH/2 - iH/2
2123  imageRect = wx.Rect(x, y, iW, iH)
2124 
2125  return imageRect
2126 
2127  def RedrawSelectBox(self, id):
2128  """!Redraws select box when selected object changes its size"""
2129  if self.dragId == id:
2130  rect = self.pdcObj.GetIdBounds(id)
2131  if self.instruction[id].type != 'line':
2132  rect = rect.Inflate(3,3)
2133  # draw select box around object
2134  self.Draw(pen = self.pen['select'], brush = self.brush['select'], pdc = self.pdcTmp,
2135  drawid = self.idBoxTmp, pdctype = 'rect', bb = rect)
2136 
2137  # draw small marks signalizing resizing
2138  if self.instruction[id].type in ('map', 'rectangle'):
2139  controlP = self.pdcObj.GetIdBounds(id).GetBottomRight()
2140  rect = wx.RectPS(controlP, self.resizeBoxSize)
2141  self.Draw(pen = self.pen['resize'], brush = self.brush['resize'], pdc = self.pdcTmp,
2142  drawid = self.idResizeBoxTmp, pdctype = 'rect', bb = rect)
2143 
2144  elif self.instruction[id].type == 'line':
2145  p1Paper = self.instruction[id]['where'][0]
2146  p2Paper = self.instruction[id]['where'][1]
2147  p1Canvas = self.CanvasPaperCoordinates(rect = Rect2DPS(p1Paper, (0, 0)), canvasToPaper = False)[:2]
2148  p2Canvas = self.CanvasPaperCoordinates(rect = Rect2DPS(p2Paper, (0, 0)), canvasToPaper = False)[:2]
2149  rect = []
2150  box = wx.RectS(self.resizeBoxSize)
2151  rect.append(box.CenterIn(wx.RectPS(p1Canvas, wx.Size())))
2152  rect.append(box.CenterIn(wx.RectPS(p2Canvas, wx.Size())))
2153  for i, point in enumerate((p1Canvas, p2Canvas)):
2154  self.Draw(pen = self.pen['resize'], brush = self.brush['resize'], pdc = self.pdcTmp,
2155  drawid = self.idLinePointsTmp[i], pdctype = 'rect', bb = rect[i])
2156 
2157  def UpdateMapLabel(self):
2158  """!Updates map frame label"""
2159 
2160  vector = self.instruction.FindInstructionByType('vector')
2161  if vector:
2162  vectorId = vector.id
2163  else:
2164  vectorId = None
2165 
2166  raster = self.instruction.FindInstructionByType('raster')
2167  if raster:
2168  rasterId = raster.id
2169  else:
2170  rasterId = None
2171 
2172  rasterName = 'None'
2173  if rasterId:
2174  rasterName = self.instruction[rasterId]['raster'].split('@')[0]
2175 
2176  mapId = self.instruction.FindInstructionByType('map').id
2177  self.itemLabels[mapId] = []
2178  self.itemLabels[mapId].append(self.itemLabelsDict['map'])
2179  self.itemLabels[mapId].append("raster: " + rasterName)
2180  if vectorId:
2181  for map in self.instruction[vectorId]['list']:
2182  self.itemLabels[mapId].append('vector: ' + map[0].split('@')[0])
2183 
2184  def UpdateLabel(self, itype, id):
2185  self.itemLabels[id] = []
2186  self.itemLabels[id].append(self.itemLabelsDict[itype])
2187  if itype == 'image':
2188  file = os.path.basename(self.instruction[id]['epsfile'])
2189  self.itemLabels[id].append(file)
2190 
2191  def OnSize(self, event):
2192  """!Init image size to match window size
2193  """
2194  # not zoom all when notebook page is changed
2195  if self.preview and self.parent.currentPage == 1 or not self.preview and self.parent.currentPage == 0:
2196  self.ZoomAll()
2197  self.OnIdle(None)
2198  event.Skip()
2199 
2200  def OnIdle(self, event):
2201  """!Only re-render a image during idle time instead of
2202  multiple times during resizing.
2203  """
2204 
2205  width, height = self.GetClientSize()
2206  # Make new off screen bitmap: this bitmap will always have the
2207  # current drawing in it, so it can be used to save the image
2208  # to a file, or whatever.
2209  self._buffer = wx.EmptyBitmap(width, height)
2210  # re-render image on idle
2211  self.resize = True
2212 
2213  def ScaleRect(self, rect, scale):
2214  """! Scale rectangle"""
2215  return wx.Rect(rect.GetLeft()*scale, rect.GetTop()*scale,
2216  rect.GetSize()[0]*scale, rect.GetSize()[1]*scale)
2217 
2218 
2219 def main():
2220  import gettext
2221  gettext.install('grasswxpy', os.path.join(os.getenv("GISBASE"), 'locale'), unicode = True)
2222 
2223  app = wx.PySimpleApp()
2224  wx.InitAllImageHandlers()
2225  frame = PsMapFrame()
2226  frame.Show()
2227 
2228  app.MainLoop()
2229 
2230 if __name__ == "__main__":
2231  main()