Files
linuxcncAllWasm/lib/python/qtvcp/widgets/entry_widget.py
2026-08-20 06:17:39 +08:00

286 lines
10 KiB
Python

#!/usr/bin/env python3
# QTVcp Widget
#
# Copyright (c) 2017 Chris Morley
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# base code Found on Code Cookbook
#
# This widget pops up an onscreen keyboard for entries
# Used in the Macro and MDI line widget
from qtpy import QtWidgets, QtCore, QtGui
#from decimal import Decimal
# applicationle widgets
SIP_WIDGETS = [QtWidgets.QLineEdit]
class MyFlatPushButton(QtWidgets.QPushButton):
def __init__(self, caption, min_size=(50, 50)):
self.MIN_SIZE = min_size
super(MyFlatPushButton, self).__init__(caption)
self.setFocusPolicy(QtCore.Qt.NoFocus)
def sizeHint(self):
return QtCore.QSize(self.MIN_SIZE[0], self.MIN_SIZE[1])
class SoftInputWidget(QtWidgets.QDialog):
def __init__(self, parent=None, keyboard_type='default'):
super(SoftInputWidget, self).__init__(parent)
self.setWindowModality(QtCore.Qt.ApplicationModal)
self.setWindowFlags(self.windowFlags() | QtCore.Qt.FramelessWindowHint |
QtCore.Qt.WindowStaysOnTopHint)
self.INPUT_WIDGET = None
self.PARENT_OBJECT = parent
self.signalMapper = QtCore.QSignalMapper(self)
self.NO_ORD_KEY_LIST = list()
self.NO_ORD_KEY_LIST.append(QtCore.Qt.Key_Left)
self.NO_ORD_KEY_LIST.append(QtCore.Qt.Key_Up)
self.NO_ORD_KEY_LIST.append(QtCore.Qt.Key_Right)
self.NO_ORD_KEY_LIST.append(QtCore.Qt.Key_Down)
self.NO_ORD_KEY_LIST.append(QtCore.Qt.Key_Backspace)
self.NO_ORD_KEY_LIST.append(QtCore.Qt.Key_Enter)
self.NO_ORD_KEY_LIST.append(QtCore.Qt.Key_Tab)
self.NO_ORD_KEY_LIST.append(QtCore.Qt.Key_Escape)
self.do_layout(keyboard_type)
mapped = getattr(self.signalMapper, 'mappedInt', None) or self.signalMapper.mapped[int]
mapped.connect(self.buttonClicked)
def do_layout(self, keyboard_type='default'):
"""
@param keyboard_type:
@return:
"""
gl = QtWidgets.QVBoxLayout()
self.setFont(self.PARENT_OBJECT.font())
number_widget_list = []
sym_list = list(range(0, 10))
for sym in sym_list:
button = MyFlatPushButton(str(sym))
button.KEY_CHAR = ord(str(sym))
number_widget_list.append(button)
# alphabets
alpha_widget_list = []
sym_list = ['Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P',
'new_row',
'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L',
'new_row',
'Z', 'X', 'C', 'V', 'B', 'N', 'M']
for sym in sym_list:
if sym == 'new_row':
alpha_widget_list.append('new_row')
else:
button = MyFlatPushButton(sym)
button.KEY_CHAR = ord(sym)
alpha_widget_list.append(button)
button = MyFlatPushButton('.')
button.KEY_CHAR = ord('.')
alpha_widget_list.append(button)
button = MyFlatPushButton('-')
button.KEY_CHAR = ord('-')
alpha_widget_list.append(button)
control_widget_list = []
button = MyFlatPushButton('LINE\nUP')
button.setToolTip('Cursor Up')
button.KEY_CHAR = QtCore.Qt.Key_Up
control_widget_list.append(button)
button = MyFlatPushButton('LINE\nDOWN')
button.setToolTip('Cursor Down')
button.KEY_CHAR = QtCore.Qt.Key_Down
control_widget_list.append(button)
# space
button = MyFlatPushButton('SPACE', min_size=(160, 50))
button.KEY_CHAR = QtCore.Qt.Key_Space
control_widget_list.append(button)
# back space
button = MyFlatPushButton('BACK')
button.setToolTip('Backspace')
button.KEY_CHAR = QtCore.Qt.Key_Backspace
control_widget_list.append(button)
# close
button = MyFlatPushButton('CLOSE')
button.setToolTip('Close Keyboard')
button.KEY_CHAR = QtCore.Qt.Key_Escape
control_widget_list.append(button)
# enter
button = MyFlatPushButton('ENTER', min_size=(105, 50))
button.setToolTip('Enter Command')
button.KEY_CHAR = QtCore.Qt.Key_Enter
control_widget_list.append(button)
MAX_COL = 10
col = 0
tlist = list()
if keyboard_type == 'numeric':
widget_list = number_widget_list
elif keyboard_type == 'alpha':
widget_list = alpha_widget_list
else:
widget_list = list()
widget_list.extend(number_widget_list)
widget_list.append('new_row')
widget_list.extend(alpha_widget_list)
widget_list.append('new_row')
widget_list.extend(control_widget_list)
for widget in widget_list:
if widget == 'new_row':
col = MAX_COL
else:
tlist.append(widget)
widget.clicked.connect(self.signalMapper.map)
self.signalMapper.setMapping(widget, widget.KEY_CHAR)
if col == MAX_COL:
col = 0
v = QtWidgets.QHBoxLayout()
v.addStretch()
v.setSpacing(5)
list(map(v.addWidget, tlist))
v.addStretch()
gl.addLayout(v)
tlist = []
else:
col += 1
v = QtWidgets.QHBoxLayout()
v.setSpacing(5)
v.addStretch()
list(map(v.addWidget, tlist))
v.addStretch()
gl.addLayout(v)
gl.setContentsMargins(4, 4, 4, 4)
gl.setSpacing(4)
gl.setSizeConstraint(gl.SetFixedSize)
self.setLayout(gl)
def reject(self):
self.buttonClicked(QtCore.Qt.Key_Escape)
def buttonClicked(self, char_ord):
w = self.INPUT_WIDGET
if char_ord in self.NO_ORD_KEY_LIST:
keyPress = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, char_ord, QtCore.Qt.NoModifier, '')
else:
keyPress = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, char_ord, QtCore.Qt.NoModifier, chr(char_ord))
# hide on enter or esc button click
if char_ord == QtCore.Qt.Key_Escape:
self.hide()
else:
# send keypress event to widget
QtWidgets.QApplication.sendEvent(w, keyPress)
if char_ord == QtCore.Qt.Key_Enter:
self.hide()
# line edit returnPressed event is triggering twice for press and release both
# that is why do not send release event for special key
if char_ord not in self.NO_ORD_KEY_LIST:
keyRelease = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, char_ord, QtCore.Qt.NoModifier, '')
QtWidgets.QApplication.sendEvent(w, keyRelease)
def show_input_panel(self, widget):
self.INPUT_WIDGET = widget
self.show()
self.update_panel_position()
self.setFocus()
self.raise_()
def update_panel_position(self):
widget = self.INPUT_WIDGET
if not widget: return
widget_rect = widget.rect()
widget_bottom = widget.mapToGlobal(QtCore.QPoint(widget.frameGeometry().x(),
widget.frameGeometry().y())).y()
screen_height = QtWidgets.QApplication.primaryScreen().availableGeometry().height()
input_panel_height = self.geometry().height() + 5
if (screen_height - widget_bottom) > input_panel_height:
# display input panel at bottom of widget
panelPos = QtCore.QPoint(widget_rect.left(), widget_rect.bottom() + 2)
else:
# display input panel at top of widget
panelPos = QtCore.QPoint(widget_rect.left(), widget_rect.top() - input_panel_height)
panelPos = widget.mapToGlobal(panelPos)
self.move(panelPos)
# a widget that calls our keyboard dialog
class TouchInterface(QtWidgets.QWidget):
def __init__(self, parent):
super(TouchInterface, self).__init__(parent)
self._PARENT_WIDGET = parent
self._input_panel_alpha = SoftInputWidget(parent, 'alpha')
self._input_panel_numeric = SoftInputWidget(parent, 'numeric')
self._input_panel_full = SoftInputWidget(parent, 'default')
self.keyboard_enable = True
self.keyboard_type = 'numeric'
def childEvent(self, event):
if event.type() == QtCore.QEvent.ChildAdded:
if isinstance(event.child(), *SIP_WIDGETS):
event.child().installEventFilter(self)
def eventFilter(self, widget, event):
try:
if self._PARENT_WIDGET.focusWidget() == widget and event.type() == QtCore.QEvent.MouseButtonPress:
if self.keyboard_enable is False:
return False
self.callDialog(widget, self.keyboard_type.lower())
return False
except:
return False
# can be class patched to call other entries - like qtvcp dialogs
def callDialog(self, widget, ktype):
if ktype == 'alpha':
self._input_panel_alpha.show_input_panel(widget)
elif ktype == 'numeric':
self._input_panel_numeric.show_input_panel(widget)
else:
self._input_panel_full.show_input_panel(widget)
## testing ##
if __name__ == '__main__':
import sys
from qtpy.QtWidgets import QWidget
app = QtWidgets.QApplication([])
w = QWidget()
w.setGeometry(100, 100, 200, 100)
w.setWindowTitle('Entry Widget')
line = QtWidgets.QLineEdit()
layout = QtWidgets.QVBoxLayout()
layout.addWidget(line)
test = TouchInterface(line)
# test.callDialog(line, 'numeric')
# test.callDialog(line, 'alpha')
test.callDialog(line, 'default')
w.setLayout(layout)
w.show()
sys.exit(app.exec())