psmap.py 75 KB

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