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