Import LinuxCNC source tree
This commit is contained in:
435
lib/python/qtvcp/widgets/overlay_widget.py
Normal file
435
lib/python/qtvcp/widgets/overlay_widget.py
Normal file
@@ -0,0 +1,435 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user