Exemplo n.º 1
0
    def __init__(self,
                 canvas,
                 image,
                 rects=None,
                 show_moved=False,
                 show_id=False):
        """
        Rectangular selected/ing region,
        :canvas: Canvas containing the region
        :image: displayed in frame
        :rects: single, or list of Rectangles (upper left x,y), (lower right x,y)
                each being a region
                Default is no rectangles
        """
        self.parts = []  # Parts of scene, corners, edges, regions
        self.parts_by_id = {}  # By part id
        self.show_id = show_id
        self.show_moved = show_moved
        self.record_md = SelectMoveDisplay(self, show_moved=show_moved)
        self.canvas = canvas
        self.is_enclosed = False
        self.inside = False
        self.is_down = False
        self.highlights = {}  # Highlighted handles object REFERENCE
        self.selects = {}  # Select objects(handle)  REFERENCE
        self.motion_xy = None
        self.regions = []  # list of regions

        if rects is not None:
            if not isinstance(rects, list):
                rects = [rects]  # list of one
            for rect in rects:
                self.add_rect(rect)

        self.canvas.bind("<ButtonPress-1>", self.down)
        self.canvas.bind("<ButtonRelease-1>", self.up)
        self.canvas.bind("<Enter>", self.enter)
        self.canvas.bind("<Leave>", self.leave)
Exemplo n.º 2
0
    def __init__(self,
                 canvas,
                 mw=None,
                 image=None,
                 rects=None,
                 show_moved=False,
                 show_id=False,
                 highlight_limit=None,
                 check_mod=None,
                 down_click_call=None,
                 tbmove=.1,
                 max_select=1):
        """
        Rectangular selected/ing region,
        :canvas: Canvas containing the region
        :mw: Master/Parent widget, if one
        :image: displayed in frame    Not necessary/used
        :rects: single, or list of Rectangles (upper left x,y), (lower right x,y)
                each being a region
                Default is no rectangles
        :highlight_limit: Limt higlighting to time (seconds)
                Default: no time limit
        :check_mod: called, if present, before and after part is modified
        :down_click_call: processes down clicks if present'
                default: no remote processing
        :tbmove: minimum time (seconds) between move recognition
        :max_select: maximum number of simultaneous selections
                    allowed
                    default = 1
        """
        self.parts = []  # Parts of scene, corners, edges, regions
        self.parts_by_id = {}  # By part id
        self.parts_by_loc = {}  # By type, location:
        """ hash/dictionary of parts by type, location
            type: edge, corner, region
            loc: (pt), (x,y)
                 (pt1 (upper right), pt2 (lower right)
        """
        self.image = image
        if mw is None:
            mw = tk.Tk()
            mw.withdraw()  # Hide main window
        if not hasattr(mw, "update_idletasks"):
            SlTrace.lg("mw %s" % type(mw))
            raise SelectError("mw has no update_idletasks")

        self.mw = mw
        ###self.mw.protocol("WM_DELETE_WINDOW", self.on_exit)
        self.running = True  # Set false on window delete

        self.check_mod = check_mod
        self.tbmove = tbmove
        self.highlight_limit = highlight_limit
        self.show_id = show_id
        self.show_moved = show_moved
        self.record_md = SelectMoveDisplay(self, show_moved=show_moved)
        self.canvas = canvas
        self.is_enclosed = False
        self.inside = False
        self.is_down = False
        self.highlights = {}  # Highlighted handles object REFERENCE
        self.selects = {}  # Select objects(handle)  REFERENCE
        self.motion_xy = (-1, -1)
        self.regions = []  # list of regions
        self.down_click_call = down_click_call  # call for processing down events
        self.stroke_checking = True  # Check for mouse/finger stroking
        self.stroke_info = SelectStroke()  # Any ongoing stroke
        self.stroke_call = None  # Call back if stroke
        self.max_select = max_select
        if rects is not None:
            if not isinstance(rects, list):
                rects = [rects]  # list of one
            for rect in rects:
                self.add_rect(rect)
        self.canvas.bind("<ButtonPress-1>", self.down)
        self.canvas.bind("<ButtonRelease-1>", self.up)
        self.canvas.bind("<Enter>", self.enter)
        self.canvas.bind("<Leave>", self.leave)
        self.enable_moves_ = True
Exemplo n.º 3
0
class SelectArea(object):
    """
    Selected region of image
    Candidate for selection.
    """
    def __deepcopy__(self, memo=None):
        """ provide deep copy by just passing shallow copy of self,
        avoiding tkparts inside sel_area
        """
        SlTrace.lg("SelectArea __deepcopy__", "copy")
        return self

    def __init__(self,
                 canvas,
                 mw=None,
                 image=None,
                 rects=None,
                 show_moved=False,
                 show_id=False,
                 highlight_limit=None,
                 check_mod=None,
                 down_click_call=None,
                 tbmove=.1,
                 max_select=1):
        """
        Rectangular selected/ing region,
        :canvas: Canvas containing the region
        :mw: Master/Parent widget, if one
        :image: displayed in frame    Not necessary/used
        :rects: single, or list of Rectangles (upper left x,y), (lower right x,y)
                each being a region
                Default is no rectangles
        :highlight_limit: Limt higlighting to time (seconds)
                Default: no time limit
        :check_mod: called, if present, before and after part is modified
        :down_click_call: processes down clicks if present'
                default: no remote processing
        :tbmove: minimum time (seconds) between move recognition
        :max_select: maximum number of simultaneous selections
                    allowed
                    default = 1
        """
        self.parts = []  # Parts of scene, corners, edges, regions
        self.parts_by_id = {}  # By part id
        self.parts_by_loc = {}  # By type, location:
        """ hash/dictionary of parts by type, location
            type: edge, corner, region
            loc: (pt), (x,y)
                 (pt1 (upper right), pt2 (lower right)
        """
        self.image = image
        if mw is None:
            mw = tk.Tk()
            mw.withdraw()  # Hide main window
        if not hasattr(mw, "update_idletasks"):
            SlTrace.lg("mw %s" % type(mw))
            raise SelectError("mw has no update_idletasks")

        self.mw = mw
        ###self.mw.protocol("WM_DELETE_WINDOW", self.on_exit)
        self.running = True  # Set false on window delete

        self.check_mod = check_mod
        self.tbmove = tbmove
        self.highlight_limit = highlight_limit
        self.show_id = show_id
        self.show_moved = show_moved
        self.record_md = SelectMoveDisplay(self, show_moved=show_moved)
        self.canvas = canvas
        self.is_enclosed = False
        self.inside = False
        self.is_down = False
        self.highlights = {}  # Highlighted handles object REFERENCE
        self.selects = {}  # Select objects(handle)  REFERENCE
        self.motion_xy = (-1, -1)
        self.regions = []  # list of regions
        self.down_click_call = down_click_call  # call for processing down events
        self.stroke_checking = True  # Check for mouse/finger stroking
        self.stroke_info = SelectStroke()  # Any ongoing stroke
        self.stroke_call = None  # Call back if stroke
        self.max_select = max_select
        if rects is not None:
            if not isinstance(rects, list):
                rects = [rects]  # list of one
            for rect in rects:
                self.add_rect(rect)
        self.canvas.bind("<ButtonPress-1>", self.down)
        self.canvas.bind("<ButtonRelease-1>", self.up)
        self.canvas.bind("<Enter>", self.enter)
        self.canvas.bind("<Leave>", self.leave)
        self.enable_moves_ = True

    def on_exit(self):
        """ Window destroyed
        """
        self.running = False

    def is_running(self):
        return self.running

    def add_down_click_call(self, down_click_call):
        """ Add processing function for down events
        :down_click_call: processing routine for down click over highlighted part
                    None - remove call function
        """
        self.down_click_call = down_click_call

    def add_rect(self,
                 rect,
                 color=None,
                 on_color=None,
                 off_color=None,
                 row=None,
                 col=None,
                 do_corners=True,
                 do_edges=True,
                 draggable_edge=True,
                 draggable_corner=True,
                 draggable_region=True,
                 invisible_region=False,
                 invisible_edge=False,
                 edge_width=1,
                 invisible_corner=False):
        """ Add rectangle to object as another region
        """
        rec_ps = [None] * 4
        ulX, ulY = rect[0][0], rect[0][1]
        lrX, lrY = rect[1][0], rect[1][1]
        sr = SelectRegion(self,
                          rect=[(ulX, ulY), (lrX, lrY)],
                          draggable=draggable_region,
                          do_corners=do_corners,
                          do_edges=do_edges,
                          invisible=invisible_region,
                          edge_width=edge_width,
                          edge_visible=not invisible_edge,
                          on_color=on_color,
                          off_color=off_color,
                          color=color,
                          row=row,
                          col=col)
        self.regions.append(sr)  # Add region
        self.add_part(sr)
        rec_ps[0] = (ulX, ulY)
        rec_ps[1] = (lrX, ulY)
        rec_ps[2] = (lrX, lrY)
        rec_ps[3] = (ulX, lrY)
        for pi1 in range(0, 3 + 1):
            pi2 = pi1 + 1
            if pi2 >= len(rec_ps):
                pi2 = 0  # Ends at first
            pt1 = rec_ps[pi1]
            pt2 = rec_ps[pi2]
            if do_edges:
                self.add_edge(sr,
                              pt1,
                              pt2,
                              draggable_edge=draggable_edge,
                              draggable_corner=draggable_corner,
                              invisible_edge=invisible_edge,
                              edge_width=edge_width,
                              do_corners=do_corners,
                              invisible_corner=invisible_corner)

    def add_edge(self,
                 region,
                 pt1,
                 pt2,
                 do_corners=True,
                 draggable_edge=True,
                 draggable_corner=True,
                 invisible_edge=False,
                 edge_width=1,
                 invisible_corner=False):
        """ Add edge handles to region
        Also adds corner handles
        """
        edge = self.get_edge_with(pt1, pt2)
        if edge is None:
            edge = SelectEdge(self,
                              rect=[pt1, pt2],
                              draggable=draggable_edge,
                              edge_width=edge_width,
                              invisible=invisible_edge)
            self.add_part(edge)
        if do_corners:
            self.add_corners(region, [pt1, pt2],
                             edge=edge,
                             draggable=draggable_corner,
                             invisible=invisible_corner)  # So we get corners
        region.add_connected(edge)
        edge.add_adjacent(region)  # Connect region to edge

    def add_corners(self,
                    region,
                    points,
                    edge=None,
                    draggable=True,
                    invisible=False):
        """ Add corners to region
        :region: region to which to add corners
        :points: one or more corner locations
        If corner == first corner, set region enclosed
        but DON't add corner
        """
        if not isinstance(points, list):
            points = [points]  # Treat as an array of one
        for point in points:
            if self.is_first_corner(point):
                self.is_enclosed = True
            if self.has_corner(point):
                corner = self.get_corner_part(point[0], point[1])
            else:
                corner = SelectCorner(self,
                                      point=point,
                                      draggable=draggable,
                                      invisible=invisible)
            self.add_part(corner)  # Add new corners
            corner.add_connected(region)

            region.add_connected(corner)
            if edge is not None:
                corner.add_connected(edge)  # Connect edge to corner
                edge.add_connected(corner)  # Connect corner to edge

    def add_turned_on_part_call(self, call_back):
        """ Add function to be called upon if edge is turned on
        :call_back: call back (part) - None - remove call back
        """
        self.turned_on_part_call = call_back

    def has_corner(self, point):
        """ Check if corner already present in region
        """
        for corner in self.parts:
            if not corner.is_corner():
                continue
            loc = corner.loc
            pt = loc.coord
            if pt[0] == point[0] and pt[1] == point[1]:
                return True  # Corner already present
        return False  # No such corner present

    def is_square_complete(self, part, squares=None, ifadd=False):
        """ Determine if this edge completes one or more square(s) region(s)
        :part: - possibly completing edge
        :squares: - any squares completed are appended to this list
                default: no regions are appended
        :ifadd: If part is added
                default: part must already be added
        """
        if part.is_edge():
            SlTrace.lg("complete square testing %s" % part,
                       "square_completion")
            regions = part.get_adjacents()  # Look if we completed any square
            ncomp = 0
            for square in regions:
                if (ifadd and square.is_complete(added=part)
                        or square.is_complete()):
                    ncomp += 1
                    if squares is not None:
                        squares.append(square)
            if ncomp > 0:
                return True

        return False

    def square_complete_distance(self, edge, squares_distances=None):
        """ Determine minimum number of moves, including this
        move to complete a square
        :edge: - potential completing edge
        :squares_distances: list, of (distance, square) pairs
                Default: no entries returned
                if no connected squares - empty list returned
        :returns: closest distance, NOT_CLOSE if no squares
        """
        NOT_CLOSE = 99
        SlTrace.lg("square distance testing %s" % edge, "square_completion")
        regions = edge.get_adjacents()  # Look if we completed any square
        distances = []  # distances as found
        sqds = []  # distance, square pairs
        for square in regions:
            if not square.is_region():
                continue  # Not a region
            square_edges = square.get_edges()
            distance = 0
            for sq_edge in square_edges:
                if not sq_edge.is_turned_on():
                    distance += 1
            sqds.append((distance, square))
            distances.append(distance)
        if squares_distances is not None:
            squares_distances = sqds

        if len(distances) == 0:
            return NOT_CLOSE  # No squares at all

        return min(distances)

    def is_selected(self, part):
        """ Check if part is selected
        :part: part/part_id to check
        """
        if isinstance(part, int):
            part_id = part
        else:
            part_id = part.part_id
        for selected_part_id in self.selects:
            if part_id == selected_part_id:
                return True

        return False

    def is_first_corner(self, point):
        """ Determine if this is the first point (equal to)
        """
        corner1 = None  # Set if found
        for part in self.parts:
            if part.is_corner():
                corner1 = part
                break
        if corner1 is None:
            return False  # No corners to be had

        cpt = corner1.loc.coord
        if cpt[0] == point[0] and cpt[1] == point[1]:
            return True

        return False

    def get_color(self, row, col):
        """ Get color on square(region) at row, col
        :row: row in nx,ny
        :col: column in nx,ny
        :returns: current color None if none
        """
        square = self.get_square(row, col)
        if square is None:
            return None

        color = square.get_color()
        return color

    def color_set(self, color, row, col):
        """ Set color on square(region) at row, col
        :color: color to set square
        :row: row in nx,ny
        :col: column in nx,ny
        :returns: previous color None if none
        """
        square = self.get_square(row, col)
        if square is None:
            return None

        prev_color = square.set_color(color)
        square.display()
        return prev_color

    def current_edge(self, pt1, pt2):
        """ Return edge with these specifications, None if none found
        """
        return self.current_part("edge")

    def current_part(self, part_type, pt1, pt2=None):
        """ Return part with this type, location, None if none found
        """
        loc_key = SelectPart.part_loc_key(part_type, pt1, pt2=None)
        if loc_key in self.parts_by_loc:
            return self.parts_by_loc[loc_key]  # current entry

        return None  # No entry at this location

    def destroy(self):
        """ Relinquish 
        """
        for part in self.parts:
            part.destroy()

    def display(self, parts=None):
        """ Display parts list
        :parts: list of parts to display
                default: all  parts
        """
        if parts is None:
            parts = self.parts
        SlTrace.lg("\ndisplay %d parts" % (len(parts)), "display")
        for part in self.display_order(parts):
            part.display()

    def display_order(self, parts):
        """ return parts in display order: Do all regions, then edges, then corners
         so corners are not blocked by regions
         :parts: part/id list
        """
        if len(parts) == 0:
            return []

        pts = parts
        if isinstance(next(iter(parts)), int):
            pts = []
            for part in parts:
                pts.append(self.get_part(part))  # Convert id to part

        ordered = []
        for part in pts:
            if part.is_region():
                ordered.append(part)
        for part in pts:
            if part.is_edge():
                ordered.append(part)
        for part in pts:
            if part.is_corner():
                ordered.append(part)
        return ordered

    def display_set(self, part=None):
        if part.is_corner():
            part.display()
        elif part.is_edge():
            part.display()
        elif part.is_region():
            part.display()

    def display_corner(self, corner):
        """ Display corner on inside upper left corner
        """
        return corner.display()

        self.display_clear(corner)
        if self.is_highlighted(corner):
            """ Highlight given corner
            :hand: Corner handle
            :Returns: object tag for deletion
            """
            c1x, c1y, c3x, c3y = corner.get_rect(enlarge=True)
            corner.display_tag = self.canvas.create_rectangle(
                c1x, c1y, c3x, c3y, fill=SelectPart.corner_fill_highlight)
        else:
            loc = corner.loc
            SlTrace.lg("corner: %s" % str(loc), "display")
            c1x, c1y, c3x, c3y = corner.get_rect()
            corner.display_tag = self.canvas.create_rectangle(
                c1x, c1y, c3x, c3y, fill=SelectPart.corner_fill)

    def display_text(self, position, **kwargs):
        """ Add text to display, returning tag
        """
        tag = self.canvas.create_text(position, **kwargs)
        return tag

    def get_parts(self, pt_type=None):
        """ Get parts in figure
        :pt_type: part type, default: all parts
                "corner", "edge", "region"
        """
        parts = []
        for part in self.parts:
            if pt_type is None or pt_type == part.part_type:
                parts.append(part)
        return parts

    def get_part(self, id=None, type=None, sub_type=None, row=None, col=None):
        """ Get basic part
        :id: unique part id
        :type: part type e.g., edge, region, corner default: "edge"
        :sub_type: must match if present e.g. v for vertical, h for horizontal
        :row:  part row
        :col: part column
        :returns: part, None if not found
        """
        if id is not None:
            if id not in self.parts_by_id:
                SlTrace.lg("part not in parts_by_id")
                return None

            part = self.parts_by_id[id]
            return part

        if type is None:
            type = "edge"
        for part in self.parts:
            if part.part_type == type and (sub_type is None
                                           or part.sub_type() == sub_type):
                if part.row == row and part.col == col:
                    return part

    def get_parts_at(self, x, y, sz_type=SelectPart.SZ_SELECT):
        """ Check if any part is at canvas location provided
        If found list of parts
        :Returns: SelectPart[]
        """
        parts = []
        for part in self.parts:
            if not isinstance(part, SelectPart):
                SlTrace.lg("part(%s) is not SelectPart" % part)
            if part.is_over(x, y, sz_type=sz_type):
                parts.append(part)
        if len(parts) > 1:
            SlTrace.lg("get_parts at (%d, %d) sz_type=%d" % (x, y, sz_type),
                       "get_parts")
            for part in parts:
                c1x, c1y, c3x, c3y = part.get_rect(sz_type=sz_type)
                SlTrace.lg(
                    "    %s : c1x:%d, c1y:%d, c3x:%d, c3y:%d" %
                    (part, c1x, c1y, c3x, c3y), "get_parts")
            SlTrace.lg("", "get_parts")
            olap_rect = SelectPart.get_olaps(parts,
                                             sz_type=SelectPart.SZ_SELECT)
            if olap_rect is not None:
                if SlTrace.trace("overlapping"):
                    SlTrace.lg(
                        "Overlapping %d,%d, %d,%d" %
                        (olap_rect[0][0], olap_rect[0][1], olap_rect[1][0],
                         olap_rect[1][1]), "overlapping")
                    SlTrace.lg("")
        return parts

    def get_corner_part(self, x, y):
        """ Returns corner SelectPart if at corner handle
        else None
        """
        for corner in self.parts:
            if not corner.is_corner():
                continue
            p1c1x, p1c1y, p1c3x, p1c3y = corner.get_rect(
                sz_type=SelectPart.SZ_SELECT)
            if p1c1x <= x and x <= p1c3x and p1c1y <= y and y <= p1c3y:
                return corner

        return None

    def get_edge_part(self, x, y):
        """ Returns edge object if at corner handle
        else None
        """
        for edge in self.parts:
            if not edge.is_edge():
                continue
            p1c1x, p1c1y, p1c3x, p1c3y = edge.get_rect()
            if p1c1x <= x and x <= p1c3x and p1c1y <= y and y <= p1c3y:
                return edge

        return None

    def get_edge_with(self, pt1, pt2):
        """ Get edge part with corners at pt1, pt2, if one
            :pt1:  end point uL or lR
            :pt2:  end point
            :Returns: edge or None
        """
        for part in self.parts:
            if not part.is_edge():
                continue
            points = part.get_points()
            if (SelectPart.is_point_equal(points[0], pt1)
                    and SelectPart.is_point_equal(points[1], pt2)):
                return part
            if (SelectPart.is_point_equal(points[1], pt1)
                    and SelectPart.is_point_equal(points[0], pt2)):
                return part

        return None

    def get_region_part(self, x, y):
        """ Returns region object if at region handle
        else None
        """
        for region in self.parts:
            if not region.is_region():
                continue
            p1c1x, p1c1y, p1c3x, p1c3y = region.get_rect()
            if p1c1x <= x and x <= p1c3x and p1c1y <= y and y <= p1c3y:
                return region

        return None

    def get_square(self, row, col):
        """ return part for region at row, col
        :returns: part at row,col else None
        """
        """ Create dictionary only if we have to
        Maybe numpy would be better but we need to store parts, not numbers
        """
        if not hasattr(self,
                       "parts_by_row_col") or self.parts_by_row_col is None:
            parts_by_row_col = {}
            for part in self.parts:
                if part.row != 0 and part.col != 0:
                    key = (part.row, part.col)
                    parts_by_row_col[key] = part
            self.parts_by_row_col = parts_by_row_col
        key = (row, col)

        if key not in self.parts_by_row_col:
            return None

        part = self.parts_by_row_col[key]

        return part

    def display_clear(self, handle):
        """ Clear display of this handle
        """
        if handle.display_tag is not None:
            if isinstance(handle.display_tag, list):
                for tag in handle.display_tag:
                    self.canvas.delete(tag)
            else:
                self.canvas.delete(handle.display_tag)
            handle.display_tag = None
        if handle.highlight_tag is not None:
            self.canvas.delete(handle.highlight_tag)
            handle.highlight_tag = None

    def display_edge(self, edge):
        """ Display edge as a rectangle
        We leave room for the corners at each end
        Highlight if appropriate
        """

        loc = edge.loc
        rect = loc.coord
        SlTrace.lg("edge: %s" % str(loc), "display")
        if self.is_highlighted(edge):
            c1x, c1y, c3x, c3y = edge.get_rect(enlarge=True)
            edge.highlight_tag = self.canvas.create_rectangle(
                c1x, c1y, c3x, c3y, fill=SelectPart.edge_fill_highlight)
        else:
            self.display_clear(edge)
            c1x, c1y, c3x, c3y = edge.get_rect()
            edge.display_tag = self.canvas.create_rectangle(
                c1x, c1y, c3x, c3y, fill=SelectPart.edge_fill)
        if self.show_id:
            dir_x, dir_y = edge.edge_dxy()
            chr_w = 5
            chr_h = chr_w * 2
            if dir_x != 0:  # sideways
                offset_x = -len(str(edge.part_id)) * chr_w / 2 + chr_w
                offset_y = chr_h
            if dir_y != 0:  # up/down
                offset_x = len(str(edge.part_id)) * chr_w
                offset_y = 0

            cx = (c1x + c3x) / 2 + offset_x
            cy = (c1y + c3y) / 2 + offset_y
            edge.name_tag = self.display_text((cx, cy), text=str(edge.part_id))

    def check_pick_part(self, event):
        """ Check for and process if part has been picked.  E.g., line/edge added to square
        :returns: True iff found a pick
        If so, and down_click_call has been set, we make the call
        """
        if self.has_highlighted():
            for highlight in self.highlights.values():
                part = highlight.part
                if self.down_click_call is not None:
                    res = self.down_click_call(part)
                    if res:
                        return res  # we've done the processing for this part
        return False  # No processing

    def down(self, event):
        if SlTrace.trace("part_info"):
            cnv = event.widget
            x, y = cnv.canvasx(event.x), cnv.canvasy(event.y)
            parts = self.get_parts_at(x, y)
            if parts:
                SlTrace.lg("x=%d y=%d" % (x, y))
                for part in parts:
                    SlTrace.lg("    %s\n%s" % (part, part.str_edges()))

        if not self.enable_moves_:
            return  # Low-level ignore

        self.is_down = True
        if self.inside:
            SlTrace.lg("Click in canvas event:%s" % event, "motion")
            cnv = event.widget
            x, y = cnv.canvasx(event.x), cnv.canvasy(event.y)
            SlTrace.lg("x=%d y=%d" % (x, y), "down")

        if self.has_highlighted():
            part_ids = list(self.highlights)
            for part_id in part_ids:
                part = self.parts_by_id[part_id]
                SlTrace.lg("highlighted %s" % (part), "highlight")
                if self.down_click_call is not None:
                    res = self.down_click_call(part, event=event)
                    continue  # we've done the processing for this part

                self.select_set(part)
                if part.display_tag is None:
                    pt = str(part.part_type)
                    pdtag = str(part.display_tag)
                    SlTrace.lg("select %s tag=%s" % (
                        pt,
                        pdtag,
                    ), "highlight")
                else:
                    SlTrace.lg(
                        "select %s tag=%s (%s)" %
                        (part.part_type, part.display_tag, part), "highlight")

    def motion(self, event):
        ###cnv.itemconfigure (tk.CURRENT, fill ="blue")
        cnv = event.widget
        x, y = float(cnv.canvasx(event.x)), float(cnv.canvasy(event.y))
        ###got = event.widget.coords (tk.CURRENT, x, y)

    def leave(self, event):
        SlTrace.lg("leave", "leave")
        self.inside = False
        if hasattr(self, 'motion_bind_id') and self.motion_bind_id is not None:
            self.canvas.unbind("<Motion>", self.motion_bind_id)
            self.motion_bind_id = None

    def list_blinking(self, prefix=None):
        if prefix is None:
            prefix = ""
        else:
            prefix = prefix + " "
        part_ids = list(self.parts_by_id)
        n_on = 0
        for part_id in part_ids:
            part = self.get_part(id=part_id)
            if part.blinker is not None:
                n_on += 1
        SlTrace.lg("%s parts blinking on(%d of %d):" %
                   (prefix, n_on, len(part_ids)))
        for part_id in part_ids:
            part = self.get_part(id=part_id)
            if part.blinker is not None:
                SlTrace.lg("    %s" % (part))

    def list_selected(self, prefix=None):
        if prefix is None:
            prefix = ""
        else:
            prefix = prefix + " "
        part_ids = list(self.parts_by_id)
        n_on = 0
        for part_id in part_ids:
            part = self.get_part(id=part_id)
            if part.is_selected():
                n_on += 1
        SlTrace.lg("%sparts selected on(%d of %d):" %
                   (prefix, n_on, len(part_ids)))
        for part_id in part_ids:
            part = self.get_part(id=part_id)
            if part.is_selected():
                SlTrace.lg("    %s" % (part))
        if SlTrace.trace("selected_selects"):
            selecteds = self.get_selects()
            SlTrace.lg("parts in selecteds:")
            for part_id in selecteds:
                SlTrace.lg("    %s" % self.get_part(part_id))

    def enter(self, event):
        SlTrace.lg("enter", "enter")
        self.inside = True
        self.motion_bind_id = self.canvas.bind("<Motion>", self.on_motion)

    def disable_moves(self):
        """ Disable(ignore) moves by user
        """
        self.enable_moves_ = False

    def enable_moves(self):
        """ Enable moves by user
        """
        self.enable_moves_ = True

    def get_xy(self):
        """ get current mouse position (or last one recongnized
        :returns: x,y on area canvas, -1,-1 if never been anywhere
        """
        return self.motion_xy

    def on_motion(self, event):
        cnv = event.widget
        x, y = cnv.canvasx(event.x), cnv.canvasy(event.y)
        self.mouse_move(x, y)

    def mouse_move(self, x, y):
        SlTrace.lg("on_motion: x,y=%d,%d" % (x, y), "on_motion")
        if not self.enable_moves_:
            return  # Low-level ignore

        prev_xy = self.motion_xy
        self.motion_xy = (x, y)
        if prev_xy is None:
            prev_xy = self.motion_xy
        SlTrace.lg("on_motion enable_moves: x,y=%d,%d" % (x, y), "on_motion")
        if self.is_down:
            if self.has_selected():
                parts = self.get_selected_parts(only_draggable=True)
                if len(parts) > 0 and len(parts) <= self.max_select:
                    self.record_move_setup()
                    for part in parts:
                        xinc = x - prev_xy[0]
                        yinc = y - prev_xy[1]
                        SlTrace.lg(
                            "motion on(%s) at xy=(%d,%d) by xinc=%d yinc=%d" %
                            (part.part_type, x, y, xinc, yinc), "motion")
                        self.move_part(part, xinc, yinc)
                        self.highlight_set(part)
                    self.record_move_display()
        parts = self.get_parts_at(
            x, y, sz_type=SelectPart.SZ_SELECT)  # NOTE: this is reference
        if len(parts) > 0:
            ncheck = 3
            if len(parts) > ncheck:
                SlTrace.lg("on parts(%d) > %d " % (len(parts), ncheck),
                           "motion")
                for pa in parts:
                    SlTrace.lg("part: %s" % pa, "motion")
                SlTrace.lg("", "motion")
            self.highlight_clear()  # First clear all highlighted parts
            for part in parts:
                if not part.is_region():
                    SlTrace.lg("motion over %s" % part, "is_over")
                if SlTrace.trace("part_info_over"):
                    part.display_info(tag="over:")
                self.highlight_set(part, xy=(x, y))
                self.stroke_check(part, x=x, y=y)
        else:
            if self.has_highlighted():
                self.highlight_clear()

    def highlight_set(self, part, xy=None, highlight_limit=None):
        """ highlight specified part
        Save handle in highlight to allow easy access
        Clear previous highlight, if one
        :highlight_limit: clear highlight after this (seconds)
                    Default: self.highlight_limit
        """
        if highlight_limit is None:
            highlight_limit = self.highlight_limit
        if part.turned_on and not part.on_highlighting:
            return  # Don't highlight if turned on

        part.highlight_set(highlight_limit=highlight_limit)

    def get_highlighted(self):
        if not self.has_highlighted():
            return None

        highlighted = self.highlighted
        return highlighted

    def get_highlighted_part(self):
        highlighted = self.get_highlighted()
        if highlighted is None:
            return None

        return highlighted.part

    def has_highlighted(self):
        """ Determine if any highlighted parts
        """
        if bool(self.highlights):
            return True
        return False

    def is_highlighted(self, part):
        """ Check if part is highlighted
        """
        return part.is_highlighted()

    def is_highlighted_others(self, part):
        """ Check if non-overlapping parts are highlighted
        """
        if not self.has_highlighted():
            return False  # Nobody highlighted

        for highlight in self.highlights.values():
            if highlight.part.part_type != part.part_type:
                return True  # Another type is highlighted

            if not highlight.part.is_covering(part):
                return True

        return False

    def clear_highlighted(self, parts=None, display=True):
        """ Clear highlighted parts
        :parts: parts to clear default: ALL
        :display: True display after default: True
        """
        if parts is None:
            parts = []
            for highlight in self.highlights.values():
                parts.append(highlight.part)
        for part in parts:
            part.highlight_clear(display=display)

    def highlight_clear(self, parts=None, others=False, display=True):
        """ Clear highlighted parts
            Remove part form  highlights
            :parts:  parts to consider
            :others:   False - clear these parts
                        True - clear other parts
            :display: display updated, default: True
        """
        if not self.has_highlighted():
            return

        if parts is None:
            if not others:
                SlTrace.lg("highlight_clear ALL", "highlight")
            parts = []
            for highlight in self.highlights.values():
                parts.append(highlight.part)
        if not isinstance(parts, list):
            parts = [parts]

        if others:
            other_parts = []
            for highlight in self.highlights.values():
                found = False
                for part in parts:
                    if highlight.part.is_same(part):
                        found = True
                        break
                if not found:
                    other_parts.append(part)
            parts = other_parts

        for part in parts:
            part.highlight_clear(display=display)

    def select_clear(self, parts=None):
        """ Select part(s)
        :parts: part/id or list of parts
                default: all selected
        """
        '''parts = select_copy(parts)'''
        if parts is None:
            parts = []
            for selects_part_id in self.selects:
                parts.append(selects_part_id)
        if not isinstance(parts, list):
            parts = [parts]
        if SlTrace.trace("selected"):
            self.list_selected("select_clear BEFORE")
        for part in parts:
            if isinstance(part, int):
                part_id = part
            else:
                part_id = part.part_id
            if part_id in self.selects:
                del (self.selects[part_id])
        if SlTrace.trace("selected"):
            self.list_selected("select_clear AFTER")

    def select_set(self, parts, keep=False):
        """ Select part(s)
        :parts: part/id or list
        :keep: keep previously selected default: False
        """
        parts = copy.copy(parts)
        if not keep:
            self.select_clear()
        if not isinstance(parts, list):
            parts = [parts]
        for part in parts:
            part_id = part.part_id
            self.selects[part_id] = part
        if SlTrace.trace("selected"):
            self.list_selected("select_set AFTER")

    def get_selects(self):
        """ Get list of selected parts
        :returns: list, empty if none
        """
        return self.selects.keys()

    def get_selected_part(self):
        if not self.selects:
            return None

        return self.get_part(next(iter(self.selects)))

    def has_selects(self):
        """ Determine if any highlighted parts
        """
        if self.selects:
            return True
        return False

    def set_stroke_move(self, use_stroke=True):
        """ Enable/Disable use of stroke moves
        Generally for use in touch screens
        """
        self.stroke_checking = use_stroke

    def select_remove(self, selects):
        """ Remove one or more of selections
        :select: one or list of  selections to remove
                Default: all
        """
        if not isinstance(selects, list):
            selects = [selects]
        for part_id in list(self.selects):
            self.selects.pop(part_id)

    def add_stroke_call(self, call_back=None):
        """ Add callback for completed stroke
        :call_back: call(part=part, x=, y=)
        """
        self.stroke_call = call_back

    def stroke_check(self, part, x=None, y=None):
        """ Check if user stroked part
        Used for stroking edges on touch screen
        in "squares" game
        :part: inside this part
        :x: - x coordinate
        :y: - y coordinate
        :returns: True iff got stroke
        Detection starts self.btmove seconds after the
        most recent 
        """
        if not self.stroke_checking:
            return False

        now = datetime.now()
        time_diff = (now - self.stroke_info.prev_time).total_seconds()
        if time_diff < self.tbmove:
            return

        if self.stroke_info.in_stroke():
            SlTrace.lg("in_stroke x=%d y=%d" % (x, y), "in_stroke")
            if part.is_edge() and not self.stroke_info.same_part(part):
                self.stroke_info.setup()
                return

            SlTrace.lg("same stroke x=%d y=%d" % (x, y), "in_stroke")
            if self.stroke_info.is_continued(part=part, x=x, y=y):
                self.stroke_info.new_point(x, y)
                if self.stroke_info.is_stroke():
                    SlTrace.lg("got stroke x=%d y=%d\n" % (x, y), "in_stroke")
                    if self.down_click_call is not None:
                        res = self.down_click_call(part)
                        self.stroke_info.setup()  # Reset
                        return res

        if part.is_edge():
            self.stroke_info.setup(part, x=x, y=y)

        return False

    def stroke_init(self, part=None, x=None, y=None):
        if part is None:
            self.stroke_info.setup()
            return

        self.stroke_info.setup(part=part, x=x, y=y)

    def get_selecteds(self, only_draggable=False):
        """ Return list of selected info or [] if none
        :only_draggable: True- include only if draggable
                            default: False - all
        """
        sels = []
        for part in self.selects.values():
            if not only_draggable or part.draggable:
                sels.append(part)
        return sels

    def get_selected_parts(self, only_draggable=False):
        """ Returns all selected parts
        [] if non selected
        :only_draggable: True- include only if draggable
                            default: False - all
        """
        parts = self.get_selecteds(only_draggable=only_draggable)
        return parts

    def has_selected(self):
        """ Check if any selected parts
        """
        if bool(self.selects):
            return True
        return False

    def move_part(self, part, xinc, yinc):
        """ Move selected handle, adjusting connected parts
        """

        self.display_clear(part)  # Clear display before moveing
        if part.is_region():
            self.move_region(part, xinc, yinc)
        elif part.is_edge():
            self.move_edge(part, xinc, yinc)

        elif part.is_corner():
            self.move_corner(part, xinc, yinc)

        self.display_set(part)

    def move_announce(self):
        import winsound

        winsound.Beep(100, 10)

    def move_edge(self, edge, xinc, yinc, adjusts=None):
        """ Move selected edge, adjusting connected parts
        Direction of movement is constrained to perpendicular to edge
        Connected parts are:
            the corners at each edge end
            the end-points of the edges, not including this edge,
                connected to the end-corners
        :edge:  selected edge
        :xinc:  x destination delta
        :yinc:  y destination delta
        :adjusts: adjusted connections
                    Default: all connections
        :highlight: True - highlight after move
        """
        self.display_clear(edge)  # Clear display before moveing
        delta_x = xinc
        delta_y = yinc
        edge_dx, edge_dy = edge.edge_dxy()
        if edge_dx == 0:
            delta_y = 0  # No movement parallel to edge
        if edge_dy == 0:
            delta_x = 0  # No movement parallel to edge
        coord = edge.loc.coord
        p1, p2 = coord[0], coord[1]
        SlTrace.lg(
            "move_edge: %s by delta_x=%d,delta_y=%d" %
            (edge, delta_x, delta_y), "move_part")
        """ Collect moves group:
            Collect all parts which need to be moved in whole or part.
            Each part is present once.  If an edge end is moved only the other edge's
            ends need to be adjusted
                 
        """
        mover = SelectMover(self, delta_x=delta_x, delta_y=delta_y)
        mover.add_moves(parts=edge)
        ###mover.add_moves(parts=edge.connecteds)
        ###mover.add_adjusts(edge.connecteds)      # Adjust those connected to corners and so on
        mover.move_list(delta_x, delta_y)

    def move_corner(self, corner, xinc, yinc):
        """ Move selected corner, adjusting connected edges
        :corner: selected corner
        :xinc: x destination increment
        :yinc: y destination increment
        """
        delta_x = xinc
        delta_y = yinc
        SlTrace.lg(
            "move: %s  by delta_x=%d,delta_y=%d" % (corner, delta_x, delta_y),
            "move_part")
        """ Split movements in to two directions to restrict propagation
        of affected parts
        """
        if delta_x != 0:
            mover = SelectMover(self, delta_x=delta_x)
            mover.add_moves(parts=corner)
            ###mover.add_moves(parts=edge.connecteds)
            ###mover.add_adjusts(edge.connecteds)      # Adjust those connected to corners and so on
            mover.move_list(delta_x, 0)
        if delta_y != 0:
            mover = SelectMover(self, delta_y=delta_y)
            mover.add_moves(parts=corner)
            ###mover.add_moves(parts=edge.connecteds)
            ###mover.add_adjusts(edge.connecteds)      # Adjust those connected to corners and so on
            mover.move_list(0, delta_y)

    def move_region(self, region, xinc, yinc, adjusts=None):
        """ Move region
        Currently all parts
        Implement via move all corners
        """
        for part in region.connecteds:
            if part.is_corner():
                self.move_corner(part, xinc, yinc)

    def adjust_corner_edge(self, corner, edge, delta_x, delta_y):
        """ Adjust edge's endpoint connected to this corner by the delta x,y
        """
        indexes = edge.get_connected_loc_indexes(corner)
        if indexes is None:
            raise (SelectError(
                "adjust_corner_edge: corner(%s) not connected to edge(edge)" %
                (corner, edge)))
        coord = edge.loc.coord
        coord.move_nodes(delta_x, delta_y, indexes[0])
        self.display_set(edge)

    def add_part(self, part):
        """ Add new part to area
        :part: partially completed to add
        Not added if already present
        Hash updates and pointers added to part
        We are considering adding this to the SelectPart
        constructor but have, so far, refrained because of 
        the possibility of having multiple lists as is the
        fact in EnhancedModeler
        """
        if part.part_id in self.parts_by_id:
            return

        SlTrace.lg("add_part: %s" % part, "add_part")
        """ Provide pointers to other entries
        in the parts_by_id entry to aid do/undo
        """
        loc_key = part.loc_key()
        if loc_key in self.parts_by_loc:
            SlTrace.lg("add_part %s already present", "add_part")
            return

        part.loc_key_ = loc_key
        part.parts_index = len(self.parts)
        self.parts.append(part)
        self.parts_by_id[part.part_id] = part
        self.parts_by_loc[loc_key] = part

    def up(self, event):
        self.is_down = False
        ###event.widget.itemconfigure (tk.CURRENT, fill =self.defaultcolor)
        cnv = event.widget
        x, y = cnv.canvasx(event.x), cnv.canvasy(event.y)
        ###got = event.widget.coords (tk.CURRENT, x, y)
        SlTrace.lg("up at x=%d y=%d" % (x, y), "motion")
        SlTrace.lg("up is ignored", "motion")
        return

        if self.has_selected():
            ids = list(self.selects)
            for sel_id in ids:
                self.select_remove(sel_id)
        if SlTrace.trace("selected"):
            self.list_selected("up AFTER")

    def record_move(self, move):
        """ Record move for display or subsequent action
        :move: SelectMove to be recorded
        """
        self.record_md.record_move(move)

    def record_move_setup(self):
        """ Setup for move record display
        """
        self.record_md.record_move_setup()

    def record_move_display(self):
        self.record_md.record_move_display()
Exemplo n.º 4
0
class SelectReg(object):
    """
    Selected region of image
    Candidate for selection.
    """
    def __init__(self,
                 canvas,
                 image,
                 rects=None,
                 show_moved=False,
                 show_id=False):
        """
        Rectangular selected/ing region,
        :canvas: Canvas containing the region
        :image: displayed in frame
        :rects: single, or list of Rectangles (upper left x,y), (lower right x,y)
                each being a region
                Default is no rectangles
        """
        self.parts = []  # Parts of scene, corners, edges, regions
        self.parts_by_id = {}  # By part id
        self.show_id = show_id
        self.show_moved = show_moved
        self.record_md = SelectMoveDisplay(self, show_moved=show_moved)
        self.canvas = canvas
        self.is_enclosed = False
        self.inside = False
        self.is_down = False
        self.highlights = {}  # Highlighted handles object REFERENCE
        self.selects = {}  # Select objects(handle)  REFERENCE
        self.motion_xy = None
        self.regions = []  # list of regions

        if rects is not None:
            if not isinstance(rects, list):
                rects = [rects]  # list of one
            for rect in rects:
                self.add_rect(rect)

        self.canvas.bind("<ButtonPress-1>", self.down)
        self.canvas.bind("<ButtonRelease-1>", self.up)
        self.canvas.bind("<Enter>", self.enter)
        self.canvas.bind("<Leave>", self.leave)

    def add_rect(self, rect):
        """ Add rectangle to object as another region
        """
        rec_ps = [None] * 4
        ulX, ulY = rect[0][0], rect[0][1]
        lrX, lrY = rect[1][0], rect[1][1]
        sr = SelectRegion(rect=[(ulX, ulY), (lrX, lrY)])  # Just one region now
        self.regions.append(sr)  # Add region
        self.add_part(sr)
        rec_ps[0] = (ulX, ulY)
        rec_ps[1] = (lrX, ulY)
        rec_ps[2] = (lrX, lrY)
        rec_ps[3] = (ulX, lrY)
        for pi1 in range(0, 3 + 1):
            pi2 = pi1 + 1
            if pi2 >= len(rec_ps):
                pi2 = 0  # Ends at first
            pt1 = rec_ps[pi1]
            pt2 = rec_ps[pi2]
            self.add_edge(sr, pt1, pt2)

    def add_edge(self, region, pt1, pt2):
        """ Add edge handles to region
        Also adds corner handles
        """
        edge = self.get_edge_with(pt1, pt2)
        if edge is None:
            edge = SelectEdge(rect=[pt1, pt2])
            self.add_part(edge)
        self.add_corners(region, [pt1, pt2], edge=edge)  # So we get corners
        region.add_connected(edge)

    def add_corners(self, region, points, edge=None):
        """ Add corners to region
        :region: region to which to add corners
        :points: one or more corner locations
        If corner == first corner, set region enclosed
        but DON't add corner
        """
        if not isinstance(points, list):
            points = [points]  # Treat as an array of one
        for point in points:
            if self.is_first_corner(point):
                self.is_enclosed = True
            if self.has_corner(point):
                corner = self.get_corner_part(point[0], point[1])
            else:
                corner = SelectCorner(point=point)
            self.add_part(corner)  # Add new corners

            region.add_connected(corner)
            if edge is not None:
                corner.add_connected(edge)  # Connect edge to corner
                edge.add_connected(corner)  # Connect corner to edge

    def has_corner(self, point):
        """ Check if corner already present in region
        """
        for corner in self.parts:
            if not corner.is_corner():
                continue
            loc = corner.loc
            pt = loc.coord
            if pt[0] == point[0] and pt[1] == point[1]:
                return True  # Corner already present
        return False  # No such corner present

    def is_first_corner(self, point):
        """ Determine if this is the first point (equal to)
        """
        corner1 = None  # Set if found
        for part in self.parts:
            if part.is_corner():
                corner1 = part
                break
        if corner1 is None:
            return False  # No corners to be had

        cpt = corner1.loc.coord
        if cpt[0] == point[0] and cpt[1] == point[1]:
            return True

        return False

    def display(self):
        for part in self.parts:
            self.display_set(part)

    def display_set(self, part=None):
        if part.is_corner():
            self.display_corner(part)
        elif part.is_edge():
            self.display_edge(part)

    def display_text(self, position, **kwargs):
        """ Add text to display, returning tag
        """
        tag = self.canvas.create_text(position, **kwargs)
        return tag

    def get_parts_at(self, x, y, sz_type=SelectPart.SZ_SELECT):
        """ Check if any part is at canvas location provided
        If found list of parts
        :Returns: SelectPart[]
        """
        parts = []
        for part in self.parts:
            if part.is_over(x, y, sz_type=sz_type):
                parts.append(part)
        if len(parts) > 1:
            print("get_parts at (%d, %d) sz_type=%d" % (x, y, sz_type))
            for part in parts:
                c1x, c1y, c3x, c3y = part.get_rect(sz_type=sz_type)
                print("    %s : c1x:%d, c1y:%d, c3x:%d, c3y:%d" %
                      (part, c1x, c1y, c3x, c3y))
            print()
            olap_rect = SelectPart.get_olaps(parts,
                                             sz_type=SelectPart.SZ_SELECT)
            if olap_rect is not None:
                print("Overlapping %d,%d, %d,%d" %
                      (olap_rect[0][0], olap_rect[0][1], olap_rect[1][0],
                       olap_rect[1][1]))
                print()
        return parts

    def get_corner_part(self, x, y):
        """ Returns corner SelectPart if at corner handle
        else None
        """
        for corner in self.parts:
            if not corner.is_corner():
                continue
            p1c1x, p1c1y, p1c3x, p1c3y = corner.get_rect(
                sz_type=SelectPart.SZ_SELECT)
            if p1c1x <= x and x <= p1c3x and p1c1y <= y and y <= p1c3y:
                return corner

        return None

    def get_edge_part(self, x, y):
        """ Returns edge object if at corner handle
        else None
        """
        for edge in self.parts:
            if not edge.is_edge():
                continue
            p1c1x, p1c1y, p1c3x, p1c3y = edge.get_rect()
            if p1c1x <= x and x <= p1c3x and p1c1y <= y and y <= p1c3y:
                return edge

        return None

    def get_edge_with(self, pt1, pt2):
        """ Get edge part with corners at pt1, pt2, if one
            :pt1:  end point uL or lR
            :pt2:  end point
            :Returns: edge or None
        """
        for part in self.parts:
            if not part.is_edge():
                continue
            points = part.get_points()
            if (SelectPart.is_point_equal(points[0], pt1)
                    and SelectPart.is_point_equal(points[1], pt2)):
                return part
            if (SelectPart.is_point_equal(points[1], pt1)
                    and SelectPart.is_point_equal(points[0], pt2)):
                return part

        return None

    def get_region_part(self, x, y):
        """ Returns region object if at region handle
        else None
        """
        for region in self.parts:
            if not region.is_region():
                continue
            p1c1x, p1c1y, p1c3x, p1c3y = region.get_rect()
            if p1c1x <= x and x <= p1c3x and p1c1y <= y and y <= p1c3y:
                return region

        return None

    def display_clear(self, handle):
        """ Clear display of this handle
        """
        if handle.display_tag is not None:
            if isinstance(handle.display_tag, list):
                for tag in handle.display_tag:
                    self.canvas.delete(tag)
            else:
                self.canvas.delete(handle.display_tag)
            handle.display_tag = None
        if handle.highlight_tag is not None:
            self.canvas.delete(handle.highlight_tag)
            handle.highlight_tag = None

    def display_edge(self, edge):
        """ Display edge as a rectangle
        We leave room for the corners at each end
        Highlight if appropriate
        """

        loc = edge.loc
        rect = loc.coord
        print("edge: %s" % str(loc))
        if self.is_highlighted(edge):
            c1x, c1y, c3x, c3y = edge.get_rect(enlarge=True)
            edge.highlight_tag = self.canvas.create_rectangle(
                c1x, c1y, c3x, c3y, fill=SelectPart.edge_fill_highlight)
        else:
            self.display_clear(edge)
            c1x, c1y, c3x, c3y = edge.get_rect()
            edge.display_tag = self.canvas.create_rectangle(
                c1x, c1y, c3x, c3y, fill=SelectPart.edge_fill)
        if self.show_id:
            dir_x, dir_y = edge.edge_dxy()
            chr_w = 5
            chr_h = chr_w * 2
            if dir_x != 0:  # sideways
                offset_x = -len(str(edge.part_id)) * chr_w / 2 + chr_w
                offset_y = chr_h
            if dir_y != 0:  # up/down
                offset_x = len(str(edge.part_id)) * chr_w
                offset_y = 0

            cx = (c1x + c3x) / 2 + offset_x
            cy = (c1y + c3y) / 2 + offset_y
            edge.name_tag = self.display_text((cx, cy), text=str(edge.part_id))

    def down(self, event):
        self.is_down = True
        if self.inside:
            print("Click in canvas event:%s" % event)
            cnv = event.widget
            x, y = cnv.canvasx(event.x), cnv.canvasy(event.y)
            print("x=%d y=%d" % (x, y))
        if self.has_highlighted():
            for highlight in self.highlights.values():
                part = highlight.part
                self.select_set(part)
                print("select %s tag=%d (%s)" %
                      (part.part_type, part.display_tag, part))

    def motion(self, event):
        ###cnv.itemconfigure (tk.CURRENT, fill ="blue")
        cnv = event.widget
        x, y = float(cnv.canvasx(event.x)), float(cnv.canvasy(event.y))
        ###got = event.widget.coords (tk.CURRENT, x, y)

    def leave(self, event):
        print("leave")
        self.inside = False
        if hasattr(self, 'motion_bind_id'):
            self.canvas.unbind("<Motion>", self.motion_bind_id)

    def enter(self, event):
        print("enter")
        self.inside = True
        self.motion_bind_id = self.canvas.bind("<Motion>", self.on_motion)

    def on_motion(self, event):
        cnv = event.widget
        x, y = cnv.canvasx(event.x), cnv.canvasy(event.y)
        prev_xy = self.motion_xy
        self.motion_xy = (x, y)
        if prev_xy is None:
            prev_xy = self.motion_xy

        if self.has_selected():
            parts = self.get_selected_parts()
            self.record_move_setup()
            for part in parts:
                xinc = x - prev_xy[0]
                yinc = y - prev_xy[1]
                print("motion on(%s) at xy=(%d,%d) by xinc=%d yinc=%d" %
                      (part.part_type, x, y, xinc, yinc))
                self.move_part(part, xinc, yinc)
                self.highlight_set(part)
            self.record_move_display()
        parts = self.get_parts_at(
            x, y, sz_type=SelectPart.SZ_SELECT)  # NOTE: this is reference
        if len(parts) > 0:
            ncheck = 3
            if len(parts) > ncheck:
                print("on parts(%d) > %d " % (len(parts), ncheck))
                for pa in parts:
                    print("part: %s" % pa)
                print()
            self.highlight_clear()  # First clear all highlighted parts
            for part in parts:
                if not part.is_region():
                    print("motion over %s" % part)
                self.highlight_set(part, xy=(x, y))
        else:
            if self.has_highlighted():
                self.highlight_clear()

    def highlight_set(self, part, xy=None):
        """ highlight specified part
        Save handle in highlight to allow easy access
        Clear previous highlight, if one
        
        """
        part.highlight_set()

    def get_highlighted(self):
        if not self.has_highlighted():
            return None

        highlighted = self.highlighted
        return highlighted

    def get_highlighted_part(self):
        highlighted = self.get_highlighted()
        if highlighted is None:
            return None

        return highlighted.part

    def has_highlighted(self):
        """ Determine if any highlighted parts
        """
        if bool(self.highlights):
            return True
        return False

    def is_highlighted(self, part):
        """ Check if part is highlighted
        """
        return part.is_highlighted()

    def is_highlighted_others(self, part):
        """ Check if non-overlapping parts are highlighted
        """
        if not self.has_highlighted():
            return False  # Nobody highlighted

        for highlight in self.highlights.values():
            if highlight.part.part_type != part.part_type:
                return True  # Another type is highlighted

            if not highlight.part.is_covering(part):
                return True

        return False

    def highlight_clear(self, parts=None, others=False):
        """ Clear highlighted parts
            Remove part form  highlights
            :parts:  parts to consider
            :others:   False - clear these parts
                        True - clear other parts
        """
        if not self.has_highlighted():
            return

        if parts is None:
            if not others:
                print("highlight_clear ALL")
            parts = []
            for highlight in self.highlights.values():
                parts.append(highlight.part)
        if not isinstance(parts, list):
            parts = [parts]

        if others:
            other_parts = []
            for highlight in self.highlights.values():
                found = False
                for part in parts:
                    if highlight.part.is_same(part):
                        found = True
                        break
                if not found:
                    other_parts.append(part)
            parts = other_parts

        for part in parts:
            if part.part_id in self.highlights:
                highlight_tag = part.highlight_tag
                if highlight_tag is not None:
                    self.canvas.delete(
                        highlight_tag)  # Remove highlight figure
                    part.highlight_tag = None
                part.highlighted = False
                if part.display_tag is None:
                    self.display_set(part)  # reestablish original display
                del self.highlights[part.part_id]
                print("highlight_clear %d: %s" % (part.part_id, part))

    def select_set(self, part):
        """ Select handle
        """
        self.selects[part.part_id] = part

    def select_remove(self, selects):
        """ Remove one or more of selections
        :select: one or list of  selections to remove
                Default: all
        """
        if not isinstance(selects, list):
            selects = [selects]
        for id in list(self.selects):
            self.selects.pop(id)

    def get_selecteds(self):
        """ Return list of selected info or [] if none
        """
        return self.selects.values()

    def get_selected_parts(self):
        """ Returns all selected parts
        [] if non selected
        """
        parts = self.get_selecteds()
        return parts

    def has_selected(self):
        if bool(self.selects):
            return True
        return False

    def move_part(self, part, xinc, yinc):
        """ Move selected handle, adjusting connected parts
        """
        self.display_clear(part)  # Clear display before moveing
        if part.is_region():
            self.move_region(part, xinc, yinc)
        elif part.is_edge():
            self.move_edge(part, xinc, yinc)

        elif part.is_corner():
            self.move_corner(part, xinc, yinc)

        self.display_set(part)

    def move_edge(self, edge, xinc, yinc, adjusts=None):
        """ Move selected edge, adjusting connected parts
        Direction of movement is constrained to perpendicular to edge
        Connected parts are:
            the corners at each edge end
            the end-points of the edges, not including this edge,
                connected to the end-corners
        :edge:  selected edge
        :xinc:  x destination delta
        :yinc:  y destination delta
        :adjusts: adjusted connections
                    Default: all connections
        :highlight: True - highlight after move
        """
        self.display_clear(edge)  # Clear display before moveing
        delta_x = xinc
        delta_y = yinc
        edge_dx, edge_dy = edge.edge_dxy()
        if edge_dx == 0:
            delta_y = 0  # No movement parallel to edge
        if edge_dy == 0:
            delta_x = 0  # No movement parallel to edge
        coord = edge.loc.coord
        p1, p2 = coord[0], coord[1]
        print("move_edge: %s by delta_x=%d,delta_y=%d" %
              (edge, delta_x, delta_y))
        """ Collect moves group:
            Collect all parts which need to be moved in whole or part.
            Each part is present once.  If an edge end is moved only the other edge's
            ends need to be adjusted
                 
        """
        mover = SelectMover(self, delta_x=delta_x, delta_y=delta_y)
        mover.add_moves(parts=edge)
        ###mover.add_moves(parts=edge.connecteds)
        ###mover.add_adjusts(edge.connecteds)      # Adjust those connected to corners and so on
        mover.move_list(delta_x, delta_y)

    def move_corner(self, corner, xinc, yinc):
        """ Move selected corner, adjusting connected edges
        :corner: selected corner
        :xinc: x destination increment
        :yinc: y destination increment
        """
        delta_x = xinc
        delta_y = yinc
        print("move_edge: %s  by delta_x=%d,delta_y=%d" %
              (corner, delta_x, delta_y))
        mover = SelectMover(self, delta_x=delta_x, delta_y=delta_y)
        mover.add_moves(parts=corner)
        ###mover.add_moves(parts=edge.connecteds)
        ###mover.add_adjusts(edge.connecteds)      # Adjust those connected to corners and so on
        mover.move_list(delta_x, delta_y)

    def move_region(self, region, xinc, yinc, adjusts=None):
        """ Move region
        Currently all parts
        Implement via move all corners
        """
        for part in region.connecteds:
            if part.is_corner():
                self.move_corner(part, xinc, yinc)

    def adjust_corner_edge(self, corner, edge, delta_x, delta_y):
        """ Adjust edge's endpoint connected to this corner by the delta x,y
        """
        indexes = edge.get_connected_loc_indexes(corner)
        if indexes is None:
            raise (SelectError(
                "adjust_corner_edge: corner(%s) not connected to edge(edge)" %
                (corner, edge)))
        coord = edge.loc.coord
        coord.move_nodes(delta_x, delta_y, indexes[0])
        self.display_set(edge)

    def add_part(self, part):
        """ Add new part to list
        """
        if part.part_id in self.parts_by_id:
            return

        print("add_part: %s" % part)
        self.parts.append(part)
        self.parts_by_id[part.part_id] = part

    def up(self, event):
        self.is_down = False
        ###event.widget.itemconfigure (tk.CURRENT, fill =self.defaultcolor)
        cnv = event.widget
        x, y = cnv.canvasx(event.x), cnv.canvasy(event.y)
        ###got = event.widget.coords (tk.CURRENT, x, y)
        print("up at x=%d y=%d" % (x, y))
        if self.has_selected():
            ids = list(self.selects)
            for id in ids:
                self.select_remove(self.parts[id])

    def record_move(self, move):
        """ Record move for display or subsequent action
        :move: SelectMove to be recorded
        """
        self.record_md.record_move(move)

    def record_move_setup(self):
        """ Setup for move record display
        """
        self.record_md.record_move_setup()

    def record_move_display(self):
        self.record_md.record_move_display()