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.
14 @author Anna Kratochvilova <anna.kratochvilova fsv.cvut.cz> (bachelor's project)
15 @author Martin Landa <landa.martin gmail.com> (mentor)
27 from math
import sin, cos, pi
30 if int(grass.version()[
'version'].
split(
'.')[0]) > 6:
31 sys.path.append(os.path.join(os.getenv(
'GISBASE'),
'etc',
'gui',
'wxpython',
34 sys.path.append(os.path.join(os.getenv(
'GISBASE'),
'etc',
'wxpython',
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 *
49 import wx.lib.agw.flatnotebook
as fnb
51 import wx.lib.flatnotebook
as fnb
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
58 @param parent parent window
60 @param title window title
62 @param kwargs wx.Frames' arguments
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))
73 self.
toolbar = PsMapToolbar(parent = self)
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)
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)
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
140 self.canvas.SetCursor(self.
cursors[
"default"])
150 pen = self.
pen, brush = self.
brush, preview =
True)
153 grass.use_temp_region()
162 self.SetMinSize(wx.Size(750, 600))
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)
172 def _showErrMsg(self):
173 """!Show error message (missing preview)
175 GError(parent = self,
176 message = _(
"Python Imaging Library is not available.\n"
177 "'Preview' functionality won't work."),
178 showTraceback =
False)
183 mainSizer = wx.BoxSizer(wx.VERTICAL)
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)
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)
193 self.book.AddPage(self.
canvas,
"Draft mode")
195 self.book.SetSelection(0)
197 mainSizer.Add(self.
book,1, wx.EXPAND)
199 self.SetSizer(mainSizer)
204 """!Creates mapping instructions"""
209 """!Generate PostScript"""
210 filename = self.getFile(wildcard =
"PostScript (*.ps)|*.ps|Encapsulated PostScript (*.eps)|*.eps")
215 """!Launch ps.map dialog
217 GUI(parent = self).ParseCommand(cmd = [
'ps.map'])
220 """!Generate PDF from PS with ps2pdf if available"""
222 p = grass.Popen([
"ps2pdf"], stderr = grass.PIPE)
226 GMessage(parent = self,
227 message = _(
"Program ps2pdf is not available. Please install it first to create PDF."))
230 filename = self.getFile(wildcard =
"PDF (*.pdf)|*.pdf")
232 self.
PSFile(filename, pdf =
True)
235 """!Run ps.map and show result"""
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')
247 regOld = grass.region()
254 if not filename
or (filename
and pdf):
256 filename = grass.tempfile()
258 if self.instruction.FindInstructionByType(
'map'):
259 mapId = self.instruction.FindInstructionByType(
'map').id
263 cmd = [
'ps.map',
'--overwrite']
264 if os.path.splitext(filename)[1] ==
'.eps':
268 cmd.append(
'input=%s' % instrFile)
269 cmd.append(
'output=%s' % filename)
271 self.SetStatusText(_(
'Generating PDF...'), 0)
273 self.SetStatusText(_(
'Generating PostScript...'), 0)
275 self.SetStatusText(_(
'Generating preview...'), 0)
277 self.cmdThread.RunCmd(cmd, userData = {
'instrFile' : instrFile,
'filename' : filename,
278 'pdfname' : pdfname,
'temp' : temp,
'regionOld' : regOld})
281 """!ps.map process finished"""
283 if event.returncode != 0:
284 GMessage(parent = self,
285 message = _(
"Ps.map exited with return code %s") % event.returncode)
287 grass.try_remove(event.userData[
'instrFile'])
288 if event.userData[
'temp']:
289 grass.try_remove(event.userData[
'filename'])
292 if event.userData[
'pdfname']:
294 proc = grass.Popen([
'ps2pdf',
'-dPDFSETTINGS=/prepress',
'-r1200',
295 event.userData[
'filename'], event.userData[
'pdfname']])
299 GMessage(parent = self,
300 message = _(
"ps2pdf exited with return code %s") % ret)
303 GError(parent = self,
304 message = _(
"Program ps2pdf is not available. Please install it to create PDF.\n\n %s") % e)
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'])
313 im = Image.open(event.userData[
'filename'])
314 if self.instruction[self.pageId][
'Orientation'] ==
'Landscape':
317 im.save(self.imgName, format =
'PNG')
320 GError(parent = self,
321 message = _(
"Unable to generate preview. %s") % e)
325 rect = self.previewCanvas.ImageRect()
326 self.previewCanvas.image = wx.Image(self.imgName, wx.BITMAP_TYPE_PNG)
327 self.previewCanvas.DrawImage(rect = rect)
330 self.SetStatusText(_(
'Preview generated'), 0)
331 self.book.SetSelection(1)
334 grass.try_remove(event.userData[
'instrFile'])
335 if event.userData[
'temp']:
336 grass.try_remove(event.userData[
'filename'])
340 for filter
in wildcard.split(
'|')[1::2]:
341 s = filter.strip(
'*').
split(
'.')[1]
345 raster = self.instruction.FindInstructionByType(
'raster')
352 if rasterId
and self.instruction[rasterId][
'raster']:
353 mapName = self.instruction[rasterId][
'raster'].
split(
'@')[0] + suffix[0]
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
373 filename = self.getFile(wildcard =
"*.psmap|*.psmap|Text file(*.txt)|*.txt|All files(*.*)|*.*")
375 instrFile = open(filename,
"w")
376 instrFile.write(self.InstructionFile())
380 """!Load file and read instructions"""
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()
393 readInstruction =
Instruction(parent = self, objectsToDraw = readObjectId)
394 ok = readInstruction.Read(filename)
396 GMessage(_(
"Failed to read file %s.") % filename)
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
404 self.canvas.SetPage()
407 self.DialogDataChanged(self.objectId)
410 """!Specify paper size, margins and orientation"""
411 id = self.instruction.FindInstructionByType(
'page').id
414 val = dlg.ShowModal()
416 self.canvas.SetPage()
418 self.canvas.RecalculatePosition(ids = self.objectId)
422 self.toolbar.OnTool(event)
423 self.mouse[
"use"] =
"pointer"
424 self.canvas.SetCursor(self.cursors[
"default"])
425 self.previewCanvas.SetCursor(self.cursors[
"default"])
428 self.toolbar.OnTool(event)
429 self.mouse[
"use"] =
"pan"
430 self.canvas.SetCursor(self.cursors[
"hand"])
431 self.previewCanvas.SetCursor(self.cursors[
"hand"])
434 self.toolbar.OnTool(event)
435 self.mouse[
"use"] =
"zoomin"
436 self.canvas.SetCursor(self.cursors[
"cross"])
437 self.previewCanvas.SetCursor(self.cursors[
"cross"])
440 self.toolbar.OnTool(event)
441 self.mouse[
"use"] =
"zoomout"
442 self.canvas.SetCursor(self.cursors[
"cross"])
443 self.previewCanvas.SetCursor(self.cursors[
"cross"])
446 self.mouseOld = self.mouse[
'use']
447 if self.currentPage == 0:
448 self.cursorOld = self.canvas.GetCursor()
450 self.cursorOld = self.previewCanvas.GetCursor()
451 self.previewCanvas.GetCursor()
452 self.mouse[
"use"] =
"zoomin"
453 if self.currentPage == 0:
454 self.canvas.ZoomAll()
456 self.previewCanvas.ZoomAll()
457 self.mouse[
"use"] = self.mouseOld
458 if self.currentPage == 0:
459 self.canvas.SetCursor(self.cursorOld)
461 self.previewCanvas.SetCursor(self.cursorOld)
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)
473 if self.instruction.FindInstructionByType(
'map'):
474 mapId = self.instruction.FindInstructionByType(
'map').id
476 id = [mapId,
None,
None]
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
491 self.toolbar.ToggleTool(self.actionOld,
True)
492 self.toolbar.ToggleTool(self.toolbar.action[
'id'],
False)
493 self.toolbar.action[
'id'] = self.actionOld
495 self.canvas.SetCursor(self.cursorOld)
496 except AttributeError:
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)
511 if 'mapNotebook' not in self.openDialogs:
512 dlg =
MapDialog(parent = self, id = id, settings = self.instruction,
514 self.openDialogs[
'mapNotebook'] = dlg
515 self.openDialogs[
'mapNotebook'].Show()
517 if 'mapNotebook' in self.openDialogs:
518 self.openDialogs[
'mapNotebook'].notebook.ChangeSelection(0)
520 if 'map' not in self.openDialogs:
521 dlg =
MapDialog(parent = self, id = id, settings = self.instruction,
523 self.openDialogs[
'map'] = dlg
524 self.openDialogs[
'map'].Show()
528 self.mouse[
"use"] =
"addMap"
529 self.canvas.SetCursor(self.cursors[
"cross"])
530 if self.currentPage == 1:
531 self.book.SetSelection(0)
535 """!Add raster map"""
536 if self.instruction.FindInstructionByType(
'raster'):
537 id = self.instruction.FindInstructionByType(
'raster').id
539 if self.instruction.FindInstructionByType(
'map'):
540 mapId = self.instruction.FindInstructionByType(
'map').id
545 GMessage(message = _(
"Please, create map frame first."))
550 if 'mapNotebook' in self.openDialogs:
551 self.openDialogs[
'mapNotebook'].notebook.ChangeSelection(1)
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()
559 """!Add vector map"""
560 if self.instruction.FindInstructionByType(
'vector'):
561 id = self.instruction.FindInstructionByType(
'vector').id
563 if self.instruction.FindInstructionByType(
'map'):
564 mapId = self.instruction.FindInstructionByType(
'map').id
568 GMessage(message = _(
"Please, create map frame first."))
573 if 'mapNotebook' in self.openDialogs:
574 self.openDialogs[
'mapNotebook'].notebook.ChangeSelection(2)
576 if 'vector' not in self.openDialogs:
578 self.openDialogs[
'vector'] = dlg
579 self.openDialogs[
'vector'].Show()
582 """!Decorations overlay menu
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)
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)
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)
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)
607 self.PopupMenu(decmenu)
613 GMessage(message = _(
"Scalebar is not appropriate for this projection"))
615 if self.instruction.FindInstructionByType(
'scalebar'):
616 id = self.instruction.FindInstructionByType(
'scalebar').id
619 if 'scalebar' not in self.openDialogs:
621 self.openDialogs[
'scalebar'] = dlg
622 self.openDialogs[
'scalebar'].Show()
625 """!Add raster or vector legend"""
626 if self.instruction.FindInstructionByType(
'rasterLegend'):
627 idR = self.instruction.FindInstructionByType(
'rasterLegend').id
629 if self.instruction.FindInstructionByType(
'vectorLegend'):
630 idV = self.instruction.FindInstructionByType(
'vectorLegend').id
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()
641 if self.instruction.FindInstructionByType(
'mapinfo'):
642 id = self.instruction.FindInstructionByType(
'mapinfo').id
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()
651 """!Show dialog for text adding and editing"""
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
660 dlg.SetPosition(position)
664 """!computes bounding box of rotated text, not very precisely"""
666 rotation = float(rotation)/180*pi
667 H = float(w) * sin(rotation)
668 W = float(w) * cos(rotation)
670 if pi/2 < rotation <= 3*pi/2:
672 if 0 < rotation < pi:
675 return wx.Rect(x,y, *textExtent)
677 return wx.Rect(X, Y, abs(W), abs(H)).Inflate(h,h)
680 """!creates a wx.Font object from selected postscript font. To be
681 used for estimating bounding rectangle of text"""
683 fontsize = textDict[
'fontsize'] * self.canvas.currScale
684 fontface = textDict[
'font'].
split(
'-')[0]
686 fontstyle = textDict[
'font'].
split(
'-')[1]
690 if fontface ==
"Times":
691 family = wx.FONTFAMILY_ROMAN
693 elif fontface ==
"Helvetica":
694 family = wx.FONTFAMILY_SWISS
696 elif fontface ==
"Courier":
697 family = wx.FONTFAMILY_TELETYPE
700 family = wx.FONTFAMILY_DEFAULT
703 style = wx.FONTSTYLE_NORMAL
704 weight = wx.FONTWEIGHT_NORMAL
706 if 'Oblique' in fontstyle:
707 style = wx.FONTSTYLE_SLANT
709 if 'Italic' in fontstyle:
710 style = wx.FONTSTYLE_ITALIC
712 if 'Bold' in fontstyle:
713 weight = wx.FONTWEIGHT_BOLD
716 fn = wx.Font(pointSize = fontsize, family = family, style = style,
717 weight = weight, face = face)
719 fn = wx.Font(pointSize = fontsize, family = wx.FONTFAMILY_DEFAULT,
720 style = wx.FONTSTYLE_NORMAL, weight = wx.FONTWEIGHT_NORMAL)
726 """!Estimates bounding rectangle of text"""
728 dc = wx.ClientDC(self)
730 fn = self.makePSFont(textDict)
734 w,h,lh = dc.GetMultiLineTextExtent(textDict[
'text'])
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())
747 page = self.instruction.FindInstructionByType(
'page')
748 mapInitRect =
GetMapBounds(instrFile, portrait = (page[
'Orientation'] ==
'Portrait'))
749 grass.try_remove(instrFile)
751 region = grass.region()
753 realWidth = units.convert(value = abs(region[
'w'] - region[
'e']), fromUnit =
'meter', toUnit =
'inch')
754 scale = mapInitRect.Get()[2]/realWidth
756 initMap = self.instruction.FindInstructionByType(
'initMap')
766 self.instruction.AddInstruction(initMap)
767 self.instruction[id].SetInstruction(dict(rect = mapInitRect, scale = scale))
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)
774 self.canvas.RecalculateEN()
776 self.deleteObject(self.canvas.dragId)
779 """!Deletes object, his id and redraws"""
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()
788 del self.instruction[id]
795 itype = self.instruction[id].type
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)
805 if self.instruction[id][
'rotate']:
806 rot = float(self.instruction[id][
'rotate'])
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])
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)
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)
835 if itype
in (
'map',
'vector',
'raster'):
837 if itype ==
'raster':
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'])
843 if 'rasterLegend' in self.openDialogs:
845 id = self.instruction.FindInstructionByType(
'map').id
848 if itype ==
'raster':
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()
857 self.canvas.Draw(pen = self.pen[
'map'], brush = self.brush[
'map'],
858 pdc = self.canvas.pdcObj, drawid = id, pdctype =
'rectText', bb = rectCanvas)
860 self.canvas.RedrawSelectBox(id)
861 self.canvas.pdcTmp.RemoveId(self.canvas.idZoomBoxTmp)
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)
872 self.deleteObject(id)
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)
884 self.deleteObject(id)
887 """!Flatnotebook page has changed"""
888 self.currentPage = self.book.GetPageIndex(self.book.GetCurrentPage())
892 """!Flatnotebook page is changing"""
893 if self.currentPage == 0
and self.mouse[
'use'] ==
'addMap':
898 if self.parent
and self.parent.GetName() ==
'LayerManager':
899 log = self.parent.GetLogWindow()
900 log.RunCmd([
'g.manual',
901 'entry=wxGUI.PsMap'])
905 entry =
'wxGUI.PsMap')
908 """!Display About window"""
909 info = wx.AboutDialogInfo()
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)))
923 os.remove(self.imgName)
926 grass.set_raise_on_error(
False)
932 """!A buffered window class.
934 @param parent parent window
935 @param kwargs other wx.Window parameters
937 def __init__(self, parent, id = wx.ID_ANY,
938 style = wx.NO_FULL_REPAINT_ON_RESIZE,
940 wx.Window.__init__(self, parent, id = id, style = style)
956 if kwargs.has_key(
'instruction'):
958 if kwargs.has_key(
'openDialogs'):
960 if kwargs.has_key(
'pageId'):
962 if kwargs.has_key(
'objectId'):
968 'rasterLegend': [
'RASTER LEGEND'],
969 'vectorLegend': [
'VECTOR LEGEND'],
970 'mapinfo': [
'MAP INFO'],
971 'scalebar': [
'SCALE BAR']}
979 dc = wx.ClientDC(self)
982 self.SetClientSize((700,510))
983 self.
_buffer = wx.EmptyBitmap(*self.GetClientSize())
1004 self.Bind(wx.EVT_ERASE_BACKGROUND,
lambda x:
None)
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)
1013 """!Clear canvas and set paper
1015 bg = wx.LIGHT_GREY_BRUSH
1016 self.pdcPaper.BeginDrawing()
1017 self.pdcPaper.SetBackground(bg)
1018 self.pdcPaper.Clear()
1019 self.pdcPaper.EndDrawing()
1021 self.pdcObj.RemoveAll()
1022 self.pdcTmp.RemoveAll()
1031 """!Converts canvas (pixel) -> paper (inch) coordinates and size and vice versa"""
1037 pRect = self.pdcPaper.GetIdBounds(self.
pageId)
1038 pRectx, pRecty = pRect.x, pRect.y
1040 if not canvasToPaper:
1044 pRectx = units.convert(value = - pRect.x, fromUnit =
'pixel', toUnit =
'inch' ) /scale
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
1051 return wx.Rect2D(X, Y, Width, Height)
1056 """!Sets and changes page, redraws paper"""
1061 self.instruction.AddInstruction(page)
1063 ppi = wx.ClientDC(self).GetPPI()
1064 cW, cH = self.GetClientSize()
1065 pW, pH = page[
'Width']*ppi[0], page[
'Height']*ppi[1]
1074 self.DrawPaper(wx.Rect(x, y, pW, pH))
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()))
1088 """!Recalculate east and north for texts (eps, points) after their or map's movement"""
1090 mapId = self.instruction.FindInstructionByType(
'map').id
1091 except AttributeError:
1092 mapId = self.instruction.FindInstructionByType(
'initMap').id
1094 texts = self.instruction.FindInstructionByType(
'text', list =
True)
1097 y = self.
instruction[text.id][
'where'][1], paperToMap =
True)
1101 """!Draw pseudo DC to buffer
1105 dc = wx.BufferedPaintDC(self, self.
_buffer)
1109 dc.SetBackground(wx.LIGHT_GREY_BRUSH)
1114 self.pdcPaper.DrawToDC(dc)
1117 rgn = self.GetUpdateRegion()
1120 self.pdcObj.DrawToDCClipped(dc, rgn.GetBox())
1122 self.pdcImage.DrawToDCClipped(dc, rgn.GetBox())
1123 self.pdcTmp.DrawToDCClipped(dc, rgn.GetBox())
1127 if event.GetWheelRotation():
1128 zoom = event.GetWheelRotation()
1129 use = self.
mouse[
'use']
1130 self.
mouse[
'begin'] = event.GetPosition()
1132 self.
mouse[
'use'] =
'zoomin'
1134 self.
mouse[
'use'] =
'zoomout'
1136 zoomFactor, view = self.
ComputeZoom(wx.Rect(0,0,0,0))
1137 self.
Zoom(zoomFactor, view)
1138 self.
mouse[
'use'] = use
1141 if self.
mouse[
'use']
in (
'pointer',
'resize'):
1142 pos = event.GetPosition()
1143 foundResize = self.pdcTmp.FindObjects(pos[0], pos[1])
1145 self.SetCursor(self.
cursors[
"sizenwse"])
1146 self.parent.SetStatusText(_(
'Click and drag to resize object'), 0)
1148 self.parent.SetStatusText(
'', 0)
1149 self.SetCursor(self.
cursors[
"default"])
1151 elif event.LeftDown():
1152 self.
mouse[
'begin'] = event.GetPosition()
1154 if self.
mouse[
'use']
in (
'pan',
'zoomin',
'zoomout',
'addMap'):
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])
1163 self.
mouse[
'use'] =
'resize'
1175 self.RedrawSelectBox(self.
dragId)
1182 self.pdcTmp.RemoveId(self.
idBoxTmp)
1187 elif event.Dragging()
and event.LeftIsDown():
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],
1195 pdctype =
'rect', bb = r)
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]
1202 self.
Zoom(zoomFactor, view)
1203 self.
mouse[
'begin'] = event.GetPosition()
1206 if self.
mouse[
'use'] ==
'pointer' and self.
dragId != -1:
1208 self.
mouse[
'end'] = event.GetPosition()
1210 self.pdcObj.TranslateId(self.
dragId, dx, dy)
1211 self.pdcTmp.TranslateId(self.
idBoxTmp, dx, dy)
1216 self.
begin = event.GetPosition()
1220 if self.
mouse[
'use'] ==
'resize':
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]
1230 newWidth = width + diffX
1231 newHeight = height + diffX * (float(height) / width)
1233 newWidth = width + diffY * (float(width) / height)
1234 newHeight = height + diffY
1236 newWidth = width + diffX
1237 newHeight = height + diffY
1239 if newWidth < 10
or newHeight < 10:
1242 bounds = wx.Rect(x, y, newWidth, newHeight)
1244 pdctype =
'rectText', bb = bounds)
1245 self.RedrawSelectBox(self.
dragId)
1247 elif event.LeftUp():
1249 if self.
mouse[
'use']
in (
'zoomin',
'zoomout'):
1254 self.
Zoom(zoomFactor, view)
1258 if self.
mouse[
'use'] ==
'addMap':
1261 if rectTmp.GetWidth() < 20
or rectTmp.GetHeight() < 20:
1273 self.
mouse[
'use'] = self.parent.mouseOld
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
1283 if self.
mouse[
'use'] ==
'resize':
1284 mapId = self.instruction.FindInstructionByType(
'map').id
1288 newRectCanvas = self.pdcObj.GetIdBounds(mapId)
1292 if self.
instruction[mapId][
'scaleType']
in (0, 1, 2):
1295 scale, foo, rect =
AutoAdjust(self, scaleType = 0,
1301 scale, foo, rect =
AutoAdjust(self, scaleType = 1,
1305 scale, foo, rect =
AutoAdjust(self, scaleType = 2,
1311 self.
Draw(pen = self.
pen[
'map'], brush = self.
brush[
'map'],
1312 pdc = self.
pdcObj, drawid = mapId, pdctype =
'rectText', bb = rectCanvas)
1321 self.RedrawSelectBox(mapId)
1322 self.
Zoom(zoomFactor = 1, view = (0, 0))
1323 self.
mouse[
'use'] =
'pointer'
1326 if self.
mouse[
'use']
in (
'pointer',
'resize')
and self.
dragId != -1:
1327 if self.
mouse[
'begin'] != event.GetPosition():
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)}
1345 itemCall[type](**itemArg[type])
1355 canvasToPaper =
True)
1358 elif itype
in (
'mapinfo' ,
'rasterLegend',
'vectorLegend'):
1360 canvasToPaper =
True)
1362 canvasToPaper =
True)[:2]
1363 elif itype ==
'scalebar':
1365 canvasToPaper =
True)
1370 elif itype ==
'text':
1373 extent = self.parent.getTextExtent(textDict = self.
instruction[id])
1375 rot = float(self.
instruction[id][
'rotate'])/180*pi
1384 x += extent[0] * cos(rot)
1385 y -= extent[0] * sin(rot)
1387 x += extent[0]/2 * cos(rot)
1388 y -= extent[0]/2 * sin(rot)
1391 canvasToPaper =
True)[:2]
1395 """!Computes zoom factor and scroll view"""
1397 cW, cH = self.GetClientSize()
1401 if self.
mouse[
'use'] ==
'zoomout':
1402 zoomFactor = 1./zoomFactor
1403 x,y = self.
mouse[
'begin']
1404 xView = x - x/zoomFactor
1405 yView = y - y/zoomFactor
1408 rW, rH = float(rect.GetWidth()), float(rect.GetHeight())
1410 zoomFactor = 1/
max(rW/cW, rH/cH)
1411 except ZeroDivisionError:
1414 if abs(zoomFactor - 1) > 0.01:
1415 zoomFactor = zoomFactor
1420 if self.
mouse[
'use'] ==
'zoomout':
1421 zoomFactor =
min(rW/cW, rH/cH)
1424 yView = rect.GetY() - (rW*(cH/cW) - rH)/2
1427 if self.
mouse[
'use'] ==
'zoomout':
1428 x,y = rect.GetX() + (rW-(cW/cH)*rH)/2, rect.GetY()
1429 xView, yView = -x, -y
1431 xView = rect.GetX() - (rH*(cW/cH) - rW)/2
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()
1439 return zoomFactor, (int(xView), int(yView))
1443 """! Zoom to specified region, scroll view, redraw"""
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)
1461 rect = self.
instruction[id][
'rect'], canvasToPaper =
False)
1466 self.
instruction[id][
'coords'] = coords = [(int(coord) - view[i]) * zoomFactor
1467 for i, coord
in enumerate(coords)]
1469 coords = coords, bounds = oRect )
1470 extent = self.parent.getTextExtent(textDict = self.
instruction[id])
1476 self.
instruction[id][
'rect'] = bounds = self.parent.getModifiedTextBounds(coords[0], coords[1], extent, rot)
1477 self.pdcObj.SetIdBounds(id, bounds)
1480 drawid = id, pdctype =
'rectText', bb = oRect)
1483 self.RedrawSelectBox(self.
dragId)
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)
1493 """! Zoom to full extent"""
1495 bounds = self.pdcPaper.GetIdBounds(self.
pageId)
1497 bounds = self.pdcImage.GetIdBounds(self.
imageId)
1498 zoomP = bounds.Inflate(bounds.width/20, bounds.height/20)
1500 self.
Zoom(zoomFactor, view)
1502 def Draw(self, pen, brush, pdc, drawid = None, pdctype = 'rect', bb = wx.Rect(0,0,0,0)):
1508 pdc.RemoveId(drawid)
1512 if pdctype
in (
'rect',
'rectText'):
1513 pdc.DrawRectangle(*bb)
1514 if pdctype ==
'rectText':
1515 dc = wx.ClientDC(self)
1518 font.SetPointSize(size)
1519 font.SetStyle(wx.ITALIC)
1523 w,h,lh = dc.GetMultiLineTextExtent(text)
1525 textRect = wx.Rect(0, 0, *textExtent).CenterIn(bb)
1527 while not wx.Rect(*r).ContainsRect(textRect)
and size >= 8:
1529 font.SetPointSize(size)
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)
1538 pdc.SetIdBounds(drawid, bb)
1545 if textDict[
'rotate']:
1546 rot = float(textDict[
'rotate'])
1550 fontsize = textDict[
'fontsize'] * self.
currScale
1551 if textDict[
'background'] !=
'none':
1552 background = textDict[
'background']
1556 pdc.RemoveId(drawId)
1569 pdc.SetTextBackground(
convertRGB(background))
1570 pdc.SetBackgroundMode(wx.SOLID)
1572 pdc.SetBackgroundMode(wx.TRANSPARENT)
1574 fn = self.parent.makePSFont(textDict)
1577 pdc.SetTextForeground(
convertRGB(textDict[
'color']))
1578 pdc.DrawRotatedText(textDict[
'text'], coords[0], coords[1], rot)
1580 pdc.SetIdBounds(drawId, wx.Rect(*bounds))
1585 """!Draw preview image to pseudoDC"""
1586 self.pdcImage.ClearId(self.imageId)
1587 self.pdcImage.SetId(self.imageId)
1591 if img.GetWidth() != rect.width
or img.GetHeight() != rect.height:
1592 img = img.Scale(rect.width, rect.height)
1593 bitmap = img.ConvertToBitmap()
1595 self.pdcImage.BeginDrawing()
1596 self.pdcImage.DrawBitmap(bitmap, rect.x, rect.y)
1597 self.pdcImage.SetIdBounds(self.imageId, rect)
1598 self.pdcImage.EndDrawing()
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()
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)
1617 self.pdcPaper.SetPen(self.pen[
'margins'])
1618 self.pdcPaper.SetBrush(self.brush[
'margins'])
1619 self.pdcPaper.DrawRectangle(x, y, w, h)
1621 self.pdcPaper.SetIdBounds(self.pageId, rect)
1622 self.pdcPaper.EndDrawing()
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()
1632 self.currScale =
min(float(cW)/iW, float(cH)/iH)
1633 iW = iW * self.currScale
1634 iH = iH * self.currScale
1637 imageRect = wx.Rect(x, y, iW, iH)
1642 """!Redraws select box when selected object changes its size"""
1643 if self.dragId == id:
1644 rect = [self.pdcObj.GetIdBounds(id).Inflate(3,3)]
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)
1657 """!Updates map frame label"""
1659 vector = self.instruction.FindInstructionByType(
'vector')
1661 vectorId = vector.id
1665 raster = self.instruction.FindInstructionByType(
'raster')
1667 rasterId = raster.id
1673 rasterName = self.instruction[rasterId][
'raster'].
split(
'@')[0]
1675 self.itemLabels[
'map'] = self.itemLabels[
'map'][0:1]
1676 self.itemLabels[
'map'].append(
"raster: " + rasterName)
1678 for map
in self.instruction[vectorId][
'list']:
1679 self.itemLabels[
'map'].append(
'vector: ' + map[0].
split(
'@')[0])
1682 """!Init image size to match window size
1685 if self.preview
and self.parent.currentPage == 1
or not self.preview
and self.parent.currentPage == 0:
1691 """!Only re-render a image during idle time instead of
1692 multiple times during resizing.
1695 width, height = self.GetClientSize()
1699 self._buffer = wx.EmptyBitmap(width, height)
1704 """! Scale rectangle"""
1705 return wx.Rect(rect.GetLeft()*scale, rect.GetTop()*scale,
1706 rect.GetSize()[0]*scale, rect.GetSize()[1]*scale)
1709 app = wx.PySimpleApp()
1710 wx.InitAllImageHandlers()
1716 if __name__ ==
"__main__":