1""" 2@package psmap.frame 3 4@brief GUI for ps.map 5 6Classes: 7 - frame::PsMapFrame 8 - frame::PsMapBufferedWindow 9 10(C) 2011-2012 by Anna Kratochvilova, and the GRASS Development Team 11This program is free software under the GNU General Public License 12(>=v2). Read the file COPYING that comes with GRASS for details. 13 14@author Anna Kratochvilova <kratochanna gmail.com> (bachelor's project) 15@author Martin Landa <landa.martin gmail.com> (mentor) 16""" 17 18import os 19import sys 20if sys.version_info.major == 2: 21 import Queue 22else: 23 import queue as Queue 24from math import sin, cos, pi, sqrt 25 26import wx 27 28try: 29 import wx.lib.agw.flatnotebook as fnb 30except ImportError: 31 import wx.lib.flatnotebook as fnb 32 33import grass.script as grass 34 35from core import globalvar 36from gui_core.menu import Menu 37from core.gconsole import CmdThread, EVT_CMD_DONE 38from psmap.toolbars import PsMapToolbar 39from core.gcmd import RunCommand, GError, GMessage 40from core.settings import UserSettings 41from core.utils import PilImageToWxImage 42from gui_core.forms import GUI 43from gui_core.dialogs import HyperlinkDialog 44from gui_core.ghelp import ShowAboutDialog 45from gui_core.wrap import ClientDC, PseudoDC, Rect, StockCursor, EmptyBitmap 46from psmap.menudata import PsMapMenuData 47from gui_core.toolbars import ToolSwitcher 48 49from psmap.dialogs import * 50from psmap.instructions import * 51from psmap.utils import * 52 53 54class PsMapFrame(wx.Frame): 55 56 def __init__(self, parent=None, id=wx.ID_ANY, 57 title=_("GRASS GIS Cartographic Composer"), **kwargs): 58 """Main window of ps.map GUI 59 60 :param parent: parent window 61 :param id: window id 62 :param title: window title 63 64 :param kwargs: wx.Frames' arguments 65 """ 66 self.parent = parent 67 68 wx.Frame.__init__( 69 self, 70 parent=parent, 71 id=id, 72 title=title, 73 name="PsMap", 74 **kwargs) 75 self.SetIcon( 76 wx.Icon( 77 os.path.join( 78 globalvar.ICONDIR, 79 'grass.ico'), 80 wx.BITMAP_TYPE_ICO)) 81 # menubar 82 self.menubar = Menu( 83 parent=self, 84 model=PsMapMenuData().GetModel( 85 separators=True)) 86 self.SetMenuBar(self.menubar) 87 # toolbar 88 89 self._toolSwitcher = ToolSwitcher() 90 self.toolbar = PsMapToolbar( 91 parent=self, toolSwitcher=self._toolSwitcher) 92 # workaround for http://trac.wxwidgets.org/ticket/13888 93 if sys.platform != 'darwin': 94 self.SetToolBar(self.toolbar) 95 96 self.iconsize = (16, 16) 97 # satusbar 98 self.statusbar = self.CreateStatusBar(number=1) 99 100 # mouse attributes -- position on the screen, begin and end of 101 # dragging, and type of drawing 102 self.mouse = { 103 'begin': [0, 0], # screen coordinates 104 'end': [0, 0], 105 'use': "pointer", 106 } 107 # available cursors 108 self.cursors = { 109 "default": StockCursor(wx.CURSOR_ARROW), 110 "cross": StockCursor(wx.CURSOR_CROSS), 111 "hand": StockCursor(wx.CURSOR_HAND), 112 "sizenwse": StockCursor(wx.CURSOR_SIZENWSE) 113 } 114 # pen and brush 115 self.pen = { 116 'paper': wx.Pen(colour="BLACK", width=1), 117 'margins': wx.Pen(colour="GREY", width=1), 118 'map': wx.Pen(colour=wx.Colour(86, 122, 17), width=2), 119 'rasterLegend': wx.Pen(colour=wx.Colour(219, 216, 4), width=2), 120 'vectorLegend': wx.Pen(colour=wx.Colour(219, 216, 4), width=2), 121 'mapinfo': wx.Pen(colour=wx.Colour(5, 184, 249), width=2), 122 'scalebar': wx.Pen(colour=wx.Colour(150, 150, 150), width=2), 123 'image': wx.Pen(colour=wx.Colour(255, 150, 50), width=2), 124 'northArrow': wx.Pen(colour=wx.Colour(200, 200, 200), width=2), 125 'point': wx.Pen(colour=wx.Colour(100, 100, 100), width=2), 126 'line': wx.Pen(colour=wx.Colour(0, 0, 0), width=2), 127 'box': wx.Pen(colour='RED', width=2, style=wx.SHORT_DASH), 128 'select': wx.Pen(colour='BLACK', width=1, style=wx.SHORT_DASH), 129 'resize': wx.Pen(colour='BLACK', width=1) 130 } 131 self.brush = { 132 'paper': wx.WHITE_BRUSH, 133 'margins': wx.TRANSPARENT_BRUSH, 134 'map': wx.Brush(wx.Colour(151, 214, 90)), 135 'rasterLegend': wx.Brush(wx.Colour(250, 247, 112)), 136 'vectorLegend': wx.Brush(wx.Colour(250, 247, 112)), 137 'mapinfo': wx.Brush(wx.Colour(127, 222, 252)), 138 'scalebar': wx.Brush(wx.Colour(200, 200, 200)), 139 'image': wx.Brush(wx.Colour(255, 200, 50)), 140 'northArrow': wx.Brush(wx.Colour(255, 255, 255)), 141 'point': wx.Brush(wx.Colour(200, 200, 200)), 142 'line': wx.TRANSPARENT_BRUSH, 143 'box': wx.TRANSPARENT_BRUSH, 144 'select': wx.TRANSPARENT_BRUSH, 145 'resize': wx.BLACK_BRUSH 146 } 147 148 # list of objects to draw 149 self.objectId = [] 150 151 # we need isolated environment to handle region 152 self.env = os.environ.copy() 153 154 # instructions 155 self.instruction = Instruction( 156 parent=self, objectsToDraw=self.objectId, env=self.env) 157 # open dialogs 158 self.openDialogs = dict() 159 160 self.pageId = NewId() 161 # current page of flatnotebook 162 self.currentPage = 0 163 # canvas for draft mode 164 self.canvas = PsMapBufferedWindow( 165 parent=self, 166 mouse=self.mouse, 167 pen=self.pen, 168 brush=self.brush, 169 cursors=self.cursors, 170 instruction=self.instruction, 171 openDialogs=self.openDialogs, 172 pageId=self.pageId, 173 objectId=self.objectId, 174 preview=False, 175 env=self.env) 176 177 self.canvas.SetCursor(self.cursors["default"]) 178 self.getInitMap() 179 180 # image path 181 env = grass.gisenv() 182 self.imgName = grass.tempfile() 183 184 # canvas for preview 185 self.previewCanvas = PsMapBufferedWindow( 186 parent=self, 187 mouse=self.mouse, 188 cursors=self.cursors, 189 pen=self.pen, 190 brush=self.brush, 191 preview=True, 192 env=self.env) 193 194 self.toolbar.SelectDefault() 195 196 # create queues 197 self.requestQ = Queue.Queue() 198 self.resultQ = Queue.Queue() 199 # thread 200 self.cmdThread = CmdThread(self, self.requestQ, self.resultQ) 201 202 self._layout() 203 self.SetMinSize(wx.Size(775, 600)) 204 # workaround for http://trac.wxwidgets.org/ticket/13628 205 self.SetSize(self.GetBestSize()) 206 207 self.Bind(fnb.EVT_FLATNOTEBOOK_PAGE_CHANGED, self.OnPageChanged) 208 self.Bind(wx.EVT_CLOSE, self.OnCloseWindow) 209 self.Bind(EVT_CMD_DONE, self.OnCmdDone) 210 211 if not havePILImage: 212 wx.CallAfter(self._showErrMsg) 213 214 def _showErrMsg(self): 215 """Show error message (missing preview) 216 """ 217 GError(parent=self, 218 message=_("Python Imaging Library is not available.\n" 219 "'Preview' functionality won't work."), 220 showTraceback=False) 221 222 def _layout(self): 223 """Do layout 224 """ 225 mainSizer = wx.BoxSizer(wx.VERTICAL) 226 if globalvar.hasAgw: 227 self.book = fnb.FlatNotebook(parent=self, 228 id=wx.ID_ANY, 229 agwStyle=globalvar.FNPageDStyle) 230 else: 231 self.book = fnb.FlatNotebook(parent=self, 232 id=wx.ID_ANY, 233 style=globalvar.FNPageDStyle) 234 235 self.book.AddPage(self.canvas, "Draft mode") 236 self.book.AddPage(self.previewCanvas, "Preview") 237 self.book.SetSelection(0) 238 239 mainSizer.Add(self.book, 1, wx.EXPAND) 240 241 self.SetSizer(mainSizer) 242 mainSizer.Fit(self) 243 244 def _checkMapFrameExists(self, type_id): 245 """Check if map frame exists 246 247 :param int type_id: type id (raster, vector,...) 248 249 :return bool: False if map frame doesn't exists 250 """ 251 if self.instruction.FindInstructionByType('map'): 252 mapId = self.instruction.FindInstructionByType('map').id 253 else: 254 mapId = None 255 256 if not type_id: 257 if not mapId: 258 GMessage(message=_("Please, create map frame first.")) 259 return False 260 return True 261 262 def _switchToPage(self, page_index=0): 263 """Switch to page (default to Draft page) 264 265 :param int page_index: page index where you want to switch 266 """ 267 self.book.SetSelection(page_index) 268 self.currentPage = page_index 269 270 def InstructionFile(self): 271 """Creates mapping instructions""" 272 273 text = str(self.instruction) 274 try: 275 text = text.encode('Latin_1') 276 except UnicodeEncodeError as err: 277 try: 278 pos = str(err).split('position')[1].split(':')[0].strip() 279 except IndexError: 280 pos = '' 281 if pos: 282 message = _("Characters on position %s are not supported " 283 "by ISO-8859-1 (Latin 1) encoding " 284 "which is required by module ps.map.") % pos 285 else: 286 message = _("Not all characters are supported " 287 "by ISO-8859-1 (Latin 1) encoding " 288 "which is required by module ps.map.") 289 GMessage(message=message) 290 return '' 291 return text 292 293 def OnPSFile(self, event): 294 """Generate PostScript""" 295 filename = self.getFile( 296 wildcard="PostScript (*.ps)|*.ps|Encapsulated PostScript (*.eps)|*.eps") 297 if filename: 298 self.PSFile(filename) 299 300 def OnPsMapDialog(self, event): 301 """Launch ps.map dialog 302 """ 303 GUI(parent=self).ParseCommand(cmd=['ps.map']) 304 305 def OnPDFFile(self, event): 306 """Generate PDF from PS with ps2pdf if available""" 307 if not sys.platform == 'win32': 308 try: 309 p = grass.Popen(["ps2pdf"], stderr=grass.PIPE) 310 p.stderr.close() 311 312 except OSError: 313 GMessage( 314 parent=self, 315 message=_( 316 "Program ps2pdf is not available. Please install it first to create PDF.")) 317 return 318 319 filename = self.getFile(wildcard="PDF (*.pdf)|*.pdf") 320 if filename: 321 self.PSFile(filename, pdf=True) 322 323 def OnPreview(self, event): 324 """Run ps.map and show result""" 325 self.PSFile() 326 327 def PSFile(self, filename=None, pdf=False): 328 """Create temporary instructions file and run ps.map with output = filename""" 329 instrFile = grass.tempfile() 330 instrFileFd = open(instrFile, mode='wb') 331 content = self.InstructionFile() 332 if not content: 333 return 334 instrFileFd.write(content) 335 instrFileFd.flush() 336 instrFileFd.close() 337 338 temp = False 339 regOld = grass.region(env=self.env) 340 341 if pdf: 342 pdfname = filename 343 else: 344 pdfname = None 345 #preview or pdf 346 if not filename or (filename and pdf): 347 temp = True 348 filename = grass.tempfile() 349 if not pdf: # lower resolution for preview 350 if self.instruction.FindInstructionByType('map'): 351 mapId = self.instruction.FindInstructionByType('map').id 352 SetResolution(dpi=100, width=self.instruction[mapId]['rect'][ 353 2], height=self.instruction[mapId]['rect'][3], 354 env=self.env) 355 356 cmd = ['ps.map', '--overwrite'] 357 if os.path.splitext(filename)[1] == '.eps': 358 cmd.append('-e') 359 if self.instruction[self.pageId]['Orientation'] == 'Landscape': 360 cmd.append('-r') 361 cmd.append('input=%s' % instrFile) 362 cmd.append('output=%s' % filename) 363 if pdf: 364 self.SetStatusText(_('Generating PDF...'), 0) 365 elif not temp: 366 self.SetStatusText(_('Generating PostScript...'), 0) 367 else: 368 self.SetStatusText(_('Generating preview...'), 0) 369 370 self.cmdThread.RunCmd( 371 cmd, 372 env=self.env, 373 userData={ 374 'instrFile': instrFile, 375 'filename': filename, 376 'pdfname': pdfname, 377 'temp': temp, 378 'regionOld': regOld}) 379 380 def OnCmdDone(self, event): 381 """ps.map process finished""" 382 383 if event.returncode != 0: 384 GMessage( 385 parent=self, 386 message=_("Ps.map exited with return code %s") % 387 event.returncode) 388 389 grass.try_remove(event.userData['instrFile']) 390 if event.userData['temp']: 391 grass.try_remove(event.userData['filename']) 392 return 393 394 if event.userData['pdfname']: 395 if sys.platform == 'win32': 396 command = ['gswin32c', 397 '-P-', '-dSAFER', 398 '-dCompatibilityLevel=1.4', 399 '-q', '-P-', 400 '-dNOPAUSE', '-dBATCH', 401 '-sDEVICE=pdfwrite', 402 '-dPDFSETTINGS=/prepress', '-r1200', 403 '-sstdout=%stderr', 404 '-sOutputFile=%s' % event.userData['pdfname'], 405 '-P-', '-dSAFER', 406 '-dCompatibilityLevel=1.4', 407 '-c', '.setpdfwrite', '-f', 408 event.userData['filename']] 409 else: 410 command = [ 411 'ps2pdf', 412 '-dPDFSETTINGS=/prepress', 413 '-r1200', 414 event.userData['filename'], 415 event.userData['pdfname']] 416 try: 417 proc = grass.Popen(command) 418 ret = proc.wait() 419 if ret > 0: 420 GMessage( 421 parent=self, 422 message=_("%(prg)s exited with return code %(code)s") % { 423 'prg': command[0], 424 'code': ret}) 425 else: 426 self.SetStatusText(_('PDF generated'), 0) 427 except OSError as e: 428 GError(parent=self, message=_( 429 "Program ps2pdf is not available. Please install it to create PDF.\n\n %s") % e) 430 431 elif not event.userData['temp']: 432 self.SetStatusText(_('PostScript file generated'), 0) 433 434 # show preview only when user doesn't want to create ps or pdf 435 if havePILImage and event.userData[ 436 'temp'] and not event.userData['pdfname']: 437 self.env['GRASS_REGION'] = grass.region_env(cols=event.userData['regionOld']['cols'], 438 rows=event.userData['regionOld']['rows'], 439 env=self.env) 440 # wx.BusyInfo does not display the message 441 busy = wx.BusyInfo( 442 _("Generating preview, wait please"), 443 parent=self) 444 wx.GetApp().Yield() 445 try: 446 im = PILImage.open(event.userData['filename']) 447 if self.instruction[self.pageId]['Orientation'] == 'Landscape': 448 import numpy as np 449 im_array = np.array(im) 450 im = PILImage.fromarray(np.rot90(im_array, 3)) 451 452 # hack for Windows, change method for loading EPS 453 if sys.platform == 'win32': 454 import types 455 im.load = types.MethodType(loadPSForWindows, im) 456 im.save(self.imgName, format='PNG') 457 except (IOError, OSError) as e: 458 del busy 459 dlg = HyperlinkDialog( 460 self, title=_("Preview not available"), 461 message=_( 462 "Preview is not available probably because Ghostscript is not installed or not on PATH."), 463 hyperlink='http://trac.osgeo.org/grass/wiki/CompileOnWindows#Ghostscript', 464 hyperlinkLabel=_( 465 "Please follow instructions on GRASS Trac Wiki.")) 466 dlg.ShowModal() 467 dlg.Destroy() 468 return 469 470 rect = self.previewCanvas.ImageRect() 471 self.previewCanvas.image = wx.Image( 472 self.imgName, wx.BITMAP_TYPE_PNG) 473 self.previewCanvas.DrawImage(rect=rect) 474 475 del busy 476 self.SetStatusText(_('Preview generated'), 0) 477 self.book.SetSelection(1) 478 self.currentPage = 1 479 480 grass.try_remove(event.userData['instrFile']) 481 if event.userData['temp']: 482 grass.try_remove(event.userData['filename']) 483 484 self.delayedCall = wx.CallLater( 485 4000, lambda: self.SetStatusText("", 0)) 486 487 def getFile(self, wildcard): 488 suffix = [] 489 for filter in wildcard.split('|')[1::2]: 490 s = filter.strip('*').split('.')[1] 491 if s: 492 s = '.' + s 493 suffix.append(s) 494 raster = self.instruction.FindInstructionByType('raster') 495 if raster: 496 rasterId = raster.id 497 else: 498 rasterId = None 499 500 if rasterId and self.instruction[rasterId]['raster']: 501 mapName = self.instruction[rasterId][ 502 'raster'].split('@')[0] + suffix[0] 503 else: 504 mapName = '' 505 506 filename = '' 507 dlg = wx.FileDialog( 508 self, message=_("Save file as"), 509 defaultDir="", defaultFile=mapName, wildcard=wildcard, 510 style=wx.FD_CHANGE_DIR | wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT) 511 if dlg.ShowModal() == wx.ID_OK: 512 filename = dlg.GetPath() 513 suffix = suffix[dlg.GetFilterIndex()] 514 if not os.path.splitext(filename)[1]: 515 filename = filename + suffix 516 elif os.path.splitext(filename)[1] != suffix and suffix != '': 517 filename = os.path.splitext(filename)[0] + suffix 518 519 dlg.Destroy() 520 return filename 521 522 def OnInstructionFile(self, event): 523 filename = self.getFile( 524 wildcard="*.psmap|*.psmap|Text file(*.txt)|*.txt|All files(*.*)|*.*") 525 if filename: 526 instrFile = open(filename, "wb") 527 content = self.InstructionFile() 528 if not content: 529 return 530 instrFile.write(content) 531 instrFile.close() 532 533 def OnLoadFile(self, event): 534 """Launch file dialog and load selected file""" 535 # find file 536 filename = '' 537 dlg = wx.FileDialog( 538 self, 539 message="Find instructions file", 540 defaultDir="", 541 defaultFile='', 542 wildcard="All files (*.*)|*.*", 543 style=wx.FD_CHANGE_DIR | wx.FD_OPEN) 544 if dlg.ShowModal() == wx.ID_OK: 545 filename = dlg.GetPath() 546 dlg.Destroy() 547 if not filename: 548 return 549 # load instructions 550 self.LoadFile(filename) 551 552 def LoadFile(self, filename): 553 """Load file and read instructions""" 554 readObjectId = [] 555 readInstruction = Instruction(parent=self, objectsToDraw=readObjectId, env=self.env) 556 ok = readInstruction.Read(filename) 557 if not ok: 558 GMessage(_("Failed to read file %s.") % filename) 559 else: 560 self.instruction = self.canvas.instruction = readInstruction 561 self.objectId = self.canvas.objectId = readObjectId 562 self.pageId = self.canvas.pageId = self.instruction.FindInstructionByType( 563 'page').id 564 self.canvas.UpdateMapLabel() 565 self.canvas.dragId = -1 566 self.canvas.Clear() 567 self.canvas.SetPage() 568 # self.canvas.ZoomAll() 569 570 self.DialogDataChanged(self.objectId) 571 572 def OnPageSetup(self, event=None): 573 """Specify paper size, margins and orientation""" 574 id = self.instruction.FindInstructionByType('page').id 575 dlg = PageSetupDialog(self, id=id, settings=self.instruction, env=self.env) 576 dlg.CenterOnParent() 577 val = dlg.ShowModal() 578 if val == wx.ID_OK: 579 self.canvas.SetPage() 580 self.getInitMap() 581 self.canvas.RecalculatePosition(ids=self.objectId) 582 dlg.Destroy() 583 584 def OnPointer(self, event): 585 self.mouse["use"] = "pointer" 586 self.canvas.SetCursor(self.cursors["default"]) 587 self.previewCanvas.SetCursor(self.cursors["default"]) 588 589 def OnPan(self, event): 590 self.mouse["use"] = "pan" 591 self.canvas.SetCursor(self.cursors["hand"]) 592 self.previewCanvas.SetCursor(self.cursors["hand"]) 593 594 def OnZoomIn(self, event): 595 self.mouse["use"] = "zoomin" 596 self.canvas.SetCursor(self.cursors["cross"]) 597 self.previewCanvas.SetCursor(self.cursors["cross"]) 598 599 def OnZoomOut(self, event): 600 self.mouse["use"] = "zoomout" 601 self.canvas.SetCursor(self.cursors["cross"]) 602 self.previewCanvas.SetCursor(self.cursors["cross"]) 603 604 def OnZoomAll(self, event): 605 self.mouseOld = self.mouse['use'] 606 if self.currentPage == 0: 607 self.cursorOld = self.canvas.GetCursor() 608 else: 609 self.cursorOld = self.previewCanvas.GetCursor() 610 self.previewCanvas.GetCursor() 611 self.mouse["use"] = "zoomin" 612 if self.currentPage == 0: 613 self.canvas.ZoomAll() 614 else: 615 self.previewCanvas.ZoomAll() 616 self.mouse["use"] = self.mouseOld 617 if self.currentPage == 0: 618 self.canvas.SetCursor(self.cursorOld) 619 else: 620 self.previewCanvas.SetCursor(self.cursorOld) 621 622 def OnAddMap(self, event, notebook=False): 623 """Add or edit map frame""" 624 if self.instruction.FindInstructionByType('map'): 625 mapId = self.instruction.FindInstructionByType('map').id 626 else: 627 mapId = None 628 id = [mapId, None, None] 629 630 if notebook: 631 if self.instruction.FindInstructionByType('vector'): 632 vectorId = self.instruction.FindInstructionByType('vector').id 633 else: 634 vectorId = None 635 if self.instruction.FindInstructionByType('raster'): 636 rasterId = self.instruction.FindInstructionByType('raster').id 637 else: 638 rasterId = None 639 id[1] = rasterId 640 id[2] = vectorId 641 642 if mapId: # map exists 643 self.toolbar.SelectDefault() 644 645 if notebook: 646 # check map, raster, vector and save, destroy them 647 if 'map' in self.openDialogs: 648 self.openDialogs['map'].OnOK(event=None) 649 if 'raster' in self.openDialogs: 650 self.openDialogs['raster'].OnOK(event=None) 651 if 'vector' in self.openDialogs: 652 self.openDialogs['vector'].OnOK(event=None) 653 654 if 'mapNotebook' not in self.openDialogs: 655 dlg = MapDialog( 656 parent=self, 657 id=id, 658 settings=self.instruction, 659 env=self.env, 660 notebook=notebook) 661 self.openDialogs['mapNotebook'] = dlg 662 self.openDialogs['mapNotebook'].Show() 663 else: 664 if 'mapNotebook' in self.openDialogs: 665 self.openDialogs['mapNotebook'].notebook.ChangeSelection(0) 666 else: 667 if 'map' not in self.openDialogs: 668 dlg = MapDialog( 669 parent=self, 670 id=id, 671 settings=self.instruction, 672 env=self.env, 673 notebook=notebook) 674 self.openDialogs['map'] = dlg 675 self.openDialogs['map'].Show() 676 677 else: # sofar no map 678 self.mouse["use"] = "addMap" 679 self.canvas.SetCursor(self.cursors["cross"]) 680 if self.currentPage == 1: 681 self.book.SetSelection(0) 682 self.currentPage = 0 683 684 def OnAddRaster(self, event): 685 """Add raster map""" 686 if self.instruction.FindInstructionByType('raster'): 687 id = self.instruction.FindInstructionByType('raster').id 688 else: 689 id = None 690 691 if not self._checkMapFrameExists(type_id=id): 692 return 693 694## dlg = RasterDialog(self, id = id, settings = self.instruction) 695# dlg.ShowModal() 696 if 'mapNotebook' in self.openDialogs: 697 self.openDialogs['mapNotebook'].notebook.ChangeSelection(1) 698 else: 699 if 'raster' not in self.openDialogs: 700 dlg = RasterDialog(self, id=id, settings=self.instruction, env=self.env) 701 self.openDialogs['raster'] = dlg 702 self.openDialogs['raster'].Show() 703 704 def OnAddVect(self, event): 705 """Add vector map""" 706 if self.instruction.FindInstructionByType('vector'): 707 id = self.instruction.FindInstructionByType('vector').id 708 else: 709 id = None 710 711 if not self._checkMapFrameExists(type_id=id): 712 return 713 714## dlg = MainVectorDialog(self, id = id, settings = self.instruction) 715# dlg.ShowModal() 716 if 'mapNotebook' in self.openDialogs: 717 self.openDialogs['mapNotebook'].notebook.ChangeSelection(2) 718 else: 719 if 'vector' not in self.openDialogs: 720 dlg = MainVectorDialog(self, id=id, settings=self.instruction, env=self.env) 721 self.openDialogs['vector'] = dlg 722 self.openDialogs['vector'].Show() 723 724 def OnAddScalebar(self, event): 725 """Add scalebar""" 726 if projInfo()['proj'] == 'll': 727 GMessage( 728 message=_("Scalebar is not appropriate for this projection")) 729 return 730 if self.instruction.FindInstructionByType('scalebar'): 731 id = self.instruction.FindInstructionByType('scalebar').id 732 else: 733 id = None 734 735 if 'scalebar' not in self.openDialogs: 736 dlg = ScalebarDialog(self, id=id, settings=self.instruction, env=self.env) 737 self.openDialogs['scalebar'] = dlg 738 self.openDialogs['scalebar'].Show() 739 740 def OnAddLegend(self, event, page=0): 741 """Add raster or vector legend""" 742 if self.instruction.FindInstructionByType('rasterLegend'): 743 idR = self.instruction.FindInstructionByType('rasterLegend').id 744 else: 745 idR = None 746 if self.instruction.FindInstructionByType('vectorLegend'): 747 idV = self.instruction.FindInstructionByType('vectorLegend').id 748 else: 749 idV = None 750 751 if 'rasterLegend' not in self.openDialogs: 752 dlg = LegendDialog( 753 self, id=[idR, idV], 754 settings=self.instruction, env=self.env, page=page) 755 self.openDialogs['rasterLegend'] = dlg 756 self.openDialogs['vectorLegend'] = dlg 757 self.openDialogs['rasterLegend'].notebook.ChangeSelection(page) 758 self.openDialogs['rasterLegend'].Show() 759 760 def OnAddMapinfo(self, event): 761 if self.instruction.FindInstructionByType('mapinfo'): 762 id = self.instruction.FindInstructionByType('mapinfo').id 763 else: 764 id = None 765 766 if 'mapinfo' not in self.openDialogs: 767 dlg = MapinfoDialog(self, id=id, settings=self.instruction, env=self.env) 768 self.openDialogs['mapinfo'] = dlg 769 self.openDialogs['mapinfo'].Show() 770 771 def OnAddImage(self, event, id=None): 772 """Show dialog for image adding and editing""" 773 position = None 774 if 'image' in self.openDialogs: 775 position = self.openDialogs['image'].GetPosition() 776 self.openDialogs['image'].OnApply(event=None) 777 self.openDialogs['image'].Destroy() 778 dlg = ImageDialog(self, id=id, settings=self.instruction, env=self.env) 779 self.openDialogs['image'] = dlg 780 if position: 781 dlg.SetPosition(position) 782 dlg.Show() 783 784 def OnAddNorthArrow(self, event, id=None): 785 """Show dialog for north arrow adding and editing""" 786 if self.instruction.FindInstructionByType('northArrow'): 787 id = self.instruction.FindInstructionByType('northArrow').id 788 else: 789 id = None 790 791 if 'northArrow' not in self.openDialogs: 792 dlg = NorthArrowDialog(self, id=id, settings=self.instruction, env=self.env) 793 self.openDialogs['northArrow'] = dlg 794 self.openDialogs['northArrow'].Show() 795 796 def OnAddText(self, event, id=None): 797 """Show dialog for text adding and editing""" 798 position = None 799 if 'text' in self.openDialogs: 800 position = self.openDialogs['text'].GetPosition() 801 self.openDialogs['text'].OnApply(event=None) 802 self.openDialogs['text'].Destroy() 803 dlg = TextDialog(self, id=id, settings=self.instruction, env=self.env) 804 self.openDialogs['text'] = dlg 805 if position: 806 dlg.SetPosition(position) 807 dlg.Show() 808 809 def OnAddPoint(self, event): 810 """Add point action selected""" 811 self.mouse["use"] = "addPoint" 812 self.canvas.SetCursor(self.cursors["cross"]) 813 self._switchToPage() 814 815 def AddPoint(self, id=None, coordinates=None): 816 """Add point and open property dialog. 817 818 :param id: id point id (None if creating new point) 819 :param coordinates: coordinates of new point 820 """ 821 position = None 822 if 'point' in self.openDialogs: 823 position = self.openDialogs['point'].GetPosition() 824 self.openDialogs['point'].OnApply(event=None) 825 self.openDialogs['point'].Destroy() 826 dlg = PointDialog(self, id=id, settings=self.instruction, 827 coordinates=coordinates, env=self.env) 828 self.openDialogs['point'] = dlg 829 if position: 830 dlg.SetPosition(position) 831 if coordinates: 832 dlg.OnApply(event=None) 833 dlg.Show() 834 835 def OnAddLine(self, event): 836 """Add line action selected""" 837 self.mouse["use"] = "addLine" 838 self.canvas.SetCursor(self.cursors["cross"]) 839 self._switchToPage() 840 841 def AddLine(self, id=None, coordinates=None): 842 """Add line and open property dialog. 843 844 :param id: id line id (None if creating new line) 845 :param coordinates: coordinates of new line 846 """ 847 position = None 848 if 'line' in self.openDialogs: 849 position = self.openDialogs['line'].GetPosition() 850 self.openDialogs['line'].OnApply(event=None) 851 self.openDialogs['line'].Destroy() 852 dlg = RectangleDialog(self, id=id, settings=self.instruction, 853 type='line', coordinates=coordinates, 854 env=self.env) 855 self.openDialogs['line'] = dlg 856 if position: 857 dlg.SetPosition(position) 858 if coordinates: 859 dlg.OnApply(event=None) 860 dlg.Show() 861 862 def OnAddRectangle(self, event): 863 """Add rectangle action selected""" 864 self.mouse["use"] = "addRectangle" 865 self.canvas.SetCursor(self.cursors["cross"]) 866 self._switchToPage() 867 868 def AddRectangle(self, id=None, coordinates=None): 869 """Add rectangle and open property dialog. 870 871 :param id: id rectangle id (None if creating new rectangle) 872 :param coordinates: coordinates of new rectangle 873 """ 874 position = None 875 if 'rectangle' in self.openDialogs: 876 position = self.openDialogs['rectangle'].GetPosition() 877 self.openDialogs['rectangle'].OnApply(event=None) 878 self.openDialogs['rectangle'].Destroy() 879 dlg = RectangleDialog(self, id=id, settings=self.instruction, 880 type='rectangle', coordinates=coordinates, 881 env=self.env) 882 self.openDialogs['rectangle'] = dlg 883 if position: 884 dlg.SetPosition(position) 885 if coordinates: 886 dlg.OnApply(event=None) 887 dlg.Show() 888 889 def OnAddLabels(self, event, id=None): 890 """Show dialog for labels""" 891 if self.instruction.FindInstructionByType('labels'): 892 id = self.instruction.FindInstructionByType('labels').id 893 else: 894 id = None 895 896 if not self._checkMapFrameExists(type_id=id): 897 return 898 899 if 'labels' not in self.openDialogs: 900 dlg = LabelsDialog(self, id=id, settings=self.instruction, env=self.env) 901 self.openDialogs['labels'] = dlg 902 self.openDialogs['labels'].Show() 903 904 def getModifiedTextBounds(self, x, y, textExtent, rotation): 905 """computes bounding box of rotated text, not very precisely""" 906 w, h = textExtent 907 rotation = float(rotation) / 180 * pi 908 H = float(w) * sin(rotation) 909 W = float(w) * cos(rotation) 910 X, Y = x, y 911 if pi / 2 < rotation <= 3 * pi / 2: 912 X = x + W 913 if 0 < rotation < pi: 914 Y = y - H 915 if rotation == 0: 916 return Rect(x, y, *textExtent) 917 else: 918 return Rect(X, Y, abs(W), abs(H)).Inflate(h, h) 919 920 def makePSFont(self, textDict): 921 """creates a wx.Font object from selected postscript font. To be 922 used for estimating bounding rectangle of text""" 923 924 fontsize = textDict['fontsize'] * self.canvas.currScale 925 fontface = textDict['font'].split('-')[0] 926 try: 927 fontstyle = textDict['font'].split('-')[1] 928 except IndexError: 929 fontstyle = '' 930 931 if fontface == "Times": 932 family = wx.FONTFAMILY_ROMAN 933 face = "times" 934 elif fontface == "Helvetica": 935 family = wx.FONTFAMILY_SWISS 936 face = 'helvetica' 937 elif fontface == "Courier": 938 family = wx.FONTFAMILY_TELETYPE 939 face = 'courier' 940 else: 941 family = wx.FONTFAMILY_DEFAULT 942 face = '' 943 944 style = wx.FONTSTYLE_NORMAL 945 weight = wx.FONTWEIGHT_NORMAL 946 947 if 'Oblique' in fontstyle: 948 style = wx.FONTSTYLE_SLANT 949 950 if 'Italic' in fontstyle: 951 style = wx.FONTSTYLE_ITALIC 952 953 if 'Bold' in fontstyle: 954 weight = wx.FONTWEIGHT_BOLD 955 956 try: 957 fn = wx.Font(pointSize=fontsize, family=family, style=style, 958 weight=weight, face=face) 959 except: 960 fn = wx.Font( 961 pointSize=fontsize, 962 family=wx.FONTFAMILY_DEFAULT, 963 style=wx.FONTSTYLE_NORMAL, 964 weight=wx.FONTWEIGHT_NORMAL) 965 966 return fn 967 968 def getTextExtent(self, textDict): 969 """Estimates bounding rectangle of text""" 970 #fontsize = str(fontsize if fontsize >= 4 else 4) 971 # dc created because of method GetTextExtent, which pseudoDC lacks 972 dc = ClientDC(self) 973 974 fn = self.makePSFont(textDict) 975 976 try: 977 dc.SetFont(fn) 978 w, h, lh = dc.GetFullMultiLineTextExtent(textDict['text']) 979 return (w, h) 980 except: 981 return (0, 0) 982 983 def getInitMap(self): 984 """Create default map frame when no map is selected, needed for coordinates in map units""" 985 instrFile = grass.tempfile() 986 instrFileFd = open(instrFile, mode='wb') 987 content = self.InstructionFile() 988 if not content: 989 return 990 instrFileFd.write(content) 991 instrFileFd.flush() 992 instrFileFd.close() 993 994 page = self.instruction.FindInstructionByType('page') 995 mapInitRect = GetMapBounds( 996 instrFile, portrait=( 997 page['Orientation'] == 'Portrait'), 998 env=self.env) 999 grass.try_remove(instrFile) 1000 1001 region = grass.region(env=self.env) 1002 units = UnitConversion(self) 1003 realWidth = units.convert( 1004 value=abs(region['w'] - region['e']), 1005 fromUnit='meter', toUnit='inch') 1006 scale = mapInitRect.Get()[2] / realWidth 1007 1008 initMap = self.instruction.FindInstructionByType('initMap') 1009 if initMap: 1010 id = initMap.id 1011 else: 1012 id = None 1013 1014 if not id: 1015 id = NewId() 1016 initMap = InitMap(id, env=self.env) 1017 self.instruction.AddInstruction(initMap) 1018 self.instruction[id].SetInstruction( 1019 dict(rect=mapInitRect, scale=scale)) 1020 1021 def OnDelete(self, event): 1022 if self.canvas.dragId != -1 and self.currentPage == 0: 1023 if self.instruction[self.canvas.dragId].type == 'map': 1024 self.deleteObject(self.canvas.dragId) 1025 self.getInitMap() 1026 self.canvas.RecalculateEN() 1027 else: 1028 self.deleteObject(self.canvas.dragId) 1029 1030 def deleteObject(self, id): 1031 """Deletes object, his id and redraws""" 1032 # delete from canvas 1033 self.canvas.pdcObj.RemoveId(id) 1034 if id == self.canvas.dragId: 1035 self.canvas.pdcTmp.RemoveAll() 1036 self.canvas.dragId = -1 1037 self.canvas.Refresh() 1038 1039 # delete from instructions 1040 del self.instruction[id] 1041 1042 def DialogDataChanged(self, id): 1043 ids = id 1044 if isinstance(id, int): 1045 ids = [id] 1046 for id in ids: 1047 itype = self.instruction[id].type 1048 1049 if itype in ('scalebar', 'mapinfo', 'image'): 1050 drawRectangle = self.canvas.CanvasPaperCoordinates( 1051 rect=self.instruction[id]['rect'], canvasToPaper=False) 1052 self.canvas.UpdateLabel(itype=itype, id=id) 1053 self.canvas.Draw( 1054 pen=self.pen[itype], 1055 brush=self.brush[itype], 1056 pdc=self.canvas.pdcObj, 1057 drawid=id, 1058 pdctype='rectText', 1059 bb=drawRectangle) 1060 self.canvas.RedrawSelectBox(id) 1061 if itype == 'northArrow': 1062 self.canvas.UpdateLabel(itype=itype, id=id) 1063 drawRectangle = self.canvas.CanvasPaperCoordinates( 1064 rect=self.instruction[id]['rect'], canvasToPaper=False) 1065 self.canvas.Draw( 1066 pen=self.pen[itype], 1067 brush=self.brush[itype], 1068 pdc=self.canvas.pdcObj, 1069 drawid=id, 1070 pdctype='bitmap', 1071 bb=drawRectangle) 1072 self.canvas.RedrawSelectBox(id) 1073 1074 if itype in ('point', 'line', 'rectangle'): 1075 drawRectangle = self.canvas.CanvasPaperCoordinates( 1076 rect=self.instruction[id]['rect'], canvasToPaper=False) 1077 # coords only for line 1078 coords = None 1079 if itype == 'line': 1080 point1 = self.instruction[id]['where'][0] 1081 point2 = self.instruction[id]['where'][1] 1082 point1Coords = self.canvas.CanvasPaperCoordinates( 1083 rect=Rect2DPS(point1, (0, 0)), canvasToPaper=False)[:2] 1084 point2Coords = self.canvas.CanvasPaperCoordinates( 1085 rect=Rect2DPS(point2, (0, 0)), canvasToPaper=False)[:2] 1086 coords = (point1Coords, point2Coords) 1087 1088 # fill color is not in line 1089 fcolor = None 1090 if 'fcolor' in self.instruction[id].GetInstruction(): 1091 fcolor = self.instruction[id]['fcolor'] 1092 # width is not in point 1093 width = None 1094 if 'width' in self.instruction[id].GetInstruction(): 1095 width = self.instruction[id]['width'] 1096 1097 self.canvas.DrawGraphics( 1098 drawid=id, 1099 color=self.instruction[id]['color'], 1100 shape=itype, 1101 fcolor=fcolor, 1102 width=width, 1103 bb=drawRectangle, 1104 lineCoords=coords) 1105 1106 self.canvas.RedrawSelectBox(id) 1107 1108 if itype == 'text': 1109 1110 if self.instruction[id]['rotate']: 1111 rot = float(self.instruction[id]['rotate']) 1112 else: 1113 rot = 0 1114 1115 extent = self.getTextExtent( 1116 textDict=self.instruction[id].GetInstruction()) 1117 rect = Rect2DPS(self.instruction[id]['where'], (0, 0)) 1118 self.instruction[id]['coords'] = list( 1119 self.canvas.CanvasPaperCoordinates( 1120 rect=rect, canvasToPaper=False)[: 2]) 1121 1122 # computes text coordinates according to reference point, not 1123 # precisely 1124 if self.instruction[id]['ref'].split()[0] == 'lower': 1125 self.instruction[id]['coords'][1] -= extent[1] 1126 elif self.instruction[id]['ref'].split()[0] == 'center': 1127 self.instruction[id]['coords'][1] -= extent[1] / 2 1128 if self.instruction[id]['ref'].split()[1] == 'right': 1129 self.instruction[id]['coords'][ 1130 0] -= extent[0] * cos(rot / 180 * pi) 1131 self.instruction[id]['coords'][ 1132 1] += extent[0] * sin(rot / 180 * pi) 1133 elif self.instruction[id]['ref'].split()[1] == 'center': 1134 self.instruction[id]['coords'][ 1135 0] -= extent[0] / 2 * cos(rot / 180 * pi) 1136 self.instruction[id]['coords'][ 1137 1] += extent[0] / 2 * sin(rot / 180 * pi) 1138 1139 self.instruction[id]['coords'][ 1140 0] += self.instruction[id]['xoffset'] 1141 self.instruction[id]['coords'][ 1142 1] -= self.instruction[id]['yoffset'] 1143 coords = self.instruction[id]['coords'] 1144 self.instruction[id]['rect'] = bounds = self.getModifiedTextBounds( 1145 coords[0], coords[1], extent, rot) 1146 self.canvas.DrawRotText( 1147 pdc=self.canvas.pdcObj, drawId=id, 1148 textDict=self.instruction[id].GetInstruction(), 1149 coords=coords, bounds=bounds) 1150 self.canvas.RedrawSelectBox(id) 1151 1152 if itype in ('map', 'vector', 'raster', 'labels'): 1153 1154 if itype == 'raster': # set resolution 1155 try: 1156 info = grass.raster_info(self.instruction[id]['raster']) 1157 self.env['GRASS_REGION'] = grass.region_env(nsres=info['nsres'], 1158 ewres=info['ewres'], 1159 env=self.env) 1160 except grass.CalledModuleError: # fails after switching location 1161 pass 1162 # change current raster in raster legend 1163 1164 if 'rasterLegend' in self.openDialogs: 1165 self.openDialogs['rasterLegend'].updateDialog() 1166 id = self.instruction.FindInstructionByType('map').id 1167 1168 # check resolution 1169 if itype == 'raster': 1170 SetResolution(dpi=self.instruction[id]['resolution'], 1171 width=self.instruction[id]['rect'].width, 1172 height=self.instruction[id]['rect'].height, 1173 env=self.env) 1174 rectCanvas = self.canvas.CanvasPaperCoordinates( 1175 rect=self.instruction[id]['rect'], canvasToPaper=False) 1176 self.canvas.RecalculateEN() 1177 self.canvas.UpdateMapLabel() 1178 1179 self.canvas.Draw( 1180 pen=self.pen['map'], 1181 brush=self.brush['map'], 1182 pdc=self.canvas.pdcObj, 1183 drawid=id, 1184 pdctype='rectText', 1185 bb=rectCanvas) 1186 # redraw select box 1187 self.canvas.RedrawSelectBox(id) 1188 self.canvas.pdcTmp.RemoveId(self.canvas.idZoomBoxTmp) 1189 # redraw to get map to the bottom layer 1190 #self.canvas.Zoom(zoomFactor = 1, view = (0, 0)) 1191 1192 if itype == 'rasterLegend': 1193 if self.instruction[id]['rLegend']: 1194 self.canvas.UpdateLabel(itype=itype, id=id) 1195 drawRectangle = self.canvas.CanvasPaperCoordinates( 1196 rect=self.instruction[id]['rect'], canvasToPaper=False) 1197 self.canvas.Draw( 1198 pen=self.pen[itype], 1199 brush=self.brush[itype], 1200 pdc=self.canvas.pdcObj, 1201 drawid=id, 1202 pdctype='rectText', 1203 bb=drawRectangle) 1204 self.canvas.RedrawSelectBox(id) 1205 else: 1206 self.deleteObject(id) 1207 1208 if itype == 'vectorLegend': 1209 if not self.instruction.FindInstructionByType('vector'): 1210 self.deleteObject(id) 1211 elif self.instruction[id]['vLegend']: 1212 self.canvas.UpdateLabel(itype=itype, id=id) 1213 drawRectangle = self.canvas.CanvasPaperCoordinates( 1214 rect=self.instruction[id]['rect'], canvasToPaper=False) 1215 self.canvas.Draw( 1216 pen=self.pen[itype], 1217 brush=self.brush[itype], 1218 pdc=self.canvas.pdcObj, 1219 drawid=id, 1220 pdctype='rectText', 1221 bb=drawRectangle) 1222 self.canvas.RedrawSelectBox(id) 1223 1224 else: 1225 self.deleteObject(id) 1226 1227 def OnPageChanged(self, event): 1228 """Flatnotebook page has changed""" 1229 self.currentPage = self.book.GetPageIndex(self.book.GetCurrentPage()) 1230 if self.currentPage == 1: 1231 self.SetStatusText( 1232 _("Press button with green triangle icon to generate preview.")) 1233 else: 1234 self.SetStatusText('') 1235 1236 def OnHelp(self, event): 1237 """Show help""" 1238 if self.parent and self.parent.GetName() == 'LayerManager': 1239 log = self.parent.GetLogWindow() 1240 log.RunCmd(['g.manual', 1241 'entry=wxGUI.psmap']) 1242 else: 1243 RunCommand('g.manual', 1244 quiet=True, 1245 entry='wxGUI.psmap') 1246 1247 def OnAbout(self, event): 1248 """Display About window""" 1249 ShowAboutDialog( 1250 prgName=_('wxGUI Cartographic Composer'), 1251 startYear='2011') 1252 1253 def OnCloseWindow(self, event): 1254 """Close window""" 1255 try: 1256 os.remove(self.imgName) 1257 except OSError: 1258 pass 1259 grass.set_raise_on_error(False) 1260 if hasattr(self, 'delayedCall') and self.delayedCall.IsRunning(): 1261 self.delayedCall.Stop() 1262 self.Destroy() 1263 1264 1265class PsMapBufferedWindow(wx.Window): 1266 """A buffered window class.""" 1267 1268 def __init__(self, parent, id=wx.ID_ANY, 1269 style=wx.NO_FULL_REPAINT_ON_RESIZE, 1270 **kwargs): 1271 """ 1272 :param parent: parent window 1273 :param kwargs: other wx.Window parameters 1274 """ 1275 wx.Window.__init__(self, parent, id=id, style=style) 1276 self.parent = parent 1277 1278 self.FitInside() 1279 1280 # store an off screen empty bitmap for saving to file 1281 self._buffer = None 1282 # indicates whether or not a resize event has taken place 1283 self.resize = False 1284 1285 self.mouse = kwargs['mouse'] 1286 self.cursors = kwargs['cursors'] 1287 self.preview = kwargs['preview'] 1288 self.pen = kwargs['pen'] 1289 self.brush = kwargs['brush'] 1290 1291 if 'instruction' in kwargs: 1292 self.instruction = kwargs['instruction'] 1293 if 'openDialogs' in kwargs: 1294 self.openDialogs = kwargs['openDialogs'] 1295 if 'pageId' in kwargs: 1296 self.pageId = kwargs['pageId'] 1297 if 'objectId' in kwargs: 1298 self.objectId = kwargs['objectId'] 1299 if 'env' in kwargs: 1300 self.env = kwargs['env'] 1301 # labels 1302 self.itemLabelsDict = {'map': _("MAP FRAME"), 1303 'rasterLegend': _("RASTER LEGEND"), 1304 'vectorLegend': _("VECTOR LEGEND"), 1305 'mapinfo': _("MAP INFO"), 1306 'scalebar': _("SCALE BAR"), 1307 'image': _("IMAGE"), 1308 'northArrow': _("NORTH ARROW")} 1309 self.itemLabels = {} 1310 1311 # define PseudoDC 1312 self.pdc = PseudoDC() 1313 self.pdcObj = PseudoDC() 1314 self.pdcPaper = PseudoDC() 1315 self.pdcTmp = PseudoDC() 1316 self.pdcImage = PseudoDC() 1317 1318 self.SetClientSize((700, 510)) # ? 1319 self._buffer = EmptyBitmap(*self.GetClientSize()) 1320 1321 self.idBoxTmp = NewId() 1322 self.idZoomBoxTmp = NewId() 1323 self.idResizeBoxTmp = NewId() 1324 # ids of marks for moving line vertices 1325 self.idLinePointsTmp = (NewId(), NewId()) 1326 1327 self.resizeBoxSize = wx.Size(8, 8) 1328 self.showResizeHelp = False # helper for correctly working statusbar 1329 1330 self.dragId = -1 1331 1332 if self.preview: 1333 self.image = None 1334 self.imageId = 2000 1335 self.imgName = self.parent.imgName 1336 1337 self.currScale = None 1338 1339 self.Clear() 1340 1341 self.Bind(wx.EVT_ERASE_BACKGROUND, lambda x: None) 1342 1343 self.Bind(wx.EVT_PAINT, self.OnPaint) 1344 self.Bind(wx.EVT_SIZE, self.OnSize) 1345 self.Bind(wx.EVT_IDLE, self.OnIdle) 1346 # self.Bind(wx.EVT_MOUSE_EVENTS, self.OnMouse) 1347 self.Bind(wx.EVT_MOUSE_EVENTS, self.MouseActions) 1348 1349 def Clear(self): 1350 """Clear canvas and set paper 1351 """ 1352 bg = wx.LIGHT_GREY_BRUSH 1353 self.pdcPaper.BeginDrawing() 1354 self.pdcPaper.SetBackground(bg) 1355 self.pdcPaper.Clear() 1356 self.pdcPaper.EndDrawing() 1357 1358 self.pdcObj.RemoveAll() 1359 self.pdcTmp.RemoveAll() 1360 1361 if not self.preview: 1362 self.SetPage() 1363 1364 def CanvasPaperCoordinates(self, rect, canvasToPaper=True): 1365 """Converts canvas (pixel) -> paper (inch) coordinates and size and vice versa""" 1366 1367 units = UnitConversion(self) 1368 1369 fromU = 'pixel' 1370 toU = 'inch' 1371 pRect = self.pdcPaper.GetIdBounds(self.pageId) 1372 pRectx, pRecty = pRect.x, pRect.y 1373 scale = 1 / self.currScale 1374 if not canvasToPaper: # paper -> canvas 1375 fromU = 'inch' 1376 toU = 'pixel' 1377 scale = self.currScale 1378 pRectx = units.convert( 1379 value=- pRect.x, 1380 fromUnit='pixel', 1381 toUnit='inch') / scale # inch, real, negative 1382 pRecty = units.convert( 1383 value=- pRect.y, 1384 fromUnit='pixel', 1385 toUnit='inch') / scale 1386 Width = units.convert( 1387 value=rect.GetWidth(), 1388 fromUnit=fromU, 1389 toUnit=toU) * scale 1390 Height = units.convert( 1391 value=rect.GetHeight(), 1392 fromUnit=fromU, 1393 toUnit=toU) * scale 1394 X = units.convert( 1395 value=( 1396 rect.GetX() - pRectx), 1397 fromUnit=fromU, 1398 toUnit=toU) * scale 1399 Y = units.convert( 1400 value=( 1401 rect.GetY() - pRecty), 1402 fromUnit=fromU, 1403 toUnit=toU) * scale 1404 1405 return Rect2D(X, Y, Width, Height) 1406 1407 def SetPage(self): 1408 """Sets and changes page, redraws paper""" 1409 1410 page = self.instruction[self.pageId] 1411 if not page: 1412 page = PageSetup(id=self.pageId, env=self.env) 1413 self.instruction.AddInstruction(page) 1414 1415 ppi = wx.ClientDC(self).GetPPI() 1416 cW, cH = self.GetClientSize() 1417 pW, pH = page['Width'] * ppi[0], page['Height'] * ppi[1] 1418 1419 if self.currScale is None: 1420 self.currScale = min(cW / pW, cH / pH) 1421 pW = pW * self.currScale 1422 pH = pH * self.currScale 1423 1424 x = cW / 2 - pW / 2 1425 y = cH / 2 - pH / 2 1426 self.DrawPaper(Rect(x, y, pW, pH)) 1427 1428 def modifyRectangle(self, r): 1429 """Recalculates rectangle not to have negative size""" 1430 if r.GetWidth() < 0: 1431 r.SetX(r.GetX() + r.GetWidth()) 1432 if r.GetHeight() < 0: 1433 r.SetY(r.GetY() + r.GetHeight()) 1434 r.SetWidth(abs(r.GetWidth())) 1435 r.SetHeight(abs(r.GetHeight())) 1436 return r 1437 1438 def RecalculateEN(self): 1439 """Recalculate east and north for texts (eps, points) after their or map's movement""" 1440 try: 1441 mapId = self.instruction.FindInstructionByType('map').id 1442 except AttributeError: 1443 mapId = self.instruction.FindInstructionByType('initMap').id 1444 1445 for itemType in ('text', 'image', 'northArrow', 1446 'point', 'line', 'rectangle'): 1447 items = self.instruction.FindInstructionByType(itemType, list=True) 1448 for item in items: 1449 instr = self.instruction[item.id] 1450 if itemType in ('line', 'rectangle'): 1451 if itemType == 'line': 1452 e1, n1 = PaperMapCoordinates( 1453 mapInstr=self.instruction[mapId], 1454 x=instr['where'][0][0], 1455 y=instr['where'][0][1], 1456 paperToMap=True, 1457 env=self.env) 1458 e2, n2 = PaperMapCoordinates( 1459 mapInstr=self.instruction[mapId], 1460 x=instr['where'][1][0], 1461 y=instr['where'][1][1], 1462 paperToMap=True, 1463 env=self.env) 1464 else: 1465 e1, n1 = PaperMapCoordinates( 1466 mapInstr=self.instruction[mapId], 1467 x=instr['rect'].GetLeft(), 1468 y=instr['rect'].GetTop(), 1469 paperToMap=True, 1470 env=self.env) 1471 e2, n2 = PaperMapCoordinates( 1472 mapInstr=self.instruction[mapId], 1473 x=instr['rect'].GetRight(), 1474 y=instr['rect'].GetBottom(), 1475 paperToMap=True, 1476 env=self.env) 1477 instr['east1'] = e1 1478 instr['north1'] = n1 1479 instr['east2'] = e2 1480 instr['north2'] = n2 1481 else: 1482 e, n = PaperMapCoordinates( 1483 mapInstr=self.instruction[mapId], 1484 x=instr['where'][0], 1485 y=instr['where'][1], 1486 paperToMap=True, 1487 env=self.env) 1488 instr['east'], instr['north'] = e, n 1489 1490 def OnPaint(self, event): 1491 """Draw pseudo DC to buffer 1492 """ 1493 if not self._buffer: 1494 return 1495 dc = wx.BufferedPaintDC(self, self._buffer) 1496 # use PrepareDC to set position correctly 1497 # probably does nothing, removed from wxPython 2.9 1498 # self.PrepareDC(dc) 1499 1500 dc.SetBackground(wx.LIGHT_GREY_BRUSH) 1501 dc.Clear() 1502 1503 # draw paper 1504 if not self.preview: 1505 self.pdcPaper.DrawToDC(dc) 1506 # draw to the DC using the calculated clipping rect 1507 1508 rgn = self.GetUpdateRegion() 1509 1510 if not self.preview: 1511 self.pdcObj.DrawToDCClipped(dc, rgn.GetBox()) 1512 else: 1513 self.pdcImage.DrawToDCClipped(dc, rgn.GetBox()) 1514 self.pdcTmp.DrawToDCClipped(dc, rgn.GetBox()) 1515 1516 def MouseActions(self, event): 1517 """Mouse motion and button click notifier 1518 """ 1519 disable = self.preview and self.mouse['use'] in ('pointer', 'resize', 1520 'addMap', 'addPoint', 1521 'addLine', 'addRectangle') 1522 # zoom with mouse wheel 1523 if event.GetWheelRotation() != 0: 1524 self.OnMouseWheel(event) 1525 1526 # left mouse button pressed 1527 elif event.LeftDown() and not disable: 1528 self.OnLeftDown(event) 1529 1530 # left mouse button released 1531 elif event.LeftUp() and not disable: 1532 self.OnLeftUp(event) 1533 1534 # dragging 1535 elif event.Dragging() and not disable: 1536 self.OnDragging(event) 1537 1538 # double click 1539 elif event.ButtonDClick() and not disable: 1540 self.OnButtonDClick(event) 1541 1542 # middle mouse button pressed 1543 elif event.MiddleDown(): 1544 self.OnMiddleDown(event) 1545 1546 elif event.Moving(): 1547 self.OnMouseMoving(event) 1548 1549 def OnMouseWheel(self, event): 1550 """Mouse wheel scrolled. 1551 1552 Changes zoom.""" 1553 if UserSettings.Get(group='display', 1554 key='mouseWheelZoom', 1555 subkey='selection') == 2: 1556 event.Skip() 1557 return 1558 1559 zoom = event.GetWheelRotation() 1560 oldUse = self.mouse['use'] 1561 self.mouse['begin'] = event.GetPosition() 1562 1563 if UserSettings.Get(group='display', 1564 key='scrollDirection', 1565 subkey='selection'): 1566 zoom *= -1 1567 1568 if zoom > 0: 1569 self.mouse['use'] = 'zoomin' 1570 else: 1571 self.mouse['use'] = 'zoomout' 1572 1573 zoomFactor, view = self.ComputeZoom(Rect(0, 0, 0, 0)) 1574 self.Zoom(zoomFactor, view) 1575 self.mouse['use'] = oldUse 1576 1577 def OnMouseMoving(self, event): 1578 """Mouse cursor moving. 1579 1580 Change cursor when moving over resize marker. 1581 """ 1582 if self.preview: 1583 return 1584 1585 if self.mouse['use'] in ('pointer', 'resize'): 1586 pos = event.GetPosition() 1587 foundResize = self.pdcTmp.FindObjects(pos[0], pos[1]) 1588 if foundResize and foundResize[0] in ( 1589 self.idResizeBoxTmp,) + self.idLinePointsTmp: 1590 self.SetCursor(self.cursors["sizenwse"]) 1591 self.parent.SetStatusText( 1592 _('Click and drag to resize object'), 0) 1593 self.showResizeHelp = True 1594 else: 1595 if self.showResizeHelp: 1596 self.parent.SetStatusText('', 0) 1597 self.SetCursor(self.cursors["default"]) 1598 self.showResizeHelp = False 1599 1600 def OnLeftDown(self, event): 1601 """Left mouse button pressed. 1602 1603 Select objects, redraw, prepare for moving/resizing. 1604 """ 1605 self.mouse['begin'] = event.GetPosition() 1606 self.begin = self.mouse['begin'] 1607 1608 # select 1609 if self.mouse['use'] == 'pointer': 1610 found = self.pdcObj.FindObjects( 1611 self.mouse['begin'][0], 1612 self.mouse['begin'][1]) 1613 foundResize = self.pdcTmp.FindObjects( 1614 self.mouse['begin'][0], self.mouse['begin'][1]) 1615 1616 if foundResize and foundResize[0] in ( 1617 self.idResizeBoxTmp,) + self.idLinePointsTmp: 1618 self.mouse['use'] = 'resize' 1619 1620 # when resizing, proportions match region 1621 if self.instruction[self.dragId].type == 'map': 1622 self.constraint = False 1623 self.mapBounds = self.pdcObj.GetIdBounds(self.dragId) 1624 if self.instruction[self.dragId]['scaleType'] in (0, 1, 2): 1625 self.constraint = True 1626 self.mapBounds = self.pdcObj.GetIdBounds(self.dragId) 1627 1628 if self.instruction[self.dragId].type == 'line': 1629 self.currentLinePoint = self.idLinePointsTmp.index( 1630 foundResize 1631 [0]) 1632 1633 elif found: 1634 self.dragId = found[0] 1635 self.RedrawSelectBox(self.dragId) 1636 if self.instruction[ 1637 self.dragId].type not in ( 1638 'map', 'rectangle'): 1639 self.pdcTmp.RemoveId(self.idResizeBoxTmp) 1640 self.Refresh() 1641 if self.instruction[self.dragId].type != 'line': 1642 for id in self.idLinePointsTmp: 1643 self.pdcTmp.RemoveId(id) 1644 self.Refresh() 1645 1646 else: 1647 self.dragId = -1 1648 self.pdcTmp.RemoveId(self.idBoxTmp) 1649 self.pdcTmp.RemoveId(self.idResizeBoxTmp) 1650 for id in self.idLinePointsTmp: 1651 self.pdcTmp.RemoveId(id) 1652 self.Refresh() 1653 1654 def OnLeftUp(self, event): 1655 """Left mouse button released. 1656 1657 Recalculate zooming/resizing/moving and redraw. 1658 """ 1659 # zoom in, zoom out 1660 if self.mouse['use'] in ('zoomin', 'zoomout'): 1661 zoomR = self.pdcTmp.GetIdBounds(self.idZoomBoxTmp) 1662 self.pdcTmp.RemoveId(self.idZoomBoxTmp) 1663 self.Refresh() 1664 zoomFactor, view = self.ComputeZoom(zoomR) 1665 self.Zoom(zoomFactor, view) 1666 1667 # draw map frame 1668 if self.mouse['use'] == 'addMap': 1669 rectTmp = self.pdcTmp.GetIdBounds(self.idZoomBoxTmp) 1670 # too small rectangle, it's usually some mistake 1671 if rectTmp.GetWidth() < 20 or rectTmp.GetHeight() < 20: 1672 self.pdcTmp.RemoveId(self.idZoomBoxTmp) 1673 self.Refresh() 1674 return 1675 rectPaper = self.CanvasPaperCoordinates( 1676 rect=rectTmp, canvasToPaper=True) 1677 1678 dlg = MapDialog( 1679 parent=self.parent, id=[None, None, None], 1680 settings=self.instruction, env=self.env, rect=rectPaper) 1681 self.openDialogs['map'] = dlg 1682 self.openDialogs['map'].Show() 1683 1684 self.parent.toolbar.SelectDefault() 1685 return 1686 1687 # resize resizable objects (map, line, rectangle) 1688 if self.mouse['use'] == 'resize': 1689 mapObj = self.instruction.FindInstructionByType('map') 1690 if not mapObj: 1691 mapObj = self.instruction.FindInstructionByType('initMap') 1692 mapId = mapObj.id 1693 1694 if self.dragId == mapId: 1695 # necessary to change either map frame (scaleType 0,1,2) or 1696 # region (scaletype 3) 1697 newRectCanvas = self.pdcObj.GetIdBounds(mapId) 1698 newRectPaper = self.CanvasPaperCoordinates( 1699 rect=newRectCanvas, canvasToPaper=True) 1700 self.instruction[mapId]['rect'] = newRectPaper 1701 1702 if self.instruction[mapId]['scaleType'] in (0, 1, 2): 1703 if self.instruction[mapId]['scaleType'] == 0: 1704 1705 scale, foo, rect = AutoAdjust( 1706 self, scaleType=0, map=self.instruction[mapId] 1707 ['map'], env=self.env, 1708 mapType=self.instruction[mapId]['mapType'], 1709 rect=self.instruction[mapId]['rect']) 1710 1711 elif self.instruction[mapId]['scaleType'] == 1: 1712 scale, foo, rect = AutoAdjust( 1713 self, scaleType=1, env=self.env, 1714 region=self.instruction[mapId]['region'], 1715 rect=self.instruction[mapId]['rect']) 1716 else: 1717 scale, foo, rect = AutoAdjust( 1718 self, scaleType=2, rect=self.instruction[mapId]['rect'], 1719 env=self.env) 1720 self.instruction[mapId]['rect'] = rect 1721 self.instruction[mapId]['scale'] = scale 1722 1723 rectCanvas = self.CanvasPaperCoordinates( 1724 rect=rect, canvasToPaper=False) 1725 self.Draw( 1726 pen=self.pen['map'], 1727 brush=self.brush['map'], 1728 pdc=self.pdcObj, 1729 drawid=mapId, 1730 pdctype='rectText', 1731 bb=rectCanvas) 1732 1733 elif self.instruction[mapId]['scaleType'] == 3: 1734 ComputeSetRegion( 1735 self, mapDict=self.instruction[mapId].GetInstruction(), env=self.env) 1736 # check resolution 1737 SetResolution(dpi=self.instruction[mapId]['resolution'], 1738 width=self.instruction[mapId]['rect'].width, 1739 height=self.instruction[mapId]['rect'].height, 1740 env=self.env) 1741 1742 self.RedrawSelectBox(mapId) 1743 self.Zoom(zoomFactor=1, view=(0, 0)) 1744 1745 elif self.instruction[self.dragId].type == 'line': 1746 points = self.instruction[self.dragId]['where'] 1747 self.instruction[ 1748 self.dragId]['rect'] = Rect2DPP( 1749 points[0], points[1]) 1750 self.RecalculatePosition(ids=[self.dragId]) 1751 1752 elif self.instruction[self.dragId].type == 'rectangle': 1753 self.RecalculatePosition(ids=[self.dragId]) 1754 1755 self.mouse['use'] = 'pointer' 1756 1757 # recalculate the position of objects after dragging 1758 if self.mouse['use'] in ('pointer', 'resize') and self.dragId != -1: 1759 if self.mouse['begin'] != event.GetPosition(): # for double click 1760 1761 self.RecalculatePosition(ids=[self.dragId]) 1762 if self.instruction[self.dragId].type in self.openDialogs: 1763 self.openDialogs[ 1764 self.instruction[ 1765 self.dragId].type].updateDialog() 1766 1767 elif self.mouse['use'] in ('addPoint', 'addLine', 'addRectangle'): 1768 endCoordinates = self.CanvasPaperCoordinates(rect=Rect( 1769 event.GetX(), event.GetY(), 0, 0), canvasToPaper=True)[:2] 1770 1771 diffX = event.GetX() - self.mouse['begin'][0] 1772 diffY = event.GetY() - self.mouse['begin'][1] 1773 1774 if self.mouse['use'] == 'addPoint': 1775 self.parent.AddPoint(coordinates=endCoordinates) 1776 elif self.mouse['use'] in ('addLine', 'addRectangle'): 1777 # not too small lines/rectangles 1778 if sqrt(diffX * diffX + diffY * diffY) < 5: 1779 self.pdcTmp.RemoveId(self.idZoomBoxTmp) 1780 self.Refresh() 1781 return 1782 1783 beginCoordinates = self.CanvasPaperCoordinates( 1784 rect=Rect( 1785 self.mouse['begin'][0], 1786 self.mouse['begin'][1], 1787 0, 0), 1788 canvasToPaper=True)[ 1789 : 2] 1790 if self.mouse['use'] == 'addLine': 1791 self.parent.AddLine( 1792 coordinates=[ 1793 beginCoordinates, 1794 endCoordinates]) 1795 else: 1796 self.parent.AddRectangle( 1797 coordinates=[ 1798 beginCoordinates, 1799 endCoordinates]) 1800 self.pdcTmp.RemoveId(self.idZoomBoxTmp) 1801 self.Refresh() 1802 1803 def OnButtonDClick(self, event): 1804 """Open object dialog for editing.""" 1805 if self.mouse['use'] == 'pointer' and self.dragId != -1: 1806 itemCall = {'text': self.parent.OnAddText, 1807 'mapinfo': self.parent.OnAddMapinfo, 1808 'scalebar': self.parent.OnAddScalebar, 1809 'image': self.parent.OnAddImage, 1810 'northArrow': self.parent.OnAddNorthArrow, 1811 'point': self.parent.AddPoint, 1812 'line': self.parent.AddLine, 1813 'rectangle': self.parent.AddRectangle, 1814 'rasterLegend': self.parent.OnAddLegend, 1815 'vectorLegend': self.parent.OnAddLegend, 1816 'map': self.parent.OnAddMap} 1817 1818 itemArg = {'text': dict(event=None, id=self.dragId), 1819 'mapinfo': dict(event=None), 1820 'scalebar': dict(event=None), 1821 'image': dict(event=None, id=self.dragId), 1822 'northArrow': dict(event=None, id=self.dragId), 1823 'point': dict(id=self.dragId), 1824 'line': dict(id=self.dragId), 1825 'rectangle': dict(id=self.dragId), 1826 'rasterLegend': dict(event=None), 1827 'vectorLegend': dict(event=None, page=1), 1828 'map': dict(event=None, notebook=True)} 1829 1830 type = self.instruction[self.dragId].type 1831 itemCall[type](**itemArg[type]) 1832 1833 def OnDragging(self, event): 1834 """Process panning/resizing/drawing/moving.""" 1835 if event.MiddleIsDown(): 1836 # panning 1837 self.mouse['end'] = event.GetPosition() 1838 self.Pan(begin=self.mouse['begin'], end=self.mouse['end']) 1839 self.mouse['begin'] = event.GetPosition() 1840 1841 elif event.LeftIsDown(): 1842 # draw box when zooming, creating map 1843 if self.mouse['use'] in ( 1844 'zoomin', 'zoomout', 'addMap', 'addLine', 'addRectangle'): 1845 self.mouse['end'] = event.GetPosition() 1846 r = Rect( 1847 self.mouse['begin'][0], 1848 self.mouse['begin'][1], 1849 self.mouse['end'][0] - 1850 self.mouse['begin'][0], 1851 self.mouse['end'][1] - 1852 self.mouse['begin'][1]) 1853 r = self.modifyRectangle(r) 1854 1855 if self.mouse['use'] in ('addLine', 'addRectangle'): 1856 if self.mouse['use'] == 'addLine': 1857 pdcType = 'line' 1858 lineCoords = (self.mouse['begin'], self.mouse['end']) 1859 else: 1860 pdcType = 'rect' 1861 lineCoords = None 1862 if r[2] < 2 or r[3] < 2: 1863 # to avoid strange behavoiur 1864 return 1865 1866 self.Draw(pen=self.pen['line'], brush=self.brush['line'], 1867 pdc=self.pdcTmp, drawid=self.idZoomBoxTmp, 1868 pdctype=pdcType, bb=r, lineCoords=lineCoords) 1869 1870 else: 1871 self.Draw(pen=self.pen['box'], brush=self.brush['box'], 1872 pdc=self.pdcTmp, drawid=self.idZoomBoxTmp, 1873 pdctype='rect', bb=r) 1874 1875 # panning 1876 if self.mouse["use"] == 'pan': 1877 self.mouse['end'] = event.GetPosition() 1878 self.Pan(begin=self.mouse['begin'], end=self.mouse['end']) 1879 self.mouse['begin'] = event.GetPosition() 1880 1881 # move object 1882 if self.mouse['use'] == 'pointer' and self.dragId != -1: 1883 self.mouse['end'] = event.GetPosition() 1884 dx, dy = self.mouse['end'][ 1885 0] - self.begin[0], self.mouse['end'][1] - self.begin[1] 1886 self.pdcObj.TranslateId(self.dragId, dx, dy) 1887 self.pdcTmp.TranslateId(self.idBoxTmp, dx, dy) 1888 self.pdcTmp.TranslateId(self.idResizeBoxTmp, dx, dy) 1889 for id in self.idLinePointsTmp: 1890 self.pdcTmp.TranslateId(id, dx, dy) 1891 if self.instruction[self.dragId].type == 'text': 1892 self.instruction[self.dragId]['coords'] = self.instruction[self.dragId][ 1893 'coords'][0] + dx, self.instruction[self.dragId]['coords'][1] + dy 1894 self.begin = event.GetPosition() 1895 self.Refresh() 1896 1897 # resize object 1898 if self.mouse['use'] == 'resize': 1899 pos = event.GetPosition() 1900 diffX = pos[0] - self.mouse['begin'][0] 1901 diffY = pos[1] - self.mouse['begin'][1] 1902 if self.instruction[self.dragId].type == 'map': 1903 x, y = self.mapBounds.GetX(), self.mapBounds.GetY() 1904 width, height = self.mapBounds.GetWidth(), self.mapBounds.GetHeight() 1905 # match given region 1906 if self.constraint: 1907 if width > height: 1908 newWidth = width + diffX 1909 newHeight = height + diffX * \ 1910 (float(height) / width) 1911 else: 1912 newWidth = width + diffY * (float(width) / height) 1913 newHeight = height + diffY 1914 else: 1915 newWidth = width + diffX 1916 newHeight = height + diffY 1917 1918 if newWidth < 10 or newHeight < 10: 1919 return 1920 1921 bounds = Rect(x, y, newWidth, newHeight) 1922 self.Draw( 1923 pen=self.pen['map'], 1924 brush=self.brush['map'], 1925 pdc=self.pdcObj, 1926 drawid=self.dragId, 1927 pdctype='rectText', 1928 bb=bounds) 1929 1930 elif self.instruction[self.dragId].type == 'rectangle': 1931 instr = self.instruction[self.dragId] 1932 rect = self.CanvasPaperCoordinates( 1933 rect=instr['rect'], canvasToPaper=False) 1934 rect.SetWidth(rect.GetWidth() + diffX) 1935 rect.SetHeight(rect.GetHeight() + diffY) 1936 1937 if rect.GetWidth() < 5 or rect.GetHeight() < 5: 1938 return 1939 1940 self.DrawGraphics( 1941 drawid=self.dragId, 1942 shape='rectangle', 1943 color=instr['color'], 1944 fcolor=instr['fcolor'], 1945 width=instr['width'], 1946 bb=rect) 1947 1948 elif self.instruction[self.dragId].type == 'line': 1949 instr = self.instruction[self.dragId] 1950 points = instr['where'] 1951 # moving point 1952 if self.currentLinePoint == 0: 1953 pPaper = points[1] 1954 else: 1955 pPaper = points[0] 1956 pCanvas = self.CanvasPaperCoordinates( 1957 rect=Rect2DPS(pPaper, (0, 0)), canvasToPaper=False)[:2] 1958 bounds = wx.Rect(pCanvas, pos) 1959 self.DrawGraphics( 1960 drawid=self.dragId, 1961 shape='line', 1962 color=instr['color'], 1963 width=instr['width'], 1964 bb=bounds, 1965 lineCoords=( 1966 pos, 1967 pCanvas)) 1968 1969 # update paper coordinates 1970 points[self.currentLinePoint] = self.CanvasPaperCoordinates( 1971 rect=Rect(pos[0], pos[1], 0, 0), canvasToPaper=True)[:2] 1972 1973 self.RedrawSelectBox(self.dragId) 1974 1975 def OnMiddleDown(self, event): 1976 """Middle mouse button pressed.""" 1977 self.mouse['begin'] = event.GetPosition() 1978 1979 def Pan(self, begin, end): 1980 """Move canvas while dragging. 1981 1982 :param begin: x,y coordinates of first point 1983 :param end: x,y coordinates of second point 1984 """ 1985 view = begin[0] - end[0], begin[1] - end[1] 1986 zoomFactor = 1 1987 self.Zoom(zoomFactor, view) 1988 1989 def RecalculatePosition(self, ids): 1990 for id in ids: 1991 itype = self.instruction[id].type 1992 if itype in ('map', 'rectangle'): 1993 self.instruction[id]['rect'] = self.CanvasPaperCoordinates( 1994 rect=self.pdcObj.GetIdBounds(id), canvasToPaper=True) 1995 self.RecalculateEN() 1996 1997 elif itype in ('mapinfo', 'rasterLegend', 'vectorLegend', 'image', 'northArrow'): 1998 self.instruction[id]['rect'] = self.CanvasPaperCoordinates( 1999 rect=self.pdcObj.GetIdBounds(id), canvasToPaper=True) 2000 self.instruction[id]['where'] = self.CanvasPaperCoordinates( 2001 rect=self.pdcObj.GetIdBounds(id), canvasToPaper=True)[:2] 2002 if itype in ('image', 'northArrow'): 2003 self.RecalculateEN() 2004 2005 elif itype == 'point': 2006 rect = self.pdcObj.GetIdBounds(id) 2007 self.instruction[id]['rect'] = self.CanvasPaperCoordinates( 2008 rect=rect, canvasToPaper=True) 2009 rect.Offset( 2010 dx=rect.GetWidth() / 2, dy=rect.GetHeight() / 2, 2011 ) 2012 self.instruction[id]['where'] = self.CanvasPaperCoordinates( 2013 rect=rect, canvasToPaper=True)[: 2] 2014 self.RecalculateEN() 2015 2016 elif itype == 'line': 2017 rect = self.pdcObj.GetIdBounds(id) 2018 oldRect = self.instruction[id]['rect'] 2019 newRect = self.CanvasPaperCoordinates( 2020 rect=rect, canvasToPaper=True) 2021 xDiff = newRect[0] - oldRect[0] 2022 yDiff = newRect[1] - oldRect[1] 2023 self.instruction[id]['rect'] = newRect 2024 2025 point1 = wx.Point2D(*self.instruction[id]['where'][0]) 2026 point2 = wx.Point2D(*self.instruction[id]['where'][1]) 2027 point1 += wx.Point2D(xDiff, yDiff) 2028 point2 += wx.Point2D(xDiff, yDiff) 2029 self.instruction[id]['where'] = [point1, point2] 2030 2031 self.RecalculateEN() 2032 2033 elif itype == 'scalebar': 2034 self.instruction[id]['rect'] = self.CanvasPaperCoordinates( 2035 rect=self.pdcObj.GetIdBounds(id), canvasToPaper=True) 2036 2037 self.instruction[id]['where'] = self.instruction[ 2038 id]['rect'].GetCentre() 2039 2040 elif itype == 'text': 2041 x, y = self.instruction[id]['coords'][0] - self.instruction[id]['xoffset'],\ 2042 self.instruction[id]['coords'][1] + self.instruction[id]['yoffset'] 2043 extent = self.parent.getTextExtent( 2044 textDict=self.instruction[id]) 2045 if self.instruction[id]['rotate'] is not None: 2046 rot = float(self.instruction[id]['rotate']) / 180 * pi 2047 else: 2048 rot = 0 2049 2050 if self.instruction[id]['ref'].split()[0] == 'lower': 2051 y += extent[1] 2052 elif self.instruction[id]['ref'].split()[0] == 'center': 2053 y += extent[1] / 2 2054 if self.instruction[id]['ref'].split()[1] == 'right': 2055 x += extent[0] * cos(rot) 2056 y -= extent[0] * sin(rot) 2057 elif self.instruction[id]['ref'].split()[1] == 'center': 2058 x += extent[0] / 2 * cos(rot) 2059 y -= extent[0] / 2 * sin(rot) 2060 2061 self.instruction[id]['where'] = self.CanvasPaperCoordinates( 2062 rect=Rect2D(x, y, 0, 0), canvasToPaper=True)[:2] 2063 self.RecalculateEN() 2064 2065 def ComputeZoom(self, rect): 2066 """Computes zoom factor and scroll view""" 2067 zoomFactor = 1 2068 cW, cH = self.GetClientSize() 2069 cW = float(cW) 2070 if rect.IsEmpty(): # clicked on canvas 2071 zoomFactor = 1.5 2072 if self.mouse['use'] == 'zoomout': 2073 zoomFactor = 1. / zoomFactor 2074 x, y = self.mouse['begin'] 2075 xView = x - x / zoomFactor # x - cW/(zoomFactor * 2) 2076 yView = y - y / zoomFactor # y - cH/(zoomFactor * 2) 2077 2078 else: # dragging 2079 rW, rH = float(rect.GetWidth()), float(rect.GetHeight()) 2080 try: 2081 zoomFactor = 1 / max(rW / cW, rH / cH) 2082 except ZeroDivisionError: 2083 zoomFactor = 1 2084 # when zooming to full extent, in some cases, there was zoom 2085 # 1.01..., which causes problem 2086 if abs(zoomFactor - 1) > 0.01: 2087 zoomFactor = zoomFactor 2088 else: 2089 zoomFactor = 1. 2090 2091 if self.mouse['use'] == 'zoomout': 2092 zoomFactor = min(rW / cW, rH / cH) 2093 try: 2094 if rW / rH > cW / cH: 2095 yView = rect.GetY() - (rW * (cH / cW) - rH) / 2 2096 xView = rect.GetX() 2097 2098 if self.mouse['use'] == 'zoomout': 2099 x, y = rect.GetX() + (rW - (cW / cH) * rH) / 2, rect.GetY() 2100 xView, yView = -x, -y 2101 else: 2102 xView = rect.GetX() - (rH * (cW / cH) - rW) / 2 2103 yView = rect.GetY() 2104 if self.mouse['use'] == 'zoomout': 2105 x, y = rect.GetX(), rect.GetY() + (rH - (cH / cW) * rW) / 2 2106 xView, yView = -x, -y 2107 except ZeroDivisionError: 2108 xView, yView = rect.GetX(), rect.GetY() 2109 return zoomFactor, (int(xView), int(yView)) 2110 2111 def Zoom(self, zoomFactor, view): 2112 """Zoom to specified region, scroll view, redraw""" 2113 if not self.currScale: 2114 return 2115 self.currScale = self.currScale * zoomFactor 2116 2117 if self.currScale > 10 or self.currScale < 0.1: 2118 self.currScale = self.currScale / zoomFactor 2119 return 2120 if not self.preview: 2121 # redraw paper 2122 pRect = self.pdcPaper.GetIdBounds(self.pageId) 2123 if globalvar.wxPythonPhoenix: 2124 pRect.Offset(-view[0], -view[1]) 2125 else: 2126 pRect.OffsetXY(-view[0], -view[1]) 2127 pRect = self.ScaleRect(rect=pRect, scale=zoomFactor) 2128 self.DrawPaper(pRect) 2129 2130 # redraw objects 2131 for id in self.objectId: 2132 type = self.instruction[id].type 2133 if type == 'labels': # why it's here? it should not 2134 continue 2135 oRect = self.CanvasPaperCoordinates( 2136 rect=self.instruction[id]['rect'], canvasToPaper=False) 2137 2138 if type == 'text': 2139 # recalculate coordinates, they are not equal to BB 2140 coords = self.instruction[id]['coords'] 2141 self.instruction[id]['coords'] = coords = [ 2142 (int(coord) - view[i]) * zoomFactor for i, coord in enumerate(coords)] 2143 extent = self.parent.getTextExtent( 2144 textDict=self.instruction[id]) 2145 if self.instruction[id]['rotate']: 2146 rot = float(self.instruction[id]['rotate']) 2147 else: 2148 rot = 0 2149 self.instruction[id]['rect'] = bounds = self.parent.getModifiedTextBounds( 2150 coords[0], coords[1], extent, rot) 2151 self.DrawRotText( 2152 pdc=self.pdcObj, 2153 drawId=id, 2154 textDict=self.instruction[id], 2155 coords=coords, 2156 bounds=bounds) 2157 2158 self.pdcObj.SetIdBounds(id, bounds) 2159 2160 elif type == 'northArrow': 2161 self.Draw( 2162 pen=self.pen[type], 2163 brush=self.brush[type], 2164 pdc=self.pdcObj, 2165 drawid=id, 2166 pdctype='bitmap', 2167 bb=oRect) 2168 2169 elif type in ('point', 'line', 'rectangle'): 2170 instr = self.instruction[id] 2171 color = self.instruction[id]['color'] 2172 width = fcolor = coords = None 2173 2174 if type in ('point', 'rectangle'): 2175 fcolor = self.instruction[id]['fcolor'] 2176 if type in ('line', 'rectangle'): 2177 width = self.instruction[id]['width'] 2178 if type in ('line'): 2179 point1, point2 = instr['where'][0], instr['where'][1] 2180 point1 = self.CanvasPaperCoordinates( 2181 rect=Rect2DPS(point1, (0, 0)), canvasToPaper=False)[:2] 2182 point2 = self.CanvasPaperCoordinates( 2183 rect=Rect2DPS(point2, (0, 0)), canvasToPaper=False)[:2] 2184 coords = (point1, point2) 2185 2186 self.DrawGraphics( 2187 drawid=id, 2188 shape=type, 2189 bb=oRect, 2190 lineCoords=coords, 2191 color=color, 2192 fcolor=fcolor, 2193 width=width) 2194 2195 else: 2196 self.Draw( 2197 pen=self.pen[type], 2198 brush=self.brush[type], 2199 pdc=self.pdcObj, 2200 drawid=id, 2201 pdctype='rectText', 2202 bb=oRect) 2203 # redraw tmp objects 2204 if self.dragId != -1: 2205 self.RedrawSelectBox(self.dragId) 2206 2207 # redraw preview 2208 else: # preview mode 2209 imageRect = self.pdcImage.GetIdBounds(self.imageId) 2210 if globalvar.wxPythonPhoenix: 2211 imageRect.Offset(-view[0], -view[1]) 2212 else: 2213 imageRect.OffsetXY(-view[0], -view[1]) 2214 imageRect = self.ScaleRect(rect=imageRect, scale=zoomFactor) 2215 self.DrawImage(imageRect) 2216 2217 def ZoomAll(self): 2218 """Zoom to full extent""" 2219 if not self.preview: 2220 bounds = self.pdcPaper.GetIdBounds(self.pageId) 2221 else: 2222 bounds = self.pdcImage.GetIdBounds(self.imageId) 2223 zoomP = bounds.Inflate(bounds.width / 20, bounds.height / 20) 2224 zoomFactor, view = self.ComputeZoom(zoomP) 2225 self.Zoom(zoomFactor, view) 2226 2227 def Draw(self, pen, brush, pdc, drawid=None, pdctype='rect', 2228 bb=Rect(0, 0, 0, 0), lineCoords=None): 2229 """Draw object with given pen and brush. 2230 2231 :param pdc: PseudoDC 2232 :param pdctype: 'bitmap'/'rectText'/'rect'/'point'/'line' 2233 :param bb: bounding box 2234 :param lineCoords: coordinates of line start, end points (wx.Point, wx.Point) 2235 """ 2236 if drawid is None: 2237 drawid = NewId() 2238 bb = bb.Get() 2239 pdc.BeginDrawing() 2240 pdc.RemoveId(drawid) 2241 pdc.SetId(drawid) 2242 pdc.SetPen(pen) 2243 pdc.SetBrush(brush) 2244 2245 if pdctype == 'bitmap': 2246 if havePILImage: 2247 file = self.instruction[drawid]['epsfile'] 2248 rotation = self.instruction[drawid]['rotate'] 2249 self.DrawBitmap( 2250 pdc=pdc, 2251 filePath=file, 2252 rotation=rotation, 2253 bbox=bb) 2254 else: # draw only rectangle with label 2255 pdctype = 'rectText' 2256 2257 if pdctype in ('rect', 'rectText'): 2258 pdc.DrawRectangle(*bb) 2259 2260 if pdctype == 'rectText': 2261 # dc created because of method GetTextExtent, which pseudoDC lacks 2262 dc = ClientDC(self) 2263 font = dc.GetFont() 2264 size = 10 2265 font.SetPointSize(size) 2266 font.SetStyle(wx.ITALIC) 2267 dc.SetFont(font) 2268 pdc.SetFont(font) 2269 text = '\n'.join(self.itemLabels[drawid]) 2270 w, h, lh = dc.GetFullMultiLineTextExtent(text) 2271 textExtent = (w, h) 2272 textRect = Rect(0, 0, *textExtent).CenterIn(bb) 2273 r = map(int, bb) 2274 while not Rect(*r).ContainsRect(textRect) and size >= 8: 2275 size -= 2 2276 font.SetPointSize(size) 2277 dc.SetFont(font) 2278 pdc.SetFont(font) 2279 textExtent = dc.GetTextExtent(text) 2280 textRect = Rect(0, 0, *textExtent).CenterIn(bb) 2281 pdc.SetTextForeground(wx.Colour(100, 100, 100, 200)) 2282 pdc.SetBackgroundMode(wx.TRANSPARENT) 2283 pdc.DrawLabel(text=text, rect=textRect) 2284 2285 elif pdctype == 'point': 2286 pdc.DrawCircle(x=bb[0] + bb[2] / 2, 2287 y=bb[1] + bb[3] / 2, 2288 radius=bb[2] / 2) 2289 2290 elif pdctype == 'line': 2291 pdc.DrawLinePoint(lineCoords[0], lineCoords[1]) 2292 2293 pdc.SetIdBounds(drawid, bb) 2294 pdc.EndDrawing() 2295 self.Refresh() 2296 2297 return drawid 2298 2299 def DrawGraphics(self, drawid, shape, color, bb, 2300 width=None, fcolor=None, lineCoords=None): 2301 """Draw point/line/rectangle with given color and width 2302 2303 :param drawid: id of drawn object 2304 :param shape: drawn shape 'point'/'line'/'rectangle' 2305 :param color: pen outline color ('RRR:GGG:BBB') 2306 :param fcolor: brush fill color, if meaningful ('RRR:GGG:BBB') 2307 :param width: pen width 2308 :param bb: bounding box 2309 :param lineCoords: line coordinates (for line only) 2310 """ 2311 pdctype = {'point': 'point', 2312 'line': 'line', 2313 'rectangle': 'rect'} 2314 2315 if color == 'none': 2316 pen = wx.TRANSPARENT_PEN 2317 else: 2318 if width is not None: 2319 units = UnitConversion(self) 2320 width = int( 2321 units.convert( 2322 value=width, 2323 fromUnit='point', 2324 toUnit='pixel') * 2325 self.currScale) 2326 else: 2327 width = 2 2328 pen = wx.Pen(colour=convertRGB(color), width=width) 2329 pen.SetCap(wx.CAP_BUTT) # this is how ps.map draws 2330 2331 brush = wx.TRANSPARENT_BRUSH 2332 if fcolor and fcolor != 'none': 2333 brush = wx.Brush(colour=convertRGB(fcolor)) 2334 2335 self.Draw( 2336 pen=pen, 2337 brush=brush, 2338 pdc=self.pdcObj, 2339 pdctype=pdctype[shape], 2340 drawid=drawid, 2341 bb=bb, 2342 lineCoords=lineCoords) 2343 2344 def DrawBitmap(self, pdc, filePath, rotation, bbox): 2345 """Draw bitmap using PIL""" 2346 pImg = PILImage.open(filePath) 2347 if sys.platform == 'win32' and \ 2348 'eps' in os.path.splitext(filePath)[1].lower(): 2349 import types 2350 pImg.load = types.MethodType(loadPSForWindows, pImg) 2351 2352 if rotation: 2353 # get rid of black background 2354 pImg = pImg.convert("RGBA") 2355 rot = pImg.rotate(rotation, expand=1) 2356 new = PILImage.new('RGBA', rot.size, (255,) * 4) 2357 pImg = PILImage.composite(rot, new, rot) 2358 pImg = pImg.resize( 2359 (int( 2360 bbox[2]), int( 2361 bbox[3])), resample=PILImage.BICUBIC) 2362 img = PilImageToWxImage(pImg) 2363 bitmap = img.ConvertToBitmap() 2364 mask = wx.Mask(bitmap, wx.WHITE) 2365 bitmap.SetMask(mask) 2366 pdc.DrawBitmap(bitmap, bbox[0], bbox[1], useMask=True) 2367 2368 def DrawRotText(self, pdc, drawId, textDict, coords, bounds): 2369 if textDict['rotate']: 2370 rot = float(textDict['rotate']) 2371 else: 2372 rot = 0 2373 2374 if textDict['background'] != 'none': 2375 background = textDict['background'] 2376 else: 2377 background = None 2378 2379 pdc.RemoveId(drawId) 2380 pdc.SetId(drawId) 2381 pdc.BeginDrawing() 2382 2383 # border is not redrawn when zoom changes, why? 2384# if textDict['border'] != 'none' and not rot: 2385## units = UnitConversion(self) 2386# borderWidth = units.convert(value = textDict['width'], 2387# fromUnit = 'point', toUnit = 'pixel' ) * self.currScale 2388## pdc.SetPen(wx.Pen(colour = convertRGB(textDict['border']), width = borderWidth)) 2389# pdc.DrawRectangle(*bounds) 2390 2391 if background: 2392 pdc.SetTextBackground(convertRGB(background)) 2393 pdc.SetBackgroundMode(wx.SOLID) 2394 else: 2395 pdc.SetBackgroundMode(wx.TRANSPARENT) 2396 2397 fn = self.parent.makePSFont(textDict) 2398 2399 pdc.SetFont(fn) 2400 pdc.SetTextForeground(convertRGB(textDict['color'])) 2401 if rot == 0: 2402 pdc.DrawLabel(text=textDict['text'], rect=bounds) 2403 else: 2404 pdc.DrawRotatedText(textDict['text'], coords[0], coords[1], rot) 2405 2406 pdc.SetIdBounds(drawId, Rect(*bounds)) 2407 self.Refresh() 2408 pdc.EndDrawing() 2409 2410 def DrawImage(self, rect): 2411 """Draw preview image to pseudoDC""" 2412 self.pdcImage.ClearId(self.imageId) 2413 self.pdcImage.SetId(self.imageId) 2414 img = self.image 2415 2416 if img.GetWidth() != rect.width or img.GetHeight() != rect.height: 2417 img = img.Scale(rect.width, rect.height) 2418 bitmap = img.ConvertToBitmap() 2419 2420 self.pdcImage.BeginDrawing() 2421 self.pdcImage.DrawBitmap(bitmap, rect.x, rect.y) 2422 self.pdcImage.SetIdBounds(self.imageId, rect) 2423 self.pdcImage.EndDrawing() 2424 self.Refresh() 2425 2426 def DrawPaper(self, rect): 2427 """Draw paper and margins""" 2428 page = self.instruction[self.pageId] 2429 scale = page['Width'] / rect.GetWidth() 2430 w = (page['Width'] - page['Right'] - page['Left']) / scale 2431 h = (page['Height'] - page['Top'] - page['Bottom']) / scale 2432 x = page['Left'] / scale + rect.GetX() 2433 y = page['Top'] / scale + rect.GetY() 2434 2435 self.pdcPaper.BeginDrawing() 2436 self.pdcPaper.RemoveId(self.pageId) 2437 self.pdcPaper.SetId(self.pageId) 2438 self.pdcPaper.SetPen(self.pen['paper']) 2439 self.pdcPaper.SetBrush(self.brush['paper']) 2440 self.pdcPaper.DrawRectangleRect(rect) 2441 2442 self.pdcPaper.SetPen(self.pen['margins']) 2443 self.pdcPaper.SetBrush(self.brush['margins']) 2444 self.pdcPaper.DrawRectangle(x, y, w, h) 2445 2446 self.pdcPaper.SetIdBounds(self.pageId, rect) 2447 self.pdcPaper.EndDrawing() 2448 self.Refresh() 2449 2450 def ImageRect(self): 2451 """Returns image centered in canvas, computes scale""" 2452 img = wx.Image(self.imgName, wx.BITMAP_TYPE_PNG) 2453 cW, cH = self.GetClientSize() 2454 iW, iH = img.GetWidth(), img.GetHeight() 2455 2456 self.currScale = min(float(cW) / iW, float(cH) / iH) 2457 iW = iW * self.currScale 2458 iH = iH * self.currScale 2459 x = cW / 2 - iW / 2 2460 y = cH / 2 - iH / 2 2461 imageRect = Rect(x, y, iW, iH) 2462 2463 return imageRect 2464 2465 def RedrawSelectBox(self, id): 2466 """Redraws select box when selected object changes its size""" 2467 if self.dragId == id: 2468 rect = self.pdcObj.GetIdBounds(id) 2469 if self.instruction[id].type != 'line': 2470 rect = rect.Inflate(3, 3) 2471 # draw select box around object 2472 self.Draw( 2473 pen=self.pen['select'], 2474 brush=self.brush['select'], 2475 pdc=self.pdcTmp, 2476 drawid=self.idBoxTmp, 2477 pdctype='rect', 2478 bb=rect) 2479 2480 # draw small marks signalizing resizing 2481 if self.instruction[id].type in ('map', 'rectangle'): 2482 controlP = self.pdcObj.GetIdBounds(id).GetBottomRight() 2483 rect = Rect(controlP[0], controlP[1], self.resizeBoxSize[0], self.resizeBoxSize[1]) 2484 self.Draw( 2485 pen=self.pen['resize'], 2486 brush=self.brush['resize'], 2487 pdc=self.pdcTmp, 2488 drawid=self.idResizeBoxTmp, 2489 pdctype='rect', 2490 bb=rect) 2491 2492 elif self.instruction[id].type == 'line': 2493 p1Paper = self.instruction[id]['where'][0] 2494 p2Paper = self.instruction[id]['where'][1] 2495 p1Canvas = self.CanvasPaperCoordinates( 2496 rect=Rect2DPS(p1Paper, (0, 0)), canvasToPaper=False)[:2] 2497 p2Canvas = self.CanvasPaperCoordinates( 2498 rect=Rect2DPS(p2Paper, (0, 0)), canvasToPaper=False)[:2] 2499 rect = [] 2500 box = Rect(0, 0, self.resizeBoxSize[0], self.resizeBoxSize[1]) 2501 rect.append(box.CenterIn(Rect(p1Canvas[0], p1Canvas[1], 0, 0))) 2502 rect.append(box.CenterIn(Rect(p2Canvas[0], p2Canvas[1], 0, 0))) 2503 for i, point in enumerate((p1Canvas, p2Canvas)): 2504 self.Draw( 2505 pen=self.pen['resize'], 2506 brush=self.brush['resize'], 2507 pdc=self.pdcTmp, 2508 drawid=self.idLinePointsTmp[i], 2509 pdctype='rect', 2510 bb=rect[i]) 2511 2512 def UpdateMapLabel(self): 2513 """Updates map frame label""" 2514 2515 vector = self.instruction.FindInstructionByType('vector') 2516 if vector: 2517 vectorId = vector.id 2518 else: 2519 vectorId = None 2520 2521 raster = self.instruction.FindInstructionByType('raster') 2522 if raster: 2523 rasterId = raster.id 2524 else: 2525 rasterId = None 2526 2527 rasterName = 'None' 2528 if rasterId: 2529 rasterName = self.instruction[rasterId]['raster'].split('@')[0] 2530 2531 mapId = self.instruction.FindInstructionByType('map').id 2532 self.itemLabels[mapId] = [] 2533 self.itemLabels[mapId].append(self.itemLabelsDict['map']) 2534 self.itemLabels[mapId].append("raster: " + rasterName) 2535 if vectorId: 2536 for map in self.instruction[vectorId]['list']: 2537 self.itemLabels[mapId].append( 2538 'vector: ' + map[0].split('@')[0]) 2539 2540 labels = self.instruction.FindInstructionByType('labels') 2541 if labels: 2542 labelFiles = self.instruction[labels.id]['labels'] 2543 if not labelFiles: 2544 return 2545 labelFiles = [lFile.split('@')[0] for lFile in labelFiles] 2546 self.itemLabels[mapId].append( 2547 _("labels: ") + ', '.join(labelFiles)) 2548 2549 def UpdateLabel(self, itype, id): 2550 self.itemLabels[id] = [] 2551 self.itemLabels[id].append(self.itemLabelsDict[itype]) 2552 if itype == 'image': 2553 file = os.path.basename(self.instruction[id]['epsfile']) 2554 self.itemLabels[id].append(file) 2555 2556 def OnSize(self, event): 2557 """Init image size to match window size 2558 """ 2559 # not zoom all when notebook page is changed 2560 if self.preview and self.parent.currentPage == 1 or not self.preview and self.parent.currentPage == 0: 2561 self.ZoomAll() 2562 self.OnIdle(None) 2563 event.Skip() 2564 2565 def OnIdle(self, event): 2566 """Only re-render a image during idle time instead of 2567 multiple times during resizing. 2568 """ 2569 2570 width, height = self.GetClientSize() 2571 # Make new off screen bitmap: this bitmap will always have the 2572 # current drawing in it, so it can be used to save the image 2573 # to a file, or whatever. 2574 width = max(width, 20) 2575 height = max(height, 20) 2576 self._buffer = EmptyBitmap(width, height) 2577 # re-render image on idle 2578 self.resize = True 2579 2580 def ScaleRect(self, rect, scale): 2581 """Scale rectangle""" 2582 return Rect(rect.GetLeft() * scale, rect.GetTop() * scale, 2583 rect.GetSize()[0] * scale, rect.GetSize()[1] * scale) 2584