psmap.py 75 KB

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