From c0e9568c66ed3e5ed0f45619af6285a470050027 Mon Sep 17 00:00:00 2001 From: Taylor Bockman Date: Wed, 21 Aug 2019 17:17:34 -0700 Subject: [PATCH] UI update, mouse event tracking on all events, mouse leave, and mouse position tracking --- CONTRIBUTING.md | 26 +++++++++++++ clusterview.ui | 92 ++++++++++++++++++++++++++++++++++++++++++-- clusterview/mode_handlers.py | 31 ++++++++++++++- clusterview/opengl_widget.py | 10 +++++ clusterview_ui.py | 33 +++++++++++++++- main_window.py | 22 +++++------ 6 files changed, 195 insertions(+), 19 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5f00d6c..404d6cb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -78,3 +78,29 @@ If you are importing more than one thing from a module, alphabetize those as wel should be `from x import bar, baz, foo`. + + +### Prefer Private Methods + +In Python, private methods don't exist in the way that they do in C++, Java, etc. + +To declare a method private we must tell the compiler to garble the name. To do this +add two underscores to the prefix of the method name: + +```python + +# This method can be seen by anyone on import. + +def hello_world(): + print("Hello, world!") + +# This method can only be seen by the module/class it is defined +# in. + +def __hello_module(): + print("Hello, module!") + +``` + +We only want to expose the absolute bare minimum number of functions to the consumers of +our modules. diff --git a/clusterview.ui b/clusterview.ui index 6d76062..6f30532 100644 --- a/clusterview.ui +++ b/clusterview.ui @@ -59,6 +59,12 @@ 0 + + + 100 + 0 + + 200 @@ -70,24 +76,104 @@ - + + + + 0 + 0 + + + + + 100 + 0 + + + - + Qt::Vertical + + QSizePolicy::Fixed + 20 - 40 + 20 + + + + Canvas Information + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + + + + + Mouse Position: + + + + + + + + 0 + 0 + + + + + 100 + 0 + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + diff --git a/clusterview/mode_handlers.py b/clusterview/mode_handlers.py index 96af193..ca03b7a 100644 --- a/clusterview/mode_handlers.py +++ b/clusterview/mode_handlers.py @@ -33,6 +33,9 @@ class __ClickFlag: # Size of point for drawing __POINT_SIZE = 8 +# Canvas pixel border - empirical, not sure where this is stored officially +__CANVAS_BORDER = 1 + # PointManager is a class that is filled with static methods # designed for managing state. PointManager.point_set = PointSet(__POINT_SIZE, viewport_height(), @@ -73,6 +76,10 @@ def __handle_add_point(ctx, event): @param ctx A context handle to the main window. @param event The click event. """ + + # Update information as needed + __handle_info_updates(ctx, event) + if (event.button() == Qt.LeftButton and event.type() == QEvent.MouseButtonPress): @@ -112,6 +119,8 @@ def __handle_edit_point(ctx, event): # # Should move the associated point in the list to the new location if # applicable. + + __handle_info_updates(ctx, event) PointManager.point_set.clear_selection() # Store old x, y from event @@ -168,6 +177,8 @@ def __handle_move_points(ctx, event): set_drawing_event(event) + __handle_info_updates(ctx, event) + # Necessary to capture keyboard events ctx.opengl_widget.setFocusPolicy(Qt.StrongFocus) @@ -235,6 +246,9 @@ def __handle_move_points(ctx, event): ctx.opengl_widget.update() def __handle_delete_point(ctx, event): + + __handle_info_updates(ctx, event) + if (event.button() == Qt.LeftButton and event.type() == QEvent.MouseButtonPress): @@ -247,10 +261,23 @@ def __handle_delete_point(ctx, event): ctx.opengl_widget.update() ctx.point_list_widget.update() +def __handle_info_updates(ctx, event): + """ + Updates data under the "information" header. + + @param ctx The context to the main window. + @param event The event. + """ + if event.type() == QEvent.MouseMove: + ctx.mouse_position_label.setText(f"{event.x(), event.y()}") + + + # Simple dispatcher to make it easy to dispatch the right mode -# function when the OpenGL window is clicked. +# function when the OpenGL window is acted on. MODE_HANDLER_MAP = { - Mode.OFF: lambda: None, + Mode.OFF: __handle_info_updates, + Mode.LOADED: __handle_info_updates, Mode.ADD: __handle_add_point, Mode.EDIT: __handle_edit_point, Mode.MOVE: __handle_move_points, diff --git a/clusterview/opengl_widget.py b/clusterview/opengl_widget.py index d5496f6..56b5c56 100644 --- a/clusterview/opengl_widget.py +++ b/clusterview/opengl_widget.py @@ -105,6 +105,16 @@ def set_drawing_event(event): if event is not None: __current_event = event +def mouse_leave(ctx, event): + """ + The leave event for the OpenGL widget to properly reset the mouse + position label. + + @param ctx The context. + @param event The event. + """ + ctx.mouse_position_label.setText('') + def set_move_bb_top_left(x, y): """ Called to set the move bounding box's top left corner. diff --git a/clusterview_ui.py b/clusterview_ui.py index 188eb0f..4e79b69 100644 --- a/clusterview_ui.py +++ b/clusterview_ui.py @@ -42,16 +42,45 @@ class Ui_MainWindow(object): sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.groupBox.sizePolicy().hasHeightForWidth()) self.groupBox.setSizePolicy(sizePolicy) + self.groupBox.setMinimumSize(QtCore.QSize(100, 0)) self.groupBox.setMaximumSize(QtCore.QSize(200, 200)) self.groupBox.setObjectName("groupBox") self.gridLayout = QtWidgets.QGridLayout(self.groupBox) self.gridLayout.setObjectName("gridLayout") self.point_list_widget = QtWidgets.QListWidget(self.groupBox) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.point_list_widget.sizePolicy().hasHeightForWidth()) + self.point_list_widget.setSizePolicy(sizePolicy) + self.point_list_widget.setMinimumSize(QtCore.QSize(100, 0)) self.point_list_widget.setObjectName("point_list_widget") self.gridLayout.addWidget(self.point_list_widget, 0, 0, 1, 1) self.verticalLayout.addWidget(self.groupBox) - spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + spacerItem = QtWidgets.QSpacerItem(20, 20, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) self.verticalLayout.addItem(spacerItem) + self.groupBox_2 = QtWidgets.QGroupBox(self.centralwidget) + self.groupBox_2.setObjectName("groupBox_2") + self.gridLayout_2 = QtWidgets.QGridLayout(self.groupBox_2) + self.gridLayout_2.setObjectName("gridLayout_2") + spacerItem1 = QtWidgets.QSpacerItem(20, 20, QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Minimum) + self.gridLayout_2.addItem(spacerItem1, 0, 2, 1, 1) + self.label = QtWidgets.QLabel(self.groupBox_2) + self.label.setObjectName("label") + self.gridLayout_2.addWidget(self.label, 0, 0, 1, 1) + self.mouse_position_label = QtWidgets.QLabel(self.groupBox_2) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.mouse_position_label.sizePolicy().hasHeightForWidth()) + self.mouse_position_label.setSizePolicy(sizePolicy) + self.mouse_position_label.setMinimumSize(QtCore.QSize(100, 0)) + self.mouse_position_label.setText("") + self.mouse_position_label.setObjectName("mouse_position_label") + self.gridLayout_2.addWidget(self.mouse_position_label, 0, 3, 1, 1) + spacerItem2 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.gridLayout_2.addItem(spacerItem2, 1, 0, 1, 1) + self.verticalLayout.addWidget(self.groupBox_2) self.horizontalLayout.addLayout(self.verticalLayout) MainWindow.setCentralWidget(self.centralwidget) self.menubar = QtWidgets.QMenuBar(MainWindow) @@ -108,6 +137,8 @@ class Ui_MainWindow(object): _translate = QtCore.QCoreApplication.translate MainWindow.setWindowTitle(_translate("MainWindow", "ClusterView")) self.groupBox.setTitle(_translate("MainWindow", "Point List")) + self.groupBox_2.setTitle(_translate("MainWindow", "Canvas Information")) + self.label.setText(_translate("MainWindow", "Mouse Position:")) self.menu_file.setTitle(_translate("MainWindow", "File")) self.menu_help.setTitle(_translate("MainWindow", "Help")) self.tool_bar.setWindowTitle(_translate("MainWindow", "toolBar")) diff --git a/main_window.py b/main_window.py index 23c83e1..faa4ac7 100644 --- a/main_window.py +++ b/main_window.py @@ -3,7 +3,7 @@ from functools import partial import os from PyQt5 import uic -from PyQt5.QtCore import Qt +from PyQt5.QtCore import QEvent, Qt from PyQt5.QtGui import QCursor from PyQt5.QtWidgets import QFileDialog, QMainWindow @@ -12,7 +12,7 @@ from clusterview.mode import Mode from clusterview.mode_handlers import (MODE_HANDLER_MAP, ogl_keypress_handler, refresh_point_list) from clusterview.opengl_widget import (clear_selection, initialize_gl, - paint_gl, resize_gl, + mouse_leave, paint_gl, resize_gl, set_drawing_mode, set_drawing_context) from clusterview.point_manager import PointManager from clusterview.point_list_widget import item_click_handler @@ -61,6 +61,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.opengl_widget.initializeGL = initialize_gl self.opengl_widget.paintGL = paint_gl self.opengl_widget.resizeGL = resize_gl + self.opengl_widget.leaveEvent = partial(mouse_leave, self) # ------------------------------------- # UI Handlers @@ -177,14 +178,9 @@ class MainWindow(QMainWindow, Ui_MainWindow): """ Mode dispatcher for click actions on the OpenGL widget. """ - - if self.__mode not in [Mode.OFF, Mode.LOADED]: - # Map from Mode -> function - # where the function is a handler for the - # OpenGL event. The context passed to these functions allows - # them to modify on screen widgets such as the QOpenGLWidget and - # QListWidget. - MODE_HANDLER_MAP[self.__mode](self, event) - else: - # Go back to the base state - self.__off_mode() + # Map from Mode -> function + # where the function is a handler for the + # OpenGL event. The context passed to these functions allows + # them to modify on screen widgets such as the QOpenGLWidget and + # QListWidget. + MODE_HANDLER_MAP[self.__mode](self, event)