436 lines
15 KiB
Python
436 lines
15 KiB
Python
#!/usr/bin/env python3
|
|
#
|
|
# Qtvcp widget
|
|
# Copyright (c) 2017 Chris Morley
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 2 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
###############################################################################
|
|
|
|
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('<html><head/><body><p><span style=" font-size:30pt; \
|
|
font-weight:600;">%s</span></p></body></html>' % 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('<html><head/><body><p><span style=" font-size:30pt; \
|
|
font-weight:600;">%s</span></p></body></html>' % 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()
|