QGraphicsScene, QTextEdit and lost focus - c++

QTextEdit and similar widgets embedded in QGraphicsScene lose focus after using standard context menu (copy/paste), i. e. you need to click on QTextEdit again to continue editing. Scene emits focusItemChanged with newFocusItem==0.
First question: Is it a bug or standard behavior?
My investigation shows that function QGraphicsItemPrivate::setVisibleHelper() clears focus here:
if (hasFocus && scene) {
// Hiding the focus item or the closest non-panel ancestor of the focus item
QGraphicsItem *focusItem = scene->focusItem();
bool clear = true;
if (isWidget && !focusItem->isPanel()) {
do {
if (focusItem == q_ptr) {
clear = !static_cast<QGraphicsWidget *>(q_ptr)->focusNextPrevChild(true);
break;
}
} while ((focusItem = focusItem->parentWidget()) && !focusItem->isPanel());
}
if (clear)
clearFocusHelper(/* giveFocusToParent = */ false, hiddenByPanel);
}
QGraphisItem has undocumented (internal) flag QGraphicsItem::ItemIsFocusScope. If the flag is set for QTextEdit's proxy-item it gets focus back after menu, but in any case focus cleared at first and after that Item receives it again or not.
Second Question: What is flag QGraphicsItem::ItemIsFocusScope for?

Looks like QGraphicsItem::ItemIsFocusScope is for FocusScope QML item. QtQuick1 is QGraphicsScene based and used that flag.
I'm not sure about side effects but that helps:
auto edit = new QLineEdit();
auto item = scene->addWidget(edit);
item->setFlag(QGraphicsItem::GraphicsItemFlag::ItemIsPanel);
Tested on Qt 5.9, Linux
EDIT
For me looks as bug:
add QLineEdit to scene
click to focus QLineEdit
hit ContextMenu key to show context menu
hit Esc key to exit context menu
try to type
Expected: QLineEdit is focused and text appears
Actual: QLineEdit lost input focus
Please find it or report with Qt bug tracker
So it's OK to have workaround using QGraphicsItem::ItemIsFocusScope flag for example.
#if (QT_VERSION < QT_VERSION_CHECK(<fixed in Qt version>))
// it's workaround of bug QTBUG-...
# if (QT_VERSION == QT_VERSION_CHECK(<version you are develop with>)
item.setFlag(QGraphicsItem::ItemIsFocusScope);
# else
# error("The workaround is not tested on this version of Qt. Please run tests/bug_..._workaround_test")
# endif

Related

Close button only on active tab of QTabWidget

To save space in a QTabWidget, I would like to show the close icon only for the current tab, like e.g. Firefox is doing:
Is there a simple way using a style sheet, some thing like (not working like this)
QTabBar::tab::!selected::close-button {visible: false;}
or do I have to subclass QTabWidget to get the desired behavior?
You won't need to subclass anything, you can use QTabWidget::tabBar() method to obtain a reference (i.e. QTabBar *) to the tab bar associated with your QTabWidget. (Note that this method is no longer protected, so it can be accessed without subclassing the class)
QTabBar *tabBar = tabWidget->tabBar();
You can now use tabBar reference to hide close buttons on non-current tabs. For example to hide ith button, you can do:
tabBar->tabButton(i, QTabBar::RightSide)->hide();
So a simple workflow could be as follows:
Connect QTabWidget::currentChanged(int index) signal to a slot.
In that slot hide all close buttons other than the button at index.
You can subclass QTabWidget to get access to the QTabBar widget using protected method QTabWidget::tabBar. Then you can connect to the QTabBar::currentChanged signal and hide close button for not selected tabs manually:
QTabBar::ButtonPosition closeSide =
(QTabBar::ButtonPosition)style()->styleHint(QStyle::SH_TabBar_CloseButtonPosition, 0, this);
for (int i = 0; i < toolbar->count(); ++i)
{
if (i != toolbar->currentIndex())
{
QWidget *w = toolbar->tabButton(i, closeSide);
w->hide();
}
}
hide() leaves empty space for the invisible close button. This looks funny.
Set the width to 0 instead.

No icon with QStyleOptionButton

I have got a weird problem.
I need to have some buttons in my QTableView. I used to use QAbstractItemView::setIndexWidget() method, but it is not very responsible when working with larger models. Therefore I decided to switch to QStyledItemDelegate. My buttons have icons (and icons only, no text). When working with setIndexWidget, I used the following code:
ClientDetailsButton::ClientDetailsButton(const Client* _client,
QWidget* _parent) :
QPushButton("", _parent),
__current(_client) {
setIcon(QIcon(":/uiIcons/button-details.png"));
}
And it worked perfectly. But when I switch to delegate, I use it like that:
QStyleOptionButton button;
button.rect = _option.rect;
button.text.clear();
button.icon = QIcon(":/uiIcons/button-details.png");
button.state = _option.state | QStyle::State_Enabled;
if (_index == __button)
button.state |= QStyle::State_Sunken;
QApplication::style()->drawControl(QStyle::CE_PushButton, &button, _painter);
The button itself is fine, but its empty. There is no icon visible. Suprisingly, when I use, for example:
button.icon = QIcon::fromTheme("dialog-information", QIcon(":/uiIcons/button-details.png"));
the theme icon is visible. But if Qt cannot find the theme icon, the replacement is still blank. I tried everything I could think of and have no idea why it doesn't work. Anyone has any ideas?
I solved this problem by setting button.iconSize=QSize(16,16); as the default iconsize is (-1,-1) so the icon is invisible.

How to avoid mouse click on one widget triggering the signals in others in Qt?

In my app I'm using a QTableView to show a list of images and I select some of the images by clicking left mouse button and pressing control keyboard button when I do so the app looks as the below stated image:
But then when I try to use other buttons on the app like "Destination" and then try to select a destination folder then the app looks like this below:
Problem occurs when I click the "select folder" button and try to select the folder. What happens is that click on the folder selection tab, triggers QTableView widget in which I show the image and the deselects all the selected images. I want to avoid it. The way I now track the left mouse button clicks on QTableView widget is as below:
bool MainWindow::eventFilter(QObject* obj, QEvent *ev)
{
if(obj == ui->listOfImages->viewport())
{
QMouseEvent * mouseEv = static_cast<QMouseEvent*>(ev);
if((mouseEv->buttons() & Qt::LeftButton) && (QApplication::keyboardModifiers().testFlag(Qt::ControlModifier) == true))
{
controlButtonCounter++;
fetch = true;
return QObject::eventFilter(obj,ev);
}
else if((mouseEv->buttons() & Qt::LeftButton) && (QApplication::keyboardModifiers().testFlag(Qt::ControlModifier) == false))
{
if(selectedImages.size()>0)
{
ui->listOfImages->clearSelection();
selectedImages.clear();
}
fetch = false;
controlButtonCounter = 0;
}
}
return QObject::eventFilter(obj,ev);
}
Here ui->listOfImages is the QTableView widget. Other things like controlButtonCounter are irrelevant in taking the mouse clicks, I use it for other purposes.
Please say me how I can avoid triggering QTableView widget when I'm pressing on other things that fall in the same region as the QTableView.
if(obj = ui->listOfImages->viewport())
You are not doing a comparison there, you are assigning a value to the obj variable.
It should be like this:
if(obj == ui->listOfImages->viewport())
I'm not sure but maybe it can help you:
void setWindowModality(Qt::WindowModality windowModality)
This property holds which windows are blocked by the modal widget.
This property only makes sense for windows. A modal widget prevents widgets in other windows from getting input. The value of this property controls which windows are blocked when the widget is visible. Changing this property while the window is visible has no effect; you must hide() the widget first, then show() it again.
By default, this property is Qt::NonModal.

How to make a QPushButton pressable for enter key?

I want to make my app laptop friendly. I can tab to everywhere, but if I tab to a QPushButton I can't press it with Enter, only with space.
What's the problem? How to make it pressable for Enter?
tl;dr
In the Qt Creator's UI view, select the button you want to make pressable for Enter.
In the right side at the Property Editor scroll down to the blue part titled QPushButton.
Check the checkbox by autoDefault or default.
Most of the cases the main different between autoDefault and default is how the button will be rendered. But there are cases where it can cause unexpected things so for more information read more below.
Full review
Overview
Every QPushButton has 3 properties that are not inherited. From these, two (default and autoDefault) have a major role when we place buttons on QDialogs, because these settings (and the focus on one of the buttons) decides which button will be pressed if we hit Enter.
All of these properties are set false by default. Only expection is autoDefault that will be true if the button has a QDialog parent.
Every time you press space the button with the focus on it will be pressed. The followings will describe what happens if you press Enter.
Default property
If this is set true, the button will be a default button.
If Enter is pressed on the dialog, than this button will be pressed, except when the focus is on an autoDefault button.
There should be only one default button. If you add more then the last one added will be the default button.
AutoDefault property
If this is set true, the button will be an autoDefault button.
If Enter is pressed on the dialog, than this button will be pressed if the focus is on it.
If the focus is not on an autoDefault button and there is no default button than the next autoDefault button will be pressed for Enter.
Flat property
If this is set true, then the border of the button will not be raised.
Example tables
The following tables show which button will be pressed with different buttons on different focus. The buttons are added from left to right.
Test code
The following code is a way to add buttons to a dialog. It can be used for testing by changing the boolean values at setDefault() and/or setAutoDefault().
You just need to create a window, add a QPushButton called pushButton and a QLabel called label (that is the default naming). Don't forget to #include <QMessageBox>. Then copy this code to the button's clicked() signal:
void MainWindow::on_pushButton_clicked()
{
QMessageBox msgBox;
QPushButton button("Button");
button.setDefault(false);
button.setAutoDefault(false);
msgBox.addButton(&button, QMessageBox::ActionRole);
QPushButton autodefaultbutton("AutoDefault Button");
autodefaultbutton.setDefault(false);
autodefaultbutton.setAutoDefault(true);
msgBox.addButton(&autodefaultbutton, QMessageBox::ActionRole);
QPushButton autodefaultbutton2("AutoDefault Button2");
autodefaultbutton2.setDefault(false);
autodefaultbutton2.setAutoDefault(true);
msgBox.addButton(&autodefaultbutton2, QMessageBox::ActionRole);
QPushButton defaultbutton("Default Button");
defaultbutton.setDefault(true);
defaultbutton.setAutoDefault(false);
msgBox.addButton(&defaultbutton, QMessageBox::ActionRole);
msgBox.exec();
if (msgBox.clickedButton() == &button) {
ui->label->setText("Button");
} else if (msgBox.clickedButton() == &defaultbutton) {
ui->label->setText("Default Button");
} else if (msgBox.clickedButton() == &autodefaultbutton) {
ui->label->setText("AutoDefault Button");
} else if (msgBox.clickedButton() == &autodefaultbutton2) {
ui->label->setText("AutoDefault Button2");
}
}
Display
If you compile the code you can get this window. You doesn't even have to click to the buttons because the way they are rendered by the OS shows which one will be pressed if you hit Enter or space.
Official documentation
Most of this answer was made according to the official documentation.
The QPushButton documentation made by Qt states these:
Default and autodefault buttons decide what happens when the user
presses enter in a dialog.
A button with this property set to true (i.e., the dialog's default
button,) will automatically be pressed when the user presses enter,
with one exception: if an autoDefault button currently has focus, the
autoDefault button is pressed. When the dialog has autoDefault buttons
but no default button, pressing enter will press either the
autoDefault button that currently has focus, or if no button has
focus, the next autoDefault button in the focus chain.
In a dialog, only one push button at a time can be the default button.
This button is then displayed with an additional frame (depending on
the GUI style).
The default button behavior is provided only in dialogs. Buttons can
always be clicked from the keyboard by pressing Spacebar when the
button has focus.
If the default property is set to false on the current default button
while the dialog is visible, a new default will automatically be
assigned the next time a pushbutton in the dialog receives focus.
It's also worth to check QDialog and QMessageBox.
According to Qt's documentation Enter should work
Command buttons in dialogs are by default auto-default buttons, i.e. they become the default push button automatically when they receive the keyboard input focus. A default button is a push button that is activated when the user presses the Enter or Return key in a dialog. You can change this with setAutoDefault().
http://qt-project.org/doc/qt-4.8/qpushbutton.html
totymedli's answer is great. I added a small program to test various combinations of isDefault, autoDefault, setDefault and setAutoDefault functions.
import sys
from PyQt4.QtGui import *
from PyQt4.QtCore import *
class Window(QDialog):
def __init__(self, parent=None):
super(Window, self).__init__(parent)
autoDefaultInitialState = True
defaultInitialState = False
self.lineEdit1 = QLineEdit(self)
self.lineEdit2 = QLineEdit(self)
self.lineEdit3 = QLineEdit(self)
# if we create a new button (e.g. "Print state"), with the same function,
# it doesn't work, because adding a new button (apart from our 3 buttons)
# produces total mess, so we use this lineedit for this purpose
self.lineEdit1.returnPressed.connect(self.printState)
#------------------------------------
self.chkAutoDefaultOk = QCheckBox('OK setAutoDefault', self)
self.chkAutoDefaultCancel = QCheckBox('Cancel setAutoDefault', self)
self.chkAutoDefaultApply = QCheckBox('Apply setAutoDefault', self)
self.chkDefaultOk = QCheckBox('OK setDefault', self)
self.chkDefaultCancel = QCheckBox('Cancel setDefault', self)
self.chkDefaultApply = QCheckBox('Apply setDefault', self)
self.chkAutoDefaultOk.setChecked(autoDefaultInitialState)
self.chkAutoDefaultCancel.setChecked(autoDefaultInitialState)
self.chkAutoDefaultApply.setChecked(autoDefaultInitialState)
self.chkDefaultOk.setChecked(defaultInitialState)
self.chkDefaultCancel.setChecked(defaultInitialState)
self.chkDefaultApply.setChecked(defaultInitialState)
#------------------------------------
self.pushButtonOk = QPushButton(self)
self.pushButtonOk.setText("Ok")
self.pushButtonOk.clicked.connect(lambda : print('ok'))
self.pushButtonCancel = QPushButton(self)
self.pushButtonCancel.setText("Cancel")
self.pushButtonCancel.clicked.connect(lambda : print('cancel'))
self.pushButtonApply = QPushButton(self)
self.pushButtonApply.setText("Apply")
self.pushButtonApply.clicked.connect(lambda : print('apply'))
#------------------------------------
self.pushButtonOk.setAutoDefault(autoDefaultInitialState)
self.pushButtonCancel.setAutoDefault(autoDefaultInitialState)
self.pushButtonApply.setAutoDefault(autoDefaultInitialState)
self.pushButtonOk.setDefault(defaultInitialState)
self.pushButtonCancel.setDefault(defaultInitialState)
self.pushButtonApply.setDefault(defaultInitialState)
#------------------------------------
self.chkAutoDefaultOk.stateChanged.connect(self.chkChangeState)
self.chkAutoDefaultCancel.stateChanged.connect(self.chkChangeState)
self.chkAutoDefaultApply.stateChanged.connect(self.chkChangeState)
self.chkDefaultOk.stateChanged.connect(self.chkChangeState)
self.chkDefaultCancel.stateChanged.connect(self.chkChangeState)
self.chkDefaultApply.stateChanged.connect(self.chkChangeState)
#------------------------------------
self.layout = QGridLayout(self)
self.layout.addWidget(self.lineEdit1, 0, 0, 1, 3)
self.layout.addWidget(self.lineEdit2, 1, 0, 1, 3)
self.layout.addWidget(self.lineEdit3, 2, 0, 1, 3)
self.layout.addWidget(self.chkAutoDefaultOk, 3, 0)
self.layout.addWidget(self.chkAutoDefaultCancel, 3, 1)
self.layout.addWidget(self.chkAutoDefaultApply, 3, 2)
self.layout.addWidget(self.chkDefaultOk, 4, 0)
self.layout.addWidget(self.chkDefaultCancel, 4, 1)
self.layout.addWidget(self.chkDefaultApply, 4, 2)
self.layout.addWidget(self.pushButtonOk, 5, 0)
self.layout.addWidget(self.pushButtonCancel, 5, 1)
self.layout.addWidget(self.pushButtonApply, 5, 2)
def chkChangeState(self):
obj = self.sender()
if (obj == self.chkAutoDefaultOk):
self.pushButtonOk.setAutoDefault(self.chkAutoDefaultOk.isChecked())
elif (obj == self.chkAutoDefaultCancel):
self.pushButtonCancel.setAutoDefault(self.chkAutoDefaultCancel.isChecked())
elif (obj == self.chkAutoDefaultApply):
self.pushButtonApply.setAutoDefault(self.chkAutoDefaultApply.isChecked())
elif (obj == self.chkDefaultOk):
self.pushButtonOk.setDefault(self.chkDefaultOk.isChecked())
elif (obj == self.chkDefaultCancel):
self.pushButtonCancel.setDefault(self.chkDefaultCancel.isChecked())
elif (obj == self.chkDefaultApply):
#print('sender: self.chkDefaultApply')
#print('before: ', self.pushButtonApply.isDefault())
self.pushButtonApply.setDefault(self.chkDefaultApply.isChecked())
#print('after: ', self.pushButtonApply.isDefault())
def printState(self):
print(self.pushButtonOk.autoDefault(), self.pushButtonCancel.autoDefault(), self.pushButtonApply.autoDefault())
print(self.pushButtonOk.isDefault(), self.pushButtonCancel.isDefault(), self.pushButtonApply.isDefault())
print('-' * 50)
app = QApplication(sys.argv)
main = Window()
main.show()
sys.exit(app.exec_())
Set the tab order for your widgets. This will allow usage of return key for clicking. Its in there by default inside Qt.

Fail to clear QLineEdit after selecting item from QCompleter

using PopupCompletion mode when you select an item (using arrow keys) and press return - lineEdit should become empty (i clear lineEdit when return is pressed), but lineEdit does not become empty. (If you press 'Enter' again it will empty the lineEdit). So i think pressing return does clear lineEdit, but pressing return also tells QCompleter to insert selected item into lineEdit, so it seems like nothing happens.
But, if you click the item insted of selecting it with arrows - everything works fine.
I tried to find the solution on the internet, but i found only one person that had the same problem: http://lists.trolltech.com/qt-interest/2006-10/thread00985-0.html . Sadly there are no answers. Please read his question because it will help understand my problem.
How can I clean LineEdit after QCompleter inserted selected item? (catching activated signal does not help)
The issue here is that the completer actually contains a pop-up, which is actually a separate QAbstractItemView widget (refer to the QCompleter::popup() documentation). As such, when you press 'Enter' on the QCompleter, the key event actually goes to the pop-up and not the line edit.
There are two different ways to resolve your issue:
Option 1
Connect the completer's activated signal to the line edit's clear slot, but do it as a QueuedConnection:
QObject::connect(completer, SIGNAL(activated(const QString&)),
lineEdit, SLOT(clear()),
Qt::QueuedConnection);
The reason why using a direct connection doesn't work is because your are essentially dependent on the order in which slots get called from a signal. Using a QueuedConnection gets around this. From a code maintenance standpoint, I don't really prefer this solution because it isn't clear what your intention is just by looking at the code.
Option 2
Write an event filter around the pop-up to filter out the 'Enter' key to clear the line edit explicitly. Your event filter would end up looking something like this:
class EventFilter : public QObject
{
Q_OBJECT
public:
EventFilter(QLineEdit* lineEdit, QObject* parent = NULL)
:QObject(parent)
,mLineEdit(lineEdit)
{ }
virtual ~EventFilter()
{ }
bool eventFilter(QObject* watched, QEvent* event)
{
QAbstractItemView* view = qobject_cast<QAbstractItemView*>(watched);
if (event->type() == QEvent::KeyPress)
{
QKeyEvent* keyEvent = dynamic_cast<QKeyEvent*>(event);
if (keyEvent->key() == Qt::Key_Return ||
keyEvent->key() == Qt::Key_Enter)
{
mLineEdit->clear();
view->hide();
return true;
}
}
return false;
}
private:
QLineEdit* mLineEdit;
};
You would then install the event filter on the completer's pop-up:
EventFilter* filter = new EventFilter(lineEdit);
completer->popup()->installEventFilter(filter);
This option is more work, but it's clearer as to what you are doing. Moreover, you can perform additional customization this way, if you prefer.