greenhouse/libs/labelDialog.py
Stefan Breunig 0c377fc258
move "OK" button below cursor when opening label dialog (#893)
The intent for this change is to allow faster labeling when there
are streaks of the same label occasionally, but not consistently
enough to enable "default label" mode. With this PR the user can
simply click again to confirm the previous selection, without
having to aim for the OK button, or move either hand to the
ENTER button.

The alignment and position change of the buttons is for two reasons:
- it covers less of the drawn shape, easing verification of the
  label to be selected
- the alignment wasn't taken into account for offset calculation.
  It works if the dialog is shown, but that causes the dialog to
  briefly flicker in the old position. Presumably an `adjustSize`
  or similar is needed on more widgets, but I was unable to
  figure out which.
2022-06-12 10:12:19 -07:00

96 lines
3.5 KiB
Python

try:
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
except ImportError:
from PyQt4.QtGui import *
from PyQt4.QtCore import *
from libs.utils import new_icon, label_validator, trimmed
BB = QDialogButtonBox
class LabelDialog(QDialog):
def __init__(self, text="Enter object label", parent=None, list_item=None):
super(LabelDialog, self).__init__(parent)
self.edit = QLineEdit()
self.edit.setText(text)
self.edit.setValidator(label_validator())
self.edit.editingFinished.connect(self.post_process)
model = QStringListModel()
model.setStringList(list_item)
completer = QCompleter()
completer.setModel(model)
self.edit.setCompleter(completer)
self.button_box = bb = BB(BB.Ok | BB.Cancel, Qt.Horizontal, self)
bb.button(BB.Ok).setIcon(new_icon('done'))
bb.button(BB.Cancel).setIcon(new_icon('undo'))
bb.accepted.connect(self.validate)
bb.rejected.connect(self.reject)
layout = QVBoxLayout()
layout.addWidget(bb, alignment=Qt.AlignmentFlag.AlignLeft)
layout.addWidget(self.edit)
if list_item is not None and len(list_item) > 0:
self.list_widget = QListWidget(self)
for item in list_item:
self.list_widget.addItem(item)
self.list_widget.itemClicked.connect(self.list_item_click)
self.list_widget.itemDoubleClicked.connect(self.list_item_double_click)
layout.addWidget(self.list_widget)
self.setLayout(layout)
def validate(self):
if trimmed(self.edit.text()):
self.accept()
def post_process(self):
self.edit.setText(trimmed(self.edit.text()))
def pop_up(self, text='', move=True):
"""
Shows the dialog, setting the current text to `text`, and blocks the caller until the user has made a choice.
If the user entered a label, that label is returned, otherwise (i.e. if the user cancelled the action)
`None` is returned.
"""
self.edit.setText(text)
self.edit.setSelection(0, len(text))
self.edit.setFocus(Qt.PopupFocusReason)
if move:
cursor_pos = QCursor.pos()
# move OK button below cursor
btn = self.button_box.buttons()[0]
self.adjustSize()
btn.adjustSize()
offset = btn.mapToGlobal(btn.pos()) - self.pos()
offset += QPoint(btn.size().width()/4, btn.size().height()/2)
cursor_pos.setX(max(0, cursor_pos.x() - offset.x()))
cursor_pos.setY(max(0, cursor_pos.y() - offset.y()))
parent_bottom_right = self.parentWidget().geometry()
max_x = parent_bottom_right.x() + parent_bottom_right.width() - self.sizeHint().width()
max_y = parent_bottom_right.y() + parent_bottom_right.height() - self.sizeHint().height()
max_global = self.parentWidget().mapToGlobal(QPoint(max_x, max_y))
if cursor_pos.x() > max_global.x():
cursor_pos.setX(max_global.x())
if cursor_pos.y() > max_global.y():
cursor_pos.setY(max_global.y())
self.move(cursor_pos)
return trimmed(self.edit.text()) if self.exec_() else None
def list_item_click(self, t_qlist_widget_item):
text = trimmed(t_qlist_widget_item.text())
self.edit.setText(text)
def list_item_double_click(self, t_qlist_widget_item):
self.list_item_click(t_qlist_widget_item)
self.validate()