#!/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 from qtpy.QtWidgets import (QWidget, QLabel, QHBoxLayout, QVBoxLayout, QPushButton, QDialog, QProgressBar) from qtpy.QtCore import (Qt, QEvent, Slot, Property, QChildEvent, ) from qtpy.QtGui import QColor, QImage, QResizeEvent, QPainter, QMoveEvent from qtvcp.widgets.widget_baseclass import _HalWidgetBase from qtvcp.core import Status from qtvcp import logger # Instantiate the libraries with global reference # STATUS gives us status messages from linuxcnc # LOG is for running code logging if __name__ != '__main__': STATUS = Status() LOG = logger.getLogger(__name__) # Set the log level for this module #LOG.setLevel(logger.DEBUG) # One of DEBUG, INFO, WARNING, ERROR, CRITICAL ################################################################ # Overlay base class # based on c++ code on net # Originally designed to allow an overlay on individual widgets # which might be nice too but for now just the wholw window ################################################################ class OverlayWidget(QWidget): def __init__(self, parent=None): self.last = None self.top_level = parent super(OverlayWidget, self).__init__(parent) self.parent = parent #self.setAttribute(Qt.WA_NoSystemBackground) #self.setAttribute(Qt.WA_TransparentForMouseEvents) self.setWindowFlags( self.windowFlags() |Qt.Tool | Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint ) if not self.parent is None: self.parent.installEventFilter(self) # There seems to be no way to get the very top level widget # in QT the parent widget can be owned in another parent # this adds an event filter the widget in 'top_level' # so we can track what is happening to it. def newParent(self): self.parent = self.top_level self.last = self.parent if self.last is not None: LOG.debug('last removed: {} NEW:{}'.format(self.last, self.parent)) self.last.removeEventFilter(self) if not self.parent: return self.parent.installEventFilter(self) #self.raise_() # Here we track parent window events that we need to react to. # then we pass then to the original widget def eventFilter(self, obj, event): # might be useful events to know # 103 window blocked by modal dialog # 99 activation changed # 25 deactivated # 24 activated #print event,'parent',self.parent #print event,'Event Type',self.event.type() if obj == self.parent: #Catches resize and child events from the parent widget if event.type() == QEvent.Resize: #print 'resize' self.resize(QResizeEvent.size(event)) elif event.type() == QEvent.Move: self.move(QMoveEvent.pos(event)) elif(event.type() == QEvent.ChildAdded): #print 'CHILD',QChildEvent.child(event) if not QChildEvent.child(event) is QDialog: self.raise_() if event.type() == QEvent.Close: self.hide() self.closeEvent(event) event.accept() return super(OverlayWidget, self).eventFilter(obj, event) # Tracks our instanance of focus widget changes # most importantly the paint event # if the parent changes it adjusts the event filter # but in our case the parent won't because we are # tracking the main window rather the the widget focus is in def event(self, event): #print 'overlay:',event if event.type() == QEvent.ParentAboutToChange: #print 'REMOVE FILTER' self.parent.removeEventFilter(self) return True if event.type() == QEvent.ParentChange: #print 'parentEVENT:', self.parentWidget() self.newParent() return True if event.type() == QEvent.Paint: self.paintEvent(event) return True return False #################################################################### # subclass for using as a fancy dialog focus overlay #################################################################### class FocusOverlay(OverlayWidget, _HalWidgetBase): def __init__(self, parent=None): super(FocusOverlay, self).__init__(parent) self.setAttribute(Qt.WA_TranslucentBackground) self.bg_color = QColor(0, 0, 0, 150) self.text = "Loading..." self._state = False self._image_path = '~/emc-dev/linuxcnc-wizard.gif' self._image = QImage(os.path.expanduser(self._image_path)) self._show_buttons = False self._show_image = False self._image_transp = 0.3 self._show_text = True self.box() # This is how we plant the very top level parent into # our overlay widget # QTVCP_INSTANCE is not accessible from Designer # it should work with any main window but doesn't. # this worked so i stopped looking why # this also sets up following show or hide etc based on # STATUS messages # adjust image path name at runtime def _hal_init(self): STATUS.connect('focus-overlay-changed', lambda w, data, text, color: self._status_response(data, text, color)) STATUS.connect('graphics-loading-progress',lambda w,f: self.updateProgress(f)) if self.PREFS_: self.play_sound = self.PREFS_.getpref('overlay_play_sound', False, bool, 'SCREEN_OPTIONS') self.sound_type = self.PREFS_.getpref('overlay_sound_type', 'RING', str, 'SCREEN_OPTIONS') else: self.play_sound = False # reparent on top window self.top_level = self.QTVCP_INSTANCE_ self.newParent() # adjust size and location to top window self.setGeometry(self.top_level.geometry()) self.hide() # look for special path names and change to real path if 'STD_IMAGE_DIR/' in self._image_path: t = self._image_path.split('STD_IMAGE_DIR/', )[1] d = os.path.join(self.PATHS_.IMAGEDIR, t) elif 'CONFIG_DIR/' in self._image_path: t = self._image_path.split('CONFIG_DIR/', )[1] d = os.path.join(self.PATHS_.CONFIGPATH, t) else: return if os.path.exists(d): self._image = QImage(d) else: LOG.debug('Focus Overlay image path runtime error: {}'.format(self._image_path)) def _status_response(self, data, text, color): if data: if color: self.bg_color = color else: self.bg_color = QColor(0, 0, 0, 150) if text: self.setShowText(True) self.text = text else: self.setShowText(False) self.show() self.update() LOG.debug('Overlay - Show') if self.play_sound: STATUS.emit('play-sound', '%s' % self.sound_type) #os.system("beep -f 555 ") else: self.hide() LOG.debug('Overlay - Hide') # Ok paint everything def paintEvent(self, event): qp = QPainter() qp.begin(self) self.colorBackground(qp) if self._show_image: self.draw_image(qp) self.drawText(qp) qp.end() ################################################# # Helper functions ################################################# def updateProgress(self,fraction): if fraction <0: self.progressbar.setValue(0) self.progressbar.setFormat('') self.progressbar.setVisible(False) self.hide() else: self.progressbar.setVisible(True) self.progressbar.setValue(fraction) self.progressbar.setFormat('Loading: {}%'.format(fraction)) # The basic overlay -transparence is set by bg_color def colorBackground(self, qp): qp.fillRect(self.rect(), self.bg_color) # we can add an image like a watermark transparence set by # _image_transp def draw_image(self, qp): #print self._image, self._image_transp qp.setOpacity(self._image_transp) qp.drawImage(self.rect(), self._image) # This sets the text that was built in the box # old code for text is skipped def drawText(self, qp): qp.setOpacity(1.0) self.mb.setText('

%s

' % self.text) return size = self.size() w = size.width() h = size.height() qp.setPen(self.text_color) qp.setFont(self.font) qp.drawText(self.rect(), Qt.AlignCenter, self.text) # build a black label with text and optionally some buttons # using a label rather then drawing text allows options suck as rich text # and a solid back ground so text is easy to see # buttons could be used as a dialog but would require a focus widget per # dialog action or some fancy coding for responses. def box(self): hbox2 = QHBoxLayout() hbox2.addStretch(1) self.mb = QLabel('

%s

' % self.text, self) self.mb.setStyleSheet("background-color: black; color: white") self.mb.setAlignment(Qt.AlignVCenter | Qt.AlignCenter) self.mb.setVisible(self._show_text) hbox2.addWidget(self.mb) self.progressbar = QProgressBar() self.progressbar.setStyleSheet("background-color: black; color: white") self.progressbar.setRange(0,100) self.progressbar.setVisible(False) hbox2.addWidget(self.progressbar) hbox = QHBoxLayout() hbox.addStretch(1) self.okButton = QPushButton("OK") self.okButton.pressed.connect(self.okChecked) self.cancelButton = QPushButton("Cancel") self.cancelButton.pressed.connect(self.cancelChecked) self.okButton.setVisible(self._show_buttons) self.cancelButton.setVisible(self._show_buttons) l = QLabel() hbox.addWidget(l) hbox.addWidget(self.okButton) hbox.addWidget(self.cancelButton) vbox = QVBoxLayout() vbox.addStretch(1) vbox.addLayout(hbox2) vbox.addLayout(hbox) self.setLayout(vbox) self.setGeometry(300, 300, 300, 150) # would need to class patch for something really useful def okChecked(self): self.hide() def cancelChecked(self): self.hide() ######################################################################### # 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 ######################################################################## @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 getOverayColor(self): return self.bg_color @Slot(QColor) def setOverayColor(self, value): self.bg_color = value def resetOverayColor(self, value): self.bg_color = value def setShowText(self, value): self._show_text = value self.mb.setVisible(value) def getShowText(self): return self._show_text def resetShowText(self, value): self._show_text = True self.mb.setVisible(value) def setshow_image(self, data): self._show_image = data #self.hide() #self.show() def getshow_image(self): return self._show_image def resetshow_image(self): self._show_image = False def set_image_transp(self, data): self._image_transp = data #self.hide() #self.show() def get_image_transp(self): return self._image_transp def reset_image_transp(self): self._image_transp = 0.3 def setshow_buttons(self, data): self._show_buttons = data self.okButton.setVisible(data) self.cancelButton.setVisible(data) def getshow_buttons(self): return self._show_buttons def resetshow_buttons(self): self._show_buttons = False def setimage_path(self, data): self._image_path = str(data) try: path = os.path.expanduser(self._image_path) self._image = QImage(path) except Exception as e: LOG.debug('Focus Overlay image path error: {}'.format(self._image_path)) def getimage_path(self): return self._image_path def resetimage_path(self): self._image_path = False overlay_color = Property(QColor, getOverayColor, setOverayColor, resetOverayColor) state = Property(bool, getState, setState, resetState) show_text = Property(bool, getShowText, setShowText, resetShowText) show_image_option = Property(bool, getshow_image, setshow_image, resetshow_image) image_transparency = Property(float, get_image_transp, set_image_transp, reset_image_transp) show_buttons_option = Property(bool, getshow_buttons, setshow_buttons, resetshow_buttons) image_path = Property(str, getimage_path, setimage_path, resetimage_path) ################# # Testing ################# def main(): import sys from qtpy.QtWidgets import QWidget, QApplication from qtpy.QtCore import QTimer app = QApplication(sys.argv) w = QWidget() #w.setWindowFlags( Qt.FramelessWindowHint | Qt.Dialog | Qt.WindowStaysOnTopHint ) w.setGeometry(300, 300, 250, 150) w.setWindowTitle('Test') l = QLabel('Hello, world!', w) l.show() LOG.debug('Label: {}'.format(l)) # class patching is my new thing # class patch the OK button def newOk(w): print('Ok') w.text = 'OK' # make update w.hide() w.show() def pop(): print('Pop') f.show() f.update() FocusOverlay.okChecked = newOk w.show() # could use f = FocusOverlay( w ) # then dont need to adjust top level f = FocusOverlay() f.top_level = w f.newParent() # set with qtproperty call f.setshow_buttons(True) timer2 = QTimer() timer2.timeout.connect(lambda:pop() ) timer2.start(1500) sys.exit(app.exec()) if __name__ == '__main__': main()