Skip to content

Commit 64919f1

Browse files
committed
QtVcp: adds new Operator Value widget
1 parent fcfeffc commit 64919f1

2 files changed

Lines changed: 270 additions & 0 deletions

File tree

lib/python/qtvcp/plugins/widgets_plugin.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from PyQt5.QtDesigner import QPyDesignerCustomWidgetPlugin
55
from qtvcp.widgets.dro_widget import DROLabel
66
from qtvcp.widgets.mdi_line import MDILine
7+
from qtvcp.widgets.operator_value_line import OperatorValueLine
78
from qtvcp.widgets.mdi_history import MDIHistory
89
from qtvcp.widgets.mdi_touchy import MDITouchy
910
from qtvcp.widgets.gcode_editor import GcodeEditor, GcodeDisplay
@@ -107,6 +108,50 @@ def includeFile(self):
107108
return "qtvcp.widgets.mdi_line"
108109

109110

111+
####################################
112+
# Operator Value edit line
113+
####################################
114+
class OperatorValueLinePlugin(QPyDesignerCustomWidgetPlugin):
115+
def __init__(self, parent=None):
116+
super(OperatorValueLinePlugin, self).__init__(parent)
117+
self.initialized = False
118+
119+
def initialize(self, formEditor):
120+
if self.initialized:
121+
return
122+
self.initialized = True
123+
124+
def isInitialized(self):
125+
return self.initialized
126+
127+
def createWidget(self, parent):
128+
return OperatorValueLine(parent)
129+
130+
def name(self):
131+
return "OperatorValueLine"
132+
133+
def group(self):
134+
return "Linuxcnc - Controller"
135+
136+
def icon(self):
137+
return QtGui.QIcon(QtGui.QPixmap(ICON.get_path('operatorvalueline')))
138+
139+
def toolTip(self):
140+
return "Operator value edit line Widget"
141+
142+
def whatsThis(self):
143+
return ""
144+
145+
def isContainer(self):
146+
return True
147+
148+
def domXml(self):
149+
return '<widget class="OperatorValueLine" name="operatorvalueline" />\n'
150+
151+
def includeFile(self):
152+
return "qtvcp.widgets.operator_value_line"
153+
154+
110155
####################################
111156
# MDI History widget
112157
####################################
Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
#!/usr/bin/env python3
2+
#
3+
# QTVcp Widget - Operator-entered value line edit widget
4+
# optional dialog entry and optional MDI command issuing
5+
#
6+
# Copyright (c) 2025 Steve Richardson
7+
#
8+
# This program is free software: you can redistribute it and/or modify
9+
# it under the terms of the GNU General Public License as published by
10+
# the Free Software Foundation, either version 2 of the License, or
11+
# (at your option) any later version.
12+
#
13+
# This program is distributed in the hope that it will be useful,
14+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+
# GNU General Public License for more details.
17+
###############################################################################
18+
19+
import os
20+
import linuxcnc
21+
import hal
22+
import time
23+
24+
import subprocess
25+
26+
from PyQt5.QtWidgets import QLineEdit, QApplication
27+
from PyQt5.QtCore import Qt, QEvent, pyqtProperty
28+
29+
from qtvcp.core import Status, Action, Info
30+
from qtvcp.widgets.entry_widget import SoftInputWidget
31+
from qtvcp.lib.aux_program_loader import Aux_program_loader
32+
from qtvcp import logger
33+
34+
# Instiniate the libraries with global reference
35+
# STATUS gives us status messages from linuxcnc
36+
# AUX_PRGM holds helper program loader
37+
# INI holds ini details
38+
# ACTION gives commands to linuxcnc
39+
# LOG is for running code logging
40+
STATUS = Status()
41+
AUX_PRGM = Aux_program_loader()
42+
INFO = Info()
43+
ACTION = Action()
44+
LOG = logger.getLogger(__name__)
45+
46+
class OperatorValue(QLineEdit):
47+
def __init__(self, parent=None):
48+
super(OperatorValue, self).__init__(parent)
49+
50+
STATUS.connect('state-off', lambda w: self.setEnabled(False))
51+
STATUS.connect('state-estop', lambda w: self.setEnabled(False))
52+
STATUS.connect('interp-idle', lambda w: self.setEnabled(STATUS.machine_is_on()
53+
and (STATUS.is_all_homed()
54+
or INFO.NO_HOME_REQUIRED)))
55+
STATUS.connect('interp-run', lambda w: self.setEnabled(not STATUS.is_auto_mode()))
56+
STATUS.connect('all-homed', lambda w: self.setEnabled(STATUS.machine_is_on()))
57+
STATUS.connect('general',self.return_value)
58+
59+
def getSpeedText(self):
60+
text = str(self.text()).strip()
61+
return text
62+
63+
64+
def keyPressEvent(self, event):
65+
super(OperatorValue, self).keyPressEvent(event)
66+
if event.key() == Qt.Key_Up:
67+
self.increase()
68+
if event.key() == Qt.Key_Down:
69+
self.decrease()
70+
71+
def increase(self):
72+
LOG.debug('increase')
73+
STATUS.emit('spindle-increase')
74+
75+
def decrease(self):
76+
LOG.debug('decrease')
77+
STATUS.emit('spindle-decrease')
78+
79+
80+
class OperatorValueLine(OperatorValue):
81+
def __init__(self, parent=None):
82+
super(OperatorValueLine, self).__init__(parent)
83+
self._PARENT_WIDGET = parent
84+
self.dialog_code = 'CALCULATOR'
85+
self.soft_keyboard = False
86+
self.dialog_keyboard = False
87+
self.issue_mdi_on_submit = False
88+
self.mdi_command_format = "M3 S{value}"
89+
self.pending_value = False
90+
self._input_panel_full = SoftInputWidget(self, 'default')
91+
self.installEventFilter(self)
92+
93+
self.returnPressed.connect(self.handleReturnKey)
94+
95+
96+
def handleReturnKey(self):
97+
self.setFocus()
98+
self.setCursorPosition(len(self.text())+1)
99+
if self.issue_mdi_on_submit:
100+
self.pending_value = False
101+
self._style_polish('isPendingValue', self.pending_value)
102+
self.submit(self.mdi_command_format)
103+
else:
104+
self.pending_value = True
105+
self._style_polish('isPendingValue', self.pending_value)
106+
107+
108+
def submit(self, mdi_format):
109+
value = str(self.text()).strip()
110+
if value == '': return
111+
namespace = {'value': value}
112+
113+
ACTION.CALL_MDI(mdi_format.format(**namespace) + "\n")
114+
115+
# try/except is so designer will load
116+
def eventFilter(self, widget, event):
117+
if self.focusWidget() == widget and event.type() == QEvent.MouseButtonPress:
118+
if self.soft_keyboard:
119+
self._input_panel_full.show_input_panel(widget)
120+
elif self.dialog_keyboard:
121+
self.request_keyboard()
122+
return False
123+
124+
def request_keyboard(self):
125+
mess = {'NAME':self.dialog_code,'ID':'%s__' % self.objectName(),
126+
'TITLE':'Operator Value',
127+
'GEONAME':'OperatorValue_Keyboard_Dialog_{}'.format(self.dialog_code),
128+
}
129+
STATUS.emit('dialog-request', mess)
130+
LOG.debug('message sent:{}'.format (mess))
131+
132+
# process the STATUS return message
133+
def return_value(self, w, message):
134+
value = message['RETURN']
135+
code = bool(message.get('ID') == '%s__'% self.objectName())
136+
name = bool(message.get('NAME') == self.dialog_code)
137+
if code and name and value is not None:
138+
self.setFocus()
139+
self.setText(f"{value:.0f}")
140+
self.setCursorPosition(len(self.text())+1)
141+
if self.issue_mdi_on_submit:
142+
self.pending_value = False
143+
self._style_polish('isPendingValue', self.pending_value)
144+
self.submit(self.mdi_command_format)
145+
else:
146+
self.pending_value = True
147+
self._style_polish('isPendingValue', self.pending_value)
148+
149+
LOG.debug('message return:{}'.format (message))
150+
STATUS.emit('update-machine-log', 'Set OperatorValue {}'.format(value), 'TIME')
151+
152+
def issue_mdi(self):
153+
self.submit(self.mdi_command_format)
154+
self.pending_value = False
155+
self._style_polish('isPendingValue', self.pending_value)
156+
157+
#########################################################################
158+
# This is how designer can interact with our widget properties.
159+
# designer will show the pyqtProperty properties in the editor
160+
# it will use the get set and reset calls to do those actions
161+
#########################################################################
162+
163+
# force style sheet to update
164+
def _style_polish(self, prop, state):
165+
self.setProperty(prop, state)
166+
self.style().unpolish(self)
167+
self.style().polish(self)
168+
169+
def set_issue_mdi_on_submit(self, data):
170+
self.issue_mdi_on_submit = data
171+
def get_issue_mdi_on_submit(self):
172+
return self.issue_mdi_on_submit
173+
def reset_issue_mdi_on_submit(self):
174+
self.issue_mdi_on_submit = False
175+
176+
issue_mdi_on_submit_option = pyqtProperty(bool, get_issue_mdi_on_submit, set_issue_mdi_on_submit, reset_issue_mdi_on_submit)
177+
178+
def set_mdi_command_format(self, data):
179+
self.mdi_command_format = data
180+
def get_mdi_command_format(self):
181+
return self.mdi_command_format
182+
def reset_mdi_command_format(self):
183+
self.mdi_command_format = False
184+
185+
mdi_command_format_option = pyqtProperty(str, get_mdi_command_format, set_mdi_command_format, reset_mdi_command_format)
186+
187+
188+
def set_soft_keyboard(self, data):
189+
self.soft_keyboard = data
190+
def get_soft_keyboard(self):
191+
return self.soft_keyboard
192+
def reset_soft_keyboard(self):
193+
self.soft_keyboard = False
194+
195+
soft_keyboard_option = pyqtProperty(bool, get_soft_keyboard, set_soft_keyboard, reset_soft_keyboard)
196+
197+
def set_dialog_keyboard(self, data):
198+
self.dialog_keyboard = data
199+
def get_dialog_keyboard(self):
200+
return self.dialog_keyboard
201+
def reset_dialog_keyboard(self):
202+
self.dialog_keyboard = False
203+
204+
dialog_keyboard_option = pyqtProperty(bool, get_dialog_keyboard, set_dialog_keyboard, reset_dialog_keyboard)
205+
206+
def set_pending_value(self, data):
207+
self.pending_value = data
208+
def get_pending_value(self):
209+
return self.pending_value
210+
def reset_pending_value(self):
211+
self.pending_value = False
212+
213+
isPendingValue = pyqtProperty(bool, get_pending_value, set_pending_value, reset_pending_value)
214+
215+
216+
# for testing without editor:
217+
def main():
218+
import sys
219+
from PyQt5.QtWidgets import QApplication
220+
app = QApplication(sys.argv)
221+
widget = OperatorValueLine()
222+
widget.show()
223+
sys.exit(app.exec_())
224+
if __name__ == "__main__":
225+
main()

0 commit comments

Comments
 (0)