diff --git a/clusterview/mode_handlers.py b/clusterview/mode_handlers.py index f1445b8..52699e2 100644 --- a/clusterview/mode_handlers.py +++ b/clusterview/mode_handlers.py @@ -1,3 +1,5 @@ +from enum import Enum + from PyQt5.QtCore import QEvent, Qt from .mode import Mode @@ -7,6 +9,25 @@ from .opengl_widget import (get_bb_bottom_right, get_bb_top_left, reset_move_bbs) from .points import PointSet +class __ClickFlag: + + # This is the first stage. On mouse release it goes to + # SELECTION_MOVE. + NONE = 0 + + # We are now in selection box mode. + SELECTION_BOX = 1 + + # Second stage - we have selected a number of points + # and now we are going to track the left mouse button + # to translate those points. After a left click + # this moves to SELECTED_MOVED. + SELECTION_MOVE = 2 + + # Any subsequent click in this mode will send it back + # to NONE - we are done. + SELECTED_MOVED = 3 + # Size of point for drawing __POINT_SIZE = 8 @@ -16,7 +37,10 @@ __point_set = PointSet(__POINT_SIZE) # Module level flag for left click events (used to detect a left # click hold drag) -__left_click_flag = False +__left_click_flag = __ClickFlag.NONE + +# Variable to track the mouse state during selection movement +__mouse_start = None def __refresh_point_list(ctx): @@ -50,6 +74,9 @@ def __handle_add_point(ctx, event): if (event.button() == Qt.LeftButton and event.type() == QEvent.MouseButtonPress): + # Clear any existing selections + __point_set.clear_selection() + # No attribute at the moment. __point_set.add_point(event.x(), event.y()) @@ -74,6 +101,9 @@ def __handle_edit_point(ctx, event): # # Should move the associated point in the list to the new location if # applicable. + global __point_set + + __point_set.clear_selection() # Store old x, y from event set_drawing_event(event) @@ -83,39 +113,61 @@ def __handle_edit_point(ctx, event): def __handle_move_points(ctx, event): global __left_click_flag + global __mouse_start + global __point_set set_drawing_event(event) if event.button() == Qt.LeftButton: - __left_click_flag = True - # This if statement block is used to set the bounding box for - # drawing and call the selection procedure. - if __left_click_flag and event.type() == QEvent.MouseButtonPress: + # This if statement block is used to set the bounding box for + # drawing and call the selection procedure. + if (__left_click_flag is __ClickFlag.SELECTION_BOX and + event.type() == QEvent.MouseButtonPress): - set_move_bb_top_left(event.x(), event.y()) + if __left_click_flag is __ClickFlag.NONE: + __left_click_flag = __ClickFlag.SELECTION_BOX - elif __left_click_flag and event.type() == QEvent.MouseMove: + elif __left_click_flag is __ClickFlag.SELECTION_BOX: + # We are now in the click-and-hold to signal move + # tracking and translation + __left_click_flag = __ClickFlag.SELECTION_MOVE + else: + __left_click_flag = ClickFlag.NONE - set_move_bb_bottom_right(event.x(), event.y()) + if __left_click_flag is __ClickFlag.SELECTION_BOX: + set_move_bb_top_left(event.x(), event.y()) - elif __left_click_flag and event.type() == QEvent.MouseButtonRelease: + elif __left_click_flag is __ClickFlag.SELECTION_MOVE: + __mouse_start = (event.x(), event.y()) - __left_click_flag = False + elif (__left_click_flag is __ClickFlag.SELECTION_BOX + and event.type() == QEvent.MouseMove): - # Final bottom right corner point - set_move_bb_bottom_right(event.x(), event.y()) + set_move_bb_bottom_right(event.x(), event.y()) - # Satisfy the post condition by resetting the bounding box - reset_move_bbs() + elif (__left_click_flag is __ClickFlag.SELECTION_MOVE + and event.type() == QEvent.MouseMove): - # Fix the highlighted pointed into a set (separate from point_set) and - # prepare to move. + for p in __point_set: + if p.selected: + p.move_point(__mouse_start[0] - event.x(), + __mouse_start[1] - event.y()) - # Find and move all points from the old list to their new locations - ctx.opengl_widget.update() + elif (__left_click_flag is not __ClickFlag.NONE and + event.type() == QEvent.MouseButtonRelease): + # Final bottom right corner point + set_move_bb_bottom_right(event.x(), event.y()) + + # Satisfy the post condition by resetting the bounding box + reset_move_bbs() + + if __left_click_flag is __ClickFlag.SELECTION_MOVE: + __mouse_start = None + + ctx.opengl_widget.update() def __handle_delete_point(ctx, event): if (event.button() == Qt.LeftButton and diff --git a/clusterview/opengl_widget.py b/clusterview/opengl_widget.py index dbfd071..6227427 100644 --- a/clusterview/opengl_widget.py +++ b/clusterview/opengl_widget.py @@ -249,7 +249,6 @@ def paint_gl(): elif __current_mode is Mode.DELETE: raise NotImplementedError("Drawing for DELETE not implemented.") - def __clamp_x(x): """ X-coordinate clamping function that goes from mouse coordinates to @@ -261,7 +260,6 @@ def __clamp_x(x): x_w = (x / (__WIDTH / 2.0) - 1.0) return x_w - def __clamp_y(y): """ Y-coordinate clamping function that goes from mouse coordinates to @@ -273,7 +271,6 @@ def __clamp_y(y): y_w = -1.0 * (y / (__HEIGHT / 2.0) - 1.0) return y_w - def box_hit(tx, ty, x1, y1, x2, y2): """ Calculates whether or not a given point collides with the given bounding @@ -301,7 +298,6 @@ def box_hit(tx, ty, x1, y1, x2, y2): ty >= y1 and ty <= y2) - def highlight_selection(): """ Given the current move bounding box, highlights any points inside it. @@ -314,32 +310,6 @@ def highlight_selection(): if box_hit(point.x, point.y, top_left[0], top_left[1], bottom_right[0], bottom_right[1]): - # Paint the point and draw over it. - # - # TODO: Demonstrating movement might be difficult... come up with a - # good way on paper. Basically once the mouse release happens - # your next click sets "MOVING" and it begins translating - # points in the point set each update. During "MOVING" the - # draw points function will draw the selected points using - # the moving color. Since the points in the __current_points - # set are updating each tick, the painting should happen - # as expected. - # - # (Get this move, save, and load done IN THAT ORDER) - # (DRAW ALGORITHM ON PAPER) - # - # - # TODO: The highlight any direction bug needs to be fixed but it - # works, you need an algorithm for storing points selected, - # painting them permanently, and then moving them as the - # mouse drags. - # - # - # TODO: This movement idea will be in the after mouse release thing - # the points hit will stay permanently highlighted until the - # next mouse click. First mouse click will call - # "remove_selection" to terminate movement. "remove_selection" - # will also be called on mode change. point.select() else: point.unselect() diff --git a/clusterview/points.py b/clusterview/points.py index 33e2ab5..b0138f6 100644 --- a/clusterview/points.py +++ b/clusterview/points.py @@ -43,6 +43,18 @@ class Point: def selected(self): return self.__selected + def move(self, dx, dy): + """ + Adds the deltas dx and dy to the point. + + @param dx The delta in the x direction. + @param dy The delta in the y direction. + """ + + self.__x += dx + self.__y += dy + + def __eq__(self, other): """ Override for class equality. diff --git a/tests/test_point.py b/tests/test_point.py new file mode 100644 index 0000000..351f606 --- /dev/null +++ b/tests/test_point.py @@ -0,0 +1,7 @@ +from clusterview.points import Point + +def test_move_point(): + p = Point(1, 2, 8) + p.move(1, 1) + + assert p.x == 2 and p.y == 3 diff --git a/tests/test_point_set.py b/tests/test_point_set.py index adc7997..f337712 100644 --- a/tests/test_point_set.py +++ b/tests/test_point_set.py @@ -15,7 +15,6 @@ def test_add_to_point_set(): assert points[0] == p assert len(l.attributes(p)) == 0 - def test_add_to_point_set_with_attributes(): attribute = Attribute("thing", 1) @@ -30,7 +29,6 @@ def test_add_to_point_set_with_attributes(): assert points[0] == point assert len(l.attributes(point)) == 1 - def test_remove_point_exact_click(): attribute = Attribute("thing", 1) @@ -51,7 +49,6 @@ def test_remove_point_exact_click(): # dictionary. l.attributes(p) - def test_remove_point_bounding_box(): """ This test checks the bounding box hit heuristic. @@ -78,13 +75,11 @@ def test_remove_point_bounding_box(): # dictionary. l.attributes(p) - def test_attributes_must_be_array_of_attributes(): with pytest.raises(ValueError): l = PointSet(8) l.add_point(1, 2, attrs=[1,2,3,4,5]) - def test_clear_all_selected_points(): l = PointSet(8) l.add_point(1, 2)