psmap.py 70 KB

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