Refactor selection state tracking into its own class

This commit is contained in:
Kovid Goyal 2013-12-03 16:36:34 +05:30
parent d12fa98765
commit 107cfa8f1e

View File

@ -29,6 +29,23 @@ ucase_map = {l:string.ascii_uppercase[i] for i, l in enumerate(string.ascii_lowe
def capitalize(x): def capitalize(x):
return ucase_map[x[0]] + x[1:] return ucase_map[x[0]] + x[1:]
class SelectionState(object):
__slots__ = ('last_press_point', 'current_mode', 'rect', 'in_selection', 'drag_corner', 'dragging', 'last_drag_pos')
def __init__(self):
self.reset()
def reset(self, full=True):
self.last_press_point = None
if full:
self.current_mode = None
self.rect = None
self.in_selection = False
self.drag_corner = None
self.dragging = None
self.last_drag_pos = None
class Canvas(QWidget): class Canvas(QWidget):
BACKGROUND = QColor(60, 60, 60) BACKGROUND = QColor(60, 60, 60)
@ -38,22 +55,16 @@ class Canvas(QWidget):
def __init__(self, parent=None): def __init__(self, parent=None):
QWidget.__init__(self, parent) QWidget.__init__(self, parent)
self.setMouseTracking(True) self.setMouseTracking(True)
self.selection_state = SelectionState()
self.current_image_data = None self.current_image_data = None
self.current_image = None self.current_image = None
self.current_scaled_pixmap = None self.current_scaled_pixmap = None
self.last_canvas_size = None self.last_canvas_size = None
self.last_press_point = None
self.last_move_point = None
self.target = QRectF(0, 0, 0, 0) self.target = QRectF(0, 0, 0, 0)
self.current_mode = None
self.selection_rect = None
self.in_selection = False
self.drag_corner = (None, None)
self.last_drag_pos = None
self.drag_mode = None
def show_image(self, data): def show_image(self, data):
self.selection_state.reset()
self.current_image_data = data self.current_image_data = data
self.current_image = i = QImage() self.current_image = i = QImage()
i.loadFromData(data) i.loadFromData(data)
@ -62,23 +73,23 @@ class Canvas(QWidget):
@property @property
def dc_size(self): def dc_size(self):
sr = self.selection_rect sr = self.selection_state.rect
dx = min(75, sr.width() / 4) dx = min(75, sr.width() / 4)
dy = min(75, sr.height() / 4) dy = min(75, sr.height() / 4)
return dx, dy return dx, dy
def get_drag_corner(self, pos): def get_drag_corner(self, pos):
dx, dy = self.dc_size dx, dy = self.dc_size
sr = self.selection_rect sr = self.selection_state.rect
x, y = pos.x(), pos.y() x, y = pos.x(), pos.y()
hedge = 'left' if x < sr.x() + dx else 'right' if x > sr.right() - dx else None hedge = 'left' if x < sr.x() + dx else 'right' if x > sr.right() - dx else None
vedge = 'top' if y < sr.y() + dy else 'bottom' if y > sr.bottom() - dy else None vedge = 'top' if y < sr.y() + dy else 'bottom' if y > sr.bottom() - dy else None
return (hedge, vedge) return (hedge, vedge) if hedge or vedge else None
def get_drag_rect(self): def get_drag_rect(self):
sr = self.selection_rect sr = self.selection_state.rect
dc = self.drag_corner dc = self.selection_state.drag_corner
if sr is None or dc == (None, None): if None in (sr, dc):
return return
dx, dy = self.dc_size dx, dy = self.dc_size
if None in dc: if None in dc:
@ -97,8 +108,8 @@ class Canvas(QWidget):
return ans return ans
def get_cursor(self): def get_cursor(self):
dc = self.drag_corner dc = self.selection_state.drag_corner
if dc == (None, None): if dc is None:
ans = Qt.CrossCursor ans = Qt.CrossCursor
elif None in dc: elif None in dc:
ans = Qt.SizeVerCursor if dc[0] is None else Qt.SizeHorCursor ans = Qt.SizeVerCursor if dc[0] is None else Qt.SizeHorCursor
@ -107,7 +118,7 @@ class Canvas(QWidget):
return ans return ans
def move_edge(self, edge, dp): def move_edge(self, edge, dp):
sr = self.selection_rect sr = self.selection_state.rect
horiz = edge in {'left', 'right'} horiz = edge in {'left', 'right'}
func = getattr(sr, 'set' + capitalize(edge)) func = getattr(sr, 'set' + capitalize(edge))
delta = getattr(dp, 'x' if horiz else 'y')() delta = getattr(dp, 'x' if horiz else 'y')()
@ -121,8 +132,8 @@ class Canvas(QWidget):
func(max(minv, min(maxv, delta + getattr(sr, edge)()))) func(max(minv, min(maxv, delta + getattr(sr, edge)())))
def move_selection(self, dp): def move_selection(self, dp):
sr = self.selection_rect sr = self.selection_state.rect
dm = self.drag_mode dm = self.selection_state.dragging
if dm == (None, None): if dm == (None, None):
half_width = sr.width() / 2.0 half_width = sr.width() / 2.0
half_height = sr.height() / 2.0 half_height = sr.height() / 2.0
@ -143,49 +154,49 @@ class Canvas(QWidget):
def mousePressEvent(self, ev): def mousePressEvent(self, ev):
if ev.button() == Qt.LeftButton and self.target.contains(ev.pos()): if ev.button() == Qt.LeftButton and self.target.contains(ev.pos()):
pos = ev.pos() pos = ev.pos()
self.last_press_point = pos self.selection_state.last_press_point = pos
if self.current_mode is None: if self.selection_state.current_mode is None:
self.current_mode = 'select' self.selection_state.current_mode = 'select'
elif self.current_mode == 'selected': elif self.selection_state.current_mode == 'selected':
if self.selection_rect is not None and self.selection_rect.contains(pos): if self.selection_state.rect is not None and self.selection_state.rect.contains(pos):
self.drag_mode = self.get_drag_corner(pos) self.selection_state.drag_corner = self.selection_state.dragging = self.get_drag_corner(pos)
self.last_drag_pos = pos self.selection_state.last_drag_pos = pos
else: else:
self.current_mode = 'select' self.selection_state.current_mode = 'select'
self.selection_rect = None self.selection_state.rect = None
def mouseMoveEvent(self, ev): def mouseMoveEvent(self, ev):
changed = False changed = False
if self.in_selection: if self.selection_state.in_selection:
changed = True changed = True
self.in_selection = False self.selection_state.in_selection = False
self.drag_corner = (None, None) self.selection_state.drag_corner = None
pos = ev.pos() pos = ev.pos()
cursor = Qt.ArrowCursor cursor = Qt.ArrowCursor
try: try:
if not self.target.contains(pos): if not self.target.contains(pos):
return return
if ev.buttons() & Qt.LeftButton: if ev.buttons() & Qt.LeftButton:
if self.last_press_point is not None: if self.selection_state.last_press_point is not None:
if self.current_mode == 'select': if self.selection_state.current_mode == 'select':
self.selection_rect = QRectF(self.last_press_point, pos).normalized() self.selection_state.rect = QRectF(self.selection_state.last_press_point, pos).normalized()
changed = True changed = True
elif self.drag_mode is not None and self.last_drag_pos is not None: elif self.selection_state.dragging is not None and self.selection_state.last_drag_pos is not None:
self.drag_corner = self.drag_mode self.selection_state.in_selection = True
self.in_selection = True self.selection_state.drag_corner = self.selection_state.dragging
dp = pos - self.last_drag_pos dp = pos - self.selection_state.last_drag_pos
self.selection_state.last_drag_pos = pos
self.move_selection(dp)
cursor = self.get_cursor() cursor = self.get_cursor()
changed = True changed = True
self.last_drag_pos = pos
self.move_selection(dp)
else: else:
if self.selection_rect is None or not self.selection_rect.contains(pos): if self.selection_state.rect is None or not self.selection_state.rect.contains(pos):
return return
if self.current_mode == 'selected': if self.selection_state.current_mode == 'selected':
if self.selection_rect is not None and self.selection_rect.contains(pos): if self.selection_state.rect is not None and self.selection_state.rect.contains(pos):
self.drag_corner = self.get_drag_corner(pos) self.selection_state.drag_corner = self.get_drag_corner(pos)
self.in_selection = True self.selection_state.in_selection = True
cursor = self.get_cursor() cursor = self.get_cursor()
changed = True changed = True
finally: finally:
@ -195,11 +206,9 @@ class Canvas(QWidget):
def mouseReleaseEvent(self, ev): def mouseReleaseEvent(self, ev):
if ev.button() == Qt.LeftButton: if ev.button() == Qt.LeftButton:
self.last_press_point = None self.selection_state.reset(full=False)
self.drag_mode = None if self.selection_state.current_mode == 'select':
self.last_drag_pos = None self.selection_state.current_mode = 'selected'
if self.current_mode == 'select':
self.current_mode = 'selected'
self.update() self.update()
@painter @painter
@ -218,9 +227,8 @@ class Canvas(QWidget):
def load_pixmap(self): def load_pixmap(self):
canvas_size = self.rect().width(), self.rect().height() canvas_size = self.rect().width(), self.rect().height()
if self.last_canvas_size != canvas_size: if self.last_canvas_size != canvas_size:
if self.last_canvas_size is not None and self.selection_rect is not None: if self.last_canvas_size is not None and self.selection_state.rect is not None:
self.current_mode = None self.selection_state.reset()
self.selection_rect = None
# TODO: Migrate the selection rect # TODO: Migrate the selection rect
self.last_canvas_size = canvas_size self.last_canvas_size = canvas_size
self.current_scaled_pixmap = None self.current_scaled_pixmap = None
@ -245,9 +253,9 @@ class Canvas(QWidget):
@painter @painter
def draw_selection_rect(self, painter): def draw_selection_rect(self, painter):
cr, sr = self.target, self.selection_rect cr, sr = self.target, self.selection_state.rect
painter.setPen(self.SELECT_PEN) painter.setPen(self.SELECT_PEN)
if self.current_mode == 'selected': if self.selection_state.current_mode == 'selected':
# Shade out areas outside the selection rect # Shade out areas outside the selection rect
for r in ( for r in (
QRectF(cr.topLeft(), QPointF(sr.left(), cr.bottom())), # left QRectF(cr.topLeft(), QPointF(sr.left(), cr.bottom())), # left
@ -258,7 +266,7 @@ class Canvas(QWidget):
painter.fillRect(r, self.SHADE_COLOR) painter.fillRect(r, self.SHADE_COLOR)
dr = self.get_drag_rect() dr = self.get_drag_rect()
if self.in_selection and dr is not None: if self.selection_state.in_selection and dr is not None:
# Draw the resize rectangle # Draw the resize rectangle
painter.save() painter.save()
painter.setCompositionMode(QPainter.RasterOp_SourceAndNotDestination) painter.setCompositionMode(QPainter.RasterOp_SourceAndNotDestination)
@ -282,7 +290,7 @@ class Canvas(QWidget):
return self.draw_image_error(p) return self.draw_image_error(p)
self.load_pixmap() self.load_pixmap()
self.draw_pixmap(p) self.draw_pixmap(p)
if self.selection_rect is not None: if self.selection_state.rect is not None:
self.draw_selection_rect(p) self.draw_selection_rect(p)
finally: finally:
p.end() p.end()