Turning WA_TranslucentBackground off stops window repainting - qt

I have a PyQt4.9 window where I would like to toggle the translucency on or off. The reason being is that it sometimes shows a full size phonon video control which doesn't work when the WA_TranslucentBackground attribute is set. (Due to a Qt bug https://bugreports.qt.io/browse/QTBUG-8119)
The problem I have is, after I turn WA_TranslucentBackground attribute back to false, after it has been true, the Window will no longer redraw, so it remains stuck showing the same thing from that point on. Interestingly, click events still respond.
Some example code follows. Click the increment button, and it will update the button text. Click the toggle button and then click the increment button again, and updates no longer show. Clicking the exit button closes the window, showing the events are still responding.
If anyone has any solutions, workarounds or fixes I'd appreciate them. Thanks.
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class Settings(QWidget):
def __init__(self, desktop):
QWidget.__init__(self)
self.setAttribute(Qt.WA_TranslucentBackground, True)
self.setWindowFlags(Qt.FramelessWindowHint)
self.istransparent = True
self.count = 0
self.setWindowTitle("Transparent")
self.resize(300, 150)
self.incr_button = QPushButton("Increment")
toggle_button = QPushButton("Toggle Transparency")
exit_button = QPushButton("Exit")
grid = QGridLayout()
grid.addWidget(self.incr_button, 0, 0)
grid.addWidget(toggle_button, 1, 0)
grid.addWidget(exit_button, 2, 0)
self.setLayout(grid)
self.connect(toggle_button, SIGNAL("clicked()"), self.toggle)
self.connect(self.incr_button, SIGNAL("clicked()"), self.increment)
self.connect(exit_button, SIGNAL("clicked()"), self.close)
def increment(self):
self.count = self.count + 1
self.incr_button.setText("Increment (%i)" % self.count)
def toggle(self):
self.istransparent = not self.istransparent
self.setAttribute(Qt.WA_TranslucentBackground, self.istransparent)
if __name__ == "__main__":
app = QApplication(sys.argv)
s = Settings(app.desktop())
s.show()
sys.exit(app.exec_())

Try to replace self.setAttribute(Qt.WA_TranslucentBackground, ...) calls in __init__ and toggle with following method.
def set_transparency(self, enabled):
if enabled:
self.setAutoFillBackground(False)
else:
self.setAttribute(Qt.WA_NoSystemBackground, False)
self.setAttribute(Qt.WA_TranslucentBackground, enabled)
self.repaint()
Tested on PyQt-Py2.7-x86-gpl-4.9-1 (Windows 7)

Related

Extending selection in either direction in a QTextEdit

Currently, QTextEdit permits selecting text and then altering that selection with shift-click-drag only on the side of the selection opposite the anchor. The anchor is placed where the selection started. If the user tries to alter the selection near the start, the selection pivots around the anchor point instead of extending. I'd like to permit changing the selection from either side.
My first attempt is to simply set the anchor on the opposite side from where the cursor is located. Say, for example, the selection is from 10 to 20. If the cursor is shift-click-dragged at position 8, then the anchor would be set to 20. If the cursor is shift-click-dragged at position 22, then the anchor would be set to 10. Later, I'll try something more robust, perhaps based on the center point of the selection.
I thought this code would work, but it does not seem to affect the default behavior at all. What have I missed?
import sys
from PySide.QtCore import *
from PySide.QtGui import *
class TextEditor(QTextEdit):
def __init__(self, parent=None):
super().__init__(parent)
self.setReadOnly(True)
self.setMouseTracking(True)
def mouseMoveEvent(self, event):
point = QPoint()
x = event.x() #these are relative to the upper left corner of the text edit window
y = event.y()
point.setX(x)
point.setY(y)
self.mousepos = self.cursorForPosition(point).position() # get character position of current mouse using local window coordinates
if event.buttons()==Qt.LeftButton:
modifiers = QApplication.keyboardModifiers()
if modifiers == Qt.ShiftModifier:
start = -1 #initialize to something impossible
end = -1
cursor = self.textCursor()
select_point1 = cursor.selectionStart()
select_point2 = cursor.selectionEnd()
if select_point1 < select_point2: # determine order of selection points
start = select_point1
end = select_point2
elif select_point2 < select_point1:
start = select_point2
end = select_point1
if self.mousepos > end: # if past end when shift-click then trying to extend right
cursor.setPosition(start, mode=QTextCursor.MoveAnchor)
elif self.mousepos < start: # if before start when shift-click then trying to extend left
cursor.setPosition(end, mode=QTextCursor.MoveAnchor)
if start != -1 and end != -1: #if selection exists then this should trigger
self.setTextCursor(cursor)
super().mouseMoveEvent(event)
Here's a first stab at implementing shift+click extension of the current selection. It seems to work okay, but I have not tested it to death, so there may be one or two glitches. The intended behaviour is that a shift+click above or below the selection should extend the whole selection in that direction; and a shift+click with drag should do the same thing, only continuously.
Note that I have also set the text-interaction flags so that the caret is visible in read-only mode, and the selection can also be manipulated with the keyboard in various ways (e.g. ctrl+shift+right extends the selection to the next word).
import sys
from PySide.QtCore import *
from PySide.QtGui import *
class TextEditor(QTextEdit):
def __init__(self, parent=None):
super().__init__(parent)
self.setReadOnly(True)
self.setTextInteractionFlags(
Qt.TextSelectableByMouse |
Qt.TextSelectableByKeyboard)
def mouseMoveEvent(self, event):
if not self.setShiftSelection(event, True):
super().mouseMoveEvent(event)
def mousePressEvent(self, event):
if not self.setShiftSelection(event):
super().mousePressEvent(event)
def setShiftSelection(self, event, moving=False):
if (event.buttons() == Qt.LeftButton and
QApplication.keyboardModifiers() == Qt.ShiftModifier):
cursor = self.textCursor()
start = cursor.selectionStart()
end = cursor.selectionEnd()
if not moving or start != end:
anchor = cursor.anchor()
pos = self.cursorForPosition(event.pos()).position()
if pos <= start:
start = pos
elif pos >= end:
end = pos
elif anchor == start:
end = pos
else:
start = pos
if pos <= anchor:
start, end = end, start
cursor.setPosition(start, QTextCursor.MoveAnchor)
cursor.setPosition(end, QTextCursor.KeepAnchor)
self.setTextCursor(cursor)
return True
return False
if __name__ == '__main__':
app = QApplication(sys.argv)
window = TextEditor()
window.setText(open(__file__).read())
window.setGeometry(600, 50, 800, 800)
window.show()
sys.exit(app.exec_())

Show button in Tkinter when mouse is over window

I am having problems with showing a button when mouse is over a window. When I go over the window, the button shows up. But when I go over the button, it hides again. However, when I tried to recreate the problem with a simple program, it works fine... But something else bugs me in the short version.
import Tkinter as TK
root = TK.Tk()
root.geometry("400x300")
root.overrideredirect(True)
button = TK.Button(root, text = "HI", command = lambda: root.destroy())
def Show(event):
button.place(x = 0, y = 0, width = 60, height = 30)
def Hide(event):
button.place_forget()
root.bind("<Enter>", Show)
root.bind("<Leave>", Hide)
root.mainloop()
This short version works. But when you go over the button and then away from it, it hides. Even though you are still above the root window. Is there any easy way of forcing the button to be visible all the time the mouse si over the root? Thanks
You Enter and Leave events fires twice (over root window and over button), because when you Enter button widget - you Leave root, so to solve problem you can check widget.master and act if there's no master.
try:
import tkinter as TK
except ImportError:
import Tkinter as TK
root = TK.Tk()
root.geometry("400x300")
root.overrideredirect(True)
button = TK.Button(root, text="HI", command=root.destroy)
def Show(event):
print('Show event triggered by %s' % event.widget.__class__)
print('Master container is %s' % event.widget.master)
if event.widget.master is None:
button.place(x=0, y=0, width=60, height=30)
def Hide(event):
print('Hide event triggered by %s' % event.widget.__class__)
print('Master container is %s' % event.widget.master)
if event.widget.master is None:
button.place_forget()
root.bind("<Enter>", Show)
root.bind("<Leave>", Hide)
root.mainloop()
You can check whether your mouse event was outside or inside the root frame and act accordingly
def Hide(event):
x, y = event.x, event.y
x_r, y_r = root.winfo_x(), root.winfo_y()
if x > x_r + root.winfo_width() or x < x_r or y > y_r + root.winfo_height() or y < y_r:
button.place_forget()

Ending a QDrag Prematurely

I want my application to terminate all drag and drops in a dragLeaveEvent, without the user releasing the mouse button.
The problem is that the loop suspends all events that could cancel a QDrag while it is happening, even though the documentation states:
"On Linux and Mac OS X, the drag and drop operation can take some
time, but this function does not block the event loop. Other events
are still delivered to the application while the operation is
performed. On Windows, the Qt event loop is blocked during the
operation. However, QDrag.exec() on Windows causes processEvents() to
be called frequently to keep the GUI responsive. If any loops or
operations are called while a drag operation is active, it will block
the drag operation."
Because of this, I cannot call events which would end the drag.
So far, I've tried what is suggested here, as seen in the code. I'm using PyQt5, but if a solution works in Qt it should work in PyQt.
Edit: I'm a little scared to delete the drag, as the scene does not own it. I suppose I could set it up to own it though, but as was posted here it should not work.
Edit2: Added code with my non-working attempts to fix it. I'd really like to solve this issue without having to make my own drag-drop framework. Also trimmed post.
import sys
from PyQt5.QtWidgets import (QMainWindow, QApplication,
QGraphicsView, QGraphicsScene, QGraphicsWidget, QGraphicsRectItem)
from PyQt5.QtCore import (QMimeData, Qt, QByteArray, QCoreApplication,
QEvent, QPoint)
from PyQt5.QtGui import QBrush, QColor, QDrag, QPen, QMouseEvent
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.scene = CustomScene()
self.view = QGraphicsView(self.scene, self)
self.setGeometry(100, 100, 600, 600)
self.view.setGeometry(0, 0, 500, 500)
self.show()
class CustomScene(QGraphicsScene):
def __init__(self):
super().__init__()
self.customWidgets = []
for i in range(5):
newItem = CustomDragWidget()
self.addItem(newItem)
self.customWidgets.append(newItem)
newItem.setGeometry(i * 50, i * 50, 50, 50)
def dragLeaveEvent(self, event):
# Work your magic here. I've tried the following:
# 1)
self.customWidgets[0].dropEvent(event)
# 2)
self.dropEvent(event)
# 3)
eve = QMouseEvent(QEvent.MouseButtonRelease, QPoint(0, 0), Qt.LeftButton, Qt.LeftButton, Qt.NoModifier)
QCoreApplication.sendEvent(self.views()[0], eve)
QCoreApplication.processEvents()
# 4)
eve = QMouseEvent(QEvent.MouseButtonRelease, QPoint(0, 0), Qt.LeftButton, Qt.LeftButton, Qt.NoModifier)
QCoreApplication.sendEvent(self.customWidgets[0], eve)
QCoreApplication.processEvents()
def dropEvent(self, QGraphicsSceneDragDropEvent):
# a dummy dropevent that tries to stop the drop, but doesnt work
QGraphicsSceneDragDropEvent.accept()
class CustomDragWidget(QGraphicsWidget):
def __init__(self,):
super().__init__()
self.squareItem = QGraphicsRectItem()
self.squareItem.setBrush(QBrush(QColor(Qt.blue)))
self.squareItem.setPen(QPen(QColor(Qt.black), 2))
self.squareItem.setRect(0, 0, 50, 50)
self.squareItem.setParentItem(self)
self.setAcceptDrops(True)
def mousePressEvent(self, event):
mime = QMimeData()
itemData = QByteArray()
mime.setData('application/x-dnditemdata', itemData)
drag = QDrag(self)
drag.setMimeData(mime)
drag.exec(Qt.MoveAction)
def dropEvent(self, event):
event.accept()
if __name__ == '__main__':
app = QApplication(sys.argv)
win = MainWindow()
win.show()
sys.exit(app.exec_())
This is a little bit hackish, but it seems to work (on Linux, anyway):
def dragLeaveEvent(self, event):
QCoreApplication.postEvent(self,
QKeyEvent(QEvent.KeyPress, Qt.Key_Escape, Qt.NoModifier))

pyqt4 - singleapplication - bring up the original window in an attempt to open the app for the second time

I want only one instance of my app to be running at each time. but when the user attempts to open it the second time, I want the first window to be brought to the front (it could be just minimized or minimized to the corner of the taskbar and the user doesn't know how to open it)
I have this code that does the detection job and it doesn't allow the second instance. I have trouble with the part that it has to open the original window. I have commented out some of my attempt.
import sys
from PyQt4 import QtGui, QtCore
import sys
class SingleApplication(QtGui.QApplication):
def __init__(self, argv, key):
QtGui.QApplication.__init__(self, argv)
self._activationWindow=None
self._memory = QtCore.QSharedMemory(self)
self._memory.setKey(key)
if self._memory.attach():
self._running = True
else:
self._running = False
if not self._memory.create(1):
raise RuntimeError(
self._memory.errorString().toLocal8Bit().data())
def isRunning(self):
return self._running
def activationWindow(self):
return self._activationWindow
def setActivationWindow(self, activationWindow):
self._activationWindow = activationWindow
def activateWindow(self):
if not self._activationWindow:
return
self._activationWindow.setWindowState(
self._activationWindow.windowState() & ~QtCore.Qt.WindowMinimized)
self._activationWindow.raise_()
self._activationWindow.activateWindow()
class Window(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.label = QtGui.QLabel(self)
self.label.setText("Hello")
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.label)
if __name__ == '__main__':
key = 'random _ text'
app = SingleApplication(sys.argv, key)
if app.isRunning():
#app.activateWindow()
sys.exit(1)
window = Window()
#app.setActivationWindow(window)
#print app.topLevelWidgets()[0].winId()
window.show()
sys.exit(app.exec_())
I've made this work on Windows using the win32 api (I'm not entirely sure, but there are probably equivalent calls on macos/unix).
Add the following import to your application,
import win32gui
set the window title to a fixed name (instead of doing this, you could store its whndl in the shared memory)
window = Window()
window.setWindowTitle('Single Application Example')
window.show()
and then change your activateWindow method to something like the following:
def activateWindow(self):
# needs to look for a named window ...
whndl = win32gui.FindWindowEx(0, 0, None, "Single Application Example")
if whndl is 0:
return #couldn't find the name window ...
#this requests the window to come to the foreground
win32gui.SetForegroundWindow(whndl)
You might be interested by the solutions proposed here
For instance, I would try:
app = SingleApplication(sys.argv, key)
if app.isRunning():
window = app.activationWindow()
window.showNormal()
window.raise()
app.activateWindow()
sys.exit(1)
window = Window()
app.setActivationWindow(window)
window.setWindowFlags(Popup)
window.show()

PySide how to set focus to new window and open it if not exists

I have started learning PySide from example of Zetcode and try to code app which had two windows: "Schematic View" which is parent of "Layout View", each with menu bar. On start that should be only schematic window, and layout win should be started by switchtoLAYOUT in root of menubar.
My questions are:
How to make "switchtoLAYOUT" in root not to show the dropdown and still do action with only one instance of "Layout View" window ?
How to switch the focus between two windows ("switchtoLAYOUT" and "switchtoSCHEMATIC")?
Please inspect my code and suggest me something smart (that should not be hard).
The Code:
import sys
from PySide import QtCore, QtGui
class schematicWindow(QtGui.QMainWindow):
def __init__(self):
super(schematicWindow, self).__init__()
self.defineSchWin()
def defineSchWin(self):
exitAction = QtGui.QAction('&Exit', self)
exitAction.setShortcut('Ctrl+Q')
exitAction.setStatusTip('Exit application')
exitAction.triggered.connect(self.close)
self.statusBar()
menubar = self.menuBar()
fileMenu = menubar.addMenu('&File')
fileMenu.addAction(exitAction)
menubar.addMenu('&Edit')
menubar.addMenu('&Passives')
menubar.addMenu('&Descretes')
menubar.addMenu('&IC\'s')
swToLayMenu = menubar.addMenu('switchtoLAYOUT')
swToLayAction = QtGui.QAction(self)
swToLayAction.triggered.connect(self.layoutWindow)
swToLayMenu.addAction(swToLayAction) # open layoutWindow (if not exists)
# and set focus to layoutWindow
self.setGeometry(0, 300, 500, 300)
self.setWindowTitle('Schematic View')
self.show()
def layoutWindow(self):
window = QtGui.QMainWindow(self)
window.setAttribute(QtCore.Qt.WA_DeleteOnClose)
window.statusBar()
menubar = window.menuBar()
switchtoSchMenu = menubar.addMenu('switchtoSCHEMATIC')
window.setGeometry(100, 600, 500, 300)
window.setWindowTitle('Layout View')
window.show()
def main():
app = QtGui.QApplication(sys.argv)
ex = schematicWindow()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
You need to keep a reference to the layout window in your class, (you should put self.layout_window = None in the __init__). This function now checks if the window has been initialized, makes it if it has not been, makes sure it is visible, and then sets the new window to be the active window. Something like: (this is not tested)
def layoutWindow(self):
if self.layout_window is None:
window = QtGui.QMainWindow(self)
self.layout_window = window
window.setAttribute(QtCore.Qt.WA_DeleteOnClose)
window.statusBar()
menubar = window.menuBar()
switchtoSchMenu = menubar.addMenu('switchtoSCHEMATIC')
window.setGeometry(100, 600, 500, 300)
window.setWindowTitle('Layout View')
else:
window = self.layout_window
window.show()
window.activateWindow()
window.raise() # just to be sure it's on top
(doc)

Resources