Implemented Grabber Handles for easier usage.

This commit is contained in:
Nicole Rappe 2025-03-10 06:09:48 -06:00
parent 7991aa751f
commit 415f1842cd

View File

@ -32,7 +32,7 @@ pytesseract.pytesseract.tesseract_cmd = r"C:\\Program Files\\Tesseract-OCR\\tess
DEFAULT_WIDTH = 180 DEFAULT_WIDTH = 180
DEFAULT_HEIGHT = 130 DEFAULT_HEIGHT = 130
HANDLE_SIZE = 8 HANDLE_SIZE = 5
LABEL_HEIGHT = 20 LABEL_HEIGHT = 20
collector_mutex = QMutex() collector_mutex = QMutex()
@ -196,9 +196,6 @@ def find_word_positions(region_id, word, offset_x=0, offset_y=0, margin=5, ocr_e
print(f"[ERROR] Failed to capture OCR region: {e}") print(f"[ERROR] Failed to capture OCR region: {e}")
return [] return []
def draw_identification_boxes(region_id, positions, color=(0, 0, 255), thickness=2): def draw_identification_boxes(region_id, positions, color=(0, 0, 255), thickness=2):
""" """
Draws non-interactive rectangles at specified positions within the given OCR region. Draws non-interactive rectangles at specified positions within the given OCR region.
@ -236,6 +233,10 @@ class OCRRegionWidget(QWidget):
self.previous_positions = [] # This prevents redundant redraws self.previous_positions = [] # This prevents redundant redraws
self.num_slices = 1 # Ensures slice count is initialized self.num_slices = 1 # Ensures slice count is initialized
# --- Initialization for interactive handles ---
self.selected_handle = None # Tracks which handle is being dragged/resized
self.drag_offset = None # Tracks the offset for moving the widget
self.show() self.show()
def paintEvent(self, event): def paintEvent(self, event):
@ -260,6 +261,13 @@ class OCRRegionWidget(QWidget):
for i in range(1, self.num_slices): # Do not draw the last one at the bottom for i in range(1, self.num_slices): # Do not draw the last one at the bottom
painter.drawLine(0, i * strip_height, self.width(), i * strip_height) painter.drawLine(0, i * strip_height, self.width(), i * strip_height)
# --- Draw interactive handles (grabbers) with reduced opacity (15%) ---
# 15% opacity of 255 is approximately 38
handle_color = QColor(0, 0, 0, 50)
for handle in self._resize_handles():
painter.fillRect(handle, handle_color)
painter.drawRect(handle) # Optional: draw a border around the handle
def set_draw_positions(self, positions, color, thickness): def set_draw_positions(self, positions, color, thickness):
""" """
@ -292,57 +300,99 @@ class OCRRegionWidget(QWidget):
self.update() self.update()
def _resize_handles(self): def _resize_handles(self):
"""
Returns a list of QRect objects representing the interactive handles:
- Index 0: Top-left (resize)
- Index 1: Top-right (resize)
- Index 2: Bottom-left (resize)
- Index 3: Bottom-right (resize)
- Index 4: Top-center (dragger)
"""
w, h = self.width(), self.height() w, h = self.width(), self.height()
return [ handles = [
QRect(0, 0, HANDLE_SIZE, HANDLE_SIZE), # Top-left QRect(0, 0, HANDLE_SIZE, HANDLE_SIZE), # Top-left
QRect(w - HANDLE_SIZE, h - HANDLE_SIZE, HANDLE_SIZE, HANDLE_SIZE) # Bottom-right QRect(w - HANDLE_SIZE, 0, HANDLE_SIZE, HANDLE_SIZE), # Top-right
QRect(0, h - HANDLE_SIZE, HANDLE_SIZE, HANDLE_SIZE), # Bottom-left
QRect(w - HANDLE_SIZE, h - HANDLE_SIZE, HANDLE_SIZE, HANDLE_SIZE) # Bottom-right
] ]
# Top-center handle: centered along the top edge
top_center_x = (w - HANDLE_SIZE) // 2
top_center = QRect(top_center_x, 0, HANDLE_SIZE, HANDLE_SIZE)
handles.append(top_center)
return handles
def mousePressEvent(self, event): def mousePressEvent(self, event):
if event.button() == Qt.LeftButton: if event.button() == Qt.LeftButton:
# Check if any handle (including the new top-center) is clicked
for i, handle in enumerate(self._resize_handles()): for i, handle in enumerate(self._resize_handles()):
if handle.contains(event.pos()): if handle.contains(event.pos()):
self.selected_handle = i self.selected_handle = i
# For the top-center handle (index 4), initialize drag offset for moving
if i == 4:
self.drag_offset = event.pos()
return return
# If no handle is clicked, allow dragging by clicking anywhere in the widget
self.drag_offset = event.pos() self.drag_offset = event.pos()
def mouseMoveEvent(self, event): def mouseMoveEvent(self, event):
if self.selected_handle is not None: if self.selected_handle is not None:
w, h = self.width(), self.height() if self.selected_handle == 4:
if self.selected_handle == 0: # Top-left # --- Top-center handle dragging ---
new_w = w + (self.x() - event.globalX()) new_x = event.globalX() - self.drag_offset.x()
new_h = h + (self.y() - event.globalY()) new_y = event.globalY() - self.drag_offset.y()
new_x = event.globalX() self.move(new_x, new_y)
new_y = event.globalY() collector_mutex.lock()
if self.region_id in regions:
regions[self.region_id]["bbox"] = [new_x, new_y, self.width(), self.height()]
collector_mutex.unlock()
self.update()
else:
# --- Resizing logic for corner handles ---
if self.selected_handle == 0: # Top-left
new_w = self.width() + (self.x() - event.globalX())
new_h = self.height() + (self.y() - event.globalY())
new_x = event.globalX()
new_y = event.globalY()
elif self.selected_handle == 1: # Top-right
new_w = event.globalX() - self.x()
new_h = self.height() + (self.y() - event.globalY())
new_x = self.x()
new_y = event.globalY()
elif self.selected_handle == 2: # Bottom-left
new_w = self.width() + (self.x() - event.globalX())
new_h = event.globalY() - self.y()
new_x = event.globalX()
new_y = self.y()
elif self.selected_handle == 3: # Bottom-right
new_w = event.globalX() - self.x()
new_h = event.globalY() - self.y()
new_x = self.x()
new_y = self.y()
if new_w < 20: if new_w < 20:
new_w = 20 new_w = 20
if new_h < 20: if new_h < 20:
new_h = 20 new_h = 20
self.setGeometry(new_x, new_y, new_w, new_h) self.setGeometry(new_x, new_y, new_w, new_h)
elif self.selected_handle == 1: # Bottom-right collector_mutex.lock()
new_w = event.globalX() - self.x() if self.region_id in regions:
new_h = event.globalY() - self.y() regions[self.region_id]["bbox"] = [self.x(), self.y(), self.width(), self.height()]
if new_w < 20: collector_mutex.unlock()
new_w = 20 self.update()
if new_h < 20:
new_h = 20
self.setGeometry(self.x(), self.y(), new_w, new_h)
collector_mutex.lock()
if self.region_id in regions:
regions[self.region_id]["bbox"] = [self.x(), self.y(), self.width(), self.height()]
collector_mutex.unlock()
self.update()
elif self.drag_offset: elif self.drag_offset:
# --- General widget dragging (if no handle was clicked) ---
new_x = event.globalX() - self.drag_offset.x() new_x = event.globalX() - self.drag_offset.x()
new_y = event.globalY() - self.drag_offset.y() new_y = event.globalY() - self.drag_offset.y()
self.move(new_x, new_y) self.move(new_x, new_y)
collector_mutex.lock() collector_mutex.lock()
if self.region_id in regions: if self.region_id in regions:
regions[self.region_id]["bbox"] = [new_x, new_y, self.width(), self.height()] regions[self.region_id]["bbox"] = [new_x, new_y, self.width(), self.height()]
collector_mutex.unlock() collector_mutex.unlock()
def mouseReleaseEvent(self, event):
"""
Resets the drag/resize state once the mouse button is released.
"""
self.selected_handle = None
self.drag_offset = None