예제 #1
0
    def __init__(self, parent, giface, Map, properties, tree=None,
                 id=wx.ID_ANY, lmgr=None,
                 style=wx.NO_FULL_REPAINT_ON_RESIZE, **kwargs):
        BufferedMapWindow.__init__(self, parent=parent, giface=giface, Map=Map,
                                   properties=properties,
                                   style=style, **kwargs)
        self.lmgr = lmgr
        self.tree = tree
        self.pdcVector = PseudoDC()
        self.toolbar = self.parent.GetToolbar('vdigit')
        self.digit = None  # wxvdigit.IVDigit
        self._digitizingInfo = False  # digitizing with info

        # Emitted when info about digitizing updated
        # Parameter text is a string with information
        # currently used only for coordinates of mouse cursor + segmnt and
        # total feature length
        self.digitizingInfo = Signal('VDigitWindow.digitizingInfo')
        # Emitted when some info about digitizing is or will be availbale
        self.digitizingInfoAvailable = Signal('VDigitWindow.digitizingInfo')
        # Emitted when some info about digitizing is or will be availbale
        # digitizingInfo signal is emmited only between digitizingInfoAvailable
        # and digitizingInfoUnavailable signals
        self.digitizingInfoUnavailable = Signal('VDigitWindow.digitizingInfo')

        self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
        self.mouseMoving.connect(self._mouseMovingToDigitizingInfo)
예제 #2
0
파일: histogram.py 프로젝트: neteler/grass
    def __init__(
        self,
        parent,
        id=wx.ID_ANY,
        style=wx.NO_FULL_REPAINT_ON_RESIZE,
        Map=None,
        **kwargs,
    ):

        wx.Window.__init__(self, parent, id=id, style=style, **kwargs)

        self.parent = parent
        self.Map = Map
        self.mapname = self.parent.mapname

        #
        # Flags
        #
        self.render = True  # re-render the map from GRASS or just redraw image
        self.resize = False  # indicates whether or not a resize event has taken place
        self.dragimg = None  # initialize variable for map panning
        self.pen = None  # pen for drawing zoom boxes, etc.
        self._oldfont = self._oldencoding = None

        #
        # Event bindings
        #
        self.Bind(wx.EVT_PAINT, self.OnPaint)
        self.Bind(wx.EVT_SIZE, self.OnSize)
        self.Bind(wx.EVT_IDLE, self.OnIdle)

        #
        # Render output objects
        #
        self.mapfile = None  # image file to be rendered
        self.img = None  # wx.Image object (self.mapfile)

        self.imagedict = {
        }  # images and their PseudoDC ID's for painting and dragging

        self.pdc = PseudoDC()
        # will store an off screen empty bitmap for saving to file
        self._buffer = EmptyBitmap(max(1, self.Map.width),
                                   max(1, self.Map.height))

        # make sure that extents are updated at init
        self.Map.region = self.Map.GetRegion()
        self.Map.SetRegion()

        self._finishRenderingInfo = None

        self.Bind(wx.EVT_ERASE_BACKGROUND, lambda x: None)
예제 #3
0
    def __init__(self, parent, id=wx.ID_ANY,
                 style=wx.DEFAULT_FRAME_STYLE | wx.FULL_REPAINT_ON_RESIZE |
                 wx.BORDER_RAISED):
        Debug.msg(2, "AnimationWindow.__init__()")

        self.bitmap = EmptyBitmap(1, 1)
        self.parent = parent
        self._pdc = PseudoDC()
        self._overlay = None
        self._tmpMousePos = None
        self.x = self.y = 0
        self.bitmap_overlay = None

        BufferedWindow.__init__(self, parent=parent, id=id, style=style)
        self.SetBackgroundColour(wx.BLACK)
        self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM)
        self.Bind(wx.EVT_SIZE, self.OnSize)
        self.Bind(wx.EVT_MOUSE_EVENTS, self.OnMouseEvents)
예제 #4
0
class AnimationWindow(BufferedWindow):

    def __init__(self, parent, id=wx.ID_ANY,
                 style=wx.DEFAULT_FRAME_STYLE | wx.FULL_REPAINT_ON_RESIZE |
                 wx.BORDER_RAISED):
        Debug.msg(2, "AnimationWindow.__init__()")

        self.bitmap = EmptyBitmap(1, 1)
        self.parent = parent
        self._pdc = PseudoDC()
        self._overlay = None
        self._tmpMousePos = None
        self.x = self.y = 0
        self.bitmap_overlay = None

        BufferedWindow.__init__(self, parent=parent, id=id, style=style)
        self.SetBackgroundColour(wx.BLACK)
        self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM)
        self.Bind(wx.EVT_SIZE, self.OnSize)
        self.Bind(wx.EVT_MOUSE_EVENTS, self.OnMouseEvents)

    def Draw(self, dc):
        """Draws bitmap."""
        Debug.msg(5, "AnimationWindow.Draw()")

        dc.Clear()  # make sure you clear the bitmap!
        if self.bitmap.GetWidth() > 1:
            dc.DrawBitmap(self.bitmap, x=self.x, y=self.y)

    def OnSize(self, event):
        Debug.msg(5, "AnimationWindow.OnSize()")

        BufferedWindow.OnSize(self, event)
        if event:
            event.Skip()

    def _rescaleIfNeeded(self, bitmap):
        """!If the bitmap has different size than the window, rescale it."""
        bW, bH = bitmap.GetSize()
        wW, wH = self.GetClientSize()
        if abs(bW - wW) > 5 or abs(bH - wH) > 5:
            params = ComputeScaledRect((bW, bH), (wW, wH))
            im = wx.ImageFromBitmap(bitmap)
            im.Rescale(params['width'], params['height'])
            self.x = params['x']
            self.y = params['y']
            bitmap = wx.BitmapFromImage(im)
            if self._overlay:
                im = wx.ImageFromBitmap(self.bitmap_overlay)
                im.Rescale(
                    im.GetWidth() *
                    params['scale'],
                    im.GetHeight() *
                    params['scale'])
                self._setOverlay(
                    wx.BitmapFromImage(im),
                    xperc=self.perc[0],
                    yperc=self.perc[1])
        else:
            self.x = 0
            self.y = 0
        return bitmap

    def DrawBitmap(self, bitmap):
        """Draws bitmap.
        Does not draw the bitmap if it is the same one as last time.
        """
        bitmap = self._rescaleIfNeeded(bitmap)
        if self.bitmap == bitmap:
            return

        self.bitmap = bitmap
        self.UpdateDrawing()

    def DrawOverlay(self, x, y):
        self._pdc.BeginDrawing()
        self._pdc.SetId(1)
        self._pdc.DrawBitmap(bmp=self._overlay, x=x, y=y)
        self._pdc.SetIdBounds(1, Rect(x, y, self._overlay.GetWidth(),
                                         self._overlay.GetHeight()))
        self._pdc.EndDrawing()

    def _setOverlay(self, bitmap, xperc, yperc):
        if self._overlay:
            self._pdc.RemoveAll()
        self._overlay = bitmap
        size = self.GetClientSize()
        x = xperc * size[0]
        y = yperc * size[1]
        self.DrawOverlay(x, y)

    def SetOverlay(self, bitmap, xperc, yperc):
        """Sets overlay bitmap (legend)

        :param bitmap: instance of wx.Bitmap
        :param xperc: x coordinate of bitmap top left corner in % of screen
        :param yperc: y coordinate of bitmap top left corner in % of screen
        """
        Debug.msg(3, "AnimationWindow.SetOverlay()")
        if bitmap:
            self._setOverlay(bitmap, xperc, yperc)
            self.bitmap_overlay = bitmap
            self.perc = (xperc, yperc)
        else:
            self._overlay = None
            self._pdc.RemoveAll()
            self.bitmap_overlay = None
        self.UpdateDrawing()

    def ClearOverlay(self):
        """Clear overlay (legend) """
        Debug.msg(3, "AnimationWindow.ClearOverlay()")
        self._overlay = None
        self.bitmap_overlay = None
        self._pdc.RemoveAll()
        self.UpdateDrawing()

    def OnPaint(self, event):
        Debug.msg(5, "AnimationWindow.OnPaint()")
        # All that is needed here is to draw the buffer to screen
        dc = wx.BufferedPaintDC(self, self._Buffer)
        if self._overlay:
            self._pdc.DrawToDC(dc)

    def OnMouseEvents(self, event):
        """Handle mouse events."""
        # If it grows larger, split it.
        current = event.GetPosition()
        if event.LeftDown():
            self._dragid = None
            idlist = self._pdc.FindObjects(current[0], current[1],
                                           radius=10)
            if 1 in idlist:
                self._dragid = 1
            self._tmpMousePos = current

        elif event.LeftUp():
            self._dragid = None
            self._tmpMousePos = None

        elif event.Dragging():
            if self._dragid is None:
                return
            dx = current[0] - self._tmpMousePos[0]
            dy = current[1] - self._tmpMousePos[1]
            self._pdc.TranslateId(self._dragid, dx, dy)
            self.UpdateDrawing()
            self._tmpMousePos = current

    def GetOverlayPos(self):
        """Returns x, y position in pixels"""
        rect = self._pdc.GetIdBounds(1)
        return rect.GetX(), rect.GetY()
예제 #5
0
class BufferedWindow(wx.Window):
    """A Buffered window class.

    When the drawing needs to change, you app needs to call the
    UpdateHist() method. Since the drawing is stored in a bitmap, you
    can also save the drawing to file by calling the
    SaveToFile(self,file_name,file_type) method.
    """
    def __init__(self,
                 parent,
                 id=wx.ID_ANY,
                 style=wx.NO_FULL_REPAINT_ON_RESIZE,
                 Map=None,
                 **kwargs):

        wx.Window.__init__(self, parent, id=id, style=style, **kwargs)

        self.parent = parent
        self.Map = Map
        self.mapname = self.parent.mapname

        #
        # Flags
        #
        self.render = True  # re-render the map from GRASS or just redraw image
        self.resize = False  # indicates whether or not a resize event has taken place
        self.dragimg = None  # initialize variable for map panning
        self.pen = None  # pen for drawing zoom boxes, etc.
        self._oldfont = self._oldencoding = None

        #
        # Event bindings
        #
        self.Bind(wx.EVT_PAINT, self.OnPaint)
        self.Bind(wx.EVT_SIZE, self.OnSize)
        self.Bind(wx.EVT_IDLE, self.OnIdle)

        #
        # Render output objects
        #
        self.mapfile = None  # image file to be rendered
        self.img = None  # wx.Image object (self.mapfile)

        self.imagedict = {
        }  # images and their PseudoDC ID's for painting and dragging

        self.pdc = PseudoDC()
        # will store an off screen empty bitmap for saving to file
        self._buffer = EmptyBitmap(max(1, self.Map.width),
                                   max(1, self.Map.height))

        # make sure that extents are updated at init
        self.Map.region = self.Map.GetRegion()
        self.Map.SetRegion()

        self._finishRenderingInfo = None

        self.Bind(wx.EVT_ERASE_BACKGROUND, lambda x: None)

    def Draw(self,
             pdc,
             img=None,
             drawid=None,
             pdctype='image',
             coords=[0, 0, 0, 0]):
        """Draws histogram or clears window
        """
        if drawid is None:
            if pdctype == 'image':
                drawid = self.imagedict[img]
            elif pdctype == 'clear':
                drawid is None
            else:
                drawid = NewId()
        else:
            pdc.SetId(drawid)

        pdc.BeginDrawing()

        Debug.msg(
            3, "BufferedWindow.Draw(): id=%s, pdctype=%s, coord=%s" %
            (drawid, pdctype, coords))

        if pdctype == 'clear':  # erase the display
            bg = wx.WHITE_BRUSH
            pdc.SetBackground(bg)
            pdc.Clear()
            self.Refresh()
            pdc.EndDrawing()
            return

        if pdctype == 'image':
            bg = wx.TRANSPARENT_BRUSH
            pdc.SetBackground(bg)
            bitmap = BitmapFromImage(img)
            w, h = bitmap.GetSize()
            pdc.DrawBitmap(bitmap, coords[0], coords[1],
                           True)  # draw the composite map
            pdc.SetIdBounds(drawid, (coords[0], coords[1], w, h))

        pdc.EndDrawing()
        self.Refresh()

    def OnPaint(self, event):
        """Draw psuedo DC to buffer
        """
        dc = wx.BufferedPaintDC(self, self._buffer)

        # use PrepareDC to set position correctly
        # probably does nothing, removed from wxPython 2.9
        # self.PrepareDC(dc)
        # we need to clear the dc BEFORE calling PrepareDC
        bg = wx.Brush(self.GetBackgroundColour())
        dc.SetBackground(bg)
        dc.Clear()
        # create a clipping rect from our position and size
        # and the Update Region
        rgn = self.GetUpdateRegion()
        r = rgn.GetBox()
        # draw to the dc using the calculated clipping rect
        self.pdc.DrawToDCClipped(dc, r)

    def OnSize(self, event):
        """Init image size to match window size
        """
        # set size of the input image
        self.Map.width, self.Map.height = self.GetClientSize()

        # Make new off screen bitmap: this bitmap will always have the
        # current drawing in it, so it can be used to save the image to
        # a file, or whatever.
        self._buffer = EmptyBitmap(self.Map.width, self.Map.height)

        # get the image to be rendered
        self.img = self.GetImage()

        # update map display
        if self.img and self.Map.width + self.Map.height > 0:  # scale image during resize
            self.img = self.img.Scale(self.Map.width, self.Map.height)
            self.render = False
            self.UpdateHist()

        # re-render image on idle
        self.resize = True

    def OnIdle(self, event):
        """Only re-render a histogram image from GRASS during idle
        time instead of multiple times during resizing.
        """
        if self.resize:
            self.render = True
            self.UpdateHist()
        event.Skip()

    def SaveToFile(self, FileName, FileType, width, height):
        """This will save the contents of the buffer to the specified
        file. See the wx.Windows docs for wx.Bitmap::SaveFile for the
        details
        """
        wx.GetApp().Yield()
        self._finishRenderingInfo = (FileName, FileType, width, height)
        self.Map.GetRenderMgr().updateMap.connect(self._finishSaveToFile)
        self.Map.ChangeMapSize((width, height))
        self.Map.Render(force=True, windres=True)

    def _finishSaveToFile(self):
        img = self.GetImage()
        self.Draw(self.pdc, img, drawid=99)
        FileName, FileType, width, height = self._finishRenderingInfo
        ibuffer = EmptyBitmap(max(1, width), max(1, height))
        dc = wx.BufferedDC(None, ibuffer)
        dc.Clear()
        self.pdc.DrawToDC(dc)
        ibuffer.SaveFile(FileName, FileType)
        self.Map.GetRenderMgr().updateMap.disconnect(self._finishSaveToFile)
        self._finishRenderingInfo = None

    def GetImage(self):
        """Converts files to wx.Image
        """
        if self.Map.mapfile and os.path.isfile(self.Map.mapfile) and \
                os.path.getsize(self.Map.mapfile):
            img = wx.Image(self.Map.mapfile, wx.BITMAP_TYPE_ANY)
        else:
            img = None

        self.imagedict[img] = 99  # set image PeudoDC ID
        return img

    def UpdateHist(self, img=None):
        """Update canvas if histogram options changes or window
        changes geometry
        """
        Debug.msg(
            2, "BufferedWindow.UpdateHist(%s): render=%s" % (img, self.render))

        if not self.render:
            return

        # render new map images
        # set default font and encoding environmental variables
        if "GRASS_FONT" in os.environ:
            self._oldfont = os.environ["GRASS_FONT"]
        if self.parent.font:
            os.environ["GRASS_FONT"] = self.parent.font
        if "GRASS_ENCODING" in os.environ:
            self._oldencoding = os.environ["GRASS_ENCODING"]
        if self.parent.encoding is not None and self.parent.encoding != "ISO-8859-1":
            os.environ["GRASS_ENCODING"] = self.parent.encoding

        # using active comp region
        self.Map.GetRegion(update=True)

        self.Map.width, self.Map.height = self.GetClientSize()
        self.mapfile = self.Map.Render(force=self.render)
        self.Map.GetRenderMgr().renderDone.connect(self.UpdateHistDone)

    def UpdateHistDone(self):
        """Histogram image generated, finish rendering."""
        self.img = self.GetImage()
        self.resize = False

        if not self.img:
            return
        try:
            id = self.imagedict[self.img]
        except:
            return

        # paint images to PseudoDC
        self.pdc.Clear()
        self.pdc.RemoveAll()
        self.Draw(self.pdc, self.img, drawid=id)  # draw map image background

        self.resize = False

        # update statusbar
        self.Map.SetRegion()
        self.parent.statusbar.SetStatusText("Image/Raster map <%s>" %
                                            self.parent.mapname)

        # set default font and encoding environmental variables
        if self._oldfont:
            os.environ["GRASS_FONT"] = self._oldfont
        if self._oldencoding:
            os.environ["GRASS_ENCODING"] = self._oldencoding

    def EraseMap(self):
        """Erase the map display
        """
        self.Draw(self.pdc, pdctype='clear')
예제 #6
0
파일: toolbars.py 프로젝트: cwhite911/grass
    def StartEditing(self, mapLayer):
        """Start editing selected vector map layer.

        :param mapLayer: MapLayer to be edited
        """
        # check if topology is available (skip for hidden - temporary
        # maps, see iclass for details)
        if (
            not mapLayer.IsHidden()
            and grass.vector_info(mapLayer.GetName())["level"] != 2
        ):
            dlg = wx.MessageDialog(
                parent=self.MapWindow,
                message=_(
                    "Topology for vector map <%s> is not available. "
                    "Topology is required by digitizer.\nDo you want to "
                    "rebuild topology (takes some time) and open the vector map "
                    "for editing?"
                )
                % mapLayer.GetName(),
                caption=_("Digitizer error"),
                style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION | wx.CENTRE,
            )
            if dlg.ShowModal() == wx.ID_YES:
                RunCommand("v.build", map=mapLayer.GetName())
            else:
                return

        # deactive layer
        self.Map.ChangeLayerActive(mapLayer, False)

        # clean map canvas
        self.MapWindow.EraseMap()

        # unset background map if needed
        if mapLayer:
            if (
                UserSettings.Get(
                    group="vdigit",
                    key="bgmap",
                    subkey="value",
                    settings_type="internal",
                )
                == mapLayer.GetName()
            ):
                UserSettings.Set(
                    group="vdigit",
                    key="bgmap",
                    subkey="value",
                    value="",
                    settings_type="internal",
                )

            self.parent.SetStatusText(
                _("Please wait, " "opening vector map <%s> for editing...")
                % mapLayer.GetName(),
                0,
            )

        self.MapWindow.pdcVector = PseudoDC()
        self.digit = self.MapWindow.digit = self.digitClass(mapwindow=self.MapWindow)

        self.mapLayer = mapLayer
        # open vector map (assume that 'hidden' map layer is temporary vector
        # map)
        if self.digit.OpenMap(mapLayer.GetName(), tmp=mapLayer.IsHidden()) is None:
            self.mapLayer = None
            self.StopEditing()
            return False

        # check feature type (only for OGR layers)
        self.fType = self.digit.GetFeatureType()
        self.EnableAll()
        self.EnableUndo(False)
        self.EnableRedo(False)

        if self.fType == "point":
            for tool in (
                self.addLine,
                self.addArea,
                self.moveVertex,
                self.addVertex,
                self.removeVertex,
                self.editLine,
            ):
                self.EnableTool(tool, False)
        elif self.fType == "linestring":
            for tool in (self.addPoint, self.addArea):
                self.EnableTool(tool, False)
        elif self.fType == "polygon":
            for tool in (self.addPoint, self.addLine):
                self.EnableTool(tool, False)
        elif self.fType:
            GError(
                parent=self,
                message=_(
                    "Unsupported feature type '%(type)s'. Unable to edit "
                    "OGR layer <%(layer)s>."
                )
                % {"type": self.fType, "layer": mapLayer.GetName()},
            )
            self.digit.CloseMap()
            self.mapLayer = None
            self.StopEditing()
            return False

        # update toolbar
        if self.combo:
            self.combo.SetValue(mapLayer.GetName())
        if "map" in self.parent.toolbars:
            self.parent.toolbars["map"].combo.SetValue(_("Vector digitizer"))

        # here was dead code to enable vdigit button in toolbar
        # with if to ignore iclass
        # some signal (DigitizerStarted) can be emitted here

        Debug.msg(4, "VDigitToolbar.StartEditing(): layer=%s" % mapLayer.GetName())

        # change cursor
        if self.MapWindow.mouse["use"] == "pointer":
            self.MapWindow.SetNamedCursor("cross")

        if not self.MapWindow.resize:
            self.MapWindow.UpdateMap(render=True)

        # respect opacity
        opacity = mapLayer.GetOpacity()

        if opacity < 1.0:
            alpha = int(opacity * 255)
            self.digit.GetDisplay().UpdateSettings(alpha=alpha)

        # emit signal
        layerTree = self._giface.GetLayerTree()
        if layerTree:
            item = layerTree.FindItemByData("maplayer", self.mapLayer)
        else:
            item = None
        self.editingStarted.emit(
            vectMap=mapLayer.GetName(), digit=self.digit, layerItem=item
        )

        return True
예제 #7
0
class VDigitWindow(BufferedMapWindow):
    """A Buffered window extended for vector digitizer."""

    def __init__(
        self,
        parent,
        giface,
        Map,
        properties,
        tree=None,
        id=wx.ID_ANY,
        lmgr=None,
        style=wx.NO_FULL_REPAINT_ON_RESIZE,
        **kwargs,
    ):
        BufferedMapWindow.__init__(
            self,
            parent=parent,
            giface=giface,
            Map=Map,
            properties=properties,
            style=style,
            **kwargs,
        )
        self.lmgr = lmgr
        self.tree = tree
        self.pdcVector = PseudoDC()
        self.toolbar = self.parent.GetToolbar("vdigit")
        self.digit = None  # wxvdigit.IVDigit
        self._digitizingInfo = False  # digitizing with info

        # Emitted when info about digitizing updated
        # Parameter text is a string with information
        # currently used only for coordinates of mouse cursor + segmnt and
        # total feature length
        self.digitizingInfo = Signal("VDigitWindow.digitizingInfo")
        # Emitted when some info about digitizing is or will be availbale
        self.digitizingInfoAvailable = Signal("VDigitWindow.digitizingInfo")
        # Emitted when some info about digitizing is or will be availbale
        # digitizingInfo signal is emitted only between digitizingInfoAvailable
        # and digitizingInfoUnavailable signals
        self.digitizingInfoUnavailable = Signal("VDigitWindow.digitizingInfo")

        self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
        self.mouseMoving.connect(self._mouseMovingToDigitizingInfo)

    def GetDisplay(self):
        if self.digit:
            return self.digit.GetDisplay()
        return None

    def GetDigit(self):
        """Get digit class"""
        return self.digit

    def SetToolbar(self, toolbar):
        """Set up related toolbar"""
        self.toolbar = toolbar

    def _mouseMovingToDigitizingInfo(self, x, y):
        e, n = x, y
        precision = int(
            UserSettings.Get(group="projection", key="format", subkey="precision")
        )
        if (
            self.toolbar.GetAction() != "addLine"
            or self.toolbar.GetAction("type") not in ("line", "boundary")
            or len(self.polycoords) == 0
        ):
            # we cannot provide info, so find out if it is something new
            if self._digitizingInfo:
                self._digitizingInfo = False
                self.digitizingInfoUnavailable.emit()
            return
        # else, we can provide info, so find out if it is first time
        if not self._digitizingInfo:
            self._digitizingInfo = True
            self.digitizingInfoAvailable.emit()

        # for linear feature show segment and total length
        distance_seg = self.Distance(self.polycoords[-1], (e, n), screen=False)[0]
        distance_tot = distance_seg
        for idx in range(1, len(self.polycoords)):
            distance_tot += self.Distance(
                self.polycoords[idx - 1], self.polycoords[idx], screen=False
            )[0]
        text = "seg: %.*f; tot: %.*f" % (
            precision,
            distance_seg,
            precision,
            distance_tot,
        )
        self.digitizingInfo.emit(text=text)

    def OnKeyDown(self, event):
        """Key pressed"""
        shift = event.ShiftDown()
        kc = event.GetKeyCode()

        event = None
        if not shift:
            if kc == ord("P"):
                event = wx.CommandEvent(winid=self.toolbar.addPoint)
                tool = self.toolbar.OnAddPoint
            elif kc == ord("L"):
                event = wx.CommandEvent(winid=self.toolbar.addLine)
                tool = self.toolbar.OnAddLine
        if event:
            self.toolbar.OnTool(event)
            tool(event)

    def _updateMap(self):
        if not self.toolbar or not self.toolbar.GetLayer():
            return

        # set region
        self.digit.GetDisplay().UpdateRegion()
        # re-calculate threshold for digitization tool
        # self.parent.digit.GetDisplay().GetThreshold()
        # draw map
        # self.pdcVector.Clear()
        self.pdcVector.RemoveAll()

        item = None
        if self.tree:
            try:
                item = self.tree.FindItemByData("maplayer", self.toolbar.GetLayer())
            except TypeError:
                pass

        if not self.tree or (self.tree and item and self.tree.IsItemChecked(item)):
            self.redrawAll = True
            self.digit.GetDisplay().DrawMap()

        # translate tmp objects (pointer position)
        if self.toolbar.GetAction() == "moveLine" and hasattr(self, "moveInfo"):
            if "beginDiff" in self.moveInfo:
                # move line
                for id in self.moveInfo["id"]:
                    self.pdcTmp.TranslateId(
                        id, self.moveInfo["beginDiff"][0], self.moveInfo["beginDiff"][1]
                    )
                del self.moveInfo["beginDiff"]

    def OnLeftDownAddLine(self, event):
        """Left mouse button pressed - add new feature"""
        try:
            mapLayer = self.toolbar.GetLayer().GetName()
        except:
            return

        if self.toolbar.GetAction("type") in ["point", "centroid"]:
            # add new point / centroiud
            east, north = self.Pixel2Cell(self.mouse["begin"])
            nfeat, fids = self.digit.AddFeature(
                self.toolbar.GetAction("type"), [(east, north)]
            )
            if nfeat < 1:
                return

            self.UpdateMap(render=False)  # redraw map

            # add new record into attribute table
            if UserSettings.Get(group="vdigit", key="addRecord", subkey="enabled"):
                # select attributes based on layer and category
                cats = {
                    fids[0]: {
                        UserSettings.Get(group="vdigit", key="layer", subkey="value"): (
                            UserSettings.Get(
                                group="vdigit", key="category", subkey="value"
                            ),
                        )
                    }
                }

                posWindow = self.ClientToScreen(
                    (
                        self.mouse["end"][0] + self.dialogOffset,
                        self.mouse["end"][1] + self.dialogOffset,
                    )
                )

                addRecordDlg = DisplayAttributesDialog(
                    parent=self,
                    map=mapLayer,
                    cats=cats,
                    pos=posWindow,
                    action="add",
                    ignoreError=True,
                )

                if self.toolbar.GetAction("type") == "centroid":
                    for fid in fids:
                        self._geomAttrb(fid, addRecordDlg, "area")
                        self._geomAttrb(fid, addRecordDlg, "perimeter")

                if addRecordDlg.IsFound():
                    addRecordDlg.ShowModal()
                addRecordDlg.Destroy()

        elif self.toolbar.GetAction("type") in ["line", "boundary", "area"]:
            # add new point to the line
            self.polycoords.append(self.Pixel2Cell(event.GetPosition()))
            self.DrawLines(pdc=self.pdcTmp)

    def _geomAttrb(self, fid, dialog, attrb):
        """Define geometry attributes"""
        mapLayer = self.toolbar.GetLayer()
        if self.tree:
            item = self.tree.FindItemByData("maplayer", mapLayer)
            vdigit = self.tree.GetLayerInfo(item, key="vdigit")
        else:
            item = vdigit = None

        if not vdigit or "geomAttr" not in vdigit or attrb not in vdigit["geomAttr"]:
            return

        val = -1
        if attrb == "length":
            val = self.digit.GetLineLength(fid)
            type = attrb
        elif attrb == "area":
            val = self.digit.GetAreaSize(fid)
            type = attrb
        elif attrb == "perimeter":
            val = self.digit.GetAreaPerimeter(fid)
            type = "length"

        if val > 0:
            layer = int(UserSettings.Get(group="vdigit", key="layer", subkey="value"))
            column = vdigit["geomAttr"][attrb]["column"]
            val = UnitsConvertValue(val, type, vdigit["geomAttr"][attrb]["units"])
            dialog.SetColumnValue(layer, column, val)
            dialog.OnReset()

    def _geomAttrbUpdate(self, fids):
        """Update geometry atrributes of currently selected features

        :param fid: list feature id
        """
        mapLayer = self.parent.toolbars["vdigit"].GetLayer()
        vectorName = mapLayer.GetName()
        if self.tree:
            item = self.tree.FindItemByData("maplayer", mapLayer)
            vdigit = self.tree.GetLayerInfo(item, key="vdigit")
        else:
            item = vdigit = None

        if not vdigit or "geomAttr" not in vdigit:
            return

        dbInfo = gselect.VectorDBInfo(vectorName)
        sqlfile = tempfile.NamedTemporaryFile(mode="w")
        for fid in fids:
            for layer, cats in six.iteritems(self.digit.GetLineCats(fid)):
                table = dbInfo.GetTable(layer)
                for attrb, item in six.iteritems(vdigit["geomAttr"]):
                    val = -1
                    if attrb == "length":
                        val = self.digit.GetLineLength(fid)
                        type = attrb
                    elif attrb == "area":
                        val = self.digit.GetAreaSize(fid)
                        type = attrb
                    elif attrb == "perimeter":
                        val = self.digit.GetAreaPerimeter(fid)
                        type = "length"

                    if val < 0:
                        continue
                    val = UnitsConvertValue(val, type, item["units"])

                    for cat in cats:
                        sqlfile.write(
                            "UPDATE %s SET %s = %f WHERE %s = %d;\n"
                            % (
                                table,
                                item["column"],
                                val,
                                dbInfo.GetKeyColumn(layer),
                                cat,
                            )
                        )

            sqlfile.file.flush()
            RunCommand("db.execute", parent=True, quiet=True, input=sqlfile.name)

    def _updateATM(self):
        """Update open Attribute Table Manager

        .. todo::
            use AddDataRow() instead
        """
        if not self.lmgr:
            return

        # update ATM
        digitVector = self.toolbar.GetLayer().GetName()

        for atm in self.lmgr.dialogs["atm"]:
            atmVector = atm.GetVectorName()
            if atmVector == digitVector:
                layer = UserSettings.Get(group="vdigit", key="layer", subkey="value")
                # TODO: use AddDataRow instead
                atm.LoadData(layer)

    def OnLeftDownEditLine(self, event):
        """Left mouse button pressed - edit linear feature - add new
        vertex.
        """
        self.polycoords.append(self.Pixel2Cell(self.mouse["begin"]))
        self.moveInfo["id"].append(NewId())
        self.DrawLines(pdc=self.pdcTmp)

    def OnLeftDownMoveLine(self, event):
        """Left mouse button pressed - vector digitizer move
        feature/vertex, edit linear feature
        """
        self.moveInfo = dict()
        # geographic coordinates of initial position (left-down)
        self.moveInfo["begin"] = None
        # list of ids to modify
        self.moveInfo["id"] = list()

        # set pen
        if self.toolbar.GetAction() in ["moveVertex", "editLine"]:
            pcolor = UserSettings.Get(
                group="vdigit", key="symbol", subkey=["highlight", "color"]
            )
            self.pen = self.polypen = wx.Pen(
                colour=pcolor, width=2, style=wx.SHORT_DASH
            )
            self.pdcTmp.SetPen(self.polypen)

    def OnLeftDownDisplayCA(self, event):
        """Left mouse button pressed - vector digitizer display categories
        or attributes action
        """
        try:
            mapLayer = self.toolbar.GetLayer().GetName()
        except:
            return

        coords = self.Pixel2Cell(self.mouse["begin"])

        # unselect
        self.digit.GetDisplay().SetSelected([])

        # select feature by point
        cats = {}
        self.digit.GetDisplay().SelectLineByPoint(coords)

        if not self.digit.GetDisplay().GetSelected():
            for key in ("attributes", "category"):
                if self.parent.dialogs[key] and self.parent.dialogs[key].IsShown():
                    self.parent.dialogs[key].Hide()
            self.UpdateMap(render=False, renderVector=True)
            return

        if UserSettings.Get(group="vdigit", key="checkForDupl", subkey="enabled"):
            lines = self.digit.GetDisplay().GetSelected()
        else:
            lines = (self.digit.GetDisplay().GetSelected()[0],)  # only first found

        for line in lines:
            cats[line] = self.digit.GetLineCats(line)

        posWindow = self.ClientToScreen(
            (
                self.mouse["end"][0] + self.dialogOffset,
                self.mouse["end"][1] + self.dialogOffset,
            )
        )

        if self.toolbar.GetAction() == "displayAttrs":
            # select attributes based on coordinates (all layers)
            if self.parent.dialogs["attributes"] is None:
                self.parent.dialogs["attributes"] = DisplayAttributesDialog(
                    parent=self, map=mapLayer, cats=cats, action="update"
                )
            else:
                # upgrade dialog
                self.parent.dialogs["attributes"].UpdateDialog(cats=cats)

            if (
                self.parent.dialogs["attributes"]
                and self.parent.dialogs["attributes"].mapDBInfo
            ):
                if len(cats.keys()) > 0:
                    # highlight feature & re-draw map
                    if not self.parent.dialogs["attributes"].IsShown():
                        self.parent.dialogs["attributes"].Show()
                else:
                    if (
                        self.parent.dialogs["attributes"]
                        and self.parent.dialogs["attributes"].IsShown()
                    ):
                        self.parent.dialogs["attributes"].Hide()

        else:  # displayCats
            if self.parent.dialogs["category"] is None:
                # open new dialog
                dlg = VDigitCategoryDialog(
                    parent=self,
                    vectorName=mapLayer,
                    cats=cats,
                    pos=posWindow,
                    title=_("Update categories"),
                )
                self.parent.dialogs["category"] = dlg
            else:
                # update currently open dialog
                self.parent.dialogs["category"].UpdateDialog(cats=cats)

            if self.parent.dialogs["category"]:
                if len(cats.keys()) > 0:
                    # highlight feature & re-draw map
                    if not self.parent.dialogs["category"].IsShown():
                        self.parent.dialogs["category"].Show()
                else:
                    if self.parent.dialogs["category"].IsShown():
                        self.parent.dialogs["category"].Hide()

        self.UpdateMap(render=False, renderVector=True)

    def OnLeftDownCopyCA(self, event):
        """Left mouse button pressed - vector digitizer copy
        categories or attributes action
        """
        if not hasattr(self, "copyCatsList"):
            self.copyCatsList = []
        else:
            self.copyCatsIds = []
            self.mouse["box"] = "box"

    def OnLeftDownCopyLine(self, event):
        """Left mouse button pressed - vector digitizer copy lines
        action
        """
        if not hasattr(self, "copyIds"):
            self.copyIds = []
            self.layerTmp = None

    def OnLeftDownBulkLine(self, event):
        """Left mouse button pressed - vector digitizer label 3D
        vector lines
        """
        if len(self.polycoords) > 1:  # start new line
            self.polycoords = []
            self.ClearLines(pdc=self.pdcTmp)
        self.polycoords.append(self.Pixel2Cell(event.GetPosition()))
        if len(self.polycoords) == 1:
            begin = self.Pixel2Cell(self.polycoords[-1])
            end = self.Pixel2Cell(self.mouse["end"])
        else:
            end = self.Pixel2Cell(self.polycoords[-1])
            begin = self.Pixel2Cell(self.mouse["begin"])

            self.DrawLines(self.pdcTmp, polycoords=(begin, end))

    def OnLeftDownUndo(self, event):
        """Left mouse button pressed with control key - vector
        digitizer undo functionality
        """
        if self.mouse["use"] != "pointer" or not self.toolbar:
            return

        action = self.toolbar.GetAction()
        if (
            action == "addLine"
            and self.toolbar.GetAction("type") in ["line", "boundary", "area"]
        ) or action == "editLine":
            # add line or boundary -> remove last point from the line
            try:
                removed = self.polycoords.pop()
                Debug.msg(
                    4,
                    "VDigitWindow.OnMiddleDown(): polycoords_poped=%s"
                    % [
                        removed,
                    ],
                )
                # self.mouse['begin'] = self.Cell2Pixel(self.polycoords[-1])
            except:
                pass

        if action == "editLine":
            # remove last vertex & line
            if len(self.moveInfo["id"]) > 1:
                self.moveInfo["id"].pop()

            self.UpdateMap(render=False, renderVector=False)

        elif action in [
            "deleteLine",
            "deleteArea",
            "moveLine",
            "splitLine",
            "addVertex",
            "removeVertex",
            "moveVertex",
            "copyCats",
            "flipLine",
            "mergeLine",
            "snapLine",
            "connectLine",
            "copyLine",
            "queryLine",
            "breakLine",
            "typeConv",
        ]:
            # various tools -> unselected selected features
            self.digit.GetDisplay().SetSelected([])

            if action in ["moveLine", "moveVertex", "editLine"] and hasattr(
                self, "moveInfo"
            ):
                del self.moveInfo

            elif action == "copyCats":
                try:
                    del self.copyCatsList
                    del self.copyCatsIds
                except AttributeError:
                    pass

            elif action == "copyLine":
                del self.copyIds
                if self.layerTmp:
                    self.Map.DeleteLayer(self.layerTmp)
                    self.UpdateMap(render=True, renderVector=False)
                del self.layerTmp

            self.polycoords = []
            self.UpdateMap(render=False)  # render vector

        elif action == "zbulkLine":
            # reset polyline
            self.polycoords = []
            self.digit.GetDisplay().SetSelected([])
            self.UpdateMap(render=False)

        self.redrawAll = True
        self.UpdateMap(render=False, renderVector=False)

    def _onLeftDown(self, event):
        """Left mouse button donw - vector digitizer various actions"""
        try:
            mapLayer = self.toolbar.GetLayer().GetName()
        except:
            GMessage(parent=self, message=_("No vector map selected for editing."))
            event.Skip()
            return

        action = self.toolbar.GetAction()

        if not action:
            GMessage(
                parent=self,
                message=_(
                    "Nothing to do. " "Choose appropriate tool from digitizer toolbar."
                ),
            )
            event.Skip()
            return

        if action not in ("moveVertex", "addVertex", "removeVertex", "editLine"):
            # set pen
            self.pen = wx.Pen(
                colour=UserSettings.Get(
                    group="vdigit", key="symbol", subkey=["newSegment", "color"]
                ),
                width=2,
                style=wx.SHORT_DASH,
            )
            self.polypen = wx.Pen(
                colour=UserSettings.Get(
                    group="vdigit", key="symbol", subkey=["newLine", "color"]
                ),
                width=2,
                style=wx.SOLID,
            )

        if action in ("addVertex", "removeVertex", "splitLines"):
            # unselect
            self.digit.GetDisplay().SetSelected([])

        if action == "addLine":
            self.OnLeftDownAddLine(event)

        elif action == "editLine" and hasattr(self, "moveInfo"):
            self.OnLeftDownEditLine(event)

        elif action in ("moveLine", "moveVertex", "editLine") and not hasattr(
            self, "moveInfo"
        ):
            self.OnLeftDownMoveLine(event)

        elif action in ("displayAttrs" "displayCats"):
            self.OnLeftDownDisplayCA(event)

        elif action in ("copyCats", "copyAttrs"):
            self.OnLeftDownCopyCA(event)

        elif action == "copyLine":
            self.OnLeftDownCopyLine(event)

        elif action == "zbulkLine":
            self.OnLeftDownBulkLine(event)

    def OnLeftUpVarious(self, event):
        """Left mouse button released - vector digitizer various
        actions
        """
        pos1 = self.Pixel2Cell(self.mouse["begin"])
        pos2 = self.Pixel2Cell(self.mouse["end"])

        nselected = 0
        action = self.toolbar.GetAction()
        # -> delete line || move line || move vertex
        if action in ("moveVertex", "editLine"):
            if len(self.digit.GetDisplay().GetSelected()) == 0:
                nselected = int(
                    self.digit.GetDisplay().SelectLineByPoint(pos1)["line"] != -1
                )

                if action == "editLine":
                    try:
                        selVertex = self.digit.GetDisplay().GetSelectedVertex(pos1)[0]
                    except IndexError:
                        selVertex = None

                    if selVertex:
                        # self.UpdateMap(render=False)
                        ids = self.digit.GetDisplay().GetSelected(grassId=False)
                        # move this line to tmp layer
                        self.polycoords = []
                        for id in ids:
                            if id % 2:  # register only vertices
                                e, n = self.Pixel2Cell(
                                    self.pdcVector.GetIdBounds(id)[0:2]
                                )
                                self.polycoords.append((e, n))
                        self.digit.GetDisplay().DrawSelected(False)

                        if selVertex < ids[-1] / 2:
                            # choose first or last node of line
                            self.moveInfo["id"].reverse()
                            self.polycoords.reverse()
                    else:
                        # unselect
                        self.digit.GetDisplay().SetSelected([])
                        if hasattr(self, "moveInfo"):
                            del self.moveInfo

                    self.UpdateMap(render=False)

        elif action in ("copyCats", "copyAttrs"):
            if not hasattr(self, "copyCatsIds"):
                # 'from' -> select by point
                nselected = int(
                    self.digit.GetDisplay().SelectLineByPoint(pos1)["line"] != -1
                )
                if nselected:
                    self.copyCatsList = self.digit.GetDisplay().GetSelected()
            else:
                # -> 'to' -> select by bbox
                self.digit.GetDisplay().SetSelected([])
                # return number of selected features (by box/point)
                nselected = self.digit.GetDisplay().SelectLinesByBox((pos1, pos2))
                if nselected == 0:
                    nselected = int(
                        self.digit.GetDisplay().SelectLineByPoint(pos1)["line"] != -1
                    )

                if nselected > 0:
                    self.copyCatsIds = self.digit.GetDisplay().GetSelected()

        elif action == "queryLine":
            selected = self.digit.SelectLinesByQuery(bbox=(pos1, pos2))
            nselected = len(selected)
            if nselected > 0:
                self.digit.GetDisplay().SetSelected(selected)

        else:
            # -> moveLine || deleteLine, etc. (select by point/box)
            if action == "moveLine" and len(self.digit.GetDisplay().GetSelected()) > 0:
                nselected = 0
            else:
                if action == "deleteArea":
                    nselected = int(
                        self.digit.GetDisplay().SelectAreaByPoint(pos1)["area"] != -1
                    )
                else:
                    if action == "moveLine":
                        drawSeg = True
                    else:
                        drawSeg = False

                    nselected = self.digit.GetDisplay().SelectLinesByBox(
                        bbox=(pos1, pos2), drawSeg=drawSeg
                    )
                    if nselected == 0:
                        nselected = int(
                            self.digit.GetDisplay().SelectLineByPoint(pos1)["line"]
                            != -1
                        )

        if nselected > 0:
            if action in ("moveLine", "moveVertex") and hasattr(self, "moveInfo"):
                # get pseudoDC id of objects which should be redrawn
                if action == "moveLine":
                    # -> move line
                    self.moveInfo["id"] = self.digit.GetDisplay().GetSelected(
                        grassId=False
                    )
                else:  # moveVertex
                    self.moveInfo["id"] = self.digit.GetDisplay().GetSelectedVertex(
                        pos1
                    )
                    if len(self.moveInfo["id"]) == 0:  # no vertex found
                        self.digit.GetDisplay().SetSelected([])

            #
            # check for duplicates
            #
            if UserSettings.Get(group="vdigit", key="checkForDupl", subkey="enabled"):
                dupl = self.digit.GetDisplay().GetDuplicates()
                self.UpdateMap(render=False)

                if dupl:
                    posWindow = self.ClientToScreen(
                        (
                            self.mouse["end"][0] + self.dialogOffset,
                            self.mouse["end"][1] + self.dialogOffset,
                        )
                    )

                    dlg = VDigitDuplicatesDialog(parent=self, data=dupl, pos=posWindow)

                    if dlg.ShowModal() == wx.ID_OK:
                        self.digit.GetDisplay().UnSelect(dlg.GetUnSelected())
                        # update selected
                        self.UpdateMap(render=False)

            if action != "editLine":
                # -> move line || move vertex
                self.UpdateMap(render=False)

        else:  # no vector object found
            if not (
                action in ("moveLine", "moveVertex")
                and hasattr(self, "moveInfo")
                and len(self.moveInfo["id"]) > 0
            ):
                # avoid left-click when features are already selected
                self.UpdateMap(render=False, renderVector=False)

    def OnLeftUpModifyLine(self, event):
        """Left mouse button released - vector digitizer split line,
        add/remove vertex action
        """
        pos1 = self.Pixel2Cell(self.mouse["begin"])

        pointOnLine = self.digit.GetDisplay().SelectLineByPoint(pos1)["point"]
        if not pointOnLine:
            return

        if self.toolbar.GetAction() in ["splitLine", "addVertex"]:
            self.UpdateMap(render=False)  # highlight object
            self.DrawCross(
                pdc=self.pdcTmp,
                coords=self.Cell2Pixel((pointOnLine[0], pointOnLine[1])),
                size=5,
            )
        else:  # removeVertex
            # get only id of vertex
            try:
                id = self.digit.GetDisplay().GetSelectedVertex(pos1)[0]
            except IndexError:
                id = None

            if id:
                x, y = self.pdcVector.GetIdBounds(id)[0:2]
                self.pdcVector.RemoveId(id)
                self.UpdateMap(render=False)  # highlight object
                self.DrawCross(pdc=self.pdcTmp, coords=(x, y), size=5)
            else:
                # unselect
                self.digit.GetDisplay().SetSelected([])
                self.UpdateMap(render=False)

    def OnLeftUpCopyLine(self, event):
        """Left mouse button released - vector digitizer copy feature
        action
        """
        pos1 = self.Pixel2Cell(self.mouse["begin"])
        pos2 = self.Pixel2Cell(self.mouse["end"])

        if (
            UserSettings.Get(
                group="vdigit", key="bgmap", subkey="value", settings_type="internal"
            )
            == ""
        ):
            # no background map -> copy from current vector map layer
            nselected = self.digit.GetDisplay().SelectLinesByBox((pos1, pos2))

            if nselected > 0:
                # highlight selected features
                self.UpdateMap(render=False)
            else:
                self.UpdateMap(render=False, renderVector=False)
        else:
            # copy features from background map
            self.copyIds = self.digit.SelectLinesFromBackgroundMap(bbox=(pos1, pos2))
            if len(self.copyIds) > 0:
                color = UserSettings.Get(
                    group="vdigit", key="symbol", subkey=["highlight", "color"]
                )
                colorStr = str(color[0]) + ":" + str(color[1]) + ":" + str(color[2])
                dVectTmp = [
                    "d.vect",
                    "map=%s"
                    % UserSettings.Get(
                        group="vdigit",
                        key="bgmap",
                        subkey="value",
                        settings_type="internal",
                    ),
                    "cats=%s" % ListOfCatsToRange(self.copyIds),
                    "-i",
                    "color=%s" % colorStr,
                    "fill_color=%s" % colorStr,
                    "type=point,line,boundary,centroid",
                    "width=2",
                ]

                if not self.layerTmp:
                    self.layerTmp = self.Map.AddLayer(
                        ltype="vector", name=QUERYLAYER, command=dVectTmp
                    )
                else:
                    self.layerTmp.SetCmd(dVectTmp)
            else:
                if self.layerTmp:
                    self.Map.DeleteLayer(self.layerTmp)
                    self.layerTmp = None

            self.UpdateMap(render=True, renderVector=True)

    def OnLeftUpBulkLine(self, event):
        """Left mouse button released - vector digitizer z-bulk line
        action
        """
        # select lines to be labeled
        pos1 = self.polycoords[0]
        pos2 = self.polycoords[1]
        nselected = self.digit.GetDisplay().SelectLinesByBox((pos1, pos2))

        if nselected > 0:
            # highlight selected features
            self.UpdateMap(render=False)
            self.DrawLines(pdc=self.pdcTmp)  # redraw temp line
        else:
            self.UpdateMap(render=False, renderVector=False)

    def OnLeftUpConnectLine(self, event):
        """Left mouse button released - vector digitizer connect line
        action
        """
        if len(self.digit.GetDisplay().GetSelected()) > 0:
            self.UpdateMap(render=False)

    def _onLeftUp(self, event):
        """Left mouse button released"""
        if event.ControlDown():
            return

        if hasattr(self, "moveInfo"):
            if len(self.digit.GetDisplay().GetSelected()) == 0:
                self.moveInfo["begin"] = self.Pixel2Cell(
                    self.mouse["begin"]
                )  # left down

            # eliminate initial mouse moving efect
            self.mouse["begin"] = self.mouse["end"]

        action = self.toolbar.GetAction()
        if action in (
            "deleteLine",
            "deleteArea",
            "moveLine",
            "moveVertex",
            "copyCats",
            "copyAttrs",
            "editLine",
            "flipLine",
            "mergeLine",
            "snapLine",
            "queryLine",
            "breakLine",
            "typeConv",
            "connectLine",
        ):
            self.OnLeftUpVarious(event)

        elif action in ("splitLine", "addVertex", "removeVertex"):
            self.OnLeftUpModifyLine(event)

        elif action == "copyLine":
            self.OnLeftUpCopyLine(event)

        elif action == "zbulkLine" and len(self.polycoords) == 2:
            self.OnLeftUpBulkLine(event)

        elif action == "connectLine":
            self.OnLeftUpConnectLine(event)

        if len(self.digit.GetDisplay().GetSelected()) > 0:
            self.redrawAll = None

    def _onRightDown(self, event):
        # digitization tool (confirm action)
        action = self.toolbar.GetAction()
        if action in ("moveLine", "moveVertex") and hasattr(self, "moveInfo"):
            pFrom = self.moveInfo["begin"]
            pTo = self.Pixel2Cell(event.GetPosition())

            move = (pTo[0] - pFrom[0], pTo[1] - pFrom[1])

            if action == "moveLine":
                # move line
                if self.digit.MoveSelectedLines(move) < 0:
                    return
            elif action == "moveVertex":
                # move vertex
                fid = self.digit.MoveSelectedVertex(pFrom, move)
                if fid < 0:
                    return

                self._geomAttrbUpdate(
                    [
                        fid,
                    ]
                )

            del self.moveInfo

    def _onRightUp(self, event):
        """Right mouse button released (confirm action)"""
        action = self.toolbar.GetAction()
        if action == "addLine" and self.toolbar.GetAction("type") in [
            "line",
            "boundary",
            "area",
        ]:
            # -> add new line / boundary
            try:
                mapName = self.toolbar.GetLayer().GetName()
            except:
                mapName = None
                GError(parent=self, message=_("No vector map selected for editing."))

            if mapName:
                if self.toolbar.GetAction("type") == "line":
                    line = True
                else:
                    line = False

                if len(self.polycoords) < 2:  # ignore 'one-point' lines
                    return

                nfeat, fids = self.digit.AddFeature(
                    self.toolbar.GetAction("type"), self.polycoords
                )
                if nfeat < 0:
                    return

                position = self.Cell2Pixel(self.polycoords[-1])
                self.polycoords = []
                self.UpdateMap(render=False)
                self.redrawAll = True
                self.Refresh()

                # add new record into attribute table
                if self._addRecord() and (line is True or (not line and nfeat > 0)):
                    posWindow = self.ClientToScreen(
                        (
                            position[0] + self.dialogOffset,
                            position[1] + self.dialogOffset,
                        )
                    )

                    # select attributes based on layer and category
                    cats = {
                        fids[0]: {
                            UserSettings.Get(
                                group="vdigit", key="layer", subkey="value"
                            ): (
                                UserSettings.Get(
                                    group="vdigit", key="category", subkey="value"
                                ),
                            )
                        }
                    }

                    addRecordDlg = DisplayAttributesDialog(
                        parent=self,
                        map=mapName,
                        cats=cats,
                        pos=posWindow,
                        action="add",
                        ignoreError=True,
                    )

                    for fid in fids:
                        self._geomAttrb(fid, addRecordDlg, "length")
                        # auto-placing centroid
                        self._geomAttrb(fid, addRecordDlg, "area")
                        self._geomAttrb(fid, addRecordDlg, "perimeter")

                    if addRecordDlg.IsFound():
                        addRecordDlg.ShowModal()
                    addRecordDlg.Destroy()

        elif action == "deleteLine":
            # -> delete selected vector features
            if self.digit.DeleteSelectedLines() < 0:
                return
            self._updateATM()
        elif action == "deleteArea":
            # -> delete selected vector areas
            if self.digit.DeleteSelectedAreas() < 0:
                return
            self._updateATM()
        elif action == "splitLine":
            # split line
            if self.digit.SplitLine(self.Pixel2Cell(self.mouse["begin"])) < 0:
                return
        elif action == "addVertex":
            # add vertex
            fid = self.digit.AddVertex(self.Pixel2Cell(self.mouse["begin"]))
            if fid < 0:
                return
        elif action == "removeVertex":
            # remove vertex
            fid = self.digit.RemoveVertex(self.Pixel2Cell(self.mouse["begin"]))
            if fid < 0:
                return
            self._geomAttrbUpdate(
                [
                    fid,
                ]
            )
        elif action in ("copyCats", "copyAttrs") and hasattr(self, "copyCatsIds"):
            if action == "copyCats":
                if (
                    self.digit.CopyCats(
                        self.copyCatsList, self.copyCatsIds, copyAttrb=False
                    )
                    < 0
                ):
                    return
            else:
                if (
                    self.digit.CopyCats(
                        self.copyCatsList, self.copyCatsIds, copyAttrb=True
                    )
                    < 0
                ):
                    return

            del self.copyCatsList
            del self.copyCatsIds

            self._updateATM()

        elif action == "editLine" and hasattr(self, "moveInfo"):
            line = self.digit.GetDisplay().GetSelected()[0]
            if self.digit.EditLine(line, self.polycoords) < 0:
                return

            del self.moveInfo

        elif action == "flipLine":
            if self.digit.FlipLine() < 0:
                return
        elif action == "mergeLine":
            if self.digit.MergeLine() < 0:
                return
        elif action == "breakLine":
            if self.digit.BreakLine() < 0:
                return
        elif action == "snapLine":
            if self.digit.SnapLine() < 0:
                return
        elif action == "connectLine":
            if len(self.digit.GetDisplay().GetSelected()) > 1:
                if self.digit.ConnectLine() < 0:
                    return
        elif action == "copyLine":
            if self.digit.CopyLine(self.copyIds) < 0:
                return
            del self.copyIds
            if self.layerTmp:
                self.Map.DeleteLayer(self.layerTmp)
                self.UpdateMap(render=True, renderVector=False)
            del self.layerTmp

        elif action == "zbulkLine" and len(self.polycoords) == 2:
            pos1 = self.polycoords[0]
            pos2 = self.polycoords[1]

            selected = self.digit.GetDisplay().GetSelected()
            dlg = VDigitZBulkDialog(
                parent=self, title=_("Z bulk-labeling dialog"), nselected=len(selected)
            )
            if dlg.ShowModal() == wx.ID_OK:
                if (
                    self.digit.ZBulkLines(
                        pos1, pos2, dlg.value.GetValue(), dlg.step.GetValue()
                    )
                    < 0
                ):
                    return
            self.UpdateMap(render=False)
        elif action == "typeConv":
            # -> feature type conversion
            # - point <-> centroid
            # - line <-> boundary
            if self.digit.TypeConvForSelectedLines() < 0:
                return

        if action != "addLine":
            # unselect and re-render
            self.digit.GetDisplay().SetSelected([])
            self.polycoords = []
            self.UpdateMap(render=False)

    def _onMouseMoving(self, event):
        self.mouse["end"] = event.GetPosition()

        Debug.msg(
            5,
            "VDigitWindow.OnMouseMoving(): coords=%f,%f"
            % (self.mouse["end"][0], self.mouse["end"][1]),
        )

        action = self.toolbar.GetAction()
        if action == "addLine" and self.toolbar.GetAction("type") in [
            "line",
            "boundary",
            "area",
        ]:
            if len(self.polycoords) > 0:
                self.MouseDraw(
                    pdc=self.pdcTmp, begin=self.Cell2Pixel(self.polycoords[-1])
                )

        elif action in ["moveLine", "moveVertex", "editLine"] and hasattr(
            self, "moveInfo"
        ):
            dx = self.mouse["end"][0] - self.mouse["begin"][0]
            dy = self.mouse["end"][1] - self.mouse["begin"][1]

            # draw lines on new position
            if action == "moveLine" and len(self.moveInfo["id"]) > 0:
                # move line
                for id in self.moveInfo["id"]:
                    self.pdcTmp.TranslateId(id, dx, dy)
            elif action in ["moveVertex", "editLine"]:
                # move vertex ->
                # (vertex, left vertex, left line,
                # right vertex, right line)

                # do not draw static lines
                if action == "moveVertex" and len(self.moveInfo["id"]) > 0:
                    self.polycoords = []
                    self.pdcTmp.RemoveId(self.moveInfo["id"][0])
                    if self.moveInfo["id"][1] > 0:  # previous vertex
                        x, y = self.Pixel2Cell(
                            self.pdcTmp.GetIdBounds(self.moveInfo["id"][1])[0:2]
                        )
                        self.pdcTmp.RemoveId(self.moveInfo["id"][1] + 1)
                        self.polycoords.append((x, y))
                    self.polycoords.append(self.Pixel2Cell(self.mouse["end"]))

                    if self.moveInfo["id"][2] > 0:  # next vertex
                        x, y = self.Pixel2Cell(
                            self.pdcTmp.GetIdBounds(self.moveInfo["id"][2])[0:2]
                        )
                        self.pdcTmp.RemoveId(self.moveInfo["id"][2] - 1)
                        self.polycoords.append((x, y))

                    self.ClearLines(pdc=self.pdcTmp)
                    self.DrawLines(pdc=self.pdcTmp)

                if action == "editLine":
                    if self.polycoords:
                        self.MouseDraw(
                            pdc=self.pdcTmp, begin=self.Cell2Pixel(self.polycoords[-1])
                        )

            self.Refresh()  # TODO: use RefreshRect()
            self.mouse["begin"] = self.mouse["end"]

        elif action == "zbulkLine":
            if len(self.polycoords) == 1:
                # draw mouse moving
                self.MouseDraw(self.pdcTmp)

    def _zoom(self, event):
        tmp1 = self.mouse["end"]
        tmp2 = self.Cell2Pixel(self.moveInfo["begin"])
        dx = tmp1[0] - tmp2[0]
        dy = tmp1[1] - tmp2[1]
        self.moveInfo["beginDiff"] = (dx, dy)
        for id in self.moveInfo["id"]:
            self.pdcTmp.RemoveId(id)

    def _addRecord(self):
        return UserSettings.Get(group="vdigit", key="addRecord", subkey="enabled")