QSystemTrayIcon notification message with custom icon - qt

QSystemTrayIcon has a function :
void showMessage(const QString &title, const QString &msg,
MessageIcon icon = Information, int msecs = 10000);
is there a way to change it to custom icon , for example like this -
void showIconMessage(const QString &title, const QString &msg,
QIcon icon = QIcon(), int msecs = 10000);
without modifying the Qt sources
I know that showMessage (d is instance of QSystemTrayIconPrivate and is called with Q_D(QSystemTrayIcon) macro)
void QSystemTrayIcon::showMessage(const QString& title, const QString& msg,
QSystemTrayIcon::MessageIcon icon, int msecs)
{
Q_D(QSystemTrayIcon);
if (d->visible)
d->showMessage_sys(title, msg, icon, msecs);
}
calls showMessage_sys from QSystemTrayIconPrivate where in turn all the magic with icon happens:
void QSystemTrayIconPrivate::showMessage_sys(const QString &message,
const QString &title,
QSystemTrayIcon::MessageIcon icon,
int msecs)
{
if (!qpa_sys)
return;
QIcon notificationIcon;
switch (icon) {
case QSystemTrayIcon::Information:
notificationIcon = QApplication::style()- >standardIcon(QStyle::SP_MessageBoxInformation);
break;
case QSystemTrayIcon::Warning:
notificationIcon = QApplication::style()->standardIcon(QStyle::SP_MessageBoxWarning);
break;
case QSystemTrayIcon::Critical:
notificationIcon = QApplication::style()->standardIcon(QStyle::SP_MessageBoxCritical);
break;
default:
break;
}
qpa_sys->showMessage(message, title, notificationIcon,
static_cast<QPlatformSystemTrayIcon::MessageIcon>(icon), msecs);
}
Now, it seems, that I need to re-implement these two functions in two classes and i'm ready to go, but.. It seems that QSystemTrayIcon is closely tied to QSystemTrayIconPrivate. Instance of QSystemTrayIconPrivate is created only in QSystemTrayIcon constructor (which I can't really change if I plan to create classes that inherit both QSystemTrayIcon and QSystemTrayIconPrivate and re-implement showMessage functions):
QSystemTrayIcon::QSystemTrayIcon(QObject *parent)
: QObject(*new QSystemTrayIconPrivate(), parent)
{
}
QSystemTrayIcon::QSystemTrayIcon(const QIcon &icon, QObject *parent)
: QObject(*new QSystemTrayIconPrivate(), parent)
{
setIcon(icon);
}
So is there anything I am missing? Or is there another way to simply show notification message with custom icon?

What you could try (not sure if it will work for system tray) is do the same as described in this answer and override the SP_MessageBoxWarning / SP_MessageBoxCritical / SP_MessageBoxInformation icons, but as I said I'm not sure if the system tray just uses a downscaled version of the message box icons or if the system tray icons are separate. In the case of the latter, I guess you will have to patch QT sources, maybe add a new item to the QSystemTrayIcon and patch the switch to call some function provided by you to return the needed icon. Something like:
void QSystemTrayIconPrivate::showMessage_sys(const QString &message,
const QString &title,
QSystemTrayIcon::MessageIcon icon,
int msecs)
{
if (!qpa_sys)
return;
QIcon notificationIcon;
switch (icon) {
case QSystemTrayIcon::Information:
notificationIcon = QApplication::style()- >standardIcon(QStyle::SP_MessageBoxInformation);
break;
case QSystemTrayIcon::Warning:
notificationIcon = QApplication::style()->standardIcon(QStyle::SP_MessageBoxWarning);
break;
case QSystemTrayIcon::Critical:
notificationIcon = QApplication::style()->standardIcon(QStyle::SP_MessageBoxCritical);
break;
case QSystemTrayIcon::Custom:
// Call a function that will fetch the needed icon and assign it to notificationIcon
break;
default:
break;
}
qpa_sys->showMessage(message, title, notificationIcon,
static_cast<QPlatformSystemTrayIcon::MessageIcon>(icon), msecs);
}

Related

QToolButton prevent from moving

Is there a way to prevent QToolButton from being "pressed in" when clicked? I read somewhere that setting
button->setCheckable(false);
should do the trick, but it doesn't.
There is a way to do it via a QProxyStyle:
class ButtonProxyStyle : public QProxyStyle
{
public:
const int pixelMetric(PixelMetric metric, const QStyleOption *option = 0, const QWidget *widget = 0)
{
int ret = 0;
switch (metric)
{
case QStyle::PM_ButtonShiftHorizontal:
case QStyle::PM_ButtonShiftVertical:
ret = 0;
break;
default:
ret = QProxyStyle::pixelMetric(metric, option, widget);
break;
}
return ret;
}
};
And then, with your button:
myToolButton->setStyle(new ButtonProxyStyle);
Add QAction to the toolbar and use it to controll your tool button
// button action
QAction * poBtnAction = poToolbar->addWidget(button);
// disable button
poBtnAction->setEnabled(false);

How to monitor changes to an arbitrary widget?

I am starting a QT5 application with a rather complex design based on Qt Widgets. It runs on Beagleboard with a touchscreen. I will have a rather weird local invention instead of the LCD display. It's a laser painting on acrylic plate. It has no driver yet. To actually update a screen I must create a screenshot of the window as bitmap, turn it to grayscale and feed to a proprietary library, which will handle the laser. It should look cute, when ready. Unfortunately, the laser blinks on update, so I cannot just make screenshots on timer, or it will be jerky like hell.
I need to run a function every time a meaningful update of GUI happens, while preferably ignore things like button being pressed and released. Is there some way to create a hook without subclassing every single Qt Widget I will use? The only way to do this I know is to override paintEvent of everything. I want a simpler solution.
Possible assumptions are: the application will be running under X server with dummy display, will be the only GUI app running. Some updates happen without user input.
The code below does it. It doesn't dig too deeply into the internals of Qt, it merely leverages the fact that backing store devices are usually QImages. It could be modified to accommodate OpenGL-based backing stores as well.
The WidgetMonitor class is used to monitor the widgets for content changes. An entire top-level window is monitored no matter which particular widget is passed to the monitor(QWidget*) method. You only need to call the monitor method for one widget in the window you intend to monitor - any widget will do. The changes are sent out as a QImage of window contents.
The implementation installs itself as an event filter in the target window widget and all of its children, and monitors the repaint events. It attempts to coalesce the repaint notifications by using the zero-length timer. The additions and removals of children are tracked automagically.
When you run the example, it creates two windows: a source window, and a destination window. They may be overlapped so you need to separate them. As you resize the source window, the size of the destination's rendition of it will also change appropriately. Any changes to the source children (time label, button state) propagate automatically to the destination.
In your application, the destination could be an object that takes the QImage contents, converts them to grayscale, resizes appropriately, and passes them to your device.
I do not quite understand how your laser device works if it can't gracefully handle updates. I presume that it is a raster-scanning laser that runs continuously in a loop that looks roughly like this:
while (1) {
for (line = 0; line < nLines; ++line) {
drawLine();
}
}
You need to modify this loop so that it works as follows:
newImage = true;
QImage localImage;
while (1) {
if (newImage) localImage = newImage;
for (line = 0; line < localImage.height(); ++line) {
drawLine(line, localImage);
}
}
You'd be flipping the newImage flag from the notification slot connected to the WidgetMonitor. You may well find out that leveraging QImage, and Qt's functionality in general, in your device driver code, will make it much easier to develop. Qt provides portable timers, threads, collections, etc. I presume that your "driver" is completely userspace, and communicates via a serial port or ethernet to the micro controller that actually controls the laser device.
If you will be writing a kernel driver for the laser device, then the interface would be probably very similar, except that you end up writing the image bitmap to an open device handle.
// https://github.com/KubaO/stackoverflown/tree/master/questions/surface-20737882
#include <QtWidgets>
#include <array>
const char kFiltered[] = "WidgetMonitor_filtered";
class WidgetMonitor : public QObject {
Q_OBJECT
QVector<QPointer<QWidget>> m_awake;
QBasicTimer m_timer;
int m_counter = 0;
void queue(QWidget *window) {
Q_ASSERT(window && window->isWindow());
if (!m_awake.contains(window)) m_awake << window;
if (!m_timer.isActive()) m_timer.start(0, this);
}
void filter(QObject *obj) {
if (obj->isWidgetType() && !obj->property(kFiltered).toBool()) {
obj->installEventFilter(this);
obj->setProperty(kFiltered, true);
}
}
void unfilter(QObject *obj) {
if (obj->isWidgetType() && obj->property(kFiltered).toBool()) {
obj->removeEventFilter(this);
obj->setProperty(kFiltered, false);
}
}
bool eventFilter(QObject *obj, QEvent *ev) override {
switch (ev->type()) {
case QEvent::Paint: {
if (!obj->isWidgetType()) break;
if (auto *window = static_cast<QWidget *>(obj)->window()) queue(window);
break;
}
case QEvent::ChildAdded: {
auto *cev = static_cast<QChildEvent *>(ev);
if (auto *child = qobject_cast<QWidget *>(cev->child())) monitor(child);
break;
}
default:
break;
}
return false;
}
void timerEvent(QTimerEvent *ev) override {
if (ev->timerId() != m_timer.timerId()) return;
qDebug() << "painting: " << m_counter++ << m_awake;
for (auto w : m_awake)
if (auto *img = dynamic_cast<QImage *>(w->backingStore()->paintDevice()))
emit newContents(*img, w);
m_awake.clear();
m_timer.stop();
}
public:
explicit WidgetMonitor(QObject *parent = nullptr) : QObject{parent} {}
explicit WidgetMonitor(QWidget *w, QObject *parent = nullptr) : QObject{parent} {
monitor(w);
}
Q_SLOT void monitor(QWidget *w) {
w = w->window();
if (!w) return;
filter(w);
for (auto *obj : w->findChildren<QWidget *>()) filter(obj);
queue(w);
}
Q_SLOT void unMonitor(QWidget *w) {
w = w->window();
if (!w) return;
unfilter(w);
for (auto *obj : w->findChildren<QWidget *>()) unfilter(obj);
m_awake.removeAll(w);
}
Q_SIGNAL void newContents(const QImage &, QWidget *w);
};
class TestWidget : public QWidget {
QVBoxLayout m_layout{this};
QLabel m_time;
QBasicTimer m_timer;
void timerEvent(QTimerEvent *ev) override {
if (ev->timerId() != m_timer.timerId()) return;
m_time.setText(QTime::currentTime().toString());
}
public:
explicit TestWidget(QWidget *parent = nullptr) : QWidget{parent} {
m_layout.addWidget(&m_time);
m_layout.addWidget(new QLabel{"Static Label"});
m_layout.addWidget(new QPushButton{"A Button"});
m_timer.start(1000, this);
}
};
int main(int argc, char **argv) {
QApplication app{argc, argv};
TestWidget src;
QLabel dst;
dst.setFrameShape(QFrame::Box);
for (auto *w : std::array<QWidget *, 2>{&dst, &src}) {
w->show();
w->raise();
}
QMetaObject::invokeMethod(&dst, [&] { dst.move(src.frameGeometry().topRight()); },
Qt::QueuedConnection);
WidgetMonitor mon(&src);
src.setWindowTitle("Source");
dst.setWindowTitle("Destination");
QObject::connect(&mon, &WidgetMonitor::newContents, [&](const QImage &img) {
dst.resize(img.size());
dst.setPixmap(QPixmap::fromImage(img));
});
return app.exec();
}
#include "main.moc"

Qt: How to implement QDialogButtonBox with QSignalMapper for non-standard button ??

I have a QDialogButtonBox with All Standard Button and Non Standard Buttons (QPushbutton added to create Non-Standard Buttons).
I can implement SignalMapper saperately for Non-Standard Buttons.But in this case i have to add 2 slots (Slot for QDialogButtonBox Standard Buttons & Slot for Non-Standard Buttons).
I need to implement QSignalMapper with common slot for Standard Buttons as well as Non-Standard buttons.
Can anyone guide me for it ?
You shouldn't need to use QSignalMapper with QDialogButtonBox.
QSignalMapper is used to connect multiple signals to a single slot, and QDialogButtonBox already has a single signal that is emitted for all the buttons: clicked(QAbstractButton*).
You could simply assign a value to the button with a map (QMap, std::map) or through a dynamic property:
enum { MyRole1 = 1, MyRole2 }; // starting at 1 because an unset property would return 0
...
userButton−>setProperty("ActionRole", MyRole1); // a cast to int might be needed here
buttonBox->addButton(userButton, QDialogButtonBox::ActionRole);
connect(this, SIGNAL(clicked(QAbstractButton*)), SLOT(dialogButtonClicked(QAbstractButton *button)));
And in the slot, you would get the value back from the parameter passed by the signal:
void MyClass::dialogButtonClicked(QAbstractButton *button) {
StandardButton standardButton = buttonBox−>standardButton(button);
switch(standardButton) {
// Standard buttons:
case QDialogButtonBox::Ok:
...
break;
case QDialogButtonBox::Abort:
...
break;
// Non-standard buttons:
case QDialogButtonBox::NoButton:
int actionRole = button->property("ActionRole").toInt();
switch(actionRole) {
case MyRole1:
...
break;
case MyRole2:
...
break;
default:
// shouldn't happen
break;
}
}
}
QSignalMapper can map a QObject and a integer. So you can map your non-standardButton with its role and create a slot that takes in parameter a role:
For example:
void Widget::initMap() {
QPushButton* buttonReset = new QPushButton( "Reset" );
signalMapper = new QSignalMapper(this);
connect(buttonReset, SIGNAL(clicked()), signalMapper, SLOT(map()));
signalMapper->setMapping(button, QDialogButtonBox::ResetRole);
connect(signalMapper, SIGNAL(mapped(int)), this, SLOT(slot(int)));
}
void Widget::slot( int role) {
if ( role == QDialogButtonBox::ResetRole ) {
reset();
} else if ( QDialogButtonBox::Apply ) {
apply();
}
}

QLineEdit and Escape Signal

I want to create a class which is derived from QLineEdit,but I can not assign a signal for Escape button.
The code was working until I add the cancel_signal() and then
LNK2019/LNK1120
errors are appeared.
How can I assign a signal for Escape button?
LineEditAlphaNum.h:
#ifndef _LINEEDIT_ALPHA_NUM_
#define _LINEEDIT_ALPHA_NUM_
#include <QtGui>
class LineEditAlphaNum : public QLineEdit
{
public:
LineEditAlphaNum(QWidget* parent);
void setPrevNextWidget(QWidget* prev, QWidget* next);
protected:
void keyPressEvent(QKeyEvent *);
private:
void keyLogic(QString& str, int key);
int keyIndex;
int lastKey;
QWidget* m_pPrev;
QWidget* m_pNext;
signals:
void cancel_signal();
};
#endif // _LINEEDIT_ALPHA_NUM_
LineEditAlphaNum.cpp:
#include "LineEditAlphaNum.h"
LineEditAlphaNum::LineEditAlphaNum(QWidget *parent) :
QLineEdit(parent),
keyIndex(0),
lastKey(0)
{
}
void LineEditAlphaNum::keyPressEvent(QKeyEvent *e)
{
QString str = text();
switch( e->key() )
{
case Qt::Key_Escape:
emit cancel_signal();
case Qt::Key_Up:
m_pPrev->setFocus(Qt::OtherFocusReason);
break;
case Qt::Key_Down:
m_pNext->setFocus(Qt::OtherFocusReason);
break;
case Qt::Key_Right:
keyIndex = 0;
lastKey = e->key();
break;
case Qt::Key_0:
case Qt::Key_1:
case Qt::Key_2:
case Qt::Key_3:
case Qt::Key_4:
case Qt::Key_5:
case Qt::Key_6:
case Qt::Key_7:
case Qt::Key_8:
case Qt::Key_9:
keyLogic(str, e->key());
break;
case Qt::Key_Backspace:
str.remove(str.size()-1, 1);
break;
default:
break;
}
setText(str);
}
void LineEditAlphaNum::keyLogic(QString& str, int key)
{
char* Keys[] = {"0 ",
"1",
"2ABC",
"3DEF",
"4GHI",
"5JKL",
"6MNO",
"7PQRS",
"8TUV",
"9WXYZ"};
char* keymap = Keys[ key - Qt::Key_0 ];
int length = strlen(keymap);
if ( lastKey == key )
{
keyIndex = (++keyIndex) % length;
str.remove( str.size()-1, 1 );
}
else
{
keyIndex = 0;
lastKey = key;
}
str.append( QChar( keymap[keyIndex] ) );
}
void LineEditAlphaNum::setPrevNextWidget(QWidget* prev, QWidget* next)
{
m_pPrev = prev;
m_pNext = next;
}
You got this linkage error because the signal is a C++ function, which hasn't been defined. This is normally done by the moc, which doesn't generate some code of your class.
Whenever you want to use signals and slots in QObject derived classes or need something else which uses the QMetaObject, you need to add
Q_OBJECT
after the opening curly braces of your class definition.
Note that when you add this macro, you also need to manually run qmake, since the QtCreator tries to be smart and skips this build step if the .pro file hasn't changed. But the pre- or absence of the Q_OBJECT macro has to be considered as a change for qmake, since moc (the meta object compiler) needs to run over all files having a Q_OBJECT macro in the class definition.

How to identify when the current tab is changing in a QTabWidget?

I'm using a QTabWidget and I need a way to handle the change of the current tab before it actually happens, and possibly cancel it if certain conditions are met. The QTabWidget::currentChanged signal is received after the current tab has changed, but is there a QTabWidget::currentChanging signal or another way to achieve the behavior I need?
In my case, I connect SIGNAL and SLOT like this:
//check if user clicked at a tab
connect(ui->tabWidget, SIGNAL(currentChanged(int)), this, SLOT(tabSelected()));
and in tabSelected() function, I check current Tab Index:
void MainWindow::tabSelected(){
if(ui->tabWidget->currentIndex()==0){
// Do something here when user clicked at tab1
}
if(ui->tabWidget->currentIndex()==3){
// Do something here when user clicked at tab4
}
}
This is how I solved it
void MainWindow::on_tabWidget_currentChanged(int index)
{
if (lockTabs) ui->tabWidget->setCurrentIndex(lockedTab);
}
On click of a button, I set lockTabs to true and save the current tab index to lockedTab (int). No matter what tab you click it will just throw you back to the locked tab.
I do agree with the first comment that disabling tabs is the better way tho. This is my solution for disabling tabs:
void MainWindow::lockTabs(int except){
for (int i=0; i<ui->tabWidget->count(); i++) {
if (i!=except) ui->tabWidget->setTabEnabled(i, false);
}
}
void MainWindow::unlockTabs() {
for (int i=0; i<ui->tabWidget->count(); i++) {
ui->tabWidget->setTabEnabled(i, true);
}
}
In your header, declare:
QWidget *saveTab
Create a routine tabChanged have the slot for the currentChanged() signal. Then:
void pkgName::tabChanged
//"ask your question"
if "bad reply"
// This is where you'll "set back to your old tab"
ui->tabWidget->setCurrentWidget(savedWidget)
end if
savedWidget = ui->tabWidget-> getCurrentWidget()
// Process
Using a regular QTabWidget and flipping back to previous tab after currentChanged was emitted if change was forbidden does not look right for user as the new tab is made visible before the previous one is re-selected, this is due to QTabWidget informing the tab "changed", not "is about to change".
One option is to create your own QTabWidget. Thanks to QTabBar, this is pretty obvious.
You'll also need to create QTabWidget like function to use it "like" a QTabWidget, but there's not so many function you'll need.
Here is an example of QTabWidget like class with a aboutToChangeTab signal being emitted informing that tab is about to be changed, one may set allowed to false to forbid tab change.
#pragma once
#include <QWidget>
class QTabBar;
class QStackedWidget;
class SmartTabWidget : public QWidget
{
Q_OBJECT
typedef QWidget baseClass;
public:
SmartTabWidget( QWidget* parent );
int addTab(QWidget* page, const QString& label);
int addTab(QWidget* page, const QIcon& icon, const QString& label);
int currentIndex() const;
QWidget* widget( int index );
signals:
void aboutToChangeTab( QWidget* curTab, QWidget* nextTab, bool* allowed );
private slots:
void tryToChangeTab( int index );
private:
QTabBar* m_tab;
QStackedWidget* m_stack;
};
And:
#include "smart_tab_widget.h"
#include <QTabBar>
#include <QStackedWidget>
#include <QVBoxLayout>
#include <QIcon>
SmartTabWidget::SmartTabWidget( QWidget* widget ) :
baseClass( widget )
{
new QVBoxLayout( this );
layout()->setContentsMargins( 0,0,0,0 );
layout()->addWidget( m_tab = new QTabBar(this) );
layout()->addWidget( m_stack = new QStackedWidget(this) );
connect(m_tab, SIGNAL(currentChanged(int)), this, SLOT(tryToChangeTab(int)));
}
int SmartTabWidget::addTab(QWidget* page, const QString& label)
{
return addTab( page, QIcon(), label );
}
int SmartTabWidget::addTab(QWidget* page, const QIcon& icon, const QString & label)
{
m_stack->addWidget( page );
int index = m_tab->addTab( icon, label );
assert( m_stack->count() == index+1 );
return index;
}
int SmartTabWidget::currentIndex() const
{
return m_tab->currentIndex();
}
QWidget* SmartTabWidget::widget( int index )
{
return m_stack->widget( index );
}
void SmartTabWidget::tryToChangeTab( int index )
{
int currentIndex = m_stack->currentIndex();
bool canChange = true;
emit aboutToChangeTab( m_stack->widget( currentIndex ),
m_stack->widget( index ),
&canChange );
if ( canChange )
{
m_stack->setCurrentIndex( index );
}
else
{
// prevent this function to be called again
bool blocked = m_tab->blockSignals( true );
// unselect requested tab as change is not allowed
m_tab->setCurrentIndex( currentIndex );
m_tab->blockSignals( blocked );
}
}
One can connect aboutToChangeTab to a slot (allowTabChange) and do something like:
void MyWidget::allowTabChange( QWidget* curTab, QWidget* nextTab, bool* allowed )
{
if ( !canChange( curTab, nextTab ) )
*allowed = false;
}
There is a simple solution with event filters that doesn't require to subclass QTabWidget. In my case I needed to disable switching to a particular tab
ui->tabWidget->tabBar()->installEventFilter(this);
then:
bool MainWindow::eventFilter(QObject* watched, QEvent* event)
{
if(watched == ui->tabWidget->tabBar())
{
if(event->type() == QEvent::MouseButtonPress)// || event->type() == QEvent::MouseButtonRelease)
{
auto pos = dynamic_cast<QMouseEvent*>(event)->pos();
auto index = ui->tabWidget->tabBar()->tabAt(pos);
if(ui->tabWidget->widget(index) == ui->addButtonTab)
return true; // cancell event
}
}
return QMainWindow::eventFilter(watched, event);
}
At the stage of mouse click it is possible to retrieve index of a currently selected tab and prepare it for being switched(or cancel switching as done in my example).

Resources