Related
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);
}
I would like to highlight a QFrame, if one of it's child widgets has focus (so the users know where to look for the cursor ;-)
using something along
ui->frame->setFocusPolicy(Qt::StrongFocus);
ui->frame->setStyleSheet("QFrame:focus {background-color: #FFFFCC;}");
highlights the QFrame when I click on it, but it loses its focus once one of its child widgets is selected.
Possible approaches:
I could connect() QApplication::focusChanged(old,now) and check each new object if it is a child of my QFrame, but this gets messy.
I could also subclass each child widget and reimplement focusInEvent()/focusOutEvent() and react on that, but with a lot of different widgets, this is also a lot of work.
Is there a more elegant solution?
Well, you can extend QFrame to make it listen on focus change of its children widgets.
Or you can also install an event filter on children widgets to catch QFocusEvent.
Here is an example:
MyFrame.h
#ifndef MYFRAME_H
#define MYFRAME_H
#include <QFrame>
class MyFrame : public QFrame
{
Q_OBJECT
public:
explicit MyFrame(QWidget* parent = 0, Qt::WindowFlags f = 0);
void hookChildrenWidgetsFocus();
protected:
bool eventFilter(QObject *object, QEvent *event);
private:
QString m_originalStyleSheet;
};
#endif // MYFRAME_H
MyFrame.cpp
#include <QEvent>
#include "MyFrame.h"
MyFrame::MyFrame(QWidget *parent, Qt::WindowFlags f)
: QFrame(parent, f)
{
m_originalStyleSheet = styleSheet();
}
void MyFrame::hookChildrenWidgetsFocus()
{
foreach (QObject *child, children()) {
if (child->isWidgetType()) {
child->installEventFilter(this);
}
}
}
bool MyFrame::eventFilter(QObject *object, QEvent *event)
{
if (event->type() == QEvent::FocusIn) {
setStyleSheet("background-color: #FFFFCC;");
} else if (event->type() == QEvent::FocusOut) {
setStyleSheet(m_originalStyleSheet);
}
return QObject::eventFilter(object, event);
}
MainWindow.cpp
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QLineEdit>
#include "MyFrame.h"
#include "mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent)
{
setWindowTitle(tr("Test"));
MyFrame *frame1 = new MyFrame(this);
frame1->setLayout(new QVBoxLayout());
frame1->layout()->addWidget(new QLineEdit());
frame1->layout()->addWidget(new QLineEdit());
frame1->layout()->addWidget(new QLineEdit());
frame1->hookChildrenWidgetsFocus();
MyFrame *frame2 = new MyFrame(this);
frame2->setLayout(new QVBoxLayout());
frame2->layout()->addWidget(new QLineEdit());
frame2->layout()->addWidget(new QLineEdit());
frame2->layout()->addWidget(new QLineEdit());
frame2->hookChildrenWidgetsFocus();
QHBoxLayout *centralLayout = new QHBoxLayout();
centralLayout->addWidget(frame1);
centralLayout->addWidget(frame2);
QWidget *centralWidget = new QWidget();
centralWidget->setLayout(centralLayout);
setCentralWidget(centralWidget);
}
I believe the both answers you were given are wrong. They work for simple cases but are extremely fragile and clumsy. I believe that the best solution is what you actually suggested in your question. I would go for connecting to QApplication::focusChanged(from, to). You simply connect your main frame object to this signal and in the slot you check if the to object (the one which received focus) is a child of your frame object.
Frame::Frame(...)
{
// ...
connect(qApp, &QApplication::focusChanged, this, &Frame::onFocusChanged);
// ...
}
// a private method of your Frame object
void Frame::onFocusChanged(QWidget *from, QWidget *to)
{
auto w = to;
while (w != nullptr && w != this)
w = w->parentWidget();
if (w == this) // a child (or self) is focused
setStylesheet(highlightedStylesheet);
else // something else is focused
setStylesheet(normalStylesheet);
}
The advantage is obvious. This code is short and clean. You connect only one signal-slot, you do not need to catch and handle events. It responds well to any layout changes done after the object is created. And if you want to optimize away unnecessary redrawing, you should cache the information whether any child is focused and change the stylesheet only and only if this cached value gets changed. Then the solution would be prefect.
First, create a simple subclass of QFrame which reimplements the eventFilter(QObject*, QEvent*) virtual function:
class MyFrame : public QFrame {
Q_OBJECT
public:
MyFrame(QWidget *parent = 0, Qt::WindowFlags f = 0);
~MyFrame();
virtual bool eventFilter(QObject *watched, QEvent *event);
};
Use MyFrame instead of QFrame to contain your widgets. Then, somewhere in your code where you create the widgets contained in MyFrame, install an event filter on those widgets:
// ...
m_myFrame = new MyFrame(parentWidget);
QVBoxLayout *layout = new QVBoxLayout(myFrame);
m_button = new QPushButton("Widget 1", myFrame);
layout->addWidget(m_button);
m_button->installEventFilter(myFrame);
//...
At that point, MyFrame::eventFilter() will be called before any event is delivered to the widget, letting you act on it before the widget is aware of it. Within MyFrame::eventFilter(), return true if you want to filter the event out (i.e. you don't want the widget to process the event), or return false otherwise.
bool MyFrame::eventFilter(QObject *watched, QEvent *event)
{
if (watched == m_button) { // An event occured on m_button
switch (event -> type()) {
case QEvent::FocusIn:
// Change the stylesheet of the frame
break;
case QEvent::FocusOut:
// Change the stylesheet back
break;
default:
break;
}
}
return false; // We always want the event to propagate, so always return false
}
I'm using c++ Qt library and I want to do something which would do :
connect(actionB11, SIGNAL(triggered()), this, SLOT(SetSomething(1, 1)));
connect(actionB12, SIGNAL(triggered()), this, SLOT(SetSomething(1, 2)));
connect(actionB21, SIGNAL(triggered()), this, SLOT(SetSomething(2, 1)));
connect(actionB22, SIGNAL(triggered()), this, SLOT(SetSomething(2, 2)));
The code above doesnt work because SIGNAL function has to have same number and argument types as SLOT function.
Does exist a way how to do it? I dont want to have about 20 function as SetSomething11, SetSomething12 calling SetSomething(1, 1) etc.
In situations like this you have three simple options:
connect each QAction to its own slot (not good)
use a QSignalMapper
add each QAction to a QActionGroup and use the QActionGroup::triggered(QAction*) signal, coupled with setting each QAction's data (see QAction::setData() and QAction::data())
When you set the data for a QAction, you can only store one QVariant (i.e., one value). So if you want two values, I would recommend just creating a simple mapping, like this:
void Window::onActionGroupTriggered(QAction *action);
{
int i = action->data().toInt();
int a, b;
a = i / 10;
b = i - 10;
setSomething(a, b); // for example if i = 15, then a = 1 and b = 5
}
You may modify QAction class.
class QMyAction : public QAction
{
Q_OBJECT
QMyAction ( QObject * parent ) :
QAction(parent), _x(0), _y(0)
{
connect(this, SIGNAL(triggered(bool)), this, SLOT(re_trigger(bool)));
}
QMyAction ( const QString & text, QObject * parent ) :
QAction (text, parent), _x(0), _y(0)
{
connect(this, SIGNAL(triggered(bool)), this, SLOT(re_trigger(bool)));
}
QMyAction ( const QIcon & icon, const QString & text, QObject * parent ) :
QAction(icon, text, parent), _x(0), _y(0)
{
connect(this, SIGNAL(triggered(bool)), this, SLOT(re_trigger(bool)));
}
void setX(int x)
{
_x = x;
}
int getX()
{
return _x;
}
void setY(int y)
{
_y = y;
}
int getY()
{
return _y;
}
public slots:
void re_trigger(bool)
{
emit triggered(_x, _y);
}
signals:
void triggered(int,int);
private:
int _x;
int _y;
};
Now, you can connect triggered(int,int) to SetSomething(int,int). But, you have to set x and y. Unless, they will always be 0.
You cannot use constant in SLOT signature, you have to use types there. When connecting signal to slot the slot must have the same subset of parameters signal has, otherwise they cannot be connected and QObject::connect() will return false.
connect(actionB11, SIGNAL(triggered()),
this, SLOT(SetSomething()));
This slot takes no parameters, but you can use QObject::sender() to get pointer to the object, which emitted the signal. Then this pointer can be used to discriminate the source of the signal:
void SetSomething() {
switch(sender()) {
case actionB11;
// do something
break;
case actionB12;
// do something
break;
case actionB21;
// do something
break;
case actionB22;
// do something
break;
default:
// Exceptional situation
}
}
Alternatively you can use QSignalMapper to append additional discriminating parameters to slots.
For my GUI i would like to have two pairs of buttons that scroll up and down a scrollarea. The first set of buttons should work on say scrollarea1 and the second set of buttons should work on a scrollarea2. The widgets that I put in the scrollarea are called viewport1 and viewport2.
Since both both set of buttons should do the same (scrolling up and down) I thought I would make two slots called scrollUp and scrollDown that would handle the scrolling for both sets of buttons. Unfortunately I cannot make this work and need some help. I have tried the following:
QPushButton up;
QPushButton down;
QPushButton up2;
QPushButton down2;
connect(&up,SIGNAL(clicked()),&up,SLOT(scrollUp()));
connect(&up2,SIGNAL(clicked()),&up,SLOT(scrollUp()));
connect(&down,SIGNAL(clicked()),&down,SLOT(scrollDown()));
connect(&down2,SIGNAL(clicked()),&down,SLOT(scrollDown()));
void MainWindow::scrollDown()
{
QScrollArea area;
QWidget view;
if((QPushButton) &sender = down)
{
area=scrollArea;
view=viewport;
}
if((QPushButton) &sender = down2)
{
area=scrollArea;
view=viewport;
}
int curpos = area.verticalScrollBar()->value();
area.verticalScrollBar()->setValue(curpos+15);
int newpos = area.verticalScrollBar()->value();
QPoint topLeft = area.viewport()->rect().topLeft();
view.move(topLeft.x(),topLeft.y()-(newpos));
}
void MainWindow::scrollUp()
{
QScrollArea area;
QWidget view;
if((QPushButton) &sender = up)
{
area=scrollArea;
view=viewport;
}
if((QPushButton) &sender = up2)
{
area=scrollArea2;
view=viewport2;
}
int curpos = area.verticalScrollBar()->value();
area.verticalScrollBar()->setValue(curpos-15);
int newpos = area.verticalScrollBar()->value();
QPoint topLeft = area.viewport()->rect().topLeft();
view.move(topLeft.x(),topLeft.y()-(newpos));
}
But this doesn´t work for several reasons. I also tried giving the slot some arguments, something like:
connect(&up,SIGNAL(clicked()),&up,SLOT(scrollUp(scrollarea1,viewport1)));
connect(&up2,SIGNAL(clicked()),&up,SLOT(scrollUp(scrollarea2,viewport2)));
But again, no succes. Can anybody help me?
First of all, "It doesn't work" does not mean anything, and it is hard to help you if you do not say what errors you get. Then, there are few problems.
All QObject's derived classes are not copiable, it means you can not do
QWidget a;
QWidget b;
b = a; // Wrong
You should use pointers (or perhaps references).
QWidget a;
QWidget * b = new QWidget(...);
QWidget * c;
c = & a; // Ok
c = b; // Ok
Then your connect calls are wrong:
connect(&up, SIGNAL(clicked()), &up, SLOT(scrollUp()));
The third argument is the object who has the slot. up is a QPushButton, it does not have a scrollUp() slot, it is your MainWindow who does:
connect(&up, SIGNAL(clicked()), this, SLOT(scrollUp()));
(since connect is called in MainWindow's constructor this points to the current MainWindow object).
Also in C++ the single = sign means assignment, for equality comparison use =='. Andsender` is a function.
Your approach should work if implemented in the right way:
class MainWindow: public QWidget
{
QScrollArea * scroll1;
QScrollArea * scroll2;
QWidget * view1;
QWidget * view2;
QPushButton * up1;
QPushButton * up2;
QPushButton * down1;
QPushButton * down2;
public:
MainWindow()
{
// Here initialize member variables.
...
connect(up1, SIGNAL(clicked()), this, SLOT(scrollUp()));
connect(up2, SIGNAL(clicked()), this, SLOT(scrollUp()));
connect(down1, SIGNAL(clicked()), this, SLOT(scrollDown()));
connect(down2, SIGNAL(clicked()), this, SLOT(scrollDown()));
}
public slots:
void scrollDown()
{
QScrollArea * area;
QWidget * view;
if(qobject_cast<QPushButton>(sender()) == down1) {
area = & scroll1;
view = & view1;
} else if(qobject_cast<QPushButton>(sender()) == down2) {
area = & scroll2;
view = & view2;
} else {
// Error.
}
// Now `area` and `view` point to the right widgets.
...
}
void scrollUp()
{
// The same as before.
}
};
Another approach would be to extract the actual scrolling instructions to a separate function:
class MainWindow: public QWidget
{
// Same variables as before
...
public:
MainWindow()
{
// Here initialize member variables.
...
connect(up1, SIGNAL(clicked()), this, SLOT(scrollUp1()));
connect(up2, SIGNAL(clicked()), this, SLOT(scrollUp2()));
connect(down1, SIGNAL(clicked()), this, SLOT(scrollDown1()));
connect(down2, SIGNAL(clicked()), this, SLOT(scrollDown2()));
}
public slots:
void scrollDown(QScrollArea * area, QWidget * view)
{
// Here you scroll over `area` and `view`.
}
void scrollDown1()
{
scrollDown(scroll1, area1);
}
void scrollDown2()
{
scrollDown(scroll2, area2);
}
// Again, the same for `scrollUp`.
};
There are several mistakes in your code :
About the sender of the signal : There is not a QObject called "sender" but a method QObject * QObject::sender() const; which returns a pointer on the sender of the signal.
In the if conditions : you are casting a QPushButton** into a QPushButton ((QPushButton) &sender) and you dont compare that thing with your buttons up(2) and down(2).
In your connections between slots and signals : the scrollUp and scrollDown slots do not belong to the QPushButton class but to your MainWindow class.
Finally, you should write something like this :
connect(&up, SIGNAL(clicked()), this, SLOT(scrollUp()));
connect(&up2, SIGNAL(clicked()), this, SLOT(scrollUp()));
connect(&down, SIGNAL(clicked()), this, SLOT(scrollDown()));
connect(&down2, SIGNAL(clicked()), this, SLOT(scrollDown()));
void MainVindow::scrollDown() {
// [...]
QPushButton * senderButton = qobject_cast<QPushButton *>(this->sender());
// QPushButton * senderButton = (QPushButton *) this->sender(); works too
if (senderButton == &down) {
// [...]
}
if (senderButton == &down2) {
// [...]
}
// [...]
}
void MainVindow::scrollUp() {
// [...]
QPushButton * senderButton = qobject_cast<QPushButton *>(this->sender());
// QPushButton * senderButton = (QPushButton *) this->sender(); works too
if (senderButton == &up) {
// [...]
}
if (senderButton == &up2) {
// [...]
}
// [...]
}
First of all the slot can have no other arguments than the signal hands to it. Clicked has no arguments and there fore the slot can have no arguments.
I would think that the easiest way to check whether scrollArea 1 or 2 has focus and decide from that which one should move.
I also think that there is an error in your code. Shouldn't this:
if((QPushButton) &sender = down2)
{
area=scrollArea;
view=viewport;
}
Be this:
if((QPushButton) &sender = down2)
{
area=scrollArea2;
view=viewport2;
}
First of all, this is pseudo code. It won't compile, but it should contain the necessary information.
I believe this problem can be most elegantly solved using the QSignalMapper class. It allows parameterless signals from multiple senders to connect to one slot.
In the header, write something like this:
class QSignalMapper;
class MainWindow : public QMainWindow
{
public:
void init();
public slots:
void handleScrollButtons(int id);
private:
enum { ScrollUp1, ScrollDown1, ScrollUp2, ScrollDown2 } // just makes it more convenient to use
QSignalMapper *m_scrollbuttonhandler;
}
In the source file, write something like this
#include <QSignalMapper>
void MainWindow::init()
{
m_scrollbuttonhandler = new QSignalMapper(this);
m_scrollbuttonhandler->setMapping(scrollup1button, ScrollUp1);
m_scrollbuttonhandler->setMapping(scrolldown1button, ScrollDown1);
m_scrollbuttonhandler->setMapping(scrollup2button, ScrollUp2);
m_scrollbuttonhandler->setMapping(scrolldown2button, ScrollDown2);
connect(scrollup1button, SIGNAL(clicked(bool)), m_scrollbuttonhandler, SLOT(map()));
connect(scrolldown1button, SIGNAL(clicked(bool)), m_scrollbuttonhandler, SLOT(map()));
connect(scrollup2button, SIGNAL(clicked(bool)), m_scrollbuttonhandler, SLOT(map()));
connect(scrolldown2button, SIGNAL(clicked(bool)), m_scrollbuttonhandler, SLOT(map()));
connect(m_scrollbuttonhandler, SIGNAL(mapped(int)), this, SLOT(handleScrollButtons(int)));
}
void MainWindow::handleScrollButtons(int id)
{
switch (id)
{
case ScrollUp1:
// stuff to do for scrollup1button
case ScrollDown1:
// stuff to do for scrolldown1button
case ScrollUp2:
// stuff to do for scrollup2button
case ScrollDown2:
// stuff to do for scrolldown2button
}
}
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).