"""!
@package wxvdriver.py

@brief wxGUI vector digitizer (display driver)

Code based on wxVdigit C++ component from GRASS 6.4.0
(gui/wxpython/vdigit). Converted to Python in 2010/12-2011/01.

List of classes:
 - DisplayDriver

(C) 2007-2011 by the GRASS Development Team

This program is free software under the GNU General Public License
(>=v2). Read the file COPYING that comes with GRASS for details.

@author Martin Landa <landa.martin gmail.com>
"""

import math
import locale

import wx

from debug import Debug
from preferences import globalSettings as UserSettings

from grass.lib.gis    import *
from grass.lib.vector import *
from grass.lib.vedit  import *

log      = None
progress = None

def print_error(msg, type):
    """!Redirect stderr"""
    global log
    if log:
        log.write(msg)
    else:
        print msg
    
    return 0

def print_progress(value):
    """!Redirect progress info"""
    global progress
    if progress:
        progress.SetValue(value)
    else:
        print value
    
    return 0

errtype = CFUNCTYPE(UNCHECKED(c_int), String, c_int)
errfunc = errtype(print_error)
pertype = CFUNCTYPE(UNCHECKED(c_int), c_int)
perfunc = pertype(print_progress)

class DisplayDriver:
    def __init__(self, device, deviceTmp, mapObj, window, glog, gprogress):
        """!Display driver used by vector digitizer
        
        @param device    wx.PseudoDC device where to draw vector objects
        @param deviceTmp wx.PseudoDC device where to draw temporary vector objects
        @param mapOng    Map Object (render.Map)
        @param windiow   parent window for dialogs
        @param glog      logging device (None to discard messages)
        @param gprogress progress bar device (None to discard message)
        """
        global errfunc, perfunc, log, progress
        log = glog
        progress = gprogress
        
        G_gisinit('wxvdigit')
        locale.setlocale(locale.LC_NUMERIC, 'C')
        G_set_error_routine(errfunc) 
        G_set_percent_routine(perfunc)
        
        self.mapInfo   = None     # open vector map (Map_Info structure)
        self.poMapInfo = None     # pointer to self.mapInfo
        self.is3D      = False    # is open vector map 3D
        
        self.dc      = device     # PseudoDC devices
        self.dcTmp   = deviceTmp
        self.mapObj  = mapObj
        self.region  = mapObj.GetCurrentRegion()
        self.window  = window
        self.log     = log        # log device

        self.firstNode = True     # track PseudoDC Id of selected features
        self.lastNodeId = -1
        
        # GRASS lib
        self.poPoints = Vect_new_line_struct()
        self.poCats   = Vect_new_cats_struct()
        
        # selected objects
        self.selected = {
            'field'   : -1,      # field number
            'cats'    : list(),  # list of cats
            'ids'     : list(),  # list of ids
            'idsDupl' : list(),  # list of duplicated features
            }
        
        # digitizer settings
        self.settings = {
            'highlight'     : None,
            'highlightDupl' : { 'enabled' : False,
                                'color'   : None },
            'point'         : { 'enabled' : False,
                                'color'   : None },
            'line'          : { 'enabled' : False,
                                'color'   : None },
            'boundaryNo'    : { 'enabled' : False,
                                'color'   : None },
            'boundaryOne'   : { 'enabled' : False,
                                'color'   : None },
            'boundaryTwo'   : { 'enabled' : False,
                                'color'   : None },
            'centroidIn'    : { 'enabled' : False,
                                'color'   : None },
            'centroidOut'   : { 'enabled' : False,
                                'color'   : None },
            'centroidDup'   : { 'enabled' : False,
                                'color'   : None },
            'nodeOne'       : { 'enabled' : False,
                                'color'   : None },
            'nodeTwo'       : { 'enabled' : False,
                                'color'   : None },
            'vertex'        : { 'enabled' : False,
                                'color'   : None },
            'area'          : { 'enabled' : False,
                                'color'   : None },
            'direction'     : { 'enabled' : False,
                                'color'   : None },
            'lineWidth'     : -1,    # screen units 
            }
        
        # topology
        self._resetTopology()
        
        self._drawSelected = False
        self._drawSegments = False
        
        self.UpdateSettings()
        
    def __del__(self):
        """!Close currently open vector map"""
        G_unset_error_routine()
        G_unset_percent_routine()
        
        if self.poMapInfo:
            self.CloseMap()
        
        Vect_destroy_line_struct(self.poPoints)
        Vect_destroy_cats_struct(self.poCats)

    def _resetTopology(self):
        """!Reset topology dict
        """
        self.topology = {
            'highlight'   : 0,
            'point'       : 0,
            'line'        : 0,
            'boundaryNo'  : 0,
            'boundaryOne' : 0,
            'boundaryTwo' : 0,
            'centroidIn'  : 0,
            'centroidOut' : 0,
            'centroidDup' : 0,
            'nodeOne'     : 0,
            'nodeTwo'     : 0,
            'vertex'      : 0,
            }
        
    def _cell2Pixel(self, east, north, elev):
        """!Conversion from geographic coordinates (east, north)
        to screen (x, y)
  
        @todo 3D stuff...

        @param east, north, elev geographical coordinates

        @return x, y screen coordinates (integer)
        """
        map_res = max(self.region['ewres'], self.region['nsres'])
        w = self.region['center_easting']  - (self.mapObj.width  / 2) * map_res
        n = self.region['center_northing'] + (self.mapObj.height / 2) * map_res
        
        return int((east - w) / map_res), int((n - north) / map_res)
    
    def _drawCross(self, pdc, point, size = 5):
        """!Draw cross symbol of given size to device content
   
        Used for points, nodes, vertices

        @param[in,out] PseudoDC where to draw
        @param point coordinates of center
        @param size size of the cross symbol
   
        @return 0 on success
        @return -1 on failure
        """
        if not pdc or not point:
            return -1
        
        pdc.DrawLine(point.x - size, point.y, point.x + size, point.y)
        pdc.DrawLine(point.x, point.y - size, point.x, point.y + size)
        
        return 0
    
    def _drawObject(self, robj):
        """!Draw given object to the device
        
        The object is defined as robject() from vedit.h.
        
        @param robj object to be rendered
        
        @return  1 on success
        @return -1 on failure (vector feature marked as dead, etc.)
        """
        if not self.dc or not self.dcTmp:
            return -1
        
        Debug.msg(3, "_drawObject(): type=%d npoints=%d", robj.type, robj.npoints)
        brush = None
        if self._isSelected(robj.fid):
            pdc = self.dcTmp
            if self.settings['highlightDupl']['enabled'] and self._isDuplicated(robj.fid):
                pen = wx.Pen(self.settings['highlightDupl']['color'], self.settings['lineWidth'], wx.SOLID)
            else:            
                pen = wx.Pen(self.settings['highlight'], self.settings['lineWidth'], wx.SOLID)
            
            dcId = 1
            self.topology['highlight'] += 1
            if not self._drawSelected:
                return
        else:
            pdc = self.dc
            pen, brush = self._definePen(robj.type)
            dcId = 0
        
        pdc.SetPen(pen)        
        if brush:
            pdc.SetBrush(brush)
        
        if robj.type & (TYPE_POINT | TYPE_CENTROIDIN | TYPE_CENTROIDOUT | TYPE_CENTROIDDUP |
                        TYPE_NODEONE | TYPE_NODETWO | TYPE_VERTEX): # -> point
            if dcId > 0:
                if robj.type == TYPE_VERTEX:
                    dcId = 3 # first vertex
                elif robj.type & (TYPE_NODEONE | TYPE_NODETWO):
                    if self.firstNode:
                        dcId = 1
                        self.firstNode = False
                    else:
                        dcId = self.lastNodeId
            
            for i in range(robj.npoints):
                p = robj.point[i]
                if dcId > 0:
                    pdc.SetId(dcId)
                    dcId += 2
                self._drawCross(pdc, p)
        else:
            if dcId > 0 and self._drawSegments:
                self.fisrtNode = True
                self.lastNodeId = robj.npoints * 2 - 1
                dcId = 2 # first segment
                i = 0
                while i < robj.npoints - 1:
                    point_beg = wx.Point(robj.point[i].x, robj.point[i].y)
                    point_end = wx.Point(robj.point[i+1].x, robj.point[i+1].y)
                    pdc.SetId(dcId) # set unique id & set bbox for each segment
                    pdc.SetPen(pen)
                    pdc.SetIdBounds(dcId - 1, wx.Rect(point_beg.x, point_beg.y, 0, 0))
                    pdc.SetIdBounds(dcId, wx.RectPP(point_beg, point_end))
                    pdc.DrawLine(point_beg.x, point_beg.y,
                                 point_end.x, point_end.y)
                    i    += 1
                    dcId += 2
                pdc.SetIdBounds(dcId - 1, wx.Rect(robj.point[robj.npoints - 1].x,
                                                  robj.point[robj.npoints - 1].y,
                                                  0, 0))
            else:
                points = list()
                for i in range(robj.npoints):
                    p = robj.point[i]
                    points.append(wx.Point(p.x, p.y))
                    
                if robj.type == TYPE_AREA:
                    pdc.DrawPolygon(points)
                else:
                    pdc.DrawLines(points)
        
    def _definePen(self, rtype):
        """!Define pen/brush based on rendered object)
        
        Updates also self.topology dict

        @return pen, brush
        """
        if rtype == TYPE_POINT:
            key = 'point'
        elif rtype == TYPE_LINE:
            key = 'line'
        elif rtype == TYPE_BOUNDARYNO:
            key = 'boundaryNo'
        elif rtype == TYPE_BOUNDARYTWO:
            key = 'boundaryTwo'
        elif rtype == TYPE_BOUNDARYONE:
            key = 'boundaryOne'
        elif rtype == TYPE_CENTROIDIN:
            key = 'centroidIn'
        elif rtype == TYPE_CENTROIDOUT:
            key = 'centroidOut'
        elif rtype == TYPE_CENTROIDDUP:
            key = 'centroidDup'
        elif rtype == TYPE_NODEONE:
            key = 'nodeOne'
        elif rtype == TYPE_NODETWO:
            key = 'nodeTwo'
        elif rtype == TYPE_VERTEX:
            key = 'vertex'
        elif rtype == TYPE_AREA:
            key = 'area' 
        elif rtype == TYPE_ISLE:
            key = 'isle'
        elif rtype == TYPE_DIRECTION:
            key = 'direction'
        
        if key not in ('direction', 'area', 'isle'):
            self.topology[key] += 1
        
        if key in ('area', 'isle'):
            pen = wx.TRANSPARENT_PEN
            if key == 'area':
                brush = wx.Brush(self.settings[key]['color'], wx.SOLID)
            else:
                brush = wx.TRANSPARENT_BRUSH
        else:
            pen = wx.Pen(self.settings[key]['color'], self.settings['lineWidth'], wx.SOLID)
            brush = None
        
        return pen, brush
        
    def _getDrawFlag(self):
        """!Get draw flag from the settings
        
        See vedit.h for list of draw flags.
        
        @return draw flag (int)
        """
        ret = 0
        if self.settings['point']['enabled']:
            ret |= DRAW_POINT
        if self.settings['line']['enabled']:
            ret |= DRAW_LINE
        if self.settings['boundaryNo']['enabled']:
            ret |= DRAW_BOUNDARYNO
        if self.settings['boundaryTwo']['enabled']:
            ret |= DRAW_BOUNDARYTWO
        if self.settings['boundaryOne']['enabled']:
            ret |= DRAW_BOUNDARYONE
        if self.settings['centroidIn']['enabled']:
            ret |= DRAW_CENTROIDIN
        if self.settings['centroidOut']['enabled']:
            ret |= DRAW_CENTROIDOUT
        if self.settings['centroidDup']['enabled']:
            ret |= DRAW_CENTROIDDUP
        if self.settings['nodeOne']['enabled']:
            ret |= DRAW_NODEONE
        if self.settings['nodeTwo']['enabled']:
            ret |= DRAW_NODETWO
        if self.settings['vertex']['enabled']:
            ret |= DRAW_VERTEX
        if self.settings['area']['enabled']:
            ret |= DRAW_AREA
        if self.settings['direction']['enabled']:
            ret |= DRAW_DIRECTION
        
        return ret
        
    def _isSelected(self, line, force = False):
        """!Check if vector object selected?
   
        @param line feature id

        @return True if vector object is selected
        @return False if vector object is not selected
        """
        if len(self.selected['cats']) < 1 or force:
            # select by id
            if line in self.selected['ids']:
                return True
        else: 
            # select by cat
            cats = self.poCats.contents
            for i in range(cats.n_cats):
                if cats.field[i] == self.selected['field'] and \
                        cats.cat[i] in self.selected['cats']:
                    # remember id
                    # -> after drawing all features selected.cats is reseted */
                    self.selected['ids'].append(line)
                    return True
        
        return False

    def _isDuplicated(self, line):
        """!Check for already marked duplicates
        
        @param line feature id

        @return True line already marked as duplicated
        @return False not duplicated
        """
        return line in self.selected['idsDupl']
    
    def _getRegionBox(self):
        """!Get bound_box() from current region

        @return bound_box
        """
        box = bound_box()
        
        box.N = self.region['n']
        box.S = self.region['s']
        box.E = self.region['e']
        box.W = self.region['w']
        box.T = PORT_DOUBLE_MAX
        box.B = -PORT_DOUBLE_MAX
        
        return box

    def DrawMap(self, force = False):
        """!Draw content of the vector map to the device
        
        @param force force drawing
        @return number of drawn features
        @return -1 on error
        """
        Debug.msg(1, "DisplayDriver.DrawMap(): force=%d", force)
        
        if not self.poMapInfo or not self.dc or not self.dcTmp:
            return -1
        
        rlist = Vedit_render_map(self.poMapInfo, byref(self._getRegionBox()), self._getDrawFlag(),
                                 self.region['center_easting'], self.region['center_northing'],
                                 self.mapObj.width, self.mapObj.height,
                                 max(self.region['nsres'], self.region['ewres'])).contents
        
        self._resetTopology()
        
        self.dc.BeginDrawing()
        self.dcTmp.BeginDrawing()
        
        # draw objects
        for i in range(rlist.nitems):
            robj = rlist.item[i].contents
            self._drawObject(robj)
        
        self.dc.EndDrawing()
        self.dcTmp.EndDrawing()
        
        # reset list of selected features by cat 
        # list of ids - see IsSelected()
        self.selected['field'] = -1
        self.selected['cats'] = list()
        
    def _getSelectType(self):
        """!Get type(s) to be selected

        Used by SelectLinesByBox() and SelectLineByPoint()
        """
        ftype = 0
        for feature in (('point',    GV_POINT),
                        ('line',     GV_LINE),
                        ('centroid', GV_CENTROID),
                        ('boundary', GV_BOUNDARY)):
            if UserSettings.Get(group = 'vdigit', key = 'selectType',
                                subkey = [feature[0], 'enabled']):
                ftype |= feature[1]
        
        return ftype

    def _validLine(self, line):
        """!Check if feature id is valid

        @param line feature id

        @return True valid feature id
        @return False invalid
        """
        if line > 0 and line <= Vect_get_num_lines(self.poMapInfo):
            return True
        
        return False
    
    def SelectLinesByBox(self, bbox, drawSeg = False, poMapInfo = None):
        """!Select vector objects by given bounding box
        
        If line id is already in the list of selected lines, then it will
        be excluded from this list.
        
        @param bbox bounding box definition
        @param drawSeg True to draw segments of line
        @param poMapInfo use external Map_info, None for self.poMapInfo

        @return number of selected features
        @return None on error
        """
        thisMapInfo = poMapInfo is None
        if not poMapInfo:
            poMapInfo = self.poMapInfo
        
        if not poMapInfo:
            return None
        
        if thisMapInfo:
            self._drawSegments = drawSeg
            self._drawSelected = True
        
            # select by ids
            self.selected['cats'] = list()
        
        poList = Vect_new_list()
        x1, y1 = bbox[0]
        x2, y2 = bbox[1]
        poBbox = Vect_new_line_struct()
        Vect_append_point(poBbox, x1, y1, 0.0)
        Vect_append_point(poBbox, x2, y1, 0.0)
        Vect_append_point(poBbox, x2, y2, 0.0)
        Vect_append_point(poBbox, x1, y2, 0.0)
        Vect_append_point(poBbox, x1, y1, 0.0)
        
        Vect_select_lines_by_polygon(poMapInfo, poBbox,
                                     0, None, # isles
                                     self._getSelectType(), poList)
        
        flist = poList.contents
        nlines = flist.n_values
        Debug.msg(1, "DisplayDriver.SelectLinesByBox() num = %d", nlines)
        for i in range(nlines):
            line = flist.value[i]
            if UserSettings.Get(group = 'vdigit', key = 'selectInside',
                                subkey = 'enabled'):
                inside = True
                if not self._validLine(line):
                    return None
                Vect_read_line(poMapInfo, self.poPoints, None, line)
                points = self.poPoints.contents
                for p in range(points.n_points):
                    if not Vect_point_in_poly(points.x[p], points.y[p],
                                              poBbox):
                        inside = False
                        break
                    
                if not inside:
                    continue # skip lines just overlapping bbox
            
            if not self._isSelected(line):
                self.selected['ids'].append(line)
            else:
                self.selected['ids'].remove(line)
        
        Vect_destroy_line_struct(poBbox)
        Vect_destroy_list(poList)
        
        return nlines

    def SelectLineByPoint(self, point, poMapInfo = None):
        """!Select vector feature by given point in given
        threshold
   
        Only one vector object can be selected. Bounding boxes of
        all segments are stores.
        
        @param point points coordinates (x, y)
        @param poMapInfo use external Map_info, None for self.poMapInfo

        @return dict {'line' : feature id, 'point' : point on line}
        """
        thisMapInfo = poMapInfo is None
        if not poMapInfo:
            poMapInfo = self.poMapInfo
        
        if not poMapInfo:
            return { 'line' : -1, 'point': None }
        
        if thisMapInfo:
            self._drawSelected = True
            # select by ids 
            self.selected['cats'] = list()
        
        poFound = Vect_new_list()
        
        lineNearest = Vect_find_line_list(poMapInfo, point[0], point[1], 0,
                                           self._getSelectType(), self.GetThreshold(), self.is3D,
                                           None, poFound)
        Debug.msg(1, "DisplayDriver.SelectLineByPoint() found = %d", lineNearest)
        
        if lineNearest > 0:
            if not self._isSelected(lineNearest):
                self.selected['ids'].append(lineNearest)
            else:
                self.selected['ids'].remove(lineNearest)
        
        px = c_double()
        py = c_double()
        pz = c_double()
        if not self._validLine(lineNearest):
            return { 'line' : -1, 'point': None }
	ftype = Vect_read_line(poMapInfo, self.poPoints, self.poCats, lineNearest)
	Vect_line_distance (self.poPoints, point[0], point[1], 0.0, self.is3D,
			    byref(px), byref(py), byref(pz),
			    None, None, None)
        
	# check for duplicates 
	if self.settings['highlightDupl']['enabled']:
            found = poFound.contents
	    for i in range(found.n_values):
		line = found.value[i]
		if line != lineNearest:
                    self.selected['ids'].append(line)
	    
            self.GetDuplicates()
	    
	    for i in range(found.n_values):
		line = found.value[i]
		if line != lineNearest and not self._isDuplicated(line):
                    self.selected['ids'].remove(line)
        
        Vect_destroy_list(poFound)
        
        if thisMapInfo:
            # drawing segments can be very expensive
            # only one features selected
            self._drawSegments = True
        
        return { 'line'  : lineNearest,
                 'point' : (px.value, py.value, pz.value) }
    
    def _listToIList(self, plist):
        """!Generate from list struct_ilist
        """
        ilist = Vect_new_list()
        for val in plist:
            Vect_list_append(ilist, val)
        
        return ilist
        
    def GetSelectedIList(self, ilist = None):
        """!Get list of selected objects as struct_ilist

        Returned IList must be freed by Vect_destroy_list().
        
        @return struct_ilist
        """
        if ilist:
            return self._listToIList(ilist)
        
        return self._listToIList(self.selected['ids'])
        
    def GetSelected(self, grassId = True):
        """!Get ids of selected objects
        
        @param grassId True for feature id, False for PseudoDC id
        
        @return list of ids of selected vector objects
        """
        if grassId:
            return self.selected['ids']
        
        dc_ids = list()
        
        if not self._drawSegments:
            dc_ids.append(1)
        elif len(self.selected['ids']) > 0:
            # only first selected feature
            Vect_read_line(self.poMapInfo, self.poPoints, None,
                           self.selected['ids'][0])
            points = self.poPoints.contents
            # node - segment - vertex - segment - node
            for i in range(1, 2 * points.n_points):
                dc_ids.append(i)
        
        return dc_ids
        
    def SetSelected(self, ids, layer = -1):
        """!Set selected vector objects

        @param list of ids (None to unselect features)
        @param layer layer number for features selected based on category number
        """
        if ids:
            self._drawSelected = True
        else:
            self._drawSelected = False
        
        if layer > 0:
            selected.field = layer
            self.selected['cats'] = ids
        else:
            field = -1
            self.selected['ids'] = ids
        
    def GetSelectedVertex(self, pos):
        """!Get PseudoDC vertex id of selected line

        Set bounding box for vertices of line.
        
        @param pos position
        
        @return id of center, left and right vertex
        @return 0 no line found
        @return -1 on error
        """
        returnId = list()
        # only one object can be selected
        if len(self.selected['ids']) != 1 or not self._drawSegments:
            return returnId
        
        startId = 1
        line = self.selected['ids'][0]
        
        if not self._validLine(line):
            return -1
        ftype = Vect_read_line(self.poMapInfo, self.poPoints, self.poCats, line)
        
        minDist = 0.0
        Gid = -1
        # find the closest vertex (x, y)
        DCid = 1
        points = self.poPoints.contents
        for idx in range(points.n_points):
            dist = Vect_points_distance(pos[0], pos[1], 0.0,
                                        points.x[idx], points.y[idx], points.z[idx], 0)
            
            if idx == 0:
                minDist = dist
                Gid     = idx
            else:
                if minDist > dist:
                    minDist = dist
                    Gid = idx
            
            vx, vy = self._cell2Pixel(points.x[idx], points.y[idx], points.z[idx])
            rect = wx.Rect(vx, vy, 0, 0)
            self.dc.SetIdBounds(DCid, rect)
            DCid += 2
        
        if minDist > self.GetThreshold():
            return returnId
        
        # translate id
        DCid = Gid * 2 + 1
        
        # add selected vertex
        returnId.append(DCid)
        # left vertex
        if DCid == startId:
            returnId.append(-1)
        else:
            returnId.append(DCid - 2)
        # right vertex
        if DCid == (points.n_points - 1) * 2 + startId:
            returnId.append(-1)
        else:
            returnId.append(DCid + 2)
        
        return returnId

    def DrawSelected(self, flag):
        """!Draw selected features
        
        @param flag True to draw selected features
        """
        self._drawSelected = bool(flag)
        
    def CloseMap(self):
        """!Close vector map
        
        @return 0 on success
        @return non-zero on error
        """
        ret = 0
        if self.poMapInfo:
            # rebuild topology
            Vect_build_partial(self.poMapInfo, GV_BUILD_NONE)
            Vect_build(self.poMapInfo)

            # close map and store topo/cidx
            ret = Vect_close(self.poMapInfo)
            del self.mapInfo
            self.poMapInfo = self.mapInfo = None
        
        return ret
    
    def OpenMap(self, name, mapset, update = True):
        """!Open vector map by the driver
        
        @param name name of vector map to be open
        @param mapset name of mapset where the vector map lives
   
        @return map_info
        @return None on error
        """
        Debug.msg("DisplayDriver.OpenMap(): name=%s mapset=%s updated=%d",
                  name, mapset, update)
        if not self.mapInfo:
            self.mapInfo = Map_info()
            self.poMapInfo = pointer(self.mapInfo)
        
        # open existing map
        if update:
            ret = Vect_open_update(self.poMapInfo, name, mapset)
        else:
            ret = Vect_open_old(self.poMapInfo, name, mapset)
        self.is3D = Vect_is_3d(self.poMapInfo)
        
        if ret == -1: # error
            del self.mapInfo
            self.poMapInfo = self.mapInfo = None
        elif ret < 2:
            dlg = wx.MessageDialog(parent = self.window,
                                   message = _("Topology for vector map <%s> is not available. "
                                               "Topology is required by digitizer. Do you want to "
                                               "rebuild topology (takes some time) and open the vector map "
                                               "for editing?") % name,
                                   caption=_("Topology missing"),
                                   style = wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION | wx.CENTRE)
            ret = dlg.ShowModal()
            if ret != wx.ID_YES:
                del self.mapInfo
                self.poMapInfo = self.mapInfo = None
            else:
                Vect_build(self.poMapInfo)
        
        return self.poMapInfo
    
    def GetMapBoundingBox(self):
        """!Get bounding box of (opened) vector map layer

        @return (w,s,b,e,n,t)
        """
        if not self.poMapInfo:
            return None
        
        bbox = bound_box()
        Vect_get_map_box(self.poMapInfo, byref(bbox))

        return bbox.W, bbox.S, bbox.B, \
            bbox.E, bbox.N, bbox.T
    
    def UpdateSettings(self, alpha = 255):
        """!Update display driver settings

        @todo map units
        
        @alpha color value for aplha channel
        """
        color = dict()
        for key in self.settings.keys():
            if key == 'lineWidth':
                self.settings[key] = int(UserSettings.Get(group = 'vdigit', key = 'lineWidth',
                                                          subkey = 'value'))
                continue
            
            color = wx.Color(UserSettings.Get(group = 'vdigit', key = 'symbol',
                                              subkey = [key, 'color'])[0],
                             UserSettings.Get(group = 'vdigit', key = 'symbol',
                                              subkey = [key, 'color'])[1],
                             UserSettings.Get(group = 'vdigit', key = 'symbol',
                                              subkey = [key, 'color'])[2],
                             alpha)
            
            if key == 'highlight':
                self.settings[key] = color
                continue
            
            if key == 'highlightDupl':
                self.settings[key]['enabled'] = bool(UserSettings.Get(group = 'vdigit', key = 'checkForDupl',
                                                                      subkey = 'enabled'))
            else:
                self.settings[key]['enabled'] = bool(UserSettings.Get(group = 'vdigit', key = 'symbol',
                                                                      subkey = [key, 'enabled']))
            
            self.settings[key]['color'] = color
        
    def UpdateRegion(self):
        """!Update geographical region used by display driver
        """
        self.region = self.mapObj.GetCurrentRegion()
        
    def GetThreshold(self, type = 'snapping', value = None, units = None):
        """!Return threshold value in map units
        
        @param type snapping mode (node, vertex)
        @param value threshold to be set up
        @param units units (map, screen)

        @return threshold value
        """
        if value is None:
            value = UserSettings.Get(group = 'vdigit', key = type, subkey = 'value')
        
        if units is None:
            units = UserSettings.Get(group = 'vdigit', key = type, subkey = 'units')
        
        if value < 0:
            value = (self.region['nsres'] + self.region['ewres']) / 2.0
        
        if units == "screen pixels":
            # pixel -> cell
            res = max(self.region['nsres'], self.region['ewres'])
            return value * res
        
        return value
    
    def GetDuplicates(self):
        """!Return ids of (selected) duplicated vector features
        """
        if not self.poMapInfo:
            return
        
        ids = dict()
        APoints = Vect_new_line_struct()
        BPoints = Vect_new_line_struct()
        
        self.selected['idsDupl'] = list()
        
        for i in range(len(self.selected['ids'])):
            line1 = self.selected['ids'][i]
            if self._isDuplicated(line1):
                continue
            
            Vect_read_line(self.poMapInfo, APoints, None, line1)
            
            for line2 in self.selected['ids']:
                if line1 == line2 or self._isDuplicated(line2):
                    continue
                
                Vect_read_line(self.poMapInfo, BPoints, None, line2)
	    
                if Vect_line_check_duplicate(APoints, BPoints, WITHOUT_Z):
                    if i not in ids:
                        ids[i] = list()
                        ids[i].append((line1, self._getCatString(line1)))
                        self.selected['idsDupl'].append(line1)
                    
                    ids[i].append((line2, self._getCatString(line2)))
                    self.selected['idsDupl'].append(line2)
        
        Vect_destroy_line_struct(APoints)
        Vect_destroy_line_struct(BPoints)

        return ids
    
    def _getCatString(self, line):
        Vect_read_line(self.poMapInfo, None, self.poCats, line)
        
        cats = self.poCats.contents
        catsDict = dict()
        for i in range(cats.n_cats):
            layer = cats.field[i]
            if layer not in catsDict:
                catsDict[layer] = list()
            catsDict[layer].append(cats.cat[i])
        
        catsStr = ''
        for l, c in catsDict.iteritems():
            catsStr = '%d: (%s)' % (l, ','.join(map(str, c)))
        
        return catsStr

    def UnSelect(self, lines):
        """!Unselect vector features

        @param lines list of feature id(s)
        """
        checkForDupl = False

        for line in lines:
            if self._isSelected(line):
                self.selected['ids'].remove(line)
            if self.settings['highlightDupl']['enabled'] and self._isDuplicated(line):
                checkForDupl = True

        if checkForDupl:
            self.GetDuplicates()
        
        return len(self.selected['ids'])
    

