Using Qt to create the OS X Yosemite Translucency effect - macos

We're trying to make a QMainWindow with the Mac OSX Yosemite translucency effect. We're using PyQt, but the problem is a Qt issue. With what we've tried so far, it's always either fully transparent or fully opaque (like a normal window). If we turn on Qt.WA_TranslucentBackground, the window background becomes 100% completely transparent.
Additionally, the QGraphicsView we're displaying on it then leaves trails behind when you scroll. The mouse input also "passes through" the transparent parts - clicking on a transparent portion of the graphics view will register as a click on the window behind it. Setting a stylesheet with any custom background color then has no effect. If we turn it off, the window remains opaque. Then we can change the background color using a style sheet, but it's still opaque. Turing WA_FramelessWindowHint on and off doesn't seem to fix anything, either. Nor does setAutoFillBackground(). Do you know how to make a window with the Yosemite translucency effect?
Here's a sample Python program to test this: -
# Attempt at Mac OSX Translucency (example code)
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
Qt = QtCore.Qt
class ExampleMainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.testWidget = QtWidgets.QLabel('Test label, which should be transparent')
# Make sure the testWidget is transparent
self.testWidget.setStyleSheet('background: transparent')
self.testWidget.setAttribute(Qt.WA_TranslucentBackground, True)
self.testWidget.setAutoFillBackground(True)
self.setStyleSheet('background: rgba(255, 255, 255, 0.8)')
self.setAttribute(Qt.WA_TranslucentBackground, True)
self.setAutoFillBackground(True)
#self.setWindowFlags(Qt.FramelessWindowHint) # Doesn't seem to help
self.setCentralWidget(self.testWidget)
def main():
global app, exWindow
app = QtWidgets.QApplication(sys.argv)
exWindow = ExampleMainWindow()
exWindow.show()
exitcodesys = app.exec_()
app.deleteLater()
sys.exit(exitcodesys)
if __name__ == '__main__': main()

Your stylesheet isn't valid. Qt's rgba expects an integer in range 0-255 or a percentage for the values only. So use 80% instead of 0.8. Along with WA_TranslucentBackground that should get you going.
And please post your code in the question, as your pastes expire in a few days and this question will then be unusable.
Here is code that is working for me; I see my other applications' windows underneath. It's C++. I'm using Qt4 rather than 5; perhaps this matters.
#include <QMainWindow>
#include <QApplication>
#include <QLabel>
class MyMain : public QMainWindow
{
Q_OBJECT
public:
MyMain(QWidget* parent = 0);
};
MyMain::MyMain(QWidget* parent)
: QMainWindow(parent)
{
setAttribute(Qt::WA_TranslucentBackground, true);
setStyleSheet("background: rgba(0,0,0,80%);");
QLabel* foo = new QLabel();
foo->setText("hello");
foo->setStyleSheet("color: white;");
setCentralWidget(foo);
}
#include "main.moc"
int main(int argc, char** argv)
{
QApplication app(argc, argv);
MyMain m;
m.show();
return app.exec();
}

Related

Animating QPushButton PyQt

I'm having an issue trying to animate a QPushButtons. I've a queue of buttons that once popped, I would like to animate from one color to another. I've recreated a minimum working example below:
from PyQt5 import QtCore, QtGui
from PyQt5.QtWidgets import QHBoxLayout, QPushButton, QApplication, QWidget
from main import comboBox
app = QApplication([])
app.setStyle('Fusion')
window = QWidget()
def change_color():
anim = QtCore.QPropertyAnimation(changeButton, b"color")
anim.setDuration(1000)
anim.setStartValue(QtGui.QColor(255, 144, 0))
anim.setEndValue(QtGui.QColor(255, 255, 255))
anim.start()
hbox = QHBoxLayout()
hbox.addStretch(1)
changeButton = QPushButton()
changeButton.setText("change Grid")
hbox.addWidget((changeButton))
window.setLayout((hbox))
window.show()
app.exec()
changeButton.clicked.connect(lambda event: change_color())
When I go into the debug mode it shows that it's reaching each of the lines, however, it there is no color change happening. Am I doing something wrong here?
You have two problems.
The first is that you're connecting the signal after the app.exec() call. That call starts the event loop, and, as such, it's blocking (nothing after that line will be processed until it returns), so you must move that before starting the event loop.
Then, as the QPropertyAnimation documentation explains:
QPropertyAnimation interpolates over Qt properties.
color is not a Qt property for QPushButton (nor any of its base classes), in fact, if you look at the debug/terminal output for your program (after moving the signal connection as said above), you'll see the following:
StdErr: QPropertyAnimation: you're trying to animate a non-existing property color of your QObject
A simple solution would be to use QVariantAnimation instead, and update the color in a function connected to the animation's valueChanged signal.
def change_color():
def updateColor(color):
# ... change the color
anim = QtCore.QVariantAnimation(changeButton)
anim.setDuration(1000)
anim.setStartValue(QtGui.QColor(255, 144, 0))
anim.setEndValue(QtGui.QColor(255, 255, 255))
anim.valueChanged.connect(updateColor)
anim.start()
# ...
changeButton.clicked.connect(change_color)
The choice becomes the way you change color: if you're not using stylesheets for the button, the more appropriate way would be to use the button palette:
def change_color(button):
def updateColor(color):
palette.setColor(role, color)
button.setPalette(palette)
palette = button.palette()
role = button.foregroundRole()
anim = QtCore.QVariantAnimation(button)
anim.setDuration(1000)
anim.setStartValue(QtGui.QColor(255, 144, 0))
anim.setEndValue(QtGui.QColor(255, 255, 255))
anim.valueChanged.connect(updateColor)
anim.start(anim.DeleteWhenStopped)
# ...
changeButton.clicked.connect(lambda: change_color(changeButton))
Note that I added the button argument (you should not use globals anyway), and also the DeleteWhenStopped flag to start() in order to avoid unnecessary stacking of unused animations when they're finished.
If, instead, you're using stylesheets, you need to override the button's stylesheet. Obviously, you have to be careful: the button shouldn't have any stylesheet set directly on it.
def change_color(button):
def updateColor(color):
button.setStyleSheet('color: {}'.format(color.name()))
# ...
Note that a more appropriate approach would be to subclass the button and implement the animation directly on it. By doing that you can use a QPropertyAnimation by using a custom pyqtProperty, but you will still need to manually set the color as above in the property setter. In this case, you can just have a single animation (possibly creating it in the __init__) and just update its start/end values.
Alternatively, you can just call update in the setter, and override the paintEvent() by using QStyle functions to draw the button primitive and label using the property value.

How to save incrementally images loaded on a QGraphicsView using QPushButton & OpenCV::imwrite

I prepared this small verifiable .ui in Figure 1 that replicates the issue I have:
I am trying to use the QPushButton "Print Screen Both Images" to incrementally save images on Left and Right of the QGraphicsView into two different folders present on my Desktop, see below Figure 2:
I can take a print screen of either the leftScene or the rightScene by just clicking on their related QPushButton Print Screen Left and Print Screen Right.
However, I am trying for this specific case not to use QFileDialog as I need to silently and incrementally save the images in the two different destination folders as I move on with the right/left arrow.
See below the snipped of code I am using:
mainwindow.h
public:
void bothPrintScreen(const std::string& pathImg);
private slots:
void on_bothPrintScreen_clicked(const std::string& imgPath);
private:
int counterA=0;
int counterB=0;
mainwindow.cpp
void MainWindow::on_bothPrintScreen_clicked(const std::string& imgPath)
{
bothPrintScreen(imgPath);
}
void MainWindow::bothPrintScreen(const std::string& pathImg){
cv::Mat left, right;
std::string outA = pathImg+"/printScreenA_"+std::to_string(counterA++)+".png";
cv::imwrite(outA,left);
std::string outB = pathImg+"/printScreenB_"+std::to_string(counterB++)+".png";
cv::imwrite(outB,right);
}
I am missing something in the code but I am not sure what exactly.
The compiler is seinding this allocate()/deallocate() error that I don't understand:
Please shed light on this matter.
It need to add OpenCV libraries to the your Qt project (like this)
INCLUDEPATH += -I/usr/local/include/opencv
LIBS += -L/usr/local/lib -lopencv_stitching -lopencv_superres ...and another libraries

How to get multitouch to work in QGraphicsView, Qt 5.0.2 in Windows 8

I am struggling with getting multi-touch to work on a couple of QWidgets that I have added to a QGraphicsView. I have created a subclass of QWidget in which I set up a QGraphicsScene and QGraphicsView. This is my (test) subclass of QWidget:
#include "qttest1.h"
#include <QtWidgets>
#include <QTouchEvent>
qttest1::qttest1(QWidget *parent)
: QWidget(parent)
{
setEnabled(true);
if(!QCoreApplication::testAttribute(Qt::AA_DontCreateNativeWidgetSiblings))
setAttribute(Qt::WA_NativeWindow);
setAttribute(Qt::WA_AcceptTouchEvents);
scene = new QGraphicsScene(this);
scene->setSceneRect(0, 0, 1920, 1080);
graphicsView = new QGraphicsView(scene, this);
graphicsView->setRenderHints(QPainter::Antialiasing);
graphicsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
graphicsView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
graphicsView->setAttribute(Qt::WA_AcceptTouchEvents);
graphicsView->viewport()->setAttribute(Qt::WA_AcceptTouchEvents);
QBoxLayout *layout = new QVBoxLayout;
layout->addWidget(graphicsView);
setLayout(layout);
}
qttest1::~qttest1() {}
void qttest1::showGraphics()
{
for(int i = 0; i < 10; i++)
{
Dial *dial = new QDial();
dial->move(i * 120 + 50, 200);
dial->resize(120, 120);
dial->setAttribute(Qt::WA_AcceptTouchEvents);
QGraphicsProxyWidget *proxy = scene->addWidget(dial);
proxy->setAcceptTouchEvents(true);
}
}
This is my main:
int main(int argc, char **argv)
{
QApplication app(argc, argv);
app.setAttribute(Qt::AA_DontCreateNativeWidgetSiblings);
QRect rect = app.desktop()->screenGeometry();
qttest1 test;
test.resize(rect.width(), rect.height());
test.showFullScreen();
test.showGraphics();
return app.exec();
}
I know the code isn't pretty and probably leaking a bit, but the point is to try to get multi-touch to work.
I can see and use every kind of widget I add to the scene, but as soon as I touch a dial it swallows every touch that comes after the first. Which makes the dial jump between several positions. What I want is that every dial (or any type of widget) can be used individually and at the same time. I am using QT 5.0.2, Windows 8 with a monitor that supports up to 10 touches.
The Qt docs state : -
Reimplement QWidget::event() or QAbstractScrollArea::viewportEvent()
for widgets and QGraphicsItem::sceneEvent() for items in a graphics
view to receive touch events.
With that, I believe that you need to handle the QEvent::TouchBegin, QEvent::TouchUpdate and QEvent::TouchEnd events, which I don't see in the code you've posted.
Qt may handle the first touch for you, but it's not going to know what you want to do with the second, third, fourth etc. simultaneous touches. For example, you may want your app to do any of the following with the second touch moving: -
1) Rotate the object that the first item is over
2) Scale the object that the first item is over
3) Select the second item
4) Translate the view
5) etc.
So, you need to handle the consecutive touches to do what you want it to do. Also, you may want to look at Gestures in Qt.

Cannot get XCreateSimpleWindow to open window at the right position

The following code opens a window of the right size, w,h, but not at the correct position, x,y.
#include <iostream>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xresource.h>
using namespace std;
int main(int argc, char* argv[]){
Display *display; // A connection to X server
int screen_number;
Window canvas_window;
unsigned long white_pixel;
unsigned long black_pixel;
display = XOpenDisplay(NULL); // Connect X server by opening a display
if(!display){
cerr<<"Unable to connect X server\n";
return 1;
}
screen_number = DefaultScreen(display);
white_pixel = WhitePixel(display, screen_number);
black_pixel = BlackPixel(display, screen_number);
int x=0, y=100, w=300, h=400;
canvas_window = XCreateSimpleWindow(display,
RootWindow(display, screen_number),
x,y, // top left corner
w,h, // width and height
2, // border width
black_pixel,
white_pixel);
XMapWindow(display, canvas_window); // Map canvas window to display
XSync(display, False);
cin >> x; // just so that the program does not end
}
I compiled this with g++ xwindowtest.cpp -lX11 where g++ is version 4.6.2 and ran under Debian GNU/Linux.
The above solution is sort of correct, but not complete.
Creating a new top-level window on the desktop, and creating a new (child) window within the top-level window of your application use the same XCreateSimpleWindow() call, but the actual behaviour can be different.
When creating a new child window within your application you are in charge, and the origin coordinates (relative to its parent window's origin) and size you give for the new window will be honoured. In other words the window will go where you want it to.
However when creating a new top-level window on the desktop you have to deal with the pesky window manager, for example Motif, KDE, Gnome, etc. This intervenes when you create a top-level window to add borders ("decoration"), title, possibly icons, etc. More to the point it will, by default, ignore your requested origin coordinates in most cases and put the new window where it wants rather than where you asked it to put it. It is only when it has been mapped (somewhere) that you can then move it with XMoveWindow().
To avoid this you can ask, or in X11-speak "Hint to", the Window manager that "no, I want you to put the window where I ask, not where you want to put it". You do this with the following sequence:
(1) Define a XSizeHints structure.
(2) Set the flags bit in this structure with a mask of what you want to specify
(3) Populate the relevant arguments
(4) Call XSetNormalHints() on the newly created window (before you map it).
So in C code you would do:
XSizeHints my_hints = {0};
my_hints.flags = PPosition | PSize; /* I want to specify position and size */
my_hints.x = wanted_x_origin; /* The origin and size coords I want */
my_hints.y = wanted_y_origin;
my_hints.width = wanted_width;
my_hints.height = wanted_height;
XSetNormalHints(disp, new_window, &my_hints); /* Where new_window is the new window */
Then map it and - hopefully - it will be where you want it.
I usually declare a XSizeHints first and assign x,y coordinates etc to hints.
When creating x window you could do
XCreateSimpleWindow(display,
DefaultRootWindow(display),
hints.x, hints.y,
hints.width,hints.height,
borderWidth,
blackPixel, whitePixel)
That always works for me with 100% correct windows location.
I had the same problem. I am just starting with X11. Maybe others with more experience can clarify why this works (and simply specifying the x, y for XCreateSimpleWindow does not).
Here's my fix:
disp is your display, win0 is your canvas_window:
XMoveWindow(disp, win0, 200, 200);
XSync (disp, FALSE);
..... do something .....
XMoveWindow(disp, win0, 0, 0);
XSync (disp, FALSE);
... do something
When I run this code snippet, the window moves. You need to follow the XMoveWindow by XSync so that requests (such as a move) are handled appropriately.

How to Get a Window or Fullscreen Screenshot (without PIL)?

With python 3, I'd like to get a handle to another window (not part of my application) such that I can either:
directly capture that window as a screenshot, or
determine its position and size and capture it some other way
In case it is important, I am using Windows XP (edit: works in Windows 7 also).
I found this solution, but it is not quite what I need since it is full screen and more importantly, PIL to the best of my knowledge does not support 3.x yet.
Here's how you can do it using PIL on win32. Given a window handle (hwnd), you should only need the last 4 lines of code. The preceding simply search for a window with "firefox" in the title. Since PIL's source is available, you should be able to poke around the ImageGrab.grab(bbox) method and figure out the win32 code you need to make this happen.
from PIL import ImageGrab
import win32gui
toplist, winlist = [], []
def enum_cb(hwnd, results):
winlist.append((hwnd, win32gui.GetWindowText(hwnd)))
win32gui.EnumWindows(enum_cb, toplist)
firefox = [(hwnd, title) for hwnd, title in winlist if 'firefox' in title.lower()]
# just grab the hwnd for first window matching firefox
firefox = firefox[0]
hwnd = firefox[0]
win32gui.SetForegroundWindow(hwnd)
bbox = win32gui.GetWindowRect(hwnd)
img = ImageGrab.grab(bbox)
img.show()
Ars gave me all the pieces. I am just putting the pieces together here for anyone else who needs to get a screenshot in python 3.x. Next I need to figure out how to work with a win32 bitmap without having PIL to lean on.
Get a Screenshot (pass hwnd for a window instead of full screen):
def screenshot(hwnd = None):
import win32gui
import win32ui
import win32con
from time import sleep
if not hwnd:
hwnd=win32gui.GetDesktopWindow()
l,t,r,b=win32gui.GetWindowRect(hwnd)
h=b-t
w=r-l
hDC = win32gui.GetWindowDC(hwnd)
myDC=win32ui.CreateDCFromHandle(hDC)
newDC=myDC.CreateCompatibleDC()
myBitMap = win32ui.CreateBitmap()
myBitMap.CreateCompatibleBitmap(myDC, w, h)
newDC.SelectObject(myBitMap)
win32gui.SetForegroundWindow(hwnd)
sleep(.2) #lame way to allow screen to draw before taking shot
newDC.BitBlt((0,0),(w, h) , myDC, (0,0), win32con.SRCCOPY)
myBitMap.Paint(newDC)
myBitMap.SaveBitmapFile(newDC,'c:\\tmp.bmp')
Get a Window Handle by title (to pass to the above function):
def _get_windows_bytitle(title_text, exact = False):
def _window_callback(hwnd, all_windows):
all_windows.append((hwnd, win32gui.GetWindowText(hwnd)))
windows = []
win32gui.EnumWindows(_window_callback, windows)
if exact:
return [hwnd for hwnd, title in windows if title_text == title]
else:
return [hwnd for hwnd, title in windows if title_text in title]
This will take a new opened window and make a screenshot of it and then crop it with PIL also possible to find your specific window with pygetwindow.getAllTitles() and then fill in your window name in z3 to get screenshot of only that window.
If you definitely not want to use PIL you can maximize window with pygetwindow module and then make a screenshot with pyautogui module.
Note: not tested on Windows XP (but tested on Windows 10)
import pygetwindow
import time
import os
import pyautogui
import PIL
# get screensize
x,y = pyautogui.size()
print(f"width={x}\theight={y}")
x2,y2 = pyautogui.size()
x2,y2=int(str(x2)),int(str(y2))
print(x2//2)
print(y2//2)
# find new window title
z1 = pygetwindow.getAllTitles()
time.sleep(1)
print(len(z1))
# test with pictures folder
os.startfile("C:\\Users\\yourname\\Pictures")
time.sleep(1)
z2 = pygetwindow.getAllTitles()
print(len(z2))
time.sleep(1)
z3 = [x for x in z2 if x not in z1]
z3 = ''.join(z3)
time.sleep(3)
# also able to edit z3 to specified window-title string like: "Sublime Text (UNREGISTERED)"
my = pygetwindow.getWindowsWithTitle(z3)[0]
# quarter of screen screensize
x3 = x2 // 2
y3 = y2 // 2
my.resizeTo(x3,y3)
# top-left
my.moveTo(0, 0)
time.sleep(3)
my.activate()
time.sleep(1)
# save screenshot
p = pyautogui.screenshot()
p.save(r'C:\\Users\\yourname\\Pictures\\\\p.png')
# edit screenshot
im = PIL.Image.open('C:\\Users\\yourname\\Pictures\\p.png')
im_crop = im.crop((0, 0, x3, y3))
im_crop.save('C:\\Users\\yourname\\Pictures\\p.jpg', quality=100)
# close window
time.sleep(1)
my.close()
The solution here gets a screenshot of a single Window (so can work if the Window is in the background).
Other solutions of this page take picture of the part of the screen the window is on, and thus need to bring the Window to the front first.
Python Screenshot of inactive window PrintWindow + win32gui

Resources