#!/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. import os import hal from qtpy.QtWidgets import (QMessageBox, QFileDialog, QDialog, QDialogButtonBox, QVBoxLayout, QPushButton, QHBoxLayout, QHBoxLayout, QLineEdit, QPushButton, QDialogButtonBox, QTabWidget, QTextEdit,QLabel, QApplication) from qtpy.QtGui import QColor from qtpy.QtCore import Qt, Slot, Property, QEvent, QUrl, QTimer from qtpy import uic from qtvcp.widgets.widget_baseclass import _HalWidgetBase, hal from qtvcp.widgets.origin_offsetview import OriginOffsetView as OFFVIEW_WIDGET from qtvcp.widgets.tool_offsetview import ToolOffsetView as TOOLVIEW_WIDGET from qtvcp.widgets.tool_chooser import ToolChooser as TOOLCHOOSER_WIDGET from qtvcp.widgets.macro_widget import MacroTab from qtvcp.widgets.versa_probe import VersaProbe from qtvcp.widgets.entry_widget import SoftInputWidget from qtvcp.widgets.virtualkeyboard import VirtualKeyboard from qtvcp.widgets.calculator import Calculator from qtvcp.widgets.machine_log import MachineLog from qtvcp.lib.notify import Notify from qtvcp.widgets.dialogMixin import GeometryMixin from qtvcp.core import Status, Action, Info, Tool, Path from qtvcp import logger # Instantiate the libraries with global reference # STATUS gives us status messages from linuxcnc # ACTION gives commands to linuxcnc # INFO holds INI file details # TOOL gives tool file access # NOTICE is for the desktop notify system # LOG is for running code logging STATUS = Status() ACTION = Action() INFO = Info() TOOL = Tool() PATH = Path() NOTICE = Notify() LOG = logger.getLogger(__name__) # Force the log level for this module #LOG.setLevel(logger.DEBUG) # One of DEBUG, INFO, WARNING, ERROR, CRITICAL ######################################### # GeometryMixin helper class ######################################### # adds geometry class and _HalWidgetBase class into the dialogs. # the geometry class parses the geometry string and places # the dialog based on what it finds. # it needed to be in a separate file do to circular imports # found when using versaProbe ################################################################################ # Generic messagebox Dialog ################################################################################ class LcncDialog(QMessageBox, GeometryMixin): OK = 'OK' NONE = 'NONE' YESNO = 'YESNO' OKCANCEL = 'OKCANCEL' CLOSEPROMPT = 'CLOSEPROMPT' def __init__(self, parent=None): super(LcncDialog, self).__init__(parent) self.setTextFormat(Qt.RichText) self.setText('Sample Text?') self.setStandardButtons(QMessageBox.Yes | QMessageBox.No) self.setIcon(QMessageBox.Critical) self.setDetailedText('') self.mtype = 'OK' self._possibleTypes = ('OK','YESNO','OKCANCEL','CLOSEPROMPT','NONE') self._state = False self._color = QColor(0, 0, 0, 150) self._request_name = 'MESSAGE' self._nblock = False self._message = None self._return_callback = None self._pinname = None self._title = 'Message Dialog' self._forcedFlag = True self._use_exec = False self._geoName = 'LncMessage-geometry' self.set_default_geometry() self.hide() self.buttonClicked.connect(self.btn_callback) self.timer = QTimer() self.seconds_left = 0 self.timer.timeout.connect(self.update_timer) def _hal_init(self): self.read_preference_geometry(self._geoName) STATUS.connect('dialog-request', self._external_request) STATUS.connect('dialog-update', self._external_update) # this processes STATUS called dialog requests # We check the cmd to see if it was for us # then we check for a id string # if all good show the dialog # and then send back the dialog response via a general message def _external_request(self, w, message): self._message = message if message.get('NAME') == self._request_name: # request to close dialog early if message.get('CLOSE'): self.close() STATUS.emit('focus-overlay-changed', False, None, None) return geo = message.get('GEONAME') or 'LncMessage-geometry' t = message.get('TITLE') if not t: t = 'Message Dialog' messtext = message.get('MESSAGE') or None more = message.get('MORE') or None details = message.get('DETAILS') or None mtype = message.get('TYPE') if mtype is None: mtype = 'OK' icon = message.get('ICON') or 'INFO' pin = message.get('PINNAME') ftext = message.get('FOCUSTEXT') fcolor = message.get('FOCUSCOLOR') alert = message.get('PLAYALERT') nblock = message.get('NONBLOCKING') forceOpen = message.get('NONBLOCKING') callback = message.get('CALLBACK') # this needs testing self.showdialog(messtext, more, details, mtype, icon, pin, ftext, fcolor, alert, nblock, geoname=geo,title = t, return_callback = callback, force_open = forceOpen) # This actually builds and displays the dialog. # there are three ways to get results: # - through a return by status message (return_callback = None, use_exec = False) # - callback return (return_callback = function_name) # - by direct return statement (use_exec = True) def showdialog(self, messagetext, more_info=None, details=None, display_type='OK', icon=QMessageBox.Information, pinname=None, focus_text=None, focus_color=None, play_alert=None, nblock=False, return_callback = None, flags = None, setflags = None, title = None, use_exec = False,geoname=None, force_open = None, timer=0): self._pinname = pinname self._nblock = nblock self._return_callback = return_callback self._use_exec = use_exec # block response to main window? if nblock: self.setWindowModality(Qt.NonModal) else: self.setWindowModality(Qt.ApplicationModal) # set standard flags or allow external selection if flags is None: if nblock: self.setWindowFlags(self.windowFlags() | Qt.Tool | Qt.Dialog | Qt.WindowStaysOnTopHint | Qt.WindowSystemMenuHint) if display_type == LcncDialog.NONE: self.setWindowFlags(self.windowFlags() | Qt.CustomizeWindowHint) self.setWindowFlag(Qt.WindowCloseButtonHint, False) else: self.setWindowFlags(self.windowFlags() | Qt.Tool | Qt.FramelessWindowHint | Qt.Dialog | Qt.WindowStaysOnTopHint | Qt.WindowSystemMenuHint) else: self.setWindowFlags(self.windowFlags() | flags) # allow external setting of flags - probably hide close button if not setflags is None: for i in setflags: self.setWindowFlag(i,setflags[i]) self.setWindowTitle(title) if focus_color is not None: color = focus_color else: color = self._color # convert text descriptions to actual icons if icon == 'QUESTION': icon = QMessageBox.Question elif icon == 'WARNING': icon = QMessageBox.Warning elif icon == 'CRITICAL': icon = QMessageBox.Critical elif icon == 'INFO' or isinstance(icon,str): icon = QMessageBox.Information self.setIcon(icon) self.setText('%s' % messagetext) if more_info is not None: self.setInformativeText(more_info) else: self.setInformativeText('') if details is not None: self.setDetailedText(details) else: self.setDetailedText('') # convert display type text to buttons layouts display_type = display_type.upper() if display_type not in self._possibleTypes: display_type = LcncDialog.OK if display_type == LcncDialog.OK: self.setStandardButtons(QMessageBox.Ok) elif display_type == LcncDialog.YESNO: self.setStandardButtons(QMessageBox.Yes | QMessageBox.No) elif display_type == LcncDialog.OKCANCEL: self.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel) elif display_type == LcncDialog.NONE: self.setStandardButtons(QMessageBox.NoButton) if not nblock: STATUS.emit('focus-overlay-changed', True, focus_text, color) if play_alert: STATUS.emit('play-sound', play_alert) if geoname is None: geoname = self._geoName self.read_preference_geometry(geoname) self.show() if not force_open is None: self._forcedFlag = force_open self.forceDetailsOpen() if timer: self.seconds_left = timer self.timer.start(1000) if use_exec: retval = self.exec() STATUS.emit('focus-overlay-changed', False, None, None) LOG.debug('Value of pressed button: {}'.format(retval)) return self.qualifiedReturn(retval) # hack to force details box to present open on first display def forceDetailsOpen(self): if self._forcedFlag: return try: # force the details box open on first time display for i in self.buttons(): if self.buttonRole(i) == QMessageBox.ActionRole: for j in self.children(): for k in j.children(): if isinstance( k, QTextEdit): #i.hide() if not k.isVisible(): i.click() except Exception as e: print(e) pass self._forcedFlag = True def qualifiedReturn(self, retval): if retval in(QMessageBox.No, QMessageBox.Cancel): return False elif retval in(QMessageBox.Ok, QMessageBox.Yes): return True else: return self.buttonRole(self.clickedButton()) # move dialog when shown def showEvent(self, event): self.set_geometry() super(LcncDialog, self).showEvent(event) return if self._nblock: self.set_geometry() else: geom = self.frameGeometry() geom.moveCenter(QApplication.primaryScreen().availableGeometry().center()) self.setGeometry(geom) super(LcncDialog, self).showEvent(event) def update_timer(self): self.seconds_left -= 1 if self.seconds_left > 0: title = f"Closing in: {self.seconds_left}s" self.setText('%s' % title) else: self.timer.stop() self.accept() # Close dialog when done def btn_callback(self, i): LOG.debug('Button pressed is: {}'.format(i.text())) # update the dialog position self.record_geometry() if self._use_exec: return self.hide() btn = self.standardButton(self.clickedButton()) result = self.qualifiedReturn(btn) LOG.debug('Value of {} pressed button: {}'.format(self, result)) self.process_result(result) def process_result(self, result): self.timer.stop() # these directly call a function with btn info if not self._return_callback is None: self._return_callback(self, result) # these return via status messages elif self._message is not None: self._message['RETURN'] = result STATUS.emit('general', self._message) STATUS.emit('focus-overlay-changed', False, None, None) self._message = None # just return result else: return result def setGeometry(self,*args): #print(args,len(args)) if len(args) == 1: super().setGeometry(args[0]) width = args[0].width() else: super().setGeometry(args[0],args[1],args[2],args[3]) width = args[2] if isinstance(self,QMessageBox) and width < 200: if isinstance(self,CloseDialog): self.findChild(QLabel, "qt_msgbox_label").setFixedWidth(200) else: self.findChild(QLabel, "qt_msgbox_label").setFixedWidth(350) # callback from status 'update-dialog' def _external_update(self, w, message): if message.get('NAME') == self._request_name: if not self.isVisible(): return response = message.get('response') if not response is None: LOG.debug('Response is: {}'.format(response)) # update the dialog position self.record_geometry() self.hide() if response == 0: self.process_result(False) elif response == 1: self.process_result(True) # ********************** # Designer properties # ********************** @Slot(bool) def setState(self, value): self._state = value if value: self.show() else: self.hide() def getState(self): return self._state def resetState(self): self._state = False def getColor(self): return self._color def setColor(self, value): self._color = value def resetState(self): self._color = QColor(0, 0, 0, 150) def getIdName(self): return self._request_name def setIdName(self, name): self._request_name = name def resetIdName(self): self._request_name = 'MESSAGE' overlay_color = Property(QColor, getColor, setColor) state = Property(bool, getState, setState, resetState) launch_id = Property(str, getIdName, setIdName, resetIdName) ################################################################################ # Close Dialog ################################################################################ class CloseDialog(LcncDialog, GeometryMixin): def __init__(self, parent=None): super(CloseDialog, self).__init__(parent) self.shutdown = self.addButton('System\nShutdown',QMessageBox.DestructiveRole) self._request_name = 'CLOSEPROMPT' self._title = 'QtVCP' ################################################################################ # Tool Change Dialog ################################################################################ class ToolDialog(LcncDialog, GeometryMixin): def __init__(self, parent=None): super(ToolDialog, self).__init__(parent) self.setText('Manual Tool Change Request') self.setInformativeText('Please Insert Tool 0') self.setStandardButtons(QMessageBox.Ok) self.setDefaultButton(QMessageBox.Ok) self._useDesktopNotify = False self._frameless = False self._curLine = 0 self._actionbutton = self.addButton('Pause For Jogging', QMessageBox.ApplyRole) self._actionbutton.setEnabled(False) self._flag = True self._forcedFlag = False # We want the tool change HAL pins the same as what's used in AXIS so it is # easier for users to connect to. # So we need to trick the HAL component into doing this for these pins, # but not any other Qt widgets. # So we record the original base name of the component, make our pins, then # switch it back def _hal_init(self): self.set_default_geometry() self.read_preference_geometry('ToolChangeDialog-geometry') if not hal.component_exists('hal_manualtoolchange'): oldname = self.HAL_GCOMP_.comp.getprefix() self.HAL_GCOMP_.comp.setprefix('hal_manualtoolchange') self.hal_pin = self.HAL_GCOMP_.newpin('change', hal.HAL_BIT, hal.HAL_IN) self.hal_pin.value_changed.connect(self.tool_change) self.tool_number = self.HAL_GCOMP_.newpin('number', hal.HAL_S32, hal.HAL_IN) self.changed = self.HAL_GCOMP_.newpin('changed', hal.HAL_BIT, hal.HAL_OUT) self.ext_ack = self.HAL_GCOMP_.newpin(self.HAL_NAME_ + 'change_button', hal.HAL_BIT, hal.HAL_IN) self.ext_ack.value_changed.connect(self.external_acknowledge) self.HAL_GCOMP_.comp.setprefix(oldname) else: LOG.warning("""Detected hal_manualtoolchange component already loaded Qtvcp recommends to allow use of it's own component by not loading the original. Qtvcp Integrated toolchange dialog will not show until then""") if self.PREFS_: self.play_sound = self.PREFS_.getpref('toolDialog_play_sound', True, bool, 'DIALOG_OPTIONS') self.speak = self.PREFS_.getpref('toolDialog_speak', True, bool, 'DIALOG_OPTIONS') self.sound_type = self.PREFS_.getpref('toolDialog_sound_type', 'READY', str, 'DIALOG_OPTIONS') else: self.play_sound = False # process callback from 'change' HAL pin def tool_change(self, change): if change: try: STATUS.stat.poll() self._curLine = STATUS.stat.motion_line except: self._curLine = 0 # enable/disable pause at jog button if self._curLine > 0: self._actionbutton.setEnabled(True) jpause = True else: self._actionbutton.setEnabled(False) jpause = False MORE = 'Please Insert Tool %d' % self.tool_number.get() try: tool_table_line = TOOL.GET_TOOL_INFO(self.tool_number.get()) comment = str(tool_table_line[TOOL.COMMENTS]) except TypeError: comment = '' MESS = 'Manual Tool Change Request' DETAILS = ' Tool Info: %s'% comment STATUS.emit('focus-overlay-changed', True, MESS, self._color) if self.PREFS_: if self.speak: STATUS.emit('play-sound', 'speak %s' % MORE) if self.play_sound: STATUS.emit('play-sound', self.sound_type) # show desktop notify dialog rather then a qt dialog if self._useDesktopNotify: self.deskNotice = NOTICE.show_toolchange_notification(MESS, MORE +'\n' + comment, None, 0, self._processChange, jogpause = jpause) else: # ok show Qt dialog self.showdialog(MESS, MORE, DETAILS, frameless = self._frameless) elif not change: self.changed.set(False) # process callback for 'change-button' HAL pin # hide the message dialog or desktop notify message def external_acknowledge(self, state): #print('external acklnowledge: {}'.format(state)) if state: if self._useDesktopNotify: self.deskNotice.close() elif self.isVisible(): self.hide() self._processChange(True) # This also is called from DesktopDialog def _processChange(self,answer): #print('proces change: {}'.format(answer)) if answer == -1: self.changed.set(True) ACTION.ABORT() STATUS.emit('update-machine-log', 'tool change paused for jogging; launched run-from-line', 'TIME') STATUS.emit('dialog-request', {'NAME': 'RUNFROMLINE', 'LINE':self._curLine+2, 'NONBLOCKING':True}) elif answer == True: self.changed.set(True) elif answer == False: ACTION.ABORT() STATUS.emit('update-machine-log', 'tool change Aborted', 'TIME,WARNING') self.record_geometry() STATUS.emit('focus-overlay-changed', False, None, None) ###### overridden functions ################ def showdialog(self, message, more_info=None, details=None, play_alert=None, frameless = True): self.setWindowModality(Qt.ApplicationModal) if frameless: self.setWindowFlags(self.windowFlags() | Qt.Tool | Qt.FramelessWindowHint | Qt.Dialog | Qt.WindowStaysOnTopHint | Qt.WindowSystemMenuHint) else: self.setWindowFlags(self.windowFlags() | Qt.Tool | Qt.Dialog | Qt.WindowStaysOnTopHint | Qt.WindowSystemMenuHint) self.setWindowFlags(self.windowFlags() | Qt.CustomizeWindowHint) self.setWindowFlag(Qt.WindowCloseButtonHint, False) self.setWindowTitle(self._title) self.setIcon(QMessageBox.Critical) self.setText('%s' % message) self.setInformativeText(more_info) self.setDetailedText(details) self.setStandardButtons(QMessageBox.Ok) self.buttonClicked.connect(self.msgbtn) # force the details box open on first time display if self._flag and details != ' Tool Info: ': for i in self.buttons(): if self.buttonRole(i) == QMessageBox.ActionRole: if i is self._actionbutton: continue i.click() self._flag = False if play_alert: STATUS.emit('play-sound', play_alert) self.show() # set the geometry when dialog shown def showEvent(self, event): self.set_geometry() super(LcncDialog, self).showEvent(event) # decode button presses def msgbtn(self, i): LOG.debug('Button pressed is: {}'.format(i.text())) if self.clickedButton() == self._actionbutton: self._processChange(-1) elif self.standardButton(self.clickedButton()) == QMessageBox.Ok: self._processChange(True) else: self._processChange(False) ############################################ # ********************** # Designer properties # ********************** # inherited from lcncDialog # plus : def setFrameless(self, value): self._frameless = value def getFrameless(self): return self._frameless def resetFrameless(self): self._frameless = False frameless = Property(bool, getFrameless, setFrameless, resetFrameless) def setUseDesktopNotify(self, value): self._useDesktopNotify = value def getUseDesktopNotify(self): return self._useDesktopNotify def resetUseDesktopNotify(self): self._useDesktopNotify = False useDesktopNotify = Property(bool, getUseDesktopNotify, setUseDesktopNotify, resetUseDesktopNotify) ################################################################################ # File Open Dialog ################################################################################ class FileDialog(QFileDialog, GeometryMixin): def __init__(self, parent=None): super(FileDialog, self).__init__(parent) self._state = False self._load_request_name = 'LOAD' self._save_request_name = 'SAVE' self._color = QColor(0, 0, 0, 150) options = QFileDialog.Options() options |= QFileDialog.DontUseNativeDialog self.setOptions(options) self.setWindowModality(Qt.ApplicationModal) self.setWindowFlags(self.windowFlags() | Qt.Tool | Qt.Dialog | Qt.WindowStaysOnTopHint | Qt.WindowSystemMenuHint) self.INI_exts = INFO.get_qt_filter_extensions() self.setNameFilter(self.INI_exts) self.default_path = (os.path.join(os.path.expanduser('~'), 'linuxcnc/nc_files/examples')) # sidebar links urls = [] urls.append(QUrl.fromLocalFile(os.path.expanduser('~'))) local = os.path.join(os.path.expanduser('~'),'linuxcnc/nc_files') if os.path.exists(local): urls.append(QUrl.fromLocalFile(local)) self.setSidebarUrls(urls) def _hal_init(self): self.set_default_geometry() STATUS.connect('dialog-request', self._external_request) if self.PREFS_: self.play_sound = self.PREFS_.getpref('fileDialog_play_sound', True, bool, 'DIALOG_OPTIONS') self.sound_type = self.PREFS_.getpref('fileDialog_sound_type', 'READY', str, 'DIALOG_OPTIONS') last_path = self.PREFS_.getpref('last_loaded_directory', self.default_path, str, 'BOOK_KEEPING') self.setDirectory(last_path) else: self.play_sound = False def _external_request(self, w, message): name = message.get('NAME') if name in (self._load_request_name,self._save_request_name): ext = message.get('EXTENSIONS') pre = message.get('FILENAME') dir = message.get('DIRECTORY') if dir is None: dir = self.PREFS_.getpref('last_loaded_directory', self.default_path, str, 'BOOK_KEEPING') geo = message.get('GEONAME') or 'FileDialog-geometry' self.read_preference_geometry(geo) if name == self._load_request_name: # if there is an ID then a file name response is expected if message.get('ID'): message['RETURN'] = self.load_dialog(ext, pre, dir, True) STATUS.emit('general', message) else: self.load_dialog(extensions = ext, preselect = pre, directory = dir,) else: if message.get('ID'): message['RETURN'] = self.save_dialog(ext, pre, dir) STATUS.emit('general', message) def showdialog(self): self.load_dialog() def load_dialog(self, extensions = None, preselect = None, directory = None, return_path=False): #print(extensions,preselect,os.path.expanduser(directory)) self.setFileMode(QFileDialog.ExistingFile) self.setAcceptMode(QFileDialog.AcceptOpen) if extensions: self.setNameFilter(extensions) else: self.setNameFilter(self.INI_exts) if directory: self.setDirectory(os.path.expanduser(directory)) else: self.setDirectory(self.default_path) if preselect: self.selectFile(os.path.basename(preselect)) else: self.selectFile('') self.setWindowTitle('Open') STATUS.emit('focus-overlay-changed', True, 'Open Gcode', self._color) if self.play_sound: STATUS.emit('play-sound', self.sound_type) self.set_geometry() fname = None if (self.exec()): fname = self.selectedFiles()[0] path = self.directory().absolutePath() self.setDirectory(path) self.record_geometry() if fname and not return_path: if self.PREFS_: self.PREFS_.putpref('last_loaded_directory', path, str, 'BOOK_KEEPING') self.PREFS_.putpref('RecentPath_0', fname, str, 'BOOK_KEEPING') ACTION.OPEN_PROGRAM(fname) STATUS.emit('update-machine-log', 'Loaded: ' + fname, 'TIME') # overlay hides it's self after loading STATUS.emit('focus-overlay-changed', False, None, None) return fname def save_dialog(self, extensions = None, preselect = None, directory = None): self.setFileMode(QFileDialog.AnyFile) self.setAcceptMode(QFileDialog.AcceptSave) self.setDefaultSuffix('ngc') if extensions: self.setNameFilter(extensions) else: self.setNameFilter(self.INI_exts) if directory: self.setDirectory(os.path.expanduser(directory)) if preselect: self.selectFile(os.path.basename(preselect)) # disable filename auto completer for le in self.findChildren(QLineEdit): le.setCompleter(None) self.setWindowTitle('Save') STATUS.emit('focus-overlay-changed', True, 'Save Gcode', self._color) if self.play_sound: STATUS.emit('play-sound', self.sound_type) self.set_geometry() fname = None if (self.exec()): fname = self.selectedFiles()[0] path = self.directory().absolutePath() self.setDirectory(path) else: fname = None STATUS.emit('focus-overlay-changed', False, None, None) self.record_geometry() if fname: if self.PREFS_: self.PREFS_.putpref('last_saved_directory', path, str, 'BOOK_KEEPING') self.PREFS_.putpref('RecentSavedPath_0', fname, str, 'BOOK_KEEPING') return fname #********************** # Designer properties #********************** @Slot(bool) def setState(self, value): self._state = value if value: self.show() else: self.hide() def getState(self): return self._state def resetState(self): self._state = False def getColor(self): return self._color def setColor(self, value): self._color = value def resetState(self): self._color = QColor(0, 0, 0, 150) state = Property(bool, getState, setState, resetState) overlay_color = Property(QColor, getColor, setColor) def getLoadIdName(self): return self._load_request_name def setLoadIdName(self, name): self._load_request_name = name def resetLoadIdName(self): self._load_request_name = 'LOAD' def getSaveIdName(self): return self._save_request_name def setSaveIdName(self, name): self._save_request_name = name def resetSaveIdName(self): self._save_request_name = 'SAVE' launch_load_id = Property(str, getLoadIdName, setLoadIdName, resetLoadIdName) launch_save_id = Property(str, getSaveIdName, setSaveIdName, resetSaveIdName) ################################################################################ # origin Offset Dialog ################################################################################ class OriginOffsetDialog(QDialog, GeometryMixin): def __init__(self, parent=None): super(OriginOffsetDialog, self).__init__(parent) self._color = QColor(0, 0, 0, 150) self._state = False self._request_name = 'ORIGINOFFSET' self.setWindowModality(Qt.ApplicationModal) self.setWindowFlags(self.windowFlags() | Qt.Tool | Qt.Dialog | Qt.WindowStaysOnTopHint | Qt.WindowSystemMenuHint) self.setMinimumSize(200, 200) buttonBox = QDialogButtonBox() buttonBox.setEnabled(False) STATUS.connect('not-all-homed', lambda w, axis: buttonBox.setEnabled(False)) STATUS.connect('all-homed', lambda w: buttonBox.setEnabled(True)) STATUS.connect('state-estop', lambda w: buttonBox.setEnabled(False)) STATUS.connect('state-estop-reset', lambda w: buttonBox.setEnabled(STATUS.machine_is_on() and STATUS.is_all_homed())) STATUS.connect('interp-idle', lambda w: buttonBox.setEnabled(STATUS.machine_is_on() and STATUS.is_all_homed())) STATUS.connect('interp-run', lambda w: buttonBox.setEnabled(False)) for i in('X', 'Y', 'Z'): b = 'button_%s' % i self[b] = QPushButton('Zero %s' % i) self[b].clicked.connect(self.zeroPress('%s' % i)) buttonBox.addButton(self[b], QDialogButtonBox.ActionRole) v = QVBoxLayout() h = QHBoxLayout() self._o = OFFVIEW_WIDGET() self._o.setObjectName('__dialogOffsetViewWidget') self.setLayout(v) v.addWidget(self._o) b = QPushButton('OK') b.clicked.connect(lambda: self.close()) h.addWidget(b) h.addWidget(buttonBox) v.addLayout(h) self.setModal(True) def _hal_init(self): self._o.hal_init() self.set_default_geometry() STATUS.connect('dialog-request', self._external_request) def _external_request(self, w, message): if message['NAME'] == self._request_name: geo = message.get('GEONAME') or 'OriginOffsetDialog-geometry' self.read_preference_geometry(geo) self.load_dialog() # This weird code is just so we can get the axis # letter # using clicked.connect() apparently can't easily # add user data def zeroPress(self, data): def calluser(): self.zeroAxis(data) return calluser def zeroAxis(self, index): ACTION.SET_AXIS_ORIGIN(index, 0) def load_dialog(self): STATUS.emit('focus-overlay-changed', True, 'Set Origin Offsets', self._color) self.set_geometry() self.show() self.exec() STATUS.emit('focus-overlay-changed', False, None, None) self.record_geometry() def _hal_cleanup(self): self._o._hal_cleanup() # usual boiler code # (used so we can use code such as self[SomeDataName] def __getitem__(self, item): return getattr(self, item) def __setitem__(self, item, value): return setattr(self, item, value) # ********************** # Designer properties # ********************** @Slot(bool) def setState(self, value): self._state = value if value: self.show() else: self.hide() def getState(self): return self._state def resetState(self): self._state = False def getColor(self): return self._color def setColor(self, value): self._color = value def resetState(self): self._color = QColor(0, 0, 0, 150) def getIdName(self): return self._request_name def setIdName(self, name): self._request_name = name def resetIdName(self): self._request_name = 'ORIGINOFFSET' launch_id = Property(str, getIdName, setIdName, resetIdName) state = Property(bool, getState, setState, resetState) overlay_color = Property(QColor, getColor, setColor) ################################################################################ # Tool Offset Dialog ################################################################################ class ToolOffsetDialog(QDialog, GeometryMixin): def __init__(self, parent=None): super(ToolOffsetDialog, self).__init__(parent) self._color = QColor(0, 0, 0, 150) self._state = False self._request_name = 'TOOLOFFSET' self.setWindowModality(Qt.ApplicationModal) self.setWindowFlags(self.windowFlags() | Qt.Tool | Qt.Dialog | Qt.WindowStaysOnTopHint | Qt.WindowSystemMenuHint) self.setMinimumSize(200, 200) self._o = TOOLVIEW_WIDGET() self._o.setObjectName('__dialogToolViewWidget') self._o._hal_init() buttonBox = QDialogButtonBox() buttonBox.setEnabled(False) STATUS.connect('not-all-homed', lambda w, axis: buttonBox.setEnabled(False)) STATUS.connect('all-homed', lambda w: buttonBox.setEnabled(True)) STATUS.connect('state-estop', lambda w: buttonBox.setEnabled(False)) STATUS.connect('state-estop-reset', lambda w: buttonBox.setEnabled(STATUS.machine_is_on() and STATUS.is_all_homed())) STATUS.connect('interp-idle', lambda w: buttonBox.setEnabled(STATUS.machine_is_on() and STATUS.is_all_homed())) STATUS.connect('interp-run', lambda w: buttonBox.setEnabled(False)) self.addtool = QPushButton('Add Tool') self.addtool.clicked.connect(lambda: self.addTool()) buttonBox.addButton(self.addtool, QDialogButtonBox.ActionRole) self.deletetool = QPushButton('Delete Tool') self.deletetool.clicked.connect(lambda: self.deleteTool()) buttonBox.addButton(self.deletetool, QDialogButtonBox.ActionRole) #for i in('X', 'Y', 'Z'): # b = 'button_%s' % i # self[b] = QPushButton('Zero %s' % i) # self[b].clicked.connect(self.zeroPress('%s' % i)) # buttonBox.addButton(self[b], 3) v = QVBoxLayout() h = QHBoxLayout() self.setLayout(v) v.addWidget(self._o) b = QPushButton('OK') b.clicked.connect(lambda: self.close()) h.addWidget(b) h.addWidget(buttonBox) v.addLayout(h) self.setModal(True) def _hal_init(self): self.set_default_geometry() STATUS.connect('dialog-request', self._external_request) def _external_request(self, w, message): if message['NAME'] == self._request_name: geo = message.get('GEONAME') or 'ToolOffsetDialog-geometry' self.read_preference_geometry(geo) self.load_dialog() def addTool(self): self._o.add_tool() def deleteTool(self): self._o.delete_tools() # This weird code is just so we can get the axis # letter # using clicked.connect() apparently can't easily # add user data def zeroPress(self, data): def calluser(): self.zeroAxis(data) return calluser def zeroAxis(self, index): ACTION.SET_AXIS_ORIGIN(index, 0) def load_dialog(self): STATUS.emit('focus-overlay-changed', True, 'Set Tool Offsets', self._color) self.set_geometry() self.show() self.exec() STATUS.emit('focus-overlay-changed', False, None, None) self.record_geometry() # usual boiler code # (used so we can use code such as self[SomeDataName] def __getitem__(self, item): return getattr(self, item) def __setitem__(self, item, value): return setattr(self, item, value) # ********************** # Designer properties # ********************** @Slot(bool) def setState(self, value): self._state = value if value: self.show() else: self.hide() def getState(self): return self._state def resetState(self): self._state = False def getColor(self): return self._color def setColor(self, value): self._color = value def resetState(self): self._color = QColor(0, 0, 0, 150) def getIdName(self): return self._request_name def setIdName(self, name): self._request_name = name def resetIdName(self): self._request_name = 'TOOLOFFSET' launch_id = Property(str, getIdName, setIdName, resetIdName) state = Property(bool, getState, setState, resetState) overlay_color = Property(QColor, getColor, setColor) ################################################################################ # Tool Chooser Dialog ################################################################################ class ToolChooserDialog(QDialog, GeometryMixin): def __init__(self, parent=None): super(ToolChooserDialog, self).__init__(parent) self._color = QColor(0, 0, 0, 150) self._state = False self._request_name = 'TOOLCHOOSER' self._message = None self._title = 'Choose Tool' self.setWindowModality(Qt.ApplicationModal) self.setWindowFlags(self.windowFlags() | Qt.Tool | Qt.Dialog | Qt.WindowStaysOnTopHint | Qt.WindowSystemMenuHint) self.setMinimumSize(200, 200) self._o = TOOLCHOOSER_WIDGET() self._o.setObjectName('__dialogToolChooserWidget') self._o._hal_init() buttonBox = QDialogButtonBox() buttonBox.setEnabled(False) STATUS.connect('not-all-homed', lambda w, axis: buttonBox.setEnabled(False)) STATUS.connect('all-homed', lambda w: buttonBox.setEnabled(True)) STATUS.connect('state-estop', lambda w: buttonBox.setEnabled(False)) STATUS.connect('state-estop-reset', lambda w: buttonBox.setEnabled(STATUS.machine_is_on() and STATUS.is_all_homed())) STATUS.connect('interp-idle', lambda w: buttonBox.setEnabled(STATUS.machine_is_on() and STATUS.is_all_homed())) STATUS.connect('interp-run', lambda w: buttonBox.setEnabled(False)) v = QVBoxLayout() v.addWidget(self._o) self.bBox = QDialogButtonBox() self.bBox.addButton('Apply', QDialogButtonBox.AcceptRole) self.bBox.addButton('Cancel', QDialogButtonBox.RejectRole) self.bBox.rejected.connect(self.reject) self.bBox.accepted.connect(self.accept) self._o.doubleClicked.connect(self.accept) v.addWidget(self.bBox) self.setModal(True) self.setLayout(v) def accept(self): self.record_geometry() try: num = int(self._o.getSelectedTool()) LOG.debug('Selected tool when accepted: {}'.format(num)) if self._message is not None: self._message['RETURN'] = num STATUS.emit('general', self._message) self._message = None except Exception as e: print(e) self.hide() def reject(self): self.record_geometry() if self._message is not None: self._message['RETURN'] = None STATUS.emit('general', self._message) self._message = None self.hide() def _hal_init(self): self.set_default_geometry() STATUS.connect('dialog-request', self._external_request) def _external_request(self, w, message): self._message = message if message.get('NAME') == self._request_name: geo = message.get('GEONAME') or 'ToolChooserDialog-geometry' self.read_preference_geometry(geo) self.showdialog(message) def showdialog(self, message, more_info=None, details=None, play_alert=None, frameless = True): self.setWindowModality(Qt.ApplicationModal) if frameless: self.setWindowFlags(self.windowFlags() | Qt.Tool | Qt.FramelessWindowHint | Qt.Dialog | Qt.WindowStaysOnTopHint | Qt.WindowSystemMenuHint) else: self.setWindowFlags(self.windowFlags() | Qt.Tool | Qt.Dialog | Qt.WindowStaysOnTopHint | Qt.WindowSystemMenuHint) self.setWindowFlags(self.windowFlags() | Qt.CustomizeWindowHint) self.setWindowFlag(Qt.WindowCloseButtonHint, False) self.setWindowTitle(self._title) STATUS.emit('focus-overlay-changed', True, 'Tool Chooser', self._color) self.set_geometry() self.show() self.exec() STATUS.emit('focus-overlay-changed', False, None, None) self.record_geometry() def showEvent(self, event): self.set_geometry() super(ToolChooserDialog, self).showEvent(event) def __getitem__(self, item): return getattr(self, item) def __setitem__(self, item, value): return setattr(self, item, value) @Slot(bool) def setState(self, value): self._state = value if value: self.show() else: self.hide() def getState(self): return self._state def resetState(self): self._state = False def getColor(self): return self._color def setColor(self, value): self._color = value def resetColor(self): self._color = QColor(0, 0, 0, 150) def getIdName(self): return self._request_name def setIdName(self, name): self._request_name = name def resetIdName(self): self._request_name = 'TOOLCHOOSER' launch_id = Property(str, getIdName, setIdName, resetIdName) state = Property(bool, getState, setState, resetState) overlay_color = Property(QColor, getColor, setColor) ################################################################################ # CamView Dialog ################################################################################ class CamViewDialog(QDialog, GeometryMixin): def __init__(self, parent=None): super(CamViewDialog, self).__init__(parent) from qtvcp.widgets.camview_widget import CamView self._color = QColor(0, 0, 0, 150) self._state = False self._request_name = 'CAMVIEW' self.setWindowModality(Qt.ApplicationModal) self.setWindowFlags(self.windowFlags() | Qt.Tool | Qt.Dialog | Qt.WindowStaysOnTopHint | Qt.WindowSystemMenuHint) self.setMinimumSize(200, 200) h = QHBoxLayout() h.addStretch(1) self.b = QPushButton('Close') self.b.clicked.connect(lambda: self.close()) h.addWidget(self.b) l = QVBoxLayout() self.camV = CamView() self.setLayout(l) l.addWidget(self.camV) l.addLayout(h) def _hal_init(self): self.camV.hal_init(HAL_NAME='') self.set_default_geometry() STATUS.connect('dialog-request', self._external_request) def _external_request(self, w, message): if message['NAME'] == self._request_name: geo = message.get('GEONAME') or 'CamViewOffsetDialog-geometry' self.read_preference_geometry(geo) nblock = message.get('NONBLOCKING') if nblock: self.setWindowModality(Qt.NonModal) self.setWindowFlags(self.windowFlags() | Qt.Tool | Qt.Dialog | Qt.WindowStaysOnTopHint | Qt.WindowSystemMenuHint) self.load_dialog_nonblocking() else: self.setWindowModality(Qt.ApplicationModal) self.setWindowFlags(self.windowFlags() | Qt.Tool | Qt.FramelessWindowHint | Qt.Dialog | Qt.WindowStaysOnTopHint | Qt.WindowSystemMenuHint) self.load_dialog() def close(self): self.record_geometry() super(CamViewDialog, self).close() def load_dialog_nonblocking(self): self.set_geometry() self.show() def load_dialog(self): STATUS.emit('focus-overlay-changed', True, 'Cam View Dialog', self._color) self.set_geometry() self.show() self.exec() STATUS.emit('focus-overlay-changed', False, None, None) # ********************** # Designer properties # ********************** @Slot(bool) def setState(self, value): self._state = value if value: self.show() else: self.hide() def getState(self): return self._state def resetState(self): self._state = False def getColor(self): return self._color def setColor(self, value): self._color = value def resetState(self): self._color = QColor(0, 0, 0, 150) def getIdName(self): return self._request_name def setIdName(self, name): self._request_name = name def resetIdName(self): self._request_name = 'CAMVIEW' launch_id = Property(str, getIdName, setIdName, resetIdName) state = Property(bool, getState, setState, resetState) overlay_color = Property(QColor, getColor, setColor) ################################################################################ # MacroTab Dialog ################################################################################ class MacroTabDialog(QDialog, GeometryMixin): def __init__(self, parent=None): super(MacroTabDialog, self).__init__(parent) self.setWindowTitle('Qtvcp Macro Menu') self._color = QColor(0, 0, 0, 150) self._state = False self._request_name = 'MACROTAB' self.setWindowModality(Qt.ApplicationModal) self.setWindowFlags(self.windowFlags() | Qt.Tool | Qt.Dialog | Qt.WindowStaysOnTopHint | Qt.WindowSystemMenuHint) self.setMinimumSize(00, 200) self.resize(600, 400) # patch class to call our button methods rather then the # original methods (Gotta do before instantiation) MacroTab.closeChecked = self._close MacroTab.runChecked = self._run MacroTab.setTitle = self._setTitle # ok now instantiate patched class self.tab = MacroTab() self.tab.setObjectName('macroTabInternal_') l = QVBoxLayout() self.setLayout(l) l.addWidget(self.tab) #we need the close button self.tab.closeButton.setVisible(True) def _hal_init(self): self.set_default_geometry() # gotta call this since we instantiated this out of qtvcp's knowledge self.tab._hal_init() STATUS.connect('dialog-request', self._external_request) def _external_request(self, w, message): if message['NAME'] == self._request_name: geo = message.get('GEONAME') or 'MacroTabDialog-geometry' self.read_preference_geometry(geo) self.load_dialog() # This method is called instead of MacroTab's closeChecked method # we do this so we can use it's buttons to hide our dialog # rather then close the MacroTab widget def _close(self): self.close() # This method is called instead of MacroTab's runChecked() method # we do this so we can use it's buttons to hide our dialog # rather then close the MacroTab widget def _run(self): self.tab.runMacro() self.close() def _setTitle(self, string): self.setWindowTitle(string) def load_dialog(self): STATUS.emit('focus-overlay-changed', True, 'Lathe Macro Dialog', self._color) self.tab.stack.setCurrentIndex(0) self.set_geometry() self.show() self.exec() STATUS.emit('focus-overlay-changed', False, None, None) self.record_geometry() # ********************** # Designer properties # ********************** @Slot(bool) def setState(self, value): self._state = value if value: self.show() else: self.hide() def getState(self): return self._state def resetState(self): self._state = False def getColor(self): return self._color def setColor(self, value): self._color = value def resetState(self): self._color = QColor(0, 0, 0, 150) def getIdName(self): return self._request_name def setIdName(self, name): self._request_name = name def resetIdName(self): self._request_name = 'MACROTAB' launch_id = Property(str, getIdName, setIdName, resetIdName) state = Property(bool, getState, setState, resetState) overlay_color = Property(QColor, getColor, setColor) ################################################################################ # Versaprobe Dialog ################################################################################ class VersaProbeDialog(QDialog, GeometryMixin): def __init__(self, parent=None): super(VersaProbeDialog, self).__init__(parent) self._color = QColor(0, 0, 0, 150) self._state = False self._request_name = 'VERSAPROBE' self.setWindowModality(Qt.ApplicationModal) self.setWindowFlags(self.windowFlags() | Qt.Tool | Qt.Dialog | Qt.WindowStaysOnTopHint | Qt.WindowSystemMenuHint) self.setMinimumSize(200, 200) buttonBox = QDialogButtonBox(QDialogButtonBox.Ok) b = buttonBox.button(QDialogButtonBox.Ok) b.clicked.connect(lambda: self.close()) l = QVBoxLayout() self._o = VersaProbe() self.setLayout(l) l.addWidget(self._o) l.addWidget(buttonBox) def _hal_init(self): self._o.hal_init() self.set_default_geometry() STATUS.connect('dialog-request', self._external_request) def _hal_cleanup(self): self._o._hal_cleanup() def _external_request(self, w, message): if message['NAME'] == self._request_name: geo = message.get('GEONAME') or 'VersaProbeDialog-geometry' self.read_preference_geometry(geo) self.load_dialog() def showdialog(self): self.load_dialog() def load_dialog(self): STATUS.emit('focus-overlay-changed', True, 'VersaProbe Dialog', self._color) self.set_geometry() self.show() self.exec() STATUS.emit('focus-overlay-changed', False, None, None) self.record_geometry() # ********************** # Designer properties # ********************** @Slot(bool) def setState(self, value): self._state = value if value: self.show() else: self.hide() def getState(self): return self._state def resetState(self): self._state = False def getColor(self): return self._color def setColor(self, value): self._color = value def resetState(self): self._color = QColor(0, 0, 0, 150) def getIdName(self): return self._request_name def setIdName(self, name): self._request_name = name def resetIdName(self): self._request_name = 'VERSAPROBE' launch_id = Property(str, getIdName, setIdName, resetIdName) state = Property(bool, getState, setState, resetState) overlay_color = Property(QColor, getColor, setColor) ############################################ # Entry Dialog ############################################ class EntryDialog(QDialog, GeometryMixin): def __init__(self, parent=None): super(EntryDialog, self).__init__(parent) self._color = QColor(0, 0, 0, 150) self.play_sound = False self._request_name = 'ENTRY' self._title = 'Numerical Entry' self.setWindowFlags(self.windowFlags() | Qt.Tool | Qt.Dialog | Qt.WindowStaysOnTopHint | Qt.WindowSystemMenuHint) self._softKey = SoftInputWidget(self, 'numeric') self.Num = QLineEdit() self.Num.installEventFilter(self) self.Num.keyboard_type = 'numeric' # actiate touch input self.Num.keyboard_enable = True self.Num.returnPressed.connect(lambda: self.accept()) gl = QVBoxLayout() gl.addWidget(self.Num) self.bBox = QDialogButtonBox() self.bBox.addButton('Apply', QDialogButtonBox.AcceptRole) self.bBox.addButton('Cancel', QDialogButtonBox.RejectRole) self.bBox.rejected.connect(self.reject) self.bBox.accepted.connect(self.accept) gl.addWidget(self.bBox) self.setLayout(gl) def _hal_init(self): self.set_default_geometry() if self.PREFS_: self.play_sound = self.PREFS_.getpref('EntryDialog_play_sound', True, bool, 'DIALOG_OPTIONS') self.sound_type = self.PREFS_.getpref('EntryDialog_sound_type', 'READY', str, 'DIALOG_OPTIONS') else: self.play_sound = False STATUS.connect('dialog-request', self._external_request) def eventFilter(self, widget, event): if self.Num.focusWidget() == widget and event.type() == QEvent.MouseButtonPress: if self.Num.keyboard_enable: self._softKey.show_input_panel(widget) return False # this processes STATUS called dialog requests # We check the cmd to see if it was for us # then we check for a id string # if all good show the dialog # and then send back the dialog response via a general message def _external_request(self, w, message): if message.get('NAME') == self._request_name: geo = message.get('GEONAME') or 'EntryDialog-geometry' self.read_preference_geometry(geo) t = message.get('TITLE') if t: self._title = t else: self._title = 'Numerical Entry' overlay = message.get('OVERLAY') if overlay is None : overlay = True preload = message.get('PRELOAD') num = self.showdialog(preload=preload, overlay=overlay) message['RETURN'] = num STATUS.emit('general', message) def showdialog(self, preload=None,overlay=True): conversion = {'x':0, 'y':1, "z":2, 'a':3, "b":4, "c":5, 'u':6, 'v':7, 'w':8} if overlay: STATUS.emit('focus-overlay-changed', True, '', self._color) self.setWindowTitle(self._title) if self.play_sound: STATUS.emit('play-sound', self.sound_type) self.set_geometry() if preload is not None: self.Num.setText(str(preload)) flag = False while flag == False: self.Num.setFocus() retval = self.exec() if retval: try: answer = float(self.Num.text()) flag = True except Exception as e: try: p,relp,dtg = STATUS.get_position() otext = text = self.Num.text().lower() for let in INFO.AVAILABLE_AXES: let = let.lower() if let in text: Pos = relp[conversion[let]] text = text.replace('%s'%let,'%s'%Pos) process = eval(text) answer = float(process) STATUS.emit('update-machine-log', 'Convert Entry from: {} to {}'.format(otext,text), 'TIME,SUCCESS') flag = True except Exception as e: self.setWindowTitle('%s'%e) else: flag = True answer = None if overlay: STATUS.emit('focus-overlay-changed', False, None, None) self.record_geometry() LOG.debug('Value of pressed button: {}'.format(retval)) if answer is None: return None return answer def getColor(self): return self._color def setColor(self, value): self._color = value def resetState(self): self._color = QColor(0, 0, 0, 150) def getIdName(self): return self._request_name def setIdName(self, name): self._request_name = name def resetIdName(self): self._request_name = 'ENTRY' def set_soft_keyboard(self, data): self.Num.keyboard_enable = data def get_soft_keyboard(self): return self.Num.keyboard_enable def reset_soft_keyboard(self): self.Num.keyboard_enable = True # designer will show these properties in this order: launch_id = Property(str, getIdName, setIdName, resetIdName) overlay_color = Property(QColor, getColor, setColor) soft_keyboard_option = Property(bool, get_soft_keyboard, set_soft_keyboard, reset_soft_keyboard) ############################################ # Keyboard Dialog ############################################ class KeyboardDialog(QDialog, GeometryMixin): def __init__(self, parent=None): super(KeyboardDialog, self).__init__(parent) self._color = QColor(0, 0, 0, 150) self.play_sound = False self._request_name = 'KEYBOARD' self._title = 'Keyboard Entry' self.setWindowFlags(self.windowFlags() | Qt.Tool | Qt.Dialog | Qt.WindowStaysOnTopHint | Qt.WindowSystemMenuHint) self.keybrd = VirtualKeyboard() self.edit = QLineEdit() gl = QVBoxLayout() gl.addWidget(self.edit) gl.addWidget(self.keybrd) self.bBox = QDialogButtonBox() self.bBox.addButton('Apply', QDialogButtonBox.AcceptRole) self.bBox.addButton('Cancel', QDialogButtonBox.RejectRole) self.bBox.rejected.connect(self.reject) self.bBox.accepted.connect(self.accept) gl.addWidget(self.bBox) self.setLayout(gl) def _hal_init(self): self.set_default_geometry() if self.PREFS_: self.play_sound = self.PREFS_.getpref('KeyboardDialog_play_sound', True, bool, 'DIALOG_OPTIONS') self.sound_type = self.PREFS_.getpref('KeyboardDialog_sound_type', 'READY', str, 'DIALOG_OPTIONS') else: self.play_sound = False STATUS.connect('dialog-request', self._external_request) # this processes STATUS called dialog requests # We check the cmd to see if it was for us # then we check for a id string # if all good show the dialog # and then send back the dialog response via a general message def _external_request(self, w, message): if message.get('NAME') == self._request_name: geo = message.get('GEONAME') or 'KeyboardDialog-geometry' self.read_preference_geometry(geo) t = message.get('TITLE') if t: self._title = t else: self._title = 'Keyboard Entry' preload = message.get('PRELOAD') overlay = message.get('OVERLAY') if overlay is None : overlay = True text = self.showdialog(preload) message['RETURN'] = text STATUS.emit('general', message) def showdialog(self, preload=None, overlay=True): if overlay: STATUS.emit('focus-overlay-changed', True, '', self._color) self.setWindowTitle(self._title) if self.play_sound: STATUS.emit('play-sound', self.sound_type) self.set_geometry() self.edit.setFocus() if preload is not None: self.edit.setFocus() self.edit.setText(str(preload)) self.edit.deselect() retval = self.exec() answer = self.edit.text() if retval: STATUS.emit('update-machine-log', 'keyboard Entry {}'.format(answer), 'TIME,DEBUG') else: answer = None if overlay: STATUS.emit('focus-overlay-changed', False, None, None) self.record_geometry() LOG.debug('Value of pressed button: {}'.format(retval)) if answer is None: return None return answer def getColor(self): return self._color def setColor(self, value): self._color = value def resetState(self): self._color = QColor(0, 0, 0, 150) def getIdName(self): return self._request_name def setIdName(self, name): self._request_name = name def resetIdName(self): self._request_name = 'ENTRY' def set_soft_keyboard(self, data): self.Num.keyboard_enable = data def get_soft_keyboard(self): return self.Num.keyboard_enable def reset_soft_keyboard(self): self.Num.keyboard_enable = True # designer will show these properties in this order: launch_id = Property(str, getIdName, setIdName, resetIdName) overlay_color = Property(QColor, getColor, setColor) soft_keyboard_option = Property(bool, get_soft_keyboard, set_soft_keyboard, reset_soft_keyboard) ############################################ # Calculator Dialog ############################################ class CalculatorDialog(Calculator, GeometryMixin): def __init__(self, parent=None): super(CalculatorDialog, self).__init__(parent) self._color = QColor(0, 0, 0, 150) self.play_sound = False self._request_name = 'CALCULATOR' self._title = 'Calculator Entry' self._nblock = False self._message = None self._overlay = None self._flag = False self._result = False self.setWindowFlags(self.windowFlags() | Qt.Tool | Qt.Dialog | Qt.WindowStaysOnTopHint | Qt.WindowSystemMenuHint) def _hal_init(self): self.set_default_geometry() if self.PREFS_: self.play_sound = self.PREFS_.getpref('CalculatorDialog_play_sound', True, bool, 'DIALOG_OPTIONS') self.sound_type = self.PREFS_.getpref('CalculatorDialog_sound_type', 'READY', str, 'DIALOG_OPTIONS') else: self.play_sound = False STATUS.connect('dialog-request', self._external_request) STATUS.connect('dialog-update', self._external_update) # this processes STATUS called dialog requests # We check the cmd to see if it was for us # then we check for a id string # if all good show the dialog # and then send back the dialog response via a general message def _external_request(self, w, message): self._message = message if message.get('NAME') == self._request_name: geo = message.get('GEONAME') or 'CalculatorDialog-geometry' self.read_preference_geometry(geo) t = message.get('TITLE') if t: self._title = t else: self._title = 'Calculator Entry' preload = message.get('PRELOAD') axis = message.get('AXIS') next = message.get('NEXT', False) cycle = message.get('WIDGETCYCLE', False) if axis in ('X','Y','Z','A','B','C','U','V','W'): self.axisTriggered(axis) self._nblock = message.get('NONBLOCKING') overlay = message.get('OVERLAY') if overlay is None: overlay = True self._overlay = overlay if next: self.updatedialog(preload=preload) message['NEXT'] = False else: num = self.showdialog(preload=preload, overlay=overlay, cycle=cycle) # callback from status 'update-dialog' def _external_update(self, w, message): if message.get('NAME') == self._request_name: if not self.isVisible(): return response = message.get('response') if not response is None: if response == 0: self.reject() elif response == 1: self.accept() def updatedialog(self, preload=None): self.setWindowTitle(self._title) if self.play_sound: STATUS.emit('play-sound', self.sound_type) if self._overlay: STATUS.emit('focus-overlay-changed', True, '', self._color) if preload is not None: self.display.setText(str(preload)) def getValue(self, title=''): self._title = title self._nblock = True geo = 'CalculatorDialog-geometry' self.read_preference_geometry(geo) return self.showdialog(preload=None, overlay=False, cycle=False, wait=True) def showdialog(self, preload=None, overlay=True, cycle=False, wait=False): self.setWindowTitle(self._title) if self.play_sound: STATUS.emit('play-sound', self.sound_type) self.set_geometry() if preload is not None: self.display.setText(str(preload)) # show/hide the widget cycling buttons self.applyNextButton.setVisible(cycle) self.nextButton.setVisible(cycle) self.backButton.setVisible(cycle) if self._nblock: self.show() if wait: self._flag = True while self._flag: QApplication.processEvents() return (self.display.text(), self._result) else: if overlay: STATUS.emit('focus-overlay-changed', True, '', self._color) retval = self.exec() if overlay: STATUS.emit('focus-overlay-changed', False, None, None) def accept(self): self.record_geometry() super(CalculatorDialog, self).accept() self._result = True self._flag = False try: num = float(self.display.text()) LOG.debug('Displayed value when accepted: {}'.format(num)) if self._message is not None: self._message['RETURN'] = num self._message['NEXT'] = False STATUS.emit('general', self._message) self._message = None except Exception as e: print(e) def reject(self): self.record_geometry() super(CalculatorDialog, self).reject() self._result = False self._flag = False if self._message is not None: self._message['RETURN'] = None self._message['NEXT'] = False STATUS.emit('general', self._message) self._message = None # used for cycling between different widgets. # the actual cycling is done in the calling code def backAction(self): try: if self._message is not None: self._message['RETURN'] = None self._message['NEXT'] = False self._message['BACK'] = True STATUS.emit('general', self._message) except Exception as e: print(e) # used for cycling between different widgets. # the actual cycling is done in the calling code def nextAction(self): try: if self._message is not None: self._message['RETURN'] = None self._message['NEXT'] = True self._message['BACK'] = False STATUS.emit('general', self._message) except Exception as e: print(e) # used to apply and then cycle to the next widget. # the actual cycling is done in the calling code def applyAction(self): self._flag = False try: num = float(self.display.text()) if self._message is not None: self._message['RETURN'] = num self._message['NEXT'] = True self._message['BACK'] = False STATUS.emit('general', self._message) except Exception as e: print(e) def getColor(self): return self._color def setColor(self, value): self._color = value def resetState(self): self._color = QColor(0, 0, 0, 150) def getIdName(self): return self._request_name def setIdName(self, name): self._request_name = name def resetIdName(self): self._request_name = 'CALCULATOR' launch_id = Property(str, getIdName, setIdName, resetIdName) overlay_color = Property(QColor, getColor, setColor) ############################################ # machine Log Dialog ############################################ class MachineLogDialog(QDialog, GeometryMixin): def __init__(self, parent=None): super(MachineLogDialog, self).__init__(parent) self._color = QColor(0, 0, 0, 150) self.play_sound = False self._request_name = 'MACHINELOG' self._title = 'Machine Log' self.setWindowFlags(self.windowFlags() | Qt.Tool | Qt.Dialog | Qt.WindowStaysOnTopHint | Qt.WindowSystemMenuHint) def _hal_init(self): self.buildWidget() self.set_default_geometry() if self.PREFS_: self.play_sound = self.PREFS_.getpref('MachineLogDialog_play_sound', True, bool, 'DIALOG_OPTIONS') self.sound_type = self.PREFS_.getpref('MachineLogDialog_sound_type', 'READY', str, 'DIALOG_OPTIONS') else: self.play_sound = False STATUS.connect('dialog-request', self._external_request) def buildWidget(self): # add a vertical layout to dialog l = QVBoxLayout() self.setLayout(l) # build tab widget tabs = QTabWidget() # build tabs tab_mlog = MachineLog() tab_mlog._hal_init() tab_ilog = MachineLog() tab_ilog.set_integrator_log(True) tab_ilog._hal_init() # add tabs to tab widget tabs.addTab(tab_mlog,'Machine Log') tabs.addTab(tab_ilog,'Integrator Log') # add tab to layout l.addWidget(tabs) # build dialog buttons self.bBox = QDialogButtonBox() self.bBox.addButton('Ok', QDialogButtonBox.AcceptRole) self.bBox.accepted.connect(self.accept) # add buttons to layout l.addWidget(self.bBox) # this processes STATUS called dialog requests # We check the cmd to see if it was for us # then we check for a id string # if all good show the dialog # and then send back the dialog response via a general message def _external_request(self, w, message): if message.get('NAME') == self._request_name: geo = message.get('GEONAME') or 'MachineLogDialog-geometry' self.read_preference_geometry(geo) t = message.get('TITLE') if t: self._title = t else: self._title = 'Machine Log' nonblock = message.get('NONBLOCKING') num = self.showdialog(nonblock) message['RETURN'] = num STATUS.emit('general', message) def showdialog(self, nonblock=None): if nonblock is not None: STATUS.emit('focus-overlay-changed', True, 'Machine Log', self._color) self.setWindowTitle(self._title) if self.play_sound: STATUS.emit('play-sound', self.sound_type) self.set_geometry() if nonblock is not None: self.exec() STATUS.emit('focus-overlay-changed', False, None, None) self.record_geometry() return False else: self.show() def accept(self): self.record_geometry() super(MachineLogDialog, self).accept() def reject(self): self.record_geometry() super(MachineLogDialog, self).reject() def getColor(self): return self._color def setColor(self, value): self._color = value def resetState(self): self._color = QColor(0, 0, 0, 150) def getIdName(self): return self._request_name def setIdName(self, name): self._request_name = name def resetIdName(self): self._request_name = 'MACHINELOG' # designer will show these properties in this order: launch_id = Property(str, getIdName, setIdName, resetIdName) overlay_color = Property(QColor, getColor, setColor) ############################################ # Run from line prestart Dialog ############################################ class RunFromLineDialog(QDialog, GeometryMixin): def __init__(self, parent=None): super(RunFromLineDialog, self).__init__(parent) # Load the widgets UI file: self.filename = os.path.join(PATH.SHAREDIR,'widgets_ui', 'runFromLine_dialog.ui') try: self.instance = uic.loadUi(self.filename, self) except AttributeError as e: LOG.critical(e) self.start_line = None self._color = QColor(0, 0, 0, 150) self.play_sound = False self._request_name = 'RUNFROMLINE' self._title = 'Run from line preset Dialog' self.setWindowFlags(self.windowFlags() | Qt.Tool | Qt.Dialog | Qt.WindowStaysOnTopHint | Qt.WindowSystemMenuHint) self.buttonBox.clicked.connect(self.Clicked) def _hal_init(self): self.set_default_geometry() if self.PREFS_: self.play_sound = self.PREFS_.getpref('RunFromLineDialog_play_sound', True, bool, 'DIALOG_OPTIONS') self.sound_type = self.PREFS_.getpref('RunFromLineDialog_sound_type', 'READY', str, 'DIALOG_OPTIONS') else: self.play_sound = False STATUS.connect('dialog-request', self._external_request) def homed_on_test(): return (STATUS.machine_is_on() and (STATUS.is_all_homed() or INFO.NO_HOME_REQUIRED)) STATUS.connect('state-off', lambda w: self.setEnabled(False)) STATUS.connect('state-estop', lambda w: self.setEnabled(False)) STATUS.connect('interp-idle', lambda w: self.setEnabled(homed_on_test())) STATUS.connect('all-homed', lambda w: self.setEnabled(homed_on_test())) # this processes STATUS called dialog requests # We check the cmd to see if it was for us # then we check for a id string # if all good show the dialog # and then send back the dialog response via a general message def _external_request(self, w, message): if message.get('NAME') == self._request_name: geo = message.get('GEONAME') or 'RunFromLineDialog-geometry' self.read_preference_geometry(geo) l = message.get('LINE') t = message.get('TITLE') if t: self._title = t else: self._title = 'Run From Line: {}'.format(l) nblock = message.get('NONBLOCKING') mess = message.get('MESSAGE') num = self.showdialog(line = l, message=mess, nonblock = nblock) message['RETURN'] = num STATUS.emit('general', message) def showdialog(self, line = 1, message=None, nonblock=None): self.start_line = int(line) if message is not None: self.label_line.setText(message) if not nonblock: STATUS.emit('focus-overlay-changed', True, 'Machine Log', self._color) self.setWindowTitle(self._title) if self.play_sound: STATUS.emit('play-sound', self.sound_type) self.set_geometry() if not nonblock: self.exec() STATUS.emit('focus-overlay-changed', False, None, None) self.record_geometry() return False else: self.show() # accept button applies presets and if line number given starts linuxcnc def accept(self): self.preset() if self.start_line: ACTION.RUN(self.start_line) super(RunFromLineDialog, self).accept() #apply button only applies presets def Clicked(self, button): if self.buttonBox.buttonRole(button) == QDialogButtonBox.ApplyRole: self.preset() # preset spindle before running def preset(self): if self.radioButton_cw.isChecked(): direction = 'M3' else: direction = 'M4' speed = self.spinBox_rpm.value() ACTION.CALL_MDI_WAIT('s{} {}'.format(speed,direction), mode_return=True) ################################################################################ # About Dialog ################################################################################ class AboutDialog(QDialog, GeometryMixin): def __init__(self, parent=None): super(AboutDialog, self).__init__(parent) self._geometry_string = 'half' self._color = QColor(0, 0, 0, 150) self._request_name = 'ABOUT' self._title = 'QtVCP About' self.play_sound = False self.text = QTextEdit('This is an ABOUT dialog') self.text.setReadOnly(True) self.setWindowFlags(self.windowFlags() | Qt.Tool | Qt.Dialog | Qt.WindowStaysOnTopHint | Qt.WindowSystemMenuHint) def _hal_init(self): self.buildWidget() self.set_default_geometry() self.read_preference_geometry('AboutDialog-geometry') STATUS.connect('dialog-request', self._external_request) def buildWidget(self): # add a vertical layout to dialog l = QVBoxLayout() self.setLayout(l) l.addWidget(self.text) # build dialog buttons self.bBox = QDialogButtonBox() self.bBox.addButton('Ok', QDialogButtonBox.AcceptRole) self.bBox.accepted.connect(self.accept) # add buttons to layout l.addWidget(self.bBox) def setText(self, txt): self.text.setText(txt) self.adjustSize() # this processes STATUS called dialog requests # We check the cmd to see if it was for us # then we check for a id string # if all good show the dialog # and then send back the dialog response via a general message def _external_request(self, w, message): if message.get('NAME') == self._request_name: geo = message.get('GEONAME') or 'AboutDialog-geometry' self.read_preference_geometry(geo) t = message.get('TITLE') if t: self._title = t else: self._title = 'About' nonblock = message.get('NONBLOCKING') num = self.showdialog(nonblock) message['RETURN'] = num STATUS.emit('general', message) def showdialog(self, nonblock=None): if nonblock is not None: STATUS.emit('focus-overlay-changed', True, 'Machine Log', self._color) self.setWindowTitle(self._title) if self.play_sound: STATUS.emit('play-sound', self.sound_type) self.set_geometry() if nonblock is not None: self.exec() STATUS.emit('focus-overlay-changed', False, None, None) self.record_geometry() return False else: self.show() def accept(self): self.record_geometry() super(AboutDialog, self).accept() def getColor(self): return self._color def setColor(self, value): self._color = value def resetState(self): self._color = QColor(0, 0, 0, 150) def getIdName(self): return self._request_name def setIdName(self, name): self._request_name = name def resetIdName(self): self._request_name = 'ABOUT' # designer will show these properties in this order: launch_id = Property(str, getIdName, setIdName, resetIdName) overlay_color = Property(QColor, getColor, setColor) ################################ # for testing without editor: ################################ def main(): import sys from qtpy.QtWidgets import QApplication app = QApplication(sys.argv) #widget = AboutDialog() #widget.setText('This is new text<\b>') #widget = KeyboardDialog() #widget = CalculatorDialog() #widget = RunFromLineDialog() #widget = MachineLogDialog() #widget = EntryDialog() #widget = CamViewDialog() #widget = VersaProbeDialog() #widget = MacroTabDialog() #widget = CamViewDialog() #widget = ToolOffsetDialog() #widget = OriginOffsetDialog() #widget = FileDialog() #widget = ToolDialog() widget = ToolChooserDialog( ) widget.HAL_NAME_ = 'test' widget.PREFS_ = None widget._hal_init() t = widget.showdialog(1) print (t) sys.exit() if __name__ == '__main__': main()