Limit QKeySequence/QKeySequenceEdit to only one shortcut - c++

Is it possible to limit QKeySequence to show only one shortcut in QKeySequenceEdit? Currently now it supports up to 4 shortcuts. My application supports key sequences of only one shortcut, e.g. Ctrl+A or Ctrl+C and not e.g. Ctrl+A, D or Ctrl+C, X, Z.
Is it possible to limit QKeySequence or QKeySequenceEdit to just one key sequence?

Solved it, not the best solution but quick...If you want something more customize, I think you have to build it yourself...
customkeysequenceedit.h:
#ifndef CUSTOMKEYSEQUENCEEDIT_H
#define CUSTOMKEYSEQUENCEEDIT_H
#include <QKeySequenceEdit>
class QKeyEvent;
class CustomKeySequenceEdit : public QKeySequenceEdit
{
Q_OBJECT
public:
explicit CustomKeySequenceEdit(QWidget *parent = 0);
~CustomKeySequenceEdit();
protected:
void keyPressEvent(QKeyEvent *pEvent);
};
#endif // CUSTOMKEYSEQUENCEEDIT_H
customkeysequenceedit.cpp:
#include "customkeysequenceedit.h"
#include <QKeyEvent>
CustomKeySequenceEdit::CustomKeySequenceEdit(QWidget *parent) : QKeySequenceEdit(parent) { }
CustomKeySequenceEdit::~CustomKeySequenceEdit() { }
void CustomKeySequenceEdit::keyPressEvent(QKeyEvent *pEvent)
{
QKeySequenceEdit::keyPressEvent(pEvent);
QKeySequence seq(QKeySequence::fromString(keySequence().toString().split(", ").first()));
setKeySequence(seq);
}

You can use the [] operator of QKeySequence: http://doc.qt.io/qt-5/qkeysequence.html#operator-5b-5d
So in your interface constructor, write this:
connect(ui->editShortcut, &QKeySequenceEdit::editingFinished,
this, &dialog::truncateShortcut);
And add this private method to your dialog class:
void dialog::truncateShortcut()
{
int value = ui->editShortcut->keySequence()[0];
QKeySequence shortcut(value);
ui->editShortcut->setKeySequence(shortcut);
}
Doing that, you fully respect the API and don't depend on the , character, which is quite risky.

Most answers is to truncate shortcut after the input done. Anyway, it will show more than one shorcut in the process of inputing, which is kind of annoying.
I found a solutioin that will not even show more than one shorcut.
After one shortcut is inputed, finish the input via clear focus and setKeySequence via override QKeySequenceEdit class keyPressEvent function.
What's more, this method is very easy and graceful!
First create a class myKeySequenceEdit inheriated from QKeySequenceEdit, and below is the codes:
mykeysequenceedit.h:
#ifndef MYKEYSEQUENCEEDIT_H
#define MYKEYSEQUENCEEDIT_H
#include <QKeySequenceEdit>
#include <QWidget>
class myKeySequenceEdit : public QKeySequenceEdit
{
Q_OBJECT
public:
myKeySequenceEdit(QWidget *parent = nullptr);
void keyPressEvent(QKeyEvent *) override;
};
#endif // MYKEYSEQUENCEEDIT_H
mykeysequenceedit.cpp:
#include "mykeysequenceedit.h"
myKeySequenceEdit::myKeySequenceEdit(QWidget *parent) : QKeySequenceEdit(parent) {}
void myKeySequenceEdit::keyPressEvent(QKeyEvent *event)
{
QKeySequenceEdit::keyPressEvent(event);
if (this->keySequence().count() > 0) {
QKeySequenceEdit::setKeySequence(this->keySequence());
emit editingFinished(); // Optinal, depend on if you need the editingFinished signal to be triggered
}
}

My take on this: why would we like to wait in edit mode, if just singular shortcut is wanted. Thus interrupting edit immediately on success:
inline bool QKeySequence_valid( const QKeySequence& accelerator ){
return !accelerator.isEmpty() && accelerator[0] != Qt::Key_unknown;
}
class Single_QKeySequenceEdit : public QKeySequenceEdit
{
protected:
void keyPressEvent( QKeyEvent *e ) override {
QKeySequenceEdit::keyPressEvent( e );
if( QKeySequence_valid( keySequence() ) )
editingFinished();
}
};
Real implementation, because i was struggling to find one of this sort:
class Single_QKeySequenceEdit : public QKeySequenceEdit
{
protected:
void keyPressEvent( QKeyEvent *e ) override {
QKeySequenceEdit::keyPressEvent( e );
if( QKeySequence_valid( keySequence() ) )
clearFocus(); // trigger editingFinished(); via losing focus 🙉
// because this can still receive focus loss b4 getting deleted (practically because modal msgbox)
// and two editingFinished(); b no good
}
void focusOutEvent( QFocusEvent *event ) override {
editingFinished();
}
bool event( QEvent *event ) override { // comsume ALL key presses including Tab
if( event->type() == QEvent::KeyPress ){
keyPressEvent( static_cast<QKeyEvent*>( event ) );
return true;
}
return QKeySequenceEdit::event( event );
}
};
void accelerator_edit( QTreeWidgetItem *item ){
auto edit = new Single_QKeySequenceEdit;
QObject::connect( edit, &QKeySequenceEdit::editingFinished, [item, edit](){
const QKeySequence accelerator = edit->keySequence();
item->treeWidget()->setItemWidget( item, 1, nullptr );
if( QKeySequence_valid( accelerator ) )
accelerator_alter( item, accelerator );
} );
item->treeWidget()->setItemWidget( item, 1, edit );
edit->setFocus(); // track sanity gently via edit being focused property
}

Related

Qt highlighting selected line overwrites highlighting of individual words

I have a QPlainTextEdit where I want to highlight the current line the user is on as well as all words similar to the word the user selected. This word highlight works fine on all lines except the currently selected one, because the "selected line" background style overrides the "selected word" style applied to the selected words.
My question is how can I make sure the word highlighing is done after the line highlight is done, so that they can both be active at the same time?
Screenshot to illustrate:
The yellow line is the current line being highlighted. The first 'test' is selected, so all others should have the lightblue background applied. All except the 'test' on the highlighted line do.
Minimal reproducible example:
MainWindow.h
#pragma once
#include <QtWidgets/QMainWindow>
#include "ui_mainWindow.h"
#include "TextEditor.h"
class mainWindow : public QMainWindow
{
Q_OBJECT
public:
mainWindow(QWidget *parent = Q_NULLPTR) : QMainWindow(parent)
{
ui.setupUi(this);
auto textEdit = new TextEditor(this);
textEdit->setPlainText("test lorem ipsum test\n test dolor sit test\test amet test");
ui.tabWidget->addTab(textEdit, "Editor");
}
private:
Ui::mainWindowClass ui;
};
TextEditor.h
#pragma once
#include <QPlainTextEdit>
class TextEditor : public QPlainTextEdit
{
Q_OBJECT
public:
TextEditor(QWidget* parent) : QPlainTextEdit(parent)
{
connect(this, &QPlainTextEdit::selectionChanged, this, &TextEditor::selectChangeHandler);
connect(this, &QPlainTextEdit::cursorPositionChanged, this, &TextEditor::highlightCurrentLine);
}
private:
std::vector<std::pair<int, QTextCharFormat>> highlightedWords_;
//Highlights the current line
void highlightCurrentLine()
{
QList<QTextEdit::ExtraSelection> extraSelections;
if (!isReadOnly())
{
QTextEdit::ExtraSelection selection;
selection.format.setBackground(Qt::yellow);
selection.format.setProperty(QTextFormat::FullWidthSelection, true);
selection.cursor = textCursor();
selection.cursor.clearSelection();
extraSelections.append(selection);
}
setExtraSelections(extraSelections);
}
//Highlights all words similar to the currently selected word
void selectChangeHandler()
{
//Unset previous selection
resetHighlightedWords();
//Ignore empty selections
if (textCursor().selectionStart() >= textCursor().selectionEnd())
return;
//We only care about fully selected words (nonalphanumerical characters on either side of selection)
auto plaintext = toPlainText();
auto prevChar = plaintext.mid(textCursor().selectionStart() - 1, 1).toStdString()[0];
auto nextChar = plaintext.mid(textCursor().selectionEnd(), 1).toStdString()[0];
if (isalnum(prevChar) || isalnum(nextChar))
return;
auto qselection = textCursor().selectedText();
auto selection = qselection.toStdString();
//We also only care about selections that do not themselves contain nonalphanumerical characters
if (std::find_if(selection.begin(), selection.end(), [](char c) { return !isalnum(c); }) != selection.end())
return;
//Highlight all matches of the given word in the editor
blockSignals(true);
highlightWord(qselection);
blockSignals(false);
}
//Removes highlight from selected words
void resetHighlightedWords()
{
if (highlightedWords_.empty())
return;
blockSignals(true);
auto cur = textCursor();
for (const auto& [index, oldFormat] : highlightedWords_)
{
cur.setPosition(index);
cur.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor, 1);
cur.setCharFormat(oldFormat);
}
blockSignals(false);
highlightedWords_.clear();
}
//Applies the highlight style to all appearances of the given word
void highlightWord(const QString& word)
{
auto plaintext = toPlainText();
//Prepare text format
QTextCharFormat format;
format.setBackground(QColor::fromRgb(0x70, 0xED, 0xE0));
//Find all words in our document that match the selected word and apply the background format to them
size_t pos = 0;
auto reg = QRegExp("\\b" + word + "\\b");
auto cur = textCursor();
auto index = reg.indexIn(plaintext, pos);
while (index >= 0)
{
//Select matched text
cur.setPosition(index);
//Save old text style
highlightedWords_.push_back(std::make_pair(index, cur.charFormat()));
//Apply format
cur.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor, 1);
cur.mergeCharFormat(format);
//Move to next match
auto len = (size_t)reg.matchedLength();
pos = index + (size_t)reg.matchedLength();
index = reg.indexIn(plaintext, pos);
}
}
};
Avoiding inheritance as a first resort was the right thing to do IMO, but in this particular case, it may be the simplest approach.
#include <QPainter>
TextEditor::TextEditor( QWidget* parent ) : QPlainTextEdit( parent )
{
connect( this, SIGNAL( cursorPositionChanged() ), viewport(), SLOT( update() ) );
//Just for brevity. Instead of repainting the whole thing on every cursor change,
//you'll want to filter for changes to the current block/line and only update the.
//changed portions. And accommodate resize, etc.
}
void TextEditor::paintEvent( QPaintEvent* pEvent )
{
QPainter painter( viewport() );
QRect r = cursorRect();
r.setLeft( 0 ); r.setRight( width() - 1 ); //Or more!
painter.setPen( Qt::NoPen );
painter.setBrush( QColor( 228, 242, 244, 200 ) );
painter.drawRect( r );
QPlainTextEdit::paintEvent( pEvent );
}
A background hint behind the cursor's block is a really nice UX improvement, as it makes the cursor position more immediately apparent at a glance in all sorts of scenarios. If you have several text editors up together, little details like that become even more important.
At first glance, setExtraSelections() looks like a fast and simple way to get there. And it is... But I find that it falls short when you want to take it to the next level, and, as you discovered, it doesn't play well with ANY other highlighting.
I suspect the built-in ExtraSelection method is meant to be a kind of quick and dirty brute force tool for quickly showing errors or breakpoints, i.e. things that are meant to really visually stand out for the user. It's basically like a secondary selection highlight behind the cursor selection, and so like all other selection highlights it will render behind text but in front of everything else. That means it will also eclipse any custom text background formatting you do using QTextFormat or QTextCharFormat, or even QSyntaxHighlighter. I don't find that acceptable, personally.
Another smaller problem with built-in selections or highlighting in general for this kind of background hint use case is that they don't cover the entire background of the text block. They stop a few pixels shy at the text area boundary or worse depending on margins, etc., making it look clunky to my eyes.
In terms of UI design, a current line indication generally needs to be subtler than most other indications and all other highlights, with lower contrast, and towards the very far background. It needs to look more like a part of the widget than part of the text. It's a hint not a selection, and I've found that balancing it all visually required more than ExtraSelections or regular text formatting was able to provide.
BTW, If you plan on making this more complex, e.g. a code editor, I would also recommend that you look into using QSyntaxHighlighter for your selected word pattern highlight. ( It will remove a LOT of the cursor control logic and ALL of the signal interruption. It will also scale better for when (if?) you add keyword, variable, comment, search terms, etc. Right now, your highlighting involves editing your document/data model directly, which is fine for your post or for simple text input, but likely to be problematic for other cases. )
EDIT
Here's more code that shows using an extended highlighter together with the paintEvent override. I'm going to use headers to hopefully make it clearer how this approach might integrate with your actual projects' classes.
First, the highlighter:
#include <QSyntaxHighlighter>
class QTextDocument;
class CQSyntaxHighlighterSelectionMatch : public QSyntaxHighlighter
{
Q_OBJECT
public:
explicit CQSyntaxHighlighterSelectionMatch( QTextDocument *parent = 0 );
public slots:
void SetSelectionTerm( QString term );
protected:
virtual void highlightBlock( const QString &text );
void ApplySelectionTermHighlight( const QString &text );
private:
QString m_strSelectionTerm;
struct HighlightingRule {
QRegExp pattern;
QTextCharFormat format;
};
HighlightingRule m_HighlightRuleSelectionTerm;
};
A quick and dirty implementation:
CQSyntaxHighlighterSelectionMatch::CQSyntaxHighlighterSelectionMatch( QTextDocument *parent )
: QSyntaxHighlighter( parent )
{
m_strSelectionTerm.clear();
m_HighlightRuleSelectionTerm.format.setBackground( QColor(255, 210, 120 ) );
//m_HighlightRuleSelectionTerm.format.setFontWeight( QFont::Bold ); //or italic, etc...
}
void CQSyntaxHighlighterSelectionMatch::SetSelectionTerm( QString txtIn )
{
if( txtIn == m_strSelectionTerm )
return;
if( !txtIn.isEmpty() )
{
txtIn = "\\b" + txtIn + "\\b";
if( txtIn == m_strSelectionTerm )
return;
}
m_strSelectionTerm = txtIn;
Qt::CaseSensitivity cs = Qt::CaseSensitive;
m_HighlightRuleSelectionTerm.pattern = QRegExp( m_strSelectionTerm, cs );
rehighlight();
}
void CQSyntaxHighlighterSelectionMatch::highlightBlock( const QString &text )
{
if( m_strSelectionTerm.length() > 1 )
ApplySelectionTermHighlight( text );
}
void CQSyntaxHighlighterSelectionMatch::ApplySelectionTermHighlight( const QString &text )
{
QRegExp expression( m_HighlightRuleSelectionTerm.pattern );
int index, length;
index = expression.indexIn( text );
while ( index >= 0 )
{
length = expression.matchedLength();
setFormat( index, length, m_HighlightRuleSelectionTerm.format );
index = expression.indexIn( text, index + length );
}
}
And here's how a QPlainTextEdit derived class might use something like the above:
#include <QPlainTextEdit>
class TextEditor : public QPlainTextEdit
{
Q_OBJECT
public:
TextEditor( QWidget *parent = 0 );
protected:
virtual void paintEvent( QPaintEvent *event );
private slots:
void CheckForCurrentBlockChange();
void FilterSelectionForSingleWholeWord();
private:
unsigned int m_uiCurrentBlock;
CQSyntaxHighlighterSelectionMatch *m_pHighlighter;
};
#include <QPainter>
TextEditor::TextEditor(QWidget *parent)
: QPlainTextEdit(parent)
{
//Instead of repainting on every cursor change, we can filter for changes to the current block/line
//connect( this, SIGNAL(cursorPositionChanged()), viewport(), SLOT(update()) );
connect( this, SIGNAL(cursorPositionChanged()), this, SLOT(CheckForCurrentBlockChange()) );
m_pHighlighter = new CQSyntaxHighlighterSelectionMatch( document() );
connect( this, SIGNAL(selectionChanged()), this, SLOT(FilterSelectionForSingleWholeWord()) );
}
void TextEditor::paintEvent( QPaintEvent* pEvent )
{
QPainter painter( viewport() );
QRect r = cursorRect();
r.setLeft( 0 );
r.setRight( width()-1 );
painter.setPen( Qt::NoPen );
painter.setBrush( QColor( 228, 242, 244 ) );
painter.drawRect( r );
QPlainTextEdit::paintEvent( pEvent );
}
void TextEditor::CheckForCurrentBlockChange()
{
QTextCursor tc = textCursor();
unsigned int b = (unsigned int)tc.blockNumber();
if( b == m_uiCurrentBlock )
return;
m_uiCurrentBlock = b;
viewport()->update(); //I'll just brute force paint everything for this example. Your real code can be smarter with it's repainting it matters...
}
void TextEditor::FilterSelectionForSingleWholeWord()
{
QTextCursor tc = textCursor();
QString currentSelection = tc.selectedText();
QStringList list = currentSelection.split(QRegExp("\\s+"), QString::SkipEmptyParts);
if( list.count() > 1 )
{
m_pHighlighter->SetSelectionTerm( "" );
return;
}
tc.movePosition( QTextCursor::StartOfWord );
tc.movePosition( QTextCursor::EndOfWord, QTextCursor::KeepAnchor );
QString word = tc.selectedText();
if( currentSelection != word )
{
m_pHighlighter->SetSelectionTerm( "" );
return;
}
m_pHighlighter->SetSelectionTerm( currentSelection );
}
This is the simplest way I know to provide the selection term functionality you are after while solving the issue of the background hint interfering with the selection term highlight when they are on the same block.

Override following digits in QSpinBox

Is there a property in Q(Double)SpinBox that allows the user to override digits that are right of the curser, just by typing?
For example: If the spinbox shows 12.52 I click between 1 and 2. Now I type 3.45 to get 13.45
Just checked it out - even activating Insert mode on keyboard doesn't help.
Apparently, you'll have to subclass QSPinBox and override some QAbstractSpinBox functions - at least, keyPressEvent.
QAbstractSpinBox has a pointer to underlying QLineEdit - which has a cursor position property.
There is no property, you can go with your own implementation like this:
Header:
#include <QObject>
#include <QDoubleSpinBox>
class CustomSpinBox : public QDoubleSpinBox
{
Q_OBJECT
public:
explicit CustomSpinBox(QWidget *parent = nullptr);
void keyPressEvent(QKeyEvent *event) override;
private:
void updateTextField(const QString &text);
};
Source:
#include "CustomSpinBox.h"
#include <QKeyEvent>
#include <QLineEdit>
CustomSpinBox::CustomSpinBox(QWidget *parent)
: QDoubleSpinBox(parent)
{}
void CustomSpinBox::keyPressEvent(QKeyEvent *event)
{
auto isInt = false;
event->text().toInt(&isInt);
if (isInt)
{
updateTextField(event->text());
}
else
{
QDoubleSpinBox::keyPressEvent(event);
}
}
void CustomSpinBox::updateTextField(const QString &text)
{
auto lineEdit = this->lineEdit();
auto cursorPosition = lineEdit->cursorPosition();
auto lineEditText = lineEdit->text();
QChar currentChar;
if (cursorPosition < lineEditText.size())
{
currentChar = lineEditText.at(cursorPosition);
if (currentChar.isPunct())
{
lineEdit->cursorForward(false);
}
}
lineEdit->del();
lineEdit->insert(text);
}
But please note that the behaviour can be counter-intuitive. What if the value is 1,23? With that approach, you cannot enter any value bigger than 9,(9). Maybe it is what is expected, maybe it should not jump over the dot/comma unless the value before the punctuation mark has size length bigger than 2 digits (or 3 or any other number).

How to get a signal when a user is typing in a cell in qtablewidget? C++

QtableWidget emits a signal when a cell is changed but what I need is to know when the user is typing.
For example: if the user write "cat" in a cell, I need to get a signal when is written "c", other signal when is written "a" and another third signal when is written "t". "cellChanged" emits the signal only after lefting the cell.
I tried to use a eventFilter but it is not working unfortunately.
bool Matrix::eventFilter(QObject *object, QEvent *event)
{
if (object == ui->tableWidget && event->type() == QEvent::KeyPress)
{
...
}
}
I tried with this->state()==QAbstractItemView::EditingState but this is a protected member of QAbstractItemView and I do not know how to subclass QTablewidget.
Thank you in advance.
Not necessarily the best solution, but I would do something as follows.
• Create a subclass of QWidget or whatever fills your QTableWidget (for clarification purposes, let us name it CellWidget).
• Give this class the signals you want : a_signal(someArgument);, c_signal(someArgument);, t_signal(someArgument);.
It requires putting the Q_OBJECT macro in the class definition.
Assuming you need some properties of both QTableWidget and something such as QLineEdit (put the latter could be replaced by anything meant to hold text) :
• Override void QWidget::keyPressEvent(QKeyEvent *event) as below.
class CellWidget : public QTableWidgetItem , public QLineEdit{
Q_OBJECT
protected :
void keyPressEvent(QKeyEvent *event) override;
public signals :
//Declaration of the signals
}
void CellWidget::keyPressEvent(QKeyEvent *event){
switch(event->key()){
case(Qt::Key_A):{
emit(a_signal(someArgumentWithCorrectType));
break;
}
//etc.
default:{break;}
}
ParentClass::keyPressEvent(event); //Processes the event as seen from the parent class
event->accept();
}
• Fill the table with these widgets and connect them to the appropriate slot :
for(int i = 0 ; i < table->rowCount() ; i++){
for(int j = 0 ; j < table->columnCount() ; j++){
CellWidget *cw = new CellWidget(table);
connect(cw , &CellWidget::a_signal , someWidget , &SomeWidget::someSlotA);
connect(cw , &CellWidget::c_signal , someWidget , &SomeWidget::someSlotC);
connect(cw , &CellWidget::t_signal , someWidget , &SomeWidget::someSlotT);
table->setCellWidget(i , j , cw);
}
}
You may not specify an argument for the signal, or use this very argument to do some logic in the slot :
void SomeWidget::SomeSlot(int key){
switch(key){
case(...):{
...
break;
}
}
}
You may also check if the CellWidget is in focus in the event (look at QWiget::hasFocus()).

Stop slots from being executed

In Qt, I can emit a signal, to which I have multiple slots connected, where in the case of direct connections, the connected slots are called on after another.
Let void mySignal(int x) be the signal of the class MyClass.
Depending on the value of x I want to perform a different action, and under the assumption, I want to do exactly one action, I can connect a slot, with a switch-case construct to execute the relevant action.
This implies that I need to know beforehand what kind of values I can get, and what the actions are.
I can also connect a slot for each of my actions, and guard the execution by a if clause. Now I can just connect whatever I want, whenever I want it. But under the assumption that I want to do exactly one action, it would be performance-wise beneficial if I could stop further execution of the slots, when I found the 'match'.
[...]
QObject::connect(this, &MyClass::mySignal, this, [this](int x) {
if (x == 0) {
qDebug() << x; // Stop it now!;
}
});
QObject::connect(this, &MyClass::mySignal, this, [this](int x) {
if (x == 4) {
qDebug() << x; // Stop it now!;
}
});
QObject::connect(this, &MyClass::mySignal, this, [this](int x) {
if (x == 109) {
qDebug() << x; // Stop it now!;
}
});
Is there a way, to tell the signal, to not execute anymore slots, until the signal is emitted again?
One way to do something like this, (ab) using the Qt framework would use the QEvent-system.
The signal handler won't do more, than translating the signal in an QEvent. Instead of all the separate slots, install eventFilter. If the eventFilter accepts the event, return true to stop the propagation to other (filter)objects.
The code is just a quick and dirty test and has no security precautions. It might easily crash hard.
// testevent.h
#ifndef TESTEVENT_H
#define TESTEVENT_H
#include <QObject>
#include <QEvent>
class TestEvent: public QEvent
{
int m_x;
public:
TestEvent(int x = 0);
int x() { return m_x; }
};
class TestEventFilter: public QObject
{
Q_OBJECT
int m_fx;
public:
TestEventFilter(int fx, QObject* parent = nullptr);
bool eventFilter(QObject* obj, QEvent* event) Q_DECL_OVERRIDE;
};
Q_DECLARE_METATYPE(TestEvent)
#endif // TESTEVENT_H
// testevent.cpp
#include "testevent.h"
#include <QDebug>
TestEvent::TestEvent(int x)
: QEvent(QEvent::User)
, m_x(x)
{
}
TestEventFilter::TestEventFilter(int fx, QObject *parent)
: QObject(parent)
, m_fx(fx)
{
}
bool TestEventFilter::eventFilter(QObject *obj, QEvent *event)
{
Q_UNUSED(obj);
TestEvent* e = static_cast<TestEvent*>(event);
qDebug() << "EventFilter for" << m_fx << "got" << e;
if (e->x() == m_fx) {
qDebug() << "accept that event!";
event->accept();
return true;
}
event->ignore();
return false;
}
// run
QObject o;
TestEventFilter* f1 = new TestEventFilter(10);
TestEventFilter* f2 = new TestEventFilter(5);
TestEventFilter* f3 = new TestEventFilter(3);
TestEventFilter* f4 = new TestEventFilter(7);
o.installEventFilter(f1);
o.installEventFilter(f2);
o.installEventFilter(f3);
o.installEventFilter(f4);
qApp->sendEvent(&o, new TestEvent(5));
qApp->sendEvent(&o, new TestEvent(3));
Output:
EventFilter for 7 got 0x369f2fe0
EventFilter for 3 got 0x369f2fe0
EventFilter for 5 got 0x369f2fe0
accept that event!
EventFilter for 7 got 0x369f3250
EventFilter for 3 got 0x369f3250
accept that event!

QFileDialog: How to select multiple files with touch-screen input?

In our application that uses Qt 4 and supports touch input, we use the QFileDialog with the options QFileDialog::DontUseNativeDialog and QFileDialog::ExistingFiles.
The first is needed because we set our own stylesheet and that does not work with the native dialog. The second is for needed for selecting multiple files, which is what we want to do.
The problem ist that one can not select multiple files with touch input in the QFileDialog, because we have no "shift" or "ctrl"-key available. In Windows the problem is solved by adding checkboxes to the items. QFileDialog has no checkboxes.
I tried to manipulate the QFileDialog to make it displays check boxes for the items, but I failed.
I tried to exchanged the QFileSystemModel that is used by the underlying QTreeView and QListView, but this breaks the signal-slot connections between the model and the dialog. I could not find a way to restore them because they are burried deep in the private intestants of the dialog.
At this moment the only solution I can imagine is writing a whole new dialog, but I would like to avoid the effort.
So is there a way to add checkboxes to the QFileDialog model views ?
Do you have another idea how selecting multiple files could be made possible?
Is the problem fixed in Qt 5? We want to update anyway.
Thank you for your Help.
As I failed to add checkboxes to the item views, I implemented a "hacky" work-around. It adds an extra checkable button to the dialog that acts as a "ctrl"-key. When the button is checked, multiple files can be selected. The solution is a little bit ugly, because it relies on knowing the internals of the dialog, but it does the job.
So here is the code for the header ...
// file touchfiledialog.h
/*
Event filter that is used to add a shift modifier to left mouse button events.
This is used as a helper class for the TouchFileDialog
*/
class EventFilterCtrlModifier : public QObject
{
Q_OBJECT;
bool addCtrlModifier;
public:
EventFilterCtrlModifier( QObject* parent);
void setAddCtrlModifier(bool b);
protected:
virtual bool eventFilter( QObject* watched, QEvent* e);
};
/*
TouchDialog adds the possibility to select multiple files with touch input to the QFileDialog.
This is done by adding an extra button which can be used as control key replacement.
*/
class QTOOLS_API TouchFileDialog : public QFileDialog
{
Q_OBJECT
EventFilterCtrlModifier* listViewEventFilter;
EventFilterCtrlModifier* treeViewEventFilter;
bool initialized;
public:
TouchFileDialog( QWidget* parent);
protected:
virtual void showEvent( QShowEvent* e);
private slots:
void activateCtrlModifier(bool b);
private:
void initObjectsForMultipleFileSelection();
};
with the implementation ...
// file touchfiledialog.cpp
#include "touchfiledialog.h"
EventFilterCtrlModifier::EventFilterCtrlModifier(QObject* parent)
: QObject(parent)
, addCtrlModifier(false)
{
}
void EventFilterCtrlModifier::setAddCtrlModifier(bool b)
{
addCtrlModifier = b;
}
bool EventFilterCtrlModifier::eventFilter(QObject* watched, QEvent* e)
{
QEvent::Type type = e->type();
if( type == QEvent::MouseButtonPress || type == QEvent::MouseButtonRelease)
{
if( addCtrlModifier)
{
QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(e);
// Create and post a new event with ctrl modifier if the event does not already have one.
if( !mouseEvent->modifiers().testFlag(Qt::ControlModifier))
{
QMouseEvent* newEventWithModifier = new QMouseEvent(
type,
mouseEvent->pos(),
mouseEvent->globalPos(),
mouseEvent->button(),
mouseEvent->buttons(),
mouseEvent->modifiers() | Qt::ControlModifier
);
QCoreApplication::postEvent(watched, newEventWithModifier);
return true; // absorb the original event
}
}
}
return false;
}
//#######################################################################################
TouchFileDialog::TouchFileDialog(QWidget* parent)
: QFileDialog(parent)
, listViewEventFilter(NULL)
, treeViewEventFilter(NULL)
, initialized(false)
{
}
void TouchFileDialog::showEvent(QShowEvent* e)
{
// install objects that are needed for multiple file selection if needed
if( !initialized)
{
if( fileMode() == QFileDialog::ExistingFiles)
{
initObjectsForMultipleFileSelection();
}
initialized = true;
}
QFileDialog::showEvent(e);
}
void TouchFileDialog::initObjectsForMultipleFileSelection()
{
// install event filter to item views that are used to add ctrl modifiers to mouse events
listViewEventFilter = new EventFilterCtrlModifier(this);
QListView* listView = findChild<QListView*>();
listView->viewport()->installEventFilter(listViewEventFilter);
treeViewEventFilter = new EventFilterCtrlModifier(this);
QTreeView* treeView = findChild<QTreeView*>();
treeView->viewport()->installEventFilter(treeViewEventFilter);
QGridLayout* dialogLayout = static_cast<QGridLayout*>(layout()); // Ugly because it makes assumptions about the internals of the QFileDialog
QPushButton* pushButtonSelectMultiple = new QPushButton(this);
pushButtonSelectMultiple->setText(tr("Select multiple"));
pushButtonSelectMultiple->setCheckable(true);
connect( pushButtonSelectMultiple, SIGNAL(toggled(bool)), this, SLOT(activateCtrlModifier(bool)));
dialogLayout->addWidget(pushButtonSelectMultiple, 2, 0);
}
void ZFFileDialog::activateCtrlModifier(bool b)
{
listViewEventFilter->setAddCtrlModifier(b);
treeViewEventFilter->setAddCtrlModifier(b);
}
The TouchFileDialog installs an event filter to the item views that will add a ControlModifier to the mouse events of the views when the corresponging button in the dialog is checked.
Feel free to post other solutions, because this is somewhat improvised.