GRASS Programmer's Manual  6.4.2(2012)
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
psmap.py
Go to the documentation of this file.
1 """!
2 @package psmap.py
3 
4 @brief GUI for ps.map
5 
6 Classes:
7  - PsMapFrame
8  - PsMapBufferedWindow
9 
10 (C) 2011 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 <anna.kratochvilova fsv.cvut.cz> (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 try:
23  import Image
24  haveImage = True
25 except ImportError:
26  haveImage = False
27 from math import sin, cos, pi
28 
29 import grass.script as grass
30 if int(grass.version()['version'].split('.')[0]) > 6:
31  sys.path.append(os.path.join(os.getenv('GISBASE'), 'etc', 'gui', 'wxpython',
32  'gui_modules'))
33 else:
34  sys.path.append(os.path.join(os.getenv('GISBASE'), 'etc', 'wxpython',
35  'gui_modules'))
36 import globalvar
37 import menu
38 from goutput import CmdThread, EVT_CMD_DONE
39 from menudata import PsMapData
40 from toolbars import PsMapToolbar
41 from icon import Icons, MetaIcon, iconSet
42 from gcmd import RunCommand, GError, GMessage
43 from menuform import GUI
44 from psmap_dialogs import *
45 
46 import wx
47 
48 try:
49  import wx.lib.agw.flatnotebook as fnb
50 except ImportError:
51  import wx.lib.flatnotebook as fnb
52 
53 class PsMapFrame(wx.Frame):
54  def __init__(self, parent = None, id = wx.ID_ANY,
55  title = _("GRASS GIS Cartographic Composer (experimental prototype)"), **kwargs):
56  """!Main window of ps.map GUI
57 
58  @param parent parent window
59  @param id window id
60  @param title window title
61 
62  @param kwargs wx.Frames' arguments
63  """
64  self.parent = parent
65 
66  wx.Frame.__init__(self, parent = parent, id = id, title = title, name = "PsMap", **kwargs)
67  self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
68  #menubar
69  self.menubar = menu.Menu(parent = self, data = PsMapData())
70  self.SetMenuBar(self.menubar)
71  #toolbar
72 
73  self.toolbar = PsMapToolbar(parent = self)
74  self.SetToolBar(self.toolbar)
75 
76  self.actionOld = self.toolbar.action['id']
77  self.iconsize = (16, 16)
78  #satusbar
79  self.statusbar = self.CreateStatusBar(number = 1)
80 
81  # mouse attributes -- position on the screen, begin and end of
82  # dragging, and type of drawing
83  self.mouse = {
84  'begin': [0, 0], # screen coordinates
85  'end' : [0, 0],
86  'use' : "pointer",
87  }
88  # available cursors
89  self.cursors = {
90  "default" : wx.StockCursor(wx.CURSOR_ARROW),
91  "cross" : wx.StockCursor(wx.CURSOR_CROSS),
92  "hand" : wx.StockCursor(wx.CURSOR_HAND),
93  "sizenwse": wx.StockCursor(wx.CURSOR_SIZENWSE)
94  }
95  # pen and brush
96  self.pen = {
97  'paper': wx.Pen(colour = "BLACK", width = 1),
98  'margins': wx.Pen(colour = "GREY", width = 1),
99  'map': wx.Pen(colour = wx.Color(86, 122, 17), width = 2),
100  'rasterLegend': wx.Pen(colour = wx.Color(219, 216, 4), width = 2),
101  'vectorLegend': wx.Pen(colour = wx.Color(219, 216, 4), width = 2),
102  'mapinfo': wx.Pen(colour = wx.Color(5, 184, 249), width = 2),
103  'scalebar': wx.Pen(colour = wx.Color(150, 150, 150), 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  'box': wx.TRANSPARENT_BRUSH,
117  'select':wx.TRANSPARENT_BRUSH,
118  'resize': wx.BLACK_BRUSH
119  }
120 
121 
122  # list of objects to draw
123  self.objectId = []
124 
125  # instructions
126  self.instruction = Instruction(parent = self, objectsToDraw = self.objectId)
127  # open dialogs
128  self.openDialogs = dict()
129 
130  self.pageId = wx.NewId()
131  #current page of flatnotebook
132  self.currentPage = 0
133  #canvas for draft mode
134  self.canvas = PsMapBufferedWindow(parent = self, mouse = self.mouse, pen = self.pen,
135  brush = self.brush, cursors = self.cursors,
136  instruction = self.instruction, openDialogs = self.openDialogs,
137  pageId = self.pageId, objectId = self.objectId,
138  preview = False)
139 
140  self.canvas.SetCursor(self.cursors["default"])
141  self.getInitMap()
142 
143 
144  # image path
145  env = grass.gisenv()
146  self.imgName = grass.tempfile()
147 
148  #canvas for preview
149  self.previewCanvas = PsMapBufferedWindow(parent = self, mouse = self.mouse, cursors = self.cursors,
150  pen = self.pen, brush = self.brush, preview = True)
151 
152  # set WIND_OVERRIDE
153  grass.use_temp_region()
154 
155  # create queues
156  self.requestQ = Queue.Queue()
157  self.resultQ = Queue.Queue()
158  # thread
159  self.cmdThread = CmdThread(self, self.requestQ, self.resultQ)
160 
161  self._layout()
162  self.SetMinSize(wx.Size(750, 600))
163 
164  self.Bind(fnb.EVT_FLATNOTEBOOK_PAGE_CHANGING, self.OnPageChanging)
165  self.Bind(fnb.EVT_FLATNOTEBOOK_PAGE_CHANGED, self.OnPageChanged)
166  self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
167  self.Bind(EVT_CMD_DONE, self.OnCmdDone)
168 
169  if not haveImage:
170  wx.CallAfter(self._showErrMsg)
171 
172  def _showErrMsg(self):
173  """!Show error message (missing preview)
174  """
175  GError(parent = self,
176  message = _("Python Imaging Library is not available.\n"
177  "'Preview' functionality won't work."),
178  showTraceback = False)
179 
180  def _layout(self):
181  """!Do layout
182  """
183  mainSizer = wx.BoxSizer(wx.VERTICAL)
184  if globalvar.hasAgw:
185  self.book = fnb.FlatNotebook(parent = self, id = wx.ID_ANY,
186  agwStyle = fnb.FNB_FANCY_TABS | fnb.FNB_BOTTOM |
187  fnb.FNB_NO_NAV_BUTTONS | fnb.FNB_NO_X_BUTTON)
188  else:
189  self.book = fnb.FlatNotebook(parent = self, id = wx.ID_ANY,
190  style = fnb.FNB_FANCY_TABS | fnb.FNB_BOTTOM |
191  fnb.FNB_NO_NAV_BUTTONS | fnb.FNB_NO_X_BUTTON)
192  #self.book = fnb.FlatNotebook(self, wx.ID_ANY, style = fnb.FNB_BOTTOM)
193  self.book.AddPage(self.canvas, "Draft mode")
194  self.book.AddPage(self.previewCanvas, "Preview")
195  self.book.SetSelection(0)
196 
197  mainSizer.Add(self.book,1, wx.EXPAND)
198 
199  self.SetSizer(mainSizer)
200  mainSizer.Fit(self)
201 
202 
203  def InstructionFile(self):
204  """!Creates mapping instructions"""
205 
206  return str(self.instruction)
207 
208  def OnPSFile(self, event):
209  """!Generate PostScript"""
210  filename = self.getFile(wildcard = "PostScript (*.ps)|*.ps|Encapsulated PostScript (*.eps)|*.eps")
211  if filename:
212  self.PSFile(filename)
213 
214  def OnPsMapDialog(self, event):
215  """!Launch ps.map dialog
216  """
217  GUI(parent = self).ParseCommand(cmd = ['ps.map'])
218 
219  def OnPDFFile(self, event):
220  """!Generate PDF from PS with ps2pdf if available"""
221  try:
222  p = grass.Popen(["ps2pdf"], stderr = grass.PIPE)
223  p.stderr.close()
224 
225  except OSError:
226  GMessage(parent = self,
227  message = _("Program ps2pdf is not available. Please install it first to create PDF."))
228  return
229 
230  filename = self.getFile(wildcard = "PDF (*.pdf)|*.pdf")
231  if filename:
232  self.PSFile(filename, pdf = True)
233 
234  def OnPreview(self, event):
235  """!Run ps.map and show result"""
236  self.PSFile()
237 
238  def PSFile(self, filename = None, pdf = False):
239  """!Create temporary instructions file and run ps.map with output = filename"""
240  instrFile = grass.tempfile()
241  instrFileFd = open(instrFile, mode = 'w')
242  instrFileFd.write(self.InstructionFile())
243  instrFileFd.flush()
244  instrFileFd.close()
245 
246  temp = False
247  regOld = grass.region()
248 
249  if pdf:
250  pdfname = filename
251  else:
252  pdfname = None
253  #preview or pdf
254  if not filename or (filename and pdf):
255  temp = True
256  filename = grass.tempfile()
257  if not pdf: # lower resolution for preview
258  if self.instruction.FindInstructionByType('map'):
259  mapId = self.instruction.FindInstructionByType('map').id
260  SetResolution(dpi = 100, width = self.instruction[mapId]['rect'][2],
261  height = self.instruction[mapId]['rect'][3])
262 
263  cmd = ['ps.map', '--overwrite']
264  if os.path.splitext(filename)[1] == '.eps':
265  cmd.append('-e')
266  if self.instruction[self.pageId]['Orientation'] == 'Landscape':
267  cmd.append('-r')
268  cmd.append('input=%s' % instrFile)
269  cmd.append('output=%s' % filename)
270  if pdf:
271  self.SetStatusText(_('Generating PDF...'), 0)
272  elif not temp:
273  self.SetStatusText(_('Generating PostScript...'), 0)
274  else:
275  self.SetStatusText(_('Generating preview...'), 0)
276 
277  self.cmdThread.RunCmd(cmd, userData = {'instrFile' : instrFile, 'filename' : filename,
278  'pdfname' : pdfname, 'temp' : temp, 'regionOld' : regOld})
279 
280  def OnCmdDone(self, event):
281  """!ps.map process finished"""
282 
283  if event.returncode != 0:
284  GMessage(parent = self,
285  message = _("Ps.map exited with return code %s") % event.returncode)
286 
287  grass.try_remove(event.userData['instrFile'])
288  if event.userData['temp']:
289  grass.try_remove(event.userData['filename'])
290  return
291 
292  if event.userData['pdfname']:
293  try:
294  proc = grass.Popen(['ps2pdf', '-dPDFSETTINGS=/prepress', '-r1200',
295  event.userData['filename'], event.userData['pdfname']])
296 
297  ret = proc.wait()
298  if ret > 0:
299  GMessage(parent = self,
300  message = _("ps2pdf exited with return code %s") % ret)
301 
302  except OSError, e:
303  GError(parent = self,
304  message = _("Program ps2pdf is not available. Please install it to create PDF.\n\n %s") % e)
305 
306  # show preview only when user doesn't want to create ps or pdf
307  if haveImage and event.userData['temp'] and not event.userData['pdfname']:
308  RunCommand('g.region', cols = event.userData['regionOld']['cols'], rows = event.userData['regionOld']['rows'])
309 ## wx.BusyInfo does not display the message
310 ## busy = wx.BusyInfo(message = "Generating preview, wait please", parent = self)
311 
312  try:
313  im = Image.open(event.userData['filename'])
314  if self.instruction[self.pageId]['Orientation'] == 'Landscape':
315  im = im.rotate(270)
316 
317  im.save(self.imgName, format = 'PNG')
318 
319  except IOError, e:
320  GError(parent = self,
321  message = _("Unable to generate preview. %s") % e)
322  return
323 
324 
325  rect = self.previewCanvas.ImageRect()
326  self.previewCanvas.image = wx.Image(self.imgName, wx.BITMAP_TYPE_PNG)
327  self.previewCanvas.DrawImage(rect = rect)
328 
329 ## busy.Destroy()
330  self.SetStatusText(_('Preview generated'), 0)
331  self.book.SetSelection(1)
332  self.currentPage = 1
333 
334  grass.try_remove(event.userData['instrFile'])
335  if event.userData['temp']:
336  grass.try_remove(event.userData['filename'])
337 
338  def getFile(self, wildcard):
339  suffix = []
340  for filter in wildcard.split('|')[1::2]:
341  s = filter.strip('*').split('.')[1]
342  if s:
343  s = '.' + s
344  suffix.append(s)
345  raster = self.instruction.FindInstructionByType('raster')
346  if raster:
347  rasterId = raster.id
348  else:
349  rasterId = None
350 
351 
352  if rasterId and self.instruction[rasterId]['raster']:
353  mapName = self.instruction[rasterId]['raster'].split('@')[0] + suffix[0]
354  else:
355  mapName = ''
356 
357  filename = ''
358  dlg = wx.FileDialog(self, message = _("Save file as"), defaultDir = "",
359  defaultFile = mapName, wildcard = wildcard,
360  style = wx.CHANGE_DIR | wx.SAVE | wx.OVERWRITE_PROMPT)
361  if dlg.ShowModal() == wx.ID_OK:
362  filename = dlg.GetPath()
363  suffix = suffix[dlg.GetFilterIndex()]
364  if not os.path.splitext(filename)[1]:
365  filename = filename + suffix
366  elif os.path.splitext(filename)[1] != suffix and suffix != '':
367  filename = os.path.splitext(filename)[0] + suffix
368 
369  dlg.Destroy()
370  return filename
371 
372  def OnInstructionFile(self, event):
373  filename = self.getFile(wildcard = "*.psmap|*.psmap|Text file(*.txt)|*.txt|All files(*.*)|*.*")
374  if filename:
375  instrFile = open(filename, "w")
376  instrFile.write(self.InstructionFile())
377  instrFile.close()
378 
379  def OnLoadFile(self, event):
380  """!Load file and read instructions"""
381  #find file
382  filename = ''
383  dlg = wx.FileDialog(self, message = "Find instructions file", defaultDir = "",
384  defaultFile = '', wildcard = "All files (*.*)|*.*",
385  style = wx.CHANGE_DIR|wx.OPEN)
386  if dlg.ShowModal() == wx.ID_OK:
387  filename = dlg.GetPath()
388  dlg.Destroy()
389  if not filename:
390  return
391  # load instructions
392  readObjectId = []
393  readInstruction = Instruction(parent = self, objectsToDraw = readObjectId)
394  ok = readInstruction.Read(filename)
395  if not ok:
396  GMessage(_("Failed to read file %s.") % filename)
397  else:
398  self.instruction = self.canvas.instruction = readInstruction
399  self.objectId = self.canvas.objectId = readObjectId
400  self.pageId = self.canvas.pageId = self.instruction.FindInstructionByType('page').id
401  self.canvas.UpdateMapLabel()
402  self.canvas.dragId = -1
403  self.canvas.Clear()
404  self.canvas.SetPage()
405  #self.canvas.ZoomAll()
406 
407  self.DialogDataChanged(self.objectId)
408 
409  def OnPageSetup(self, event = None):
410  """!Specify paper size, margins and orientation"""
411  id = self.instruction.FindInstructionByType('page').id
412  dlg = PageSetupDialog(self, id = id, settings = self.instruction)
413  dlg.CenterOnScreen()
414  val = dlg.ShowModal()
415  if val == wx.ID_OK:
416  self.canvas.SetPage()
417  self.getInitMap()
418  self.canvas.RecalculatePosition(ids = self.objectId)
419  dlg.Destroy()
420 
421  def OnPointer(self, event):
422  self.toolbar.OnTool(event)
423  self.mouse["use"] = "pointer"
424  self.canvas.SetCursor(self.cursors["default"])
425  self.previewCanvas.SetCursor(self.cursors["default"])
426 
427  def OnPan(self, event):
428  self.toolbar.OnTool(event)
429  self.mouse["use"] = "pan"
430  self.canvas.SetCursor(self.cursors["hand"])
431  self.previewCanvas.SetCursor(self.cursors["hand"])
432 
433  def OnZoomIn(self, event):
434  self.toolbar.OnTool(event)
435  self.mouse["use"] = "zoomin"
436  self.canvas.SetCursor(self.cursors["cross"])
437  self.previewCanvas.SetCursor(self.cursors["cross"])
438 
439  def OnZoomOut(self, event):
440  self.toolbar.OnTool(event)
441  self.mouse["use"] = "zoomout"
442  self.canvas.SetCursor(self.cursors["cross"])
443  self.previewCanvas.SetCursor(self.cursors["cross"])
444 
445  def OnZoomAll(self, event):
446  self.mouseOld = self.mouse['use']
447  if self.currentPage == 0:
448  self.cursorOld = self.canvas.GetCursor()
449  else:
450  self.cursorOld = self.previewCanvas.GetCursor()
451  self.previewCanvas.GetCursor()
452  self.mouse["use"] = "zoomin"
453  if self.currentPage == 0:
454  self.canvas.ZoomAll()
455  else:
456  self.previewCanvas.ZoomAll()
457  self.mouse["use"] = self.mouseOld
458  if self.currentPage == 0:
459  self.canvas.SetCursor(self.cursorOld)
460  else:
461  self.previewCanvas.SetCursor(self.cursorOld)
462 
463 
464  def OnAddMap(self, event, notebook = False):
465  """!Add or edit map frame"""
466  if event is not None:
467  if event.GetId() != self.toolbar.action['id']:
468  self.actionOld = self.toolbar.action['id']
469  self.mouseOld = self.mouse['use']
470  self.cursorOld = self.canvas.GetCursor()
471  self.toolbar.OnTool(event)
472 
473  if self.instruction.FindInstructionByType('map'):
474  mapId = self.instruction.FindInstructionByType('map').id
475  else: mapId = None
476  id = [mapId, None, None]
477 
478  if notebook:
479  if self.instruction.FindInstructionByType('vector'):
480  vectorId = self.instruction.FindInstructionByType('vector').id
481  else: vectorId = None
482  if self.instruction.FindInstructionByType('raster'):
483  rasterId = self.instruction.FindInstructionByType('raster').id
484  else: rasterId = None
485  id[1] = rasterId
486  id[2] = vectorId
487 
488 
489  if mapId: # map exists
490 
491  self.toolbar.ToggleTool(self.actionOld, True)
492  self.toolbar.ToggleTool(self.toolbar.action['id'], False)
493  self.toolbar.action['id'] = self.actionOld
494  try:
495  self.canvas.SetCursor(self.cursorOld)
496  except AttributeError:
497  pass
498 
499 ## dlg = MapDialog(parent = self, id = id, settings = self.instruction,
500 ## notebook = notebook)
501 ## dlg.ShowModal()
502  if notebook:
503  #check map, raster, vector and save, destroy them
504  if 'map' in self.openDialogs:
505  self.openDialogs['map'].OnOK(event = None)
506  if 'raster' in self.openDialogs:
507  self.openDialogs['raster'].OnOK(event = None)
508  if 'vector' in self.openDialogs:
509  self.openDialogs['vector'].OnOK(event = None)
510 
511  if 'mapNotebook' not in self.openDialogs:
512  dlg = MapDialog(parent = self, id = id, settings = self.instruction,
513  notebook = notebook)
514  self.openDialogs['mapNotebook'] = dlg
515  self.openDialogs['mapNotebook'].Show()
516  else:
517  if 'mapNotebook' in self.openDialogs:
518  self.openDialogs['mapNotebook'].notebook.ChangeSelection(0)
519  else:
520  if 'map' not in self.openDialogs:
521  dlg = MapDialog(parent = self, id = id, settings = self.instruction,
522  notebook = notebook)
523  self.openDialogs['map'] = dlg
524  self.openDialogs['map'].Show()
525 
526 
527  else: # sofar no map
528  self.mouse["use"] = "addMap"
529  self.canvas.SetCursor(self.cursors["cross"])
530  if self.currentPage == 1:
531  self.book.SetSelection(0)
532  self.currentPage = 0
533 
534  def OnAddRaster(self, event):
535  """!Add raster map"""
536  if self.instruction.FindInstructionByType('raster'):
537  id = self.instruction.FindInstructionByType('raster').id
538  else: id = None
539  if self.instruction.FindInstructionByType('map'):
540  mapId = self.instruction.FindInstructionByType('map').id
541  else: mapId = None
542 
543  if not id:
544  if not mapId:
545  GMessage(message = _("Please, create map frame first."))
546  return
547 
548 ## dlg = RasterDialog(self, id = id, settings = self.instruction)
549 ## dlg.ShowModal()
550  if 'mapNotebook' in self.openDialogs:
551  self.openDialogs['mapNotebook'].notebook.ChangeSelection(1)
552  else:
553  if 'raster' not in self.openDialogs:
554  dlg = RasterDialog(self, id = id, settings = self.instruction)
555  self.openDialogs['raster'] = dlg
556  self.openDialogs['raster'].Show()
557 
558  def OnAddVect(self, event):
559  """!Add vector map"""
560  if self.instruction.FindInstructionByType('vector'):
561  id = self.instruction.FindInstructionByType('vector').id
562  else: id = None
563  if self.instruction.FindInstructionByType('map'):
564  mapId = self.instruction.FindInstructionByType('map').id
565  else: mapId = None
566  if not id:
567  if not mapId:
568  GMessage(message = _("Please, create map frame first."))
569  return
570 
571 ## dlg = MainVectorDialog(self, id = id, settings = self.instruction)
572 ## dlg.ShowModal()
573  if 'mapNotebook' in self.openDialogs:
574  self.openDialogs['mapNotebook'].notebook.ChangeSelection(2)
575  else:
576  if 'vector' not in self.openDialogs:
577  dlg = MainVectorDialog(self, id = id, settings = self.instruction)
578  self.openDialogs['vector'] = dlg
579  self.openDialogs['vector'].Show()
580 
581  def OnDecoration(self, event):
582  """!Decorations overlay menu
583  """
584  decmenu = wx.Menu()
585  # legend
586  AddLegend = wx.MenuItem(decmenu, wx.ID_ANY, Icons['psMap']["addLegend"].GetLabel())
587  AddLegend.SetBitmap(Icons['psMap']["addLegend"].GetBitmap(self.iconsize))
588  decmenu.AppendItem(AddLegend)
589  self.Bind(wx.EVT_MENU, self.OnAddLegend, AddLegend)
590  # mapinfo
591  AddMapinfo = wx.MenuItem(decmenu, wx.ID_ANY, Icons['psMap']["addMapinfo"].GetLabel())
592  AddMapinfo.SetBitmap(Icons['psMap']["addMapinfo"].GetBitmap(self.iconsize))
593  decmenu.AppendItem(AddMapinfo)
594  self.Bind(wx.EVT_MENU, self.OnAddMapinfo, AddMapinfo)
595  # scalebar
596  AddScalebar = wx.MenuItem(decmenu, wx.ID_ANY, Icons['psMap']["addScalebar"].GetLabel())
597  AddScalebar.SetBitmap(Icons['psMap']["addScalebar"].GetBitmap(self.iconsize))
598  decmenu.AppendItem(AddScalebar)
599  self.Bind(wx.EVT_MENU, self.OnAddScalebar, AddScalebar)
600  # text
601  AddText = wx.MenuItem(decmenu, wx.ID_ANY, Icons['psMap']["addText"].GetLabel())
602  AddText.SetBitmap(Icons['psMap']["addText"].GetBitmap(self.iconsize))
603  decmenu.AppendItem(AddText)
604  self.Bind(wx.EVT_MENU, self.OnAddText, AddText)
605  # Popup the menu. If an item is selected then its handler
606  # will be called before PopupMenu returns.
607  self.PopupMenu(decmenu)
608  decmenu.Destroy()
609 
610  def OnAddScalebar(self, event):
611  """!Add scalebar"""
612  if projInfo()['proj'] == 'll':
613  GMessage(message = _("Scalebar is not appropriate for this projection"))
614  return
615  if self.instruction.FindInstructionByType('scalebar'):
616  id = self.instruction.FindInstructionByType('scalebar').id
617  else: id = None
618 
619  if 'scalebar' not in self.openDialogs:
620  dlg = ScalebarDialog(self, id = id, settings = self.instruction)
621  self.openDialogs['scalebar'] = dlg
622  self.openDialogs['scalebar'].Show()
623 
624  def OnAddLegend(self, event, page = 0):
625  """!Add raster or vector legend"""
626  if self.instruction.FindInstructionByType('rasterLegend'):
627  idR = self.instruction.FindInstructionByType('rasterLegend').id
628  else: idR = None
629  if self.instruction.FindInstructionByType('vectorLegend'):
630  idV = self.instruction.FindInstructionByType('vectorLegend').id
631  else: idV = None
632 
633  if 'rasterLegend' not in self.openDialogs:
634  dlg = LegendDialog(self, id = [idR, idV], settings = self.instruction, page = page)
635  self.openDialogs['rasterLegend'] = dlg
636  self.openDialogs['vectorLegend'] = dlg
637  self.openDialogs['rasterLegend'].notebook.ChangeSelection(page)
638  self.openDialogs['rasterLegend'].Show()
639 
640  def OnAddMapinfo(self, event):
641  if self.instruction.FindInstructionByType('mapinfo'):
642  id = self.instruction.FindInstructionByType('mapinfo').id
643  else: id = None
644 
645  if 'mapinfo' not in self.openDialogs:
646  dlg = MapinfoDialog(self, id = id, settings = self.instruction)
647  self.openDialogs['mapinfo'] = dlg
648  self.openDialogs['mapinfo'].Show()
649 
650  def OnAddText(self, event, id = None):
651  """!Show dialog for text adding and editing"""
652  position = None
653  if 'text' in self.openDialogs:
654  position = self.openDialogs['text'].GetPosition()
655  self.openDialogs['text'].OnApply(event = None)
656  self.openDialogs['text'].Destroy()
657  dlg = TextDialog(self, id = id, settings = self.instruction)
658  self.openDialogs['text'] = dlg
659  if position:
660  dlg.SetPosition(position)
661  dlg.Show()
662 
663  def getModifiedTextBounds(self, x, y, textExtent, rotation):
664  """!computes bounding box of rotated text, not very precisely"""
665  w, h = textExtent
666  rotation = float(rotation)/180*pi
667  H = float(w) * sin(rotation)
668  W = float(w) * cos(rotation)
669  X, Y = x, y
670  if pi/2 < rotation <= 3*pi/2:
671  X = x + W
672  if 0 < rotation < pi:
673  Y = y - H
674  if rotation == 0:
675  return wx.Rect(x,y, *textExtent)
676  else:
677  return wx.Rect(X, Y, abs(W), abs(H)).Inflate(h,h)
678 
679  def makePSFont(self, textDict):
680  """!creates a wx.Font object from selected postscript font. To be
681  used for estimating bounding rectangle of text"""
682 
683  fontsize = textDict['fontsize'] * self.canvas.currScale
684  fontface = textDict['font'].split('-')[0]
685  try:
686  fontstyle = textDict['font'].split('-')[1]
687  except IndexError:
688  fontstyle = ''
689 
690  if fontface == "Times":
691  family = wx.FONTFAMILY_ROMAN
692  face = "times"
693  elif fontface == "Helvetica":
694  family = wx.FONTFAMILY_SWISS
695  face = 'helvetica'
696  elif fontface == "Courier":
697  family = wx.FONTFAMILY_TELETYPE
698  face = 'courier'
699  else:
700  family = wx.FONTFAMILY_DEFAULT
701  face = ''
702 
703  style = wx.FONTSTYLE_NORMAL
704  weight = wx.FONTWEIGHT_NORMAL
705 
706  if 'Oblique' in fontstyle:
707  style = wx.FONTSTYLE_SLANT
708 
709  if 'Italic' in fontstyle:
710  style = wx.FONTSTYLE_ITALIC
711 
712  if 'Bold' in fontstyle:
713  weight = wx.FONTWEIGHT_BOLD
714 
715  try:
716  fn = wx.Font(pointSize = fontsize, family = family, style = style,
717  weight = weight, face = face)
718  except:
719  fn = wx.Font(pointSize = fontsize, family = wx.FONTFAMILY_DEFAULT,
720  style = wx.FONTSTYLE_NORMAL, weight = wx.FONTWEIGHT_NORMAL)
721 
722  return fn
723 
724 
725  def getTextExtent(self, textDict):
726  """!Estimates bounding rectangle of text"""
727  #fontsize = str(fontsize if fontsize >= 4 else 4)
728  dc = wx.ClientDC(self) # dc created because of method GetTextExtent, which pseudoDC lacks
729 
730  fn = self.makePSFont(textDict)
731 
732  try:
733  dc.SetFont(fn)
734  w,h,lh = dc.GetMultiLineTextExtent(textDict['text'])
735  return (w,h)
736  except:
737  return (0,0)
738 
739  def getInitMap(self):
740  """!Create default map frame when no map is selected, needed for coordinates in map units"""
741  instrFile = grass.tempfile()
742  instrFileFd = open(instrFile, mode = 'w')
743  instrFileFd.write(self.InstructionFile())
744  instrFileFd.flush()
745  instrFileFd.close()
746 
747  page = self.instruction.FindInstructionByType('page')
748  mapInitRect = GetMapBounds(instrFile, portrait = (page['Orientation'] == 'Portrait'))
749  grass.try_remove(instrFile)
750 
751  region = grass.region()
752  units = UnitConversion(self)
753  realWidth = units.convert(value = abs(region['w'] - region['e']), fromUnit = 'meter', toUnit = 'inch')
754  scale = mapInitRect.Get()[2]/realWidth
755 
756  initMap = self.instruction.FindInstructionByType('initMap')
757  if initMap:
758  id = initMap.id
759  else:
760  id = None
761 
762 
763  if not id:
764  id = wx.NewId()
765  initMap = InitMap(id)
766  self.instruction.AddInstruction(initMap)
767  self.instruction[id].SetInstruction(dict(rect = mapInitRect, scale = scale))
768 
769  def OnDelete(self, event):
770  if self.canvas.dragId != -1 and self.currentPage == 0:
771  if self.instruction[self.canvas.dragId].type == 'map':
772  self.deleteObject(self.canvas.dragId)
773  self.getInitMap()
774  self.canvas.RecalculateEN()
775  else:
776  self.deleteObject(self.canvas.dragId)
777 
778  def deleteObject(self, id):
779  """!Deletes object, his id and redraws"""
780  #delete from canvas
781  self.canvas.pdcObj.RemoveId(id)
782  if id == self.canvas.dragId:
783  self.canvas.pdcTmp.RemoveAll()
784  self.canvas.dragId = -1
785  self.canvas.Refresh()
786 
787  # delete from instructions
788  del self.instruction[id]
789 
790  def DialogDataChanged(self, id):
791  ids = id
792  if type(id) == int:
793  ids = [id]
794  for id in ids:
795  itype = self.instruction[id].type
796 
797  if itype in ('scalebar', 'mapinfo'):
798  drawRectangle = self.canvas.CanvasPaperCoordinates(rect = self.instruction[id]['rect'], canvasToPaper = False)
799  self.canvas.Draw(pen = self.pen[itype], brush = self.brush[itype],
800  pdc = self.canvas.pdcObj, drawid = id, pdctype = 'rectText', bb = drawRectangle)
801  self.canvas.RedrawSelectBox(id)
802 
803  if itype == 'text':
804 
805  if self.instruction[id]['rotate']:
806  rot = float(self.instruction[id]['rotate'])
807  else:
808  rot = 0
809 
810  extent = self.getTextExtent(textDict = self.instruction[id].GetInstruction())
811  rect = wx.Rect2D(self.instruction[id]['where'][0], self.instruction[id]['where'][1], 0, 0)
812  self.instruction[id]['coords'] = list(self.canvas.CanvasPaperCoordinates(rect = rect, canvasToPaper = False)[:2])
813 
814  #computes text coordinates according to reference point, not precisely
815  if self.instruction[id]['ref'].split()[0] == 'lower':
816  self.instruction[id]['coords'][1] -= extent[1]
817  elif self.instruction[id]['ref'].split()[0] == 'center':
818  self.instruction[id]['coords'][1] -= extent[1]/2
819  if self.instruction[id]['ref'].split()[1] == 'right':
820  self.instruction[id]['coords'][0] -= extent[0] * cos(rot/180*pi)
821  self.instruction[id]['coords'][1] += extent[0] * sin(rot/180*pi)
822  elif self.instruction[id]['ref'].split()[1] == 'center':
823  self.instruction[id]['coords'][0] -= extent[0]/2 * cos(rot/180*pi)
824  self.instruction[id]['coords'][1] += extent[0]/2 * sin(rot/180*pi)
825 
826  self.instruction[id]['coords'][0] += self.instruction[id]['xoffset']
827  self.instruction[id]['coords'][1] -= self.instruction[id]['yoffset']
828  coords = self.instruction[id]['coords']
829  self.instruction[id]['rect'] = bounds = self.getModifiedTextBounds(coords[0], coords[1], extent, rot)
830  self.canvas.DrawRotText(pdc = self.canvas.pdcObj, drawId = id,
831  textDict = self.instruction[id].GetInstruction(),
832  coords = coords, bounds = bounds)
833  self.canvas.RedrawSelectBox(id)
834 
835  if itype in ('map', 'vector', 'raster'):
836 
837  if itype == 'raster':#set resolution
838  resol = RunCommand('r.info', read = True, flags = 's', map = self.instruction[id]['raster'])
839  resol = grass.parse_key_val(resol, val_type = float)
840  RunCommand('g.region', nsres = resol['nsres'], ewres = resol['ewres'])
841  # change current raster in raster legend
842 
843  if 'rasterLegend' in self.openDialogs:
844  self.openDialogs['rasterLegend'].updateDialog()
845  id = self.instruction.FindInstructionByType('map').id
846 
847  #check resolution
848  if itype == 'raster':
849  SetResolution(dpi = self.instruction[id]['resolution'],
850  width = self.instruction[id]['rect'].width,
851  height = self.instruction[id]['rect'].height)
852  rectCanvas = self.canvas.CanvasPaperCoordinates(rect = self.instruction[id]['rect'],
853  canvasToPaper = False)
854  self.canvas.RecalculateEN()
855  self.canvas.UpdateMapLabel()
856 
857  self.canvas.Draw(pen = self.pen['map'], brush = self.brush['map'],
858  pdc = self.canvas.pdcObj, drawid = id, pdctype = 'rectText', bb = rectCanvas)
859  # redraw select box
860  self.canvas.RedrawSelectBox(id)
861  self.canvas.pdcTmp.RemoveId(self.canvas.idZoomBoxTmp)
862  # redraw to get map to the bottom layer
863  #self.canvas.Zoom(zoomFactor = 1, view = (0, 0))
864 
865  if itype == 'rasterLegend':
866  if self.instruction[id]['rLegend']:
867  drawRectangle = self.canvas.CanvasPaperCoordinates(rect = self.instruction[id]['rect'], canvasToPaper = False)
868  self.canvas.Draw(pen = self.pen[itype], brush = self.brush[itype],
869  pdc = self.canvas.pdcObj, drawid = id, pdctype = 'rectText', bb = drawRectangle)
870  self.canvas.RedrawSelectBox(id)
871  else:
872  self.deleteObject(id)
873 
874  if itype == 'vectorLegend':
875  if not self.instruction.FindInstructionByType('vector'):
876  self.deleteObject(id)
877  elif self.instruction[id]['vLegend']:
878  drawRectangle = self.canvas.CanvasPaperCoordinates(rect = self.instruction[id]['rect'], canvasToPaper = False)
879  self.canvas.Draw(pen = self.pen[itype], brush = self.brush[itype],
880  pdc = self.canvas.pdcObj, drawid = id, pdctype = 'rectText', bb = drawRectangle)
881  self.canvas.RedrawSelectBox(id)
882 
883  else:
884  self.deleteObject(id)
885 
886  def OnPageChanged(self, event):
887  """!Flatnotebook page has changed"""
888  self.currentPage = self.book.GetPageIndex(self.book.GetCurrentPage())
889 
890 
891  def OnPageChanging(self, event):
892  """!Flatnotebook page is changing"""
893  if self.currentPage == 0 and self.mouse['use'] == 'addMap':
894  event.Veto()
895 
896  def OnHelp(self, event):
897  """!Show help"""
898  if self.parent and self.parent.GetName() == 'LayerManager':
899  log = self.parent.GetLogWindow()
900  log.RunCmd(['g.manual',
901  'entry=wxGUI.PsMap'])
902  else:
903  RunCommand('g.manual',
904  quiet = True,
905  entry = 'wxGUI.PsMap')
906 
907  def OnAbout(self, event):
908  """!Display About window"""
909  info = wx.AboutDialogInfo()
910 
911  info.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
912  info.SetName(_('wxGUI Cartographic Composer'))
913  info.SetWebSite('http://grass.osgeo.org')
914  info.SetDescription(_('(C) 2011 by the GRASS Development Team\n\n') +
915  '\n'.join(textwrap.wrap(_('This program is free software under the GNU General Public License'
916  '(>=v2). Read the file COPYING that comes with GRASS for details.'), 75)))
917 
918  wx.AboutBox(info)
919 
920  def OnCloseWindow(self, event):
921  """!Close window"""
922  try:
923  os.remove(self.imgName)
924  except OSError:
925  pass
926  grass.set_raise_on_error(False)
927  self.Destroy()
928 
929 
930 
931 class PsMapBufferedWindow(wx.Window):
932  """!A buffered window class.
933 
934  @param parent parent window
935  @param kwargs other wx.Window parameters
936  """
937  def __init__(self, parent, id = wx.ID_ANY,
938  style = wx.NO_FULL_REPAINT_ON_RESIZE,
939  **kwargs):
940  wx.Window.__init__(self, parent, id = id, style = style)
941  self.parent = parent
942 
943  self.FitInside()
944 
945  # store an off screen empty bitmap for saving to file
946  self._buffer = None
947  # indicates whether or not a resize event has taken place
948  self.resize = False
949 
950  self.mouse = kwargs['mouse']
951  self.cursors = kwargs['cursors']
952  self.preview = kwargs['preview']
953  self.pen = kwargs['pen']
954  self.brush = kwargs['brush']
955 
956  if kwargs.has_key('instruction'):
957  self.instruction = kwargs['instruction']
958  if kwargs.has_key('openDialogs'):
959  self.openDialogs = kwargs['openDialogs']
960  if kwargs.has_key('pageId'):
961  self.pageId = kwargs['pageId']
962  if kwargs.has_key('objectId'):
963  self.objectId = kwargs['objectId']
964 
965 
966  #labels
967  self.itemLabels = { 'map': ['MAP FRAME'],
968  'rasterLegend': ['RASTER LEGEND'],
969  'vectorLegend': ['VECTOR LEGEND'],
970  'mapinfo': ['MAP INFO'],
971  'scalebar': ['SCALE BAR']}
972 
973  # define PseudoDC
974  self.pdc = wx.PseudoDC()
975  self.pdcObj = wx.PseudoDC()
976  self.pdcPaper = wx.PseudoDC()
977  self.pdcTmp = wx.PseudoDC()
978  self.pdcImage = wx.PseudoDC()
979  dc = wx.ClientDC(self)
980  self.font = dc.GetFont()
981 
982  self.SetClientSize((700,510))#?
983  self._buffer = wx.EmptyBitmap(*self.GetClientSize())
984 
985  self.idBoxTmp = wx.NewId()
986  self.idZoomBoxTmp = wx.NewId()
987  self.idResizeBoxTmp = wx.NewId()
988 
989 
990 
991  self.dragId = -1
992 
993  if self.preview:
994  self.image = None
995  self.imageId = 2000
996  self.imgName = self.parent.imgName
997 
998 
999 
1000  self.currScale = None
1001 
1002  self.Clear()
1003 
1004  self.Bind(wx.EVT_ERASE_BACKGROUND, lambda x: None)
1005 
1006  self.Bind(wx.EVT_PAINT, self.OnPaint)
1007  self.Bind(wx.EVT_SIZE, self.OnSize)
1008  self.Bind(wx.EVT_IDLE, self.OnIdle)
1009  self.Bind(wx.EVT_MOUSE_EVENTS, self.OnMouse)
1010 
1011 
1012  def Clear(self):
1013  """!Clear canvas and set paper
1014  """
1015  bg = wx.LIGHT_GREY_BRUSH
1016  self.pdcPaper.BeginDrawing()
1017  self.pdcPaper.SetBackground(bg)
1018  self.pdcPaper.Clear()
1019  self.pdcPaper.EndDrawing()
1020 
1021  self.pdcObj.RemoveAll()
1022  self.pdcTmp.RemoveAll()
1023 
1024 
1025 
1026  if not self.preview:
1027  self.SetPage()
1028 
1029 
1030  def CanvasPaperCoordinates(self, rect, canvasToPaper = True):
1031  """!Converts canvas (pixel) -> paper (inch) coordinates and size and vice versa"""
1032 
1033  units = UnitConversion(self)
1034 
1035  fromU = 'pixel'
1036  toU = 'inch'
1037  pRect = self.pdcPaper.GetIdBounds(self.pageId)
1038  pRectx, pRecty = pRect.x, pRect.y
1039  scale = 1/self.currScale
1040  if not canvasToPaper: # paper -> canvas
1041  fromU = 'inch'
1042  toU = 'pixel'
1043  scale = self.currScale
1044  pRectx = units.convert(value = - pRect.x, fromUnit = 'pixel', toUnit = 'inch' ) /scale #inch, real, negative
1045  pRecty = units.convert(value = - pRect.y, fromUnit = 'pixel', toUnit = 'inch' ) /scale
1046  Width = units.convert(value = rect.width, fromUnit = fromU, toUnit = toU) * scale
1047  Height = units.convert(value = rect.height, fromUnit = fromU, toUnit = toU) * scale
1048  X = units.convert(value = (rect.x - pRectx), fromUnit = fromU, toUnit = toU) * scale
1049  Y = units.convert(value = (rect.y - pRecty), fromUnit = fromU, toUnit = toU) * scale
1050 
1051  return wx.Rect2D(X, Y, Width, Height)
1052 
1053 
1054 
1055  def SetPage(self):
1056  """!Sets and changes page, redraws paper"""
1057 
1058  page = self.instruction[self.pageId]
1059  if not page:
1060  page = PageSetup(id = self.pageId)
1061  self.instruction.AddInstruction(page)
1062 
1063  ppi = wx.ClientDC(self).GetPPI()
1064  cW, cH = self.GetClientSize()
1065  pW, pH = page['Width']*ppi[0], page['Height']*ppi[1]
1066 
1067  if self.currScale is None:
1068  self.currScale = min(cW/pW, cH/pH)
1069  pW = pW * self.currScale
1070  pH = pH * self.currScale
1071 
1072  x = cW/2 - pW/2
1073  y = cH/2 - pH/2
1074  self.DrawPaper(wx.Rect(x, y, pW, pH))
1075 
1076 
1077  def modifyRectangle(self, r):
1078  """! Recalculates rectangle not to have negative size"""
1079  if r.GetWidth() < 0:
1080  r.SetX(r.GetX() + r.GetWidth())
1081  if r.GetHeight() < 0:
1082  r.SetY(r.GetY() + r.GetHeight())
1083  r.SetWidth(abs(r.GetWidth()))
1084  r.SetHeight(abs(r.GetHeight()))
1085  return r
1086 
1087  def RecalculateEN(self):
1088  """!Recalculate east and north for texts (eps, points) after their or map's movement"""
1089  try:
1090  mapId = self.instruction.FindInstructionByType('map').id
1091  except AttributeError:
1092  mapId = self.instruction.FindInstructionByType('initMap').id
1093 
1094  texts = self.instruction.FindInstructionByType('text', list = True)
1095  for text in texts:
1096  e, n = PaperMapCoordinates(map = self.instruction[mapId], x = self.instruction[text.id]['where'][0],
1097  y = self.instruction[text.id]['where'][1], paperToMap = True)
1098  self.instruction[text.id]['east'], self.instruction[text.id]['north'] = e, n
1099 
1100  def OnPaint(self, event):
1101  """!Draw pseudo DC to buffer
1102  """
1103  if not self._buffer:
1104  return
1105  dc = wx.BufferedPaintDC(self, self._buffer)
1106  # use PrepareDC to set position correctly
1107  self.PrepareDC(dc)
1108 
1109  dc.SetBackground(wx.LIGHT_GREY_BRUSH)
1110  dc.Clear()
1111 
1112  # draw paper
1113  if not self.preview:
1114  self.pdcPaper.DrawToDC(dc)
1115  # draw to the DC using the calculated clipping rect
1116 
1117  rgn = self.GetUpdateRegion()
1118 
1119  if not self.preview:
1120  self.pdcObj.DrawToDCClipped(dc, rgn.GetBox())
1121  else:
1122  self.pdcImage.DrawToDCClipped(dc, rgn.GetBox())
1123  self.pdcTmp.DrawToDCClipped(dc, rgn.GetBox())
1124 
1125  def OnMouse(self, event):
1126 
1127  if event.GetWheelRotation():
1128  zoom = event.GetWheelRotation()
1129  use = self.mouse['use']
1130  self.mouse['begin'] = event.GetPosition()
1131  if zoom > 0:
1132  self.mouse['use'] = 'zoomin'
1133  else:
1134  self.mouse['use'] = 'zoomout'
1135 
1136  zoomFactor, view = self.ComputeZoom(wx.Rect(0,0,0,0))
1137  self.Zoom(zoomFactor, view)
1138  self.mouse['use'] = use
1139 
1140  if event.Moving():
1141  if self.mouse['use'] in ('pointer', 'resize'):
1142  pos = event.GetPosition()
1143  foundResize = self.pdcTmp.FindObjects(pos[0], pos[1])
1144  if foundResize and foundResize[0] == self.idResizeBoxTmp:
1145  self.SetCursor(self.cursors["sizenwse"])
1146  self.parent.SetStatusText(_('Click and drag to resize object'), 0)
1147  else:
1148  self.parent.SetStatusText('', 0)
1149  self.SetCursor(self.cursors["default"])
1150 
1151  elif event.LeftDown():
1152  self.mouse['begin'] = event.GetPosition()
1153  self.begin = self.mouse['begin']
1154  if self.mouse['use'] in ('pan', 'zoomin', 'zoomout', 'addMap'):
1155  pass
1156 
1157  #select
1158  if self.mouse['use'] == 'pointer':
1159  found = self.pdcObj.FindObjects(self.mouse['begin'][0], self.mouse['begin'][1])
1160  foundResize = self.pdcTmp.FindObjects(self.mouse['begin'][0], self.mouse['begin'][1])
1161 
1162  if foundResize and foundResize[0] == self.idResizeBoxTmp:
1163  self.mouse['use'] = 'resize'
1164 
1165  # when resizing, proportions match region
1166  if self.instruction[self.dragId].type == 'map':
1167  self.constraint = False
1168  self.mapBounds = self.pdcObj.GetIdBounds(self.dragId)
1169  if self.instruction[self.dragId]['scaleType'] in (0, 1, 2):
1170  self.constraint = True
1171  self.mapBounds = self.pdcObj.GetIdBounds(self.dragId)
1172 
1173  elif found:
1174  self.dragId = found[0]
1175  self.RedrawSelectBox(self.dragId)
1176  if self.instruction[self.dragId].type != 'map':
1177  self.pdcTmp.RemoveId(self.idResizeBoxTmp)
1178  self.Refresh()
1179 
1180  else:
1181  self.dragId = -1
1182  self.pdcTmp.RemoveId(self.idBoxTmp)
1183  self.pdcTmp.RemoveId(self.idResizeBoxTmp)
1184  self.Refresh()
1185 
1186 
1187  elif event.Dragging() and event.LeftIsDown():
1188  #draw box when zooming, creating map
1189  if self.mouse['use'] in ('zoomin', 'zoomout', 'addMap'):
1190  self.mouse['end'] = event.GetPosition()
1191  r = wx.Rect(self.mouse['begin'][0], self.mouse['begin'][1],
1192  self.mouse['end'][0]-self.mouse['begin'][0], self.mouse['end'][1]-self.mouse['begin'][1])
1193  r = self.modifyRectangle(r)
1194  self.Draw(pen = self.pen['box'], brush = self.brush['box'], pdc = self.pdcTmp, drawid = self.idZoomBoxTmp,
1195  pdctype = 'rect', bb = r)
1196 
1197  # panning
1198  if self.mouse["use"] == 'pan':
1199  self.mouse['end'] = event.GetPosition()
1200  view = self.mouse['begin'][0] - self.mouse['end'][0], self.mouse['begin'][1] - self.mouse['end'][1]
1201  zoomFactor = 1
1202  self.Zoom(zoomFactor, view)
1203  self.mouse['begin'] = event.GetPosition()
1204 
1205  #move object
1206  if self.mouse['use'] == 'pointer' and self.dragId != -1:
1207 
1208  self.mouse['end'] = event.GetPosition()
1209  dx, dy = self.mouse['end'][0] - self.begin[0], self.mouse['end'][1] - self.begin[1]
1210  self.pdcObj.TranslateId(self.dragId, dx, dy)
1211  self.pdcTmp.TranslateId(self.idBoxTmp, dx, dy)
1212  self.pdcTmp.TranslateId(self.idResizeBoxTmp, dx, dy)
1213  if self.instruction[self.dragId].type == 'text':
1214  self.instruction[self.dragId]['coords'] = self.instruction[self.dragId]['coords'][0] + dx,\
1215  self.instruction[self.dragId]['coords'][1] + dy
1216  self.begin = event.GetPosition()
1217  self.Refresh()
1218 
1219  # resize object
1220  if self.mouse['use'] == 'resize':
1221  type = self.instruction[self.dragId].type
1222  pos = event.GetPosition()
1223  x, y = self.mapBounds.GetX(), self.mapBounds.GetY()
1224  width, height = self.mapBounds.GetWidth(), self.mapBounds.GetHeight()
1225  diffX = pos[0] - self.mouse['begin'][0]
1226  diffY = pos[1] - self.mouse['begin'][1]
1227  # match given region
1228  if self.constraint:
1229  if width > height:
1230  newWidth = width + diffX
1231  newHeight = height + diffX * (float(height) / width)
1232  else:
1233  newWidth = width + diffY * (float(width) / height)
1234  newHeight = height + diffY
1235  else:
1236  newWidth = width + diffX
1237  newHeight = height + diffY
1238 
1239  if newWidth < 10 or newHeight < 10:
1240  return
1241 
1242  bounds = wx.Rect(x, y, newWidth, newHeight)
1243  self.Draw(pen = self.pen[type], brush = self.brush[type], pdc = self.pdcObj, drawid = self.dragId,
1244  pdctype = 'rectText', bb = bounds)
1245  self.RedrawSelectBox(self.dragId)
1246 
1247  elif event.LeftUp():
1248  # zoom in, zoom out
1249  if self.mouse['use'] in ('zoomin','zoomout'):
1250  zoomR = self.pdcTmp.GetIdBounds(self.idZoomBoxTmp)
1251  self.pdcTmp.RemoveId(self.idZoomBoxTmp)
1252  self.Refresh()
1253  zoomFactor, view = self.ComputeZoom(zoomR)
1254  self.Zoom(zoomFactor, view)
1255 
1256 
1257  # draw map frame
1258  if self.mouse['use'] == 'addMap':
1259  rectTmp = self.pdcTmp.GetIdBounds(self.idZoomBoxTmp)
1260  # too small rectangle, it's usually some mistake
1261  if rectTmp.GetWidth() < 20 or rectTmp.GetHeight() < 20:
1262  self.pdcTmp.RemoveId(self.idZoomBoxTmp)
1263  self.Refresh()
1264  return
1265  rectPaper = self.CanvasPaperCoordinates(rect = rectTmp, canvasToPaper = True)
1266 
1267  dlg = MapDialog(parent = self.parent, id = [None, None, None], settings = self.instruction,
1268  rect = rectPaper)
1269  self.openDialogs['map'] = dlg
1270  self.openDialogs['map'].Show()
1271 
1272 
1273  self.mouse['use'] = self.parent.mouseOld
1274 
1275  self.SetCursor(self.parent.cursorOld)
1276  self.parent.toolbar.ToggleTool(self.parent.actionOld, True)
1277  self.parent.toolbar.ToggleTool(self.parent.toolbar.action['id'], False)
1278  self.parent.toolbar.action['id'] = self.parent.actionOld
1279 
1280 
1281 
1282  # resize resizable objects (only map sofar)
1283  if self.mouse['use'] == 'resize':
1284  mapId = self.instruction.FindInstructionByType('map').id
1285 
1286  if self.dragId == mapId:
1287  # necessary to change either map frame (scaleType 0,1,2) or region (scaletype 3)
1288  newRectCanvas = self.pdcObj.GetIdBounds(mapId)
1289  newRectPaper = self.CanvasPaperCoordinates(rect = newRectCanvas, canvasToPaper = True)
1290  self.instruction[mapId]['rect'] = newRectPaper
1291 
1292  if self.instruction[mapId]['scaleType'] in (0, 1, 2):
1293  if self.instruction[mapId]['scaleType'] == 0:
1294 
1295  scale, foo, rect = AutoAdjust(self, scaleType = 0,
1296  map = self.instruction[mapId]['map'],
1297  mapType = self.instruction[mapId]['mapType'],
1298  rect = self.instruction[mapId]['rect'])
1299 
1300  elif self.instruction[mapId]['scaleType'] == 1:
1301  scale, foo, rect = AutoAdjust(self, scaleType = 1,
1302  region = self.instruction[mapId]['region'],
1303  rect = self.instruction[mapId]['rect'])
1304  else:
1305  scale, foo, rect = AutoAdjust(self, scaleType = 2,
1306  rect = self.instruction[mapId]['rect'])
1307  self.instruction[mapId]['rect'] = rect
1308  self.instruction[mapId]['scale'] = scale
1309 
1310  rectCanvas = self.CanvasPaperCoordinates(rect = rect, canvasToPaper = False)
1311  self.Draw(pen = self.pen['map'], brush = self.brush['map'],
1312  pdc = self.pdcObj, drawid = mapId, pdctype = 'rectText', bb = rectCanvas)
1313 
1314  elif self.instruction[mapId]['scaleType'] == 3:
1315  ComputeSetRegion(self, mapDict = self.instruction[mapId].GetInstruction())
1316  #check resolution
1317  SetResolution(dpi = self.instruction[mapId]['resolution'],
1318  width = self.instruction[mapId]['rect'].width,
1319  height = self.instruction[mapId]['rect'].height)
1320 
1321  self.RedrawSelectBox(mapId)
1322  self.Zoom(zoomFactor = 1, view = (0, 0))
1323  self.mouse['use'] = 'pointer'
1324 
1325  # recalculate the position of objects after dragging
1326  if self.mouse['use'] in ('pointer', 'resize') and self.dragId != -1:
1327  if self.mouse['begin'] != event.GetPosition(): #for double click
1328 
1329  self.RecalculatePosition(ids = [self.dragId])
1330  if self.instruction[self.dragId].type in self.openDialogs:
1331  self.openDialogs[self.instruction[self.dragId].type].updateDialog()
1332 
1333  # double click launches dialogs
1334  elif event.LeftDClick():
1335  if self.mouse['use'] == 'pointer' and self.dragId != -1:
1336  itemCall = { 'text':self.parent.OnAddText, 'mapinfo': self.parent.OnAddMapinfo,
1337  'scalebar': self.parent.OnAddScalebar,
1338  'rasterLegend': self.parent.OnAddLegend, 'vectorLegend': self.parent.OnAddLegend,
1339  'map': self.parent.OnAddMap}
1340  itemArg = { 'text': dict(event = None, id = self.dragId), 'mapinfo': dict(event = None),
1341  'scalebar': dict(event = None),
1342  'rasterLegend': dict(event = None), 'vectorLegend': dict(event = None, page = 1),
1343  'map': dict(event = None, notebook = True)}
1344  type = self.instruction[self.dragId].type
1345  itemCall[type](**itemArg[type])
1346 
1347 
1348 
1349 
1350  def RecalculatePosition(self, ids):
1351  for id in ids:
1352  itype = self.instruction[id].type
1353  if itype == 'map':
1354  self.instruction[id]['rect'] = self.CanvasPaperCoordinates(rect = self.pdcObj.GetIdBounds(id),
1355  canvasToPaper = True)
1356  self.RecalculateEN()
1357 
1358  elif itype in ('mapinfo' ,'rasterLegend', 'vectorLegend'):
1359  self.instruction[id]['rect'] = self.CanvasPaperCoordinates(rect = self.pdcObj.GetIdBounds(id),
1360  canvasToPaper = True)
1361  self.instruction[id]['where'] = self.CanvasPaperCoordinates(rect = self.pdcObj.GetIdBounds(id),
1362  canvasToPaper = True)[:2]
1363  elif itype == 'scalebar':
1364  self.instruction[id]['rect'] = self.CanvasPaperCoordinates(rect = self.pdcObj.GetIdBounds(id),
1365  canvasToPaper = True)
1366 
1367 
1368  self.instruction[id]['where'] = self.instruction[id]['rect'].GetCentre()
1369 
1370  elif itype == 'text':
1371  x, y = self.instruction[id]['coords'][0] - self.instruction[id]['xoffset'],\
1372  self.instruction[id]['coords'][1] + self.instruction[id]['yoffset']
1373  extent = self.parent.getTextExtent(textDict = self.instruction[id])
1374  if self.instruction[id]['rotate'] is not None:
1375  rot = float(self.instruction[id]['rotate'])/180*pi
1376  else:
1377  rot = 0
1378 
1379  if self.instruction[id]['ref'].split()[0] == 'lower':
1380  y += extent[1]
1381  elif self.instruction[id]['ref'].split()[0] == 'center':
1382  y += extent[1]/2
1383  if self.instruction[id]['ref'].split()[1] == 'right':
1384  x += extent[0] * cos(rot)
1385  y -= extent[0] * sin(rot)
1386  elif self.instruction[id]['ref'].split()[1] == 'center':
1387  x += extent[0]/2 * cos(rot)
1388  y -= extent[0]/2 * sin(rot)
1389 
1390  self.instruction[id]['where'] = self.CanvasPaperCoordinates(rect = wx.Rect2D(x, y, 0, 0),
1391  canvasToPaper = True)[:2]
1392  self.RecalculateEN()
1393 
1394  def ComputeZoom(self, rect):
1395  """!Computes zoom factor and scroll view"""
1396  zoomFactor = 1
1397  cW, cH = self.GetClientSize()
1398  cW = float(cW)
1399  if rect.IsEmpty(): # clicked on canvas
1400  zoomFactor = 1.5
1401  if self.mouse['use'] == 'zoomout':
1402  zoomFactor = 1./zoomFactor
1403  x,y = self.mouse['begin']
1404  xView = x - x/zoomFactor#x - cW/(zoomFactor * 2)
1405  yView = y - y/zoomFactor#y - cH/(zoomFactor * 2)
1406 
1407  else: #dragging
1408  rW, rH = float(rect.GetWidth()), float(rect.GetHeight())
1409  try:
1410  zoomFactor = 1/max(rW/cW, rH/cH)
1411  except ZeroDivisionError:
1412  zoomFactor = 1
1413  # when zooming to full extent, in some cases, there was zoom 1.01..., which causes problem
1414  if abs(zoomFactor - 1) > 0.01:
1415  zoomFactor = zoomFactor
1416  else:
1417  zoomFactor = 1.
1418 
1419 
1420  if self.mouse['use'] == 'zoomout':
1421  zoomFactor = min(rW/cW, rH/cH)
1422  try:
1423  if rW/rH > cW/cH:
1424  yView = rect.GetY() - (rW*(cH/cW) - rH)/2
1425  xView = rect.GetX()
1426 
1427  if self.mouse['use'] == 'zoomout':
1428  x,y = rect.GetX() + (rW-(cW/cH)*rH)/2, rect.GetY()
1429  xView, yView = -x, -y
1430  else:
1431  xView = rect.GetX() - (rH*(cW/cH) - rW)/2
1432  yView = rect.GetY()
1433  if self.mouse['use'] == 'zoomout':
1434  x,y = rect.GetX(), rect.GetY() + (rH-(cH/cW)*rW)/2
1435  xView, yView = -x, -y
1436  except ZeroDivisionError:
1437  xView, yView = rect.GetX(), rect.GetY()
1438 
1439  return zoomFactor, (int(xView), int(yView))
1440 
1441 
1442  def Zoom(self, zoomFactor, view):
1443  """! Zoom to specified region, scroll view, redraw"""
1444  if not self.currScale:
1445  return
1446  self.currScale = self.currScale*zoomFactor
1447 
1448  if self.currScale > 10 or self.currScale < 0.1:
1449  self.currScale = self.currScale/zoomFactor
1450  return
1451  if not self.preview:
1452  # redraw paper
1453  pRect = self.pdcPaper.GetIdBounds(self.pageId)
1454  pRect.OffsetXY(-view[0], -view[1])
1455  pRect = self.ScaleRect(rect = pRect, scale = zoomFactor)
1456  self.DrawPaper(pRect)
1457 
1458  #redraw objects
1459  for id in self.objectId:
1460  oRect = self.CanvasPaperCoordinates(
1461  rect = self.instruction[id]['rect'], canvasToPaper = False)
1462 
1463  type = self.instruction[id].type
1464  if type == 'text':
1465  coords = self.instruction[id]['coords']# recalculate coordinates, they are not equal to BB
1466  self.instruction[id]['coords'] = coords = [(int(coord) - view[i]) * zoomFactor
1467  for i, coord in enumerate(coords)]
1468  self.DrawRotText(pdc = self.pdcObj, drawId = id, textDict = self.instruction[id],
1469  coords = coords, bounds = oRect )
1470  extent = self.parent.getTextExtent(textDict = self.instruction[id])
1471  if self.instruction[id]['rotate']:
1472  rot = float(self.instruction[id]['rotate'])
1473  else:
1474  rot = 0
1475 
1476  self.instruction[id]['rect'] = bounds = self.parent.getModifiedTextBounds(coords[0], coords[1], extent, rot)
1477  self.pdcObj.SetIdBounds(id, bounds)
1478  else:
1479  self.Draw(pen = self.pen[type], brush = self.brush[type], pdc = self.pdcObj,
1480  drawid = id, pdctype = 'rectText', bb = oRect)
1481  #redraw tmp objects
1482  if self.dragId != -1:
1483  self.RedrawSelectBox(self.dragId)
1484 
1485  #redraw preview
1486  else: # preview mode
1487  imageRect = self.pdcImage.GetIdBounds(self.imageId)
1488  imageRect.OffsetXY(-view[0], -view[1])
1489  imageRect = self.ScaleRect(rect = imageRect, scale = zoomFactor)
1490  self.DrawImage(imageRect)
1491 
1492  def ZoomAll(self):
1493  """! Zoom to full extent"""
1494  if not self.preview:
1495  bounds = self.pdcPaper.GetIdBounds(self.pageId)
1496  else:
1497  bounds = self.pdcImage.GetIdBounds(self.imageId)
1498  zoomP = bounds.Inflate(bounds.width/20, bounds.height/20)
1499  zoomFactor, view = self.ComputeZoom(zoomP)
1500  self.Zoom(zoomFactor, view)
1501 
1502  def Draw(self, pen, brush, pdc, drawid = None, pdctype = 'rect', bb = wx.Rect(0,0,0,0)):
1503  """! Draw object"""
1504  if drawid is None:
1505  drawid = wx.NewId()
1506  bb = bb.Get()
1507  pdc.BeginDrawing()
1508  pdc.RemoveId(drawid)
1509  pdc.SetId(drawid)
1510  pdc.SetPen(pen)
1511  pdc.SetBrush(brush)
1512  if pdctype in ('rect', 'rectText'):
1513  pdc.DrawRectangle(*bb)
1514  if pdctype == 'rectText':
1515  dc = wx.ClientDC(self) # dc created because of method GetTextExtent, which pseudoDC lacks
1516  font = self.font
1517  size = 10
1518  font.SetPointSize(size)
1519  font.SetStyle(wx.ITALIC)
1520  dc.SetFont(font)
1521  pdc.SetFont(font)
1522  text = '\n'.join(self.itemLabels[self.instruction[drawid].type])
1523  w,h,lh = dc.GetMultiLineTextExtent(text)
1524  textExtent = (w,h)
1525  textRect = wx.Rect(0, 0, *textExtent).CenterIn(bb)
1526  r = map(int, bb)
1527  while not wx.Rect(*r).ContainsRect(textRect) and size >= 8:
1528  size -= 2
1529  font.SetPointSize(size)
1530  dc.SetFont(font)
1531  pdc.SetFont(font)
1532  textExtent = dc.GetTextExtent(text)
1533  textRect = wx.Rect(0, 0, *textExtent).CenterIn(bb)
1534  pdc.SetTextForeground(wx.Color(100,100,100,200))
1535  pdc.SetBackgroundMode(wx.TRANSPARENT)
1536  pdc.DrawText(text = text, x = textRect.x, y = textRect.y)
1537 
1538  pdc.SetIdBounds(drawid, bb)
1539  pdc.EndDrawing()
1540  self.Refresh()
1541 
1542  return drawid
1543 
1544  def DrawRotText(self, pdc, drawId, textDict, coords, bounds):
1545  if textDict['rotate']:
1546  rot = float(textDict['rotate'])
1547  else:
1548  rot = 0
1549 
1550  fontsize = textDict['fontsize'] * self.currScale
1551  if textDict['background'] != 'none':
1552  background = textDict['background']
1553  else:
1554  background = None
1555 
1556  pdc.RemoveId(drawId)
1557  pdc.SetId(drawId)
1558  pdc.BeginDrawing()
1559 
1560  # border is not redrawn when zoom changes, why?
1561 ## if textDict['border'] != 'none' and not rot:
1562 ## units = UnitConversion(self)
1563 ## borderWidth = units.convert(value = textDict['width'],
1564 ## fromUnit = 'point', toUnit = 'pixel' ) * self.currScale
1565 ## pdc.SetPen(wx.Pen(colour = convertRGB(textDict['border']), width = borderWidth))
1566 ## pdc.DrawRectangle(*bounds)
1567 
1568  if background:
1569  pdc.SetTextBackground(convertRGB(background))
1570  pdc.SetBackgroundMode(wx.SOLID)
1571  else:
1572  pdc.SetBackgroundMode(wx.TRANSPARENT)
1573 
1574  fn = self.parent.makePSFont(textDict)
1575 
1576  pdc.SetFont(fn)
1577  pdc.SetTextForeground(convertRGB(textDict['color']))
1578  pdc.DrawRotatedText(textDict['text'], coords[0], coords[1], rot)
1579 
1580  pdc.SetIdBounds(drawId, wx.Rect(*bounds))
1581  self.Refresh()
1582  pdc.EndDrawing()
1583 
1584  def DrawImage(self, rect):
1585  """!Draw preview image to pseudoDC"""
1586  self.pdcImage.ClearId(self.imageId)
1587  self.pdcImage.SetId(self.imageId)
1588  img = self.image
1589 
1590 
1591  if img.GetWidth() != rect.width or img.GetHeight() != rect.height:
1592  img = img.Scale(rect.width, rect.height)
1593  bitmap = img.ConvertToBitmap()
1594 
1595  self.pdcImage.BeginDrawing()
1596  self.pdcImage.DrawBitmap(bitmap, rect.x, rect.y)
1597  self.pdcImage.SetIdBounds(self.imageId, rect)
1598  self.pdcImage.EndDrawing()
1599  self.Refresh()
1600 
1601  def DrawPaper(self, rect):
1602  """!Draw paper and margins"""
1603  page = self.instruction[self.pageId]
1604  scale = page['Width'] / rect.GetWidth()
1605  w = (page['Width'] - page['Right'] - page['Left']) / scale
1606  h = (page['Height'] - page['Top'] - page['Bottom']) / scale
1607  x = page['Left'] / scale + rect.GetX()
1608  y = page['Top'] / scale + rect.GetY()
1609 
1610  self.pdcPaper.BeginDrawing()
1611  self.pdcPaper.RemoveId(self.pageId)
1612  self.pdcPaper.SetId(self.pageId)
1613  self.pdcPaper.SetPen(self.pen['paper'])
1614  self.pdcPaper.SetBrush(self.brush['paper'])
1615  self.pdcPaper.DrawRectangleRect(rect)
1616 
1617  self.pdcPaper.SetPen(self.pen['margins'])
1618  self.pdcPaper.SetBrush(self.brush['margins'])
1619  self.pdcPaper.DrawRectangle(x, y, w, h)
1620 
1621  self.pdcPaper.SetIdBounds(self.pageId, rect)
1622  self.pdcPaper.EndDrawing()
1623  self.Refresh()
1624 
1625 
1626  def ImageRect(self):
1627  """!Returns image centered in canvas, computes scale"""
1628  img = wx.Image(self.imgName, wx.BITMAP_TYPE_PNG)
1629  cW, cH = self.GetClientSize()
1630  iW, iH = img.GetWidth(), img.GetHeight()
1631 
1632  self.currScale = min(float(cW)/iW, float(cH)/iH)
1633  iW = iW * self.currScale
1634  iH = iH * self.currScale
1635  x = cW/2 - iW/2
1636  y = cH/2 - iH/2
1637  imageRect = wx.Rect(x, y, iW, iH)
1638 
1639  return imageRect
1640 
1641  def RedrawSelectBox(self, id):
1642  """!Redraws select box when selected object changes its size"""
1643  if self.dragId == id:
1644  rect = [self.pdcObj.GetIdBounds(id).Inflate(3,3)]
1645  type = ['select']
1646  ids = [self.idBoxTmp]
1647  if self.instruction[id].type == 'map':
1648  controlP = self.pdcObj.GetIdBounds(id).GetBottomRight()
1649  rect.append(wx.Rect(controlP.x, controlP.y, 10,10))
1650  type.append('resize')
1651  ids.append(self.idResizeBoxTmp)
1652  for id, type, rect in zip(ids, type, rect):
1653  self.Draw(pen = self.pen[type], brush = self.brush[type], pdc = self.pdcTmp,
1654  drawid = id, pdctype = 'rect', bb = rect)
1655 
1656  def UpdateMapLabel(self):
1657  """!Updates map frame label"""
1658 
1659  vector = self.instruction.FindInstructionByType('vector')
1660  if vector:
1661  vectorId = vector.id
1662  else:
1663  vectorId = None
1664 
1665  raster = self.instruction.FindInstructionByType('raster')
1666  if raster:
1667  rasterId = raster.id
1668  else:
1669  rasterId = None
1670 
1671  rasterName = 'None'
1672  if rasterId:
1673  rasterName = self.instruction[rasterId]['raster'].split('@')[0]
1674 
1675  self.itemLabels['map'] = self.itemLabels['map'][0:1]
1676  self.itemLabels['map'].append("raster: " + rasterName)
1677  if vectorId:
1678  for map in self.instruction[vectorId]['list']:
1679  self.itemLabels['map'].append('vector: ' + map[0].split('@')[0])
1680 
1681  def OnSize(self, event):
1682  """!Init image size to match window size
1683  """
1684  # not zoom all when notebook page is changed
1685  if self.preview and self.parent.currentPage == 1 or not self.preview and self.parent.currentPage == 0:
1686  self.ZoomAll()
1687  self.OnIdle(None)
1688  event.Skip()
1689 
1690  def OnIdle(self, event):
1691  """!Only re-render a image during idle time instead of
1692  multiple times during resizing.
1693  """
1694 
1695  width, height = self.GetClientSize()
1696  # Make new off screen bitmap: this bitmap will always have the
1697  # current drawing in it, so it can be used to save the image
1698  # to a file, or whatever.
1699  self._buffer = wx.EmptyBitmap(width, height)
1700  # re-render image on idle
1701  self.resize = True
1702 
1703  def ScaleRect(self, rect, scale):
1704  """! Scale rectangle"""
1705  return wx.Rect(rect.GetLeft()*scale, rect.GetTop()*scale,
1706  rect.GetSize()[0]*scale, rect.GetSize()[1]*scale)
1707 
1708 def main():
1709  app = wx.PySimpleApp()
1710  wx.InitAllImageHandlers()
1711  frame = PsMapFrame()
1712  frame.Show()
1713 
1714  app.MainLoop()
1715 
1716 if __name__ == "__main__":
1717  main()