#!/usr/bin/env python3 # # QTVcp Widget # Copyright (c) 2018 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. ############################################################################### from qtpy.QtWidgets import QWidget, QProgressBar, QToolButton, QHBoxLayout, QMenu, QAction from qtpy.QtCore import Qt, Property, QBasicTimer, Signal from qtpy.QtGui import QPainter, QFont, QIcon from qtvcp.widgets.widget_baseclass import _HalWidgetBase from qtvcp.core import Status, Action, Info from qtvcp.lib.aux_program_loader import Aux_program_loader from qtvcp import logger # Instiniate the libraries with global reference # STATUS gives us status messages from LinuxCNC # AUX_PRGM holds helper program loader # INI holds INI details # ACTION gives commands to LinuxCNC # LOG is for running code logging STATUS = Status() AUX_PRGM = Aux_program_loader() INFO = Info() ACTION = Action() LOG = logger.getLogger(__name__) # Force the log level for this module #LOG.setLevel(logger.DEBUG) # One of DEBUG, INFO, WARNING, ERROR, CRITICAL # So we can customize the label # meaning we can use a new line # to have two levels of text class LabeledBar(QProgressBar): def __init__(self, parent=None): super(LabeledBar, self).__init__(parent) self.text = '' self.setTextVisible(False) self.font = QFont() self.font.setPointSize(8) self.font.setBold(True) self.font.setWeight(75) def paintEvent(self, event): super(LabeledBar, self).paintEvent(event) qp = QPainter() qp.begin(self) self.drawText(qp) qp.end() def drawText(self, qp): qp.setOpacity(1.0) qp.setFont(self.font) qp.drawText(self.rect(), Qt.AlignCenter, self.text) class HAdjustmentBar(QWidget): valueChanged = Signal(int) def __init__(self, parent=None): super(HAdjustmentBar, self).__init__(parent) self.value = 50 self.step = 1 self.hi_value = 100 self.low_value = 50 self.timer_value = 100 self.texttemplate = 'Value = %s' self.timer = QBasicTimer() self.showToggleButton = True self.showSettingMenu = True self.bar = LabeledBar() #self.bar.setSizePolicy(QSizePolicy.Expanding,QSizePolicy.Expanding) # black magic class patching # so calling these functions actually calls the self.bar's functions. self.minimum = self.bar.minimum self.maximum = self.bar.maximum def buildWidget(self): layout = QHBoxLayout(self) layout.setContentsMargins(0,0,0,0) SettingMenu = QMenu() exitButton = QAction(QIcon('exit24.png'), 'Set As High', self) exitButton.triggered.connect(self.setHigh) SettingMenu.addAction(exitButton) setlowButton = QAction(QIcon('exit24.png'), 'Set As Low', self) setlowButton.triggered.connect(self.setLow) SettingMenu.addAction(setlowButton) self.tb_down = QToolButton() self.tb_down.pressed.connect(self.on_click_down) self.tb_down.released.connect(self.on_released) self.tb_down.setArrowType(Qt.LeftArrow) self.tb_up = QToolButton() self.tb_up.pressed.connect(self.on_click_up) self.tb_up.released.connect(self.on_released) self.tb_up.setArrowType(Qt.RightArrow) if self.showToggleButton: tb_set = QToolButton() tb_set.clicked.connect(self.on_click_set_value) tb_set.setText('<>') if self.showSettingMenu: tb_set.setMenu(SettingMenu) tb_set.setPopupMode(QToolButton.DelayedPopup) layout.addWidget(self.tb_down) layout.addWidget(self.bar) layout.addWidget(self.tb_up) if self.showToggleButton: layout.addWidget(tb_set) layout.setSpacing(0) def on_click_up(self): self.timer.start(self.timer_value, self) self.value += self.step self._setValue() def on_click_down(self): self.timer.start(self.timer_value, self) self.value -= self.step self._setValue() def on_released(self): self.timer.stop() def on_click_set_value(self): if self.value == self.hi_value: self.value = self.low_value else: self.value = self.hi_value self._setValue() def timerEvent(self, e): if self.value < self.maximum() and self.value > self.minimum(): if self.tb_down.isDown(): self.value -= self.step else: self.value += self.step self._setValue() else: self.timer.stop() def _setValue(self): if self.value < self.minimum():self. value = self.minimum() if self.value > self.maximum(): self.value = self.maximum() self.valueChanged.emit(self.value) def setValue(self, value): if value < self.minimum(): value = self.minimum() if value > self.maximum(): value = self.maximum() self.value = int(value) tmpl = lambda s: self.texttemplate % s try: self.bar.text = tmpl(int(value)) except: self.bar.text = self.texttemplate self.bar.setValue(int(value)) self.bar.update() def setHigh(self): self.hi_value = self.value def setLow(self): self.low_value = self.value def setMaximum(self, data): if data < self.hi_value: self.hi_value = data self.bar.setMaximum(data) def setMinimum(self, data): if data > self.low_value: self.low_value = data self.bar.setMinimum(data) def setStep(self, data): self.step = data class StatusAdjustmentBar(HAdjustmentBar, _HalWidgetBase): def __init__(self, parent=None): super(StatusAdjustmentBar, self).__init__(parent) self._block_signal = False self.rapid = True self.feed = False self.spindle = False self.jograte = False self.jograte_angular = False self.maxv = False self.texttemplate = 'Value = %s' # self.PREFS_ # self.HAL_NAME_ # comes from base class def _hal_init(self): # if we build the widget before now, designer options are not # taken account self.buildWidget() # when estopped disable buttons STATUS.connect('state-estop', lambda w: self.setEnabled(False)) STATUS.connect('state-estop-reset', lambda w: self.setEnabled(True)) # set options if self.rapid: STATUS.connect('rapid-override-changed', lambda w, data: self.setValue(data)) self.setMaximum(100) elif self.feed: STATUS.connect('feed-override-changed', lambda w, data: self.setValue(data)) self.setMaximum(int(INFO.MAX_FEED_OVERRIDE)) elif self.spindle: STATUS.connect('spindle-override-changed', lambda w, data: self.setValue(data)) self.setMaximum(int(INFO.MAX_SPINDLE_OVERRIDE)) self.setMinimum(int(INFO.MIN_SPINDLE_OVERRIDE)) elif self.jograte: STATUS.connect('jograte-changed', lambda w, data: self.setValue(data)) self.setMaximum(int(INFO.MAX_LINEAR_JOG_VEL)) elif self.jograte_angular: STATUS.connect('jograte-angular-changed', lambda w, data: self.setValue(data)) print(int(INFO.MAX_ANGULAR_JOG_VEL)) self.setMaximum(int(INFO.MAX_ANGULAR_JOG_VEL)) elif self.maxv: STATUS.connect('max-velocity-override-changed', lambda w, data: self.setValue(data)) self.setMaximum(int(INFO.MAX_TRAJ_VELOCITY)) else: LOG.error('{} : no option recognised'.format(self.HAL_NAME_)) # If there is a preference file object use it to load the hi/low toggle points if self.PREFS_: self.hi_value = self.PREFS_.getpref(self.HAL_NAME_+'-hi-value', 75, int, 'SCREEN_CONTROL_LAST_SETTING') self.low_value = self.PREFS_.getpref(self.HAL_NAME_+'-low-value', 25, int, 'SCREEN_CONTROL_LAST_SETTING') # connect a signal and callback function to the button self.valueChanged.connect(self._action) # when qtvcp closes this gets called def _hal_cleanup(self): if self.PREFS_: LOG.debug('Saving {} data to file.'.format(self.HAL_NAME_)) self.PREFS_.putpref(self.HAL_NAME_+'-hi-value', self.hi_value, int, 'SCREEN_CONTROL_LAST_SETTING') self.PREFS_.putpref(self.HAL_NAME_+'-low-value', self.low_value, int, 'SCREEN_CONTROL_LAST_SETTING') def _action(self, value): if self.rapid: ACTION.SET_RAPID_RATE(value) elif self.feed: ACTION.SET_FEED_RATE(value) elif self.spindle: ACTION.SET_SPINDLE_RATE(value) elif self.jograte: ACTION.SET_JOG_RATE(value) elif self.jograte_angular: ACTION.SET_JOG_RATE_ANGULAR(value) elif self.maxv: ACTION.SET_MAX_VELOCITY_RATE(value) else: LOG.error('{} no action recognised'.format(self.HAL_NAME_)) ######################################################################### # This is how designer can interact with our widget properties. # designer will show the Property properties in the editor # it will use the get set and reset calls to do those actions # # _toggle_properties makes it so we can only select one option ######################################################################## def _toggle_properties(self, picked): data = ('rapid', 'feed', 'spindle', 'jograte', 'jograte_angular', 'maxv') for i in data: if not i == picked: self[i+'_rate'] = False def setrapid(self, data): self.rapid = data if data: self._toggle_properties('rapid') def getrapid(self): return self.rapid def resetrapid(self): self.rapid = False def setfeed(self, data): self.feed = data if data: self._toggle_properties('feed') def getfeed(self): return self.feed def resetfeed(self): self.feed = False def setspindle(self, data): self.spindle = data if data: self._toggle_properties('spindle') def getspindle(self): return self.spindle def resetspindle(self): self.spindle = False def setjograte(self, data): self.jograte = data if data: self._toggle_properties('jograte') def getjograte(self): return self.jograte def resetjograte(self): self.jograte = False def setjograteangular(self, data): self.jograte_angular = data if data: self._toggle_properties('jograte_angular') def getjograteangular(self): return self.jograte_angular def resetjograteangular(self): self.jograte_angular = False def setmaxv(self, data): self.maxv = data if data: self._toggle_properties('maxv') def getmaxv(self): return self.maxv def resetmaxv(self): self.maxv = False def setshowtoggle(self, data): self.showToggleButton = data def getshowtoggle(self): return self.showToggleButton def resetshowtoggle(self): self.showToggleButton = True def setsettingmenu(self, data): self.showSettingMenu = data def getsettingmenu(self): return self.showSettingMenu def resetsettingmenu(self): self.showSettingMenu = True # Designer makes '\n' into '\\n' if added # we put fix it here so we actually get the newline def settexttemplate(self, data): data = data.replace('\\n' , '\n') self.texttemplate = data def gettexttemplate(self): return self.texttemplate def resettexttemplate(self): self.texttemplate = 'Value = %s' def setsteprate(self, data): self.step = data def getsteprate(self): return self.step def resetsteprate(self): self.steprate = 1 rapid_rate = Property(bool, getrapid, setrapid, resetrapid) feed_rate = Property(bool, getfeed, setfeed, resetfeed) spindle_rate = Property(bool, getspindle, setspindle, resetspindle) jograte_rate = Property(bool, getjograte, setjograte, resetjograte) jograte_angular_rate = Property(bool, getjograteangular, setjograteangular, resetjograteangular) max_velocity_rate = Property(bool, getmaxv, setmaxv, resetmaxv) show_toggle_button = Property(bool, getshowtoggle, setshowtoggle, resetshowtoggle) show_setting_menu = Property(bool, getsettingmenu, setsettingmenu, resetsettingmenu) text_template = Property(str, gettexttemplate, settexttemplate, resettexttemplate) step_rate = Property(int, getsteprate, setsteprate, resetsteprate) ############################## # required class boiler code # ############################## def __getitem__(self, item): return getattr(self, item) def __setitem__(self, item, value): return setattr(self, item, value) # for testing without editor: def main(): import sys from qtpy.QtWidgets import QApplication app = QApplication(sys.argv) widget = StatusAdjustmentBar() widget.PREFS_=None widget.buildWidget() widget.settexttemplate('Text %s') widget.setValue(75) widget.show() sys.exit(app.exec()) if __name__ == "__main__": main()