QTreeView: column resize from columns and not from headers? - qt

Is there any way to allow the user to interactively resize the columns when headers are hidden?

You can install an event filter on the table's viewport and implement needed behavior manually. Below is a sample implementation.
Header:
#include <QTableView>
class Table_cell_resizer : public QObject {
Q_OBJECT
public:
explicit Table_cell_resizer(QTableView *view = 0);
protected:
bool eventFilter(QObject* object, QEvent* event);
private:
QTableView* m_view;
//max distance between mouse and a cell, small enough to trigger resize
int m_sensibility;
//variables for saving state while dragging
bool m_drag_in_progress;
Qt::Orientation m_drag_orientation;
int m_drag_section;
int m_drag_previous_pos;
// check if mouse_pos is around right or bottom side of a cell
// (depending on orientation)
// and return the index of that cell if found
QModelIndex index_resizable(QPoint mouse_pos, Qt::Orientation orientation);
};
Source:
#include "Table_cell_resizer.h"
#include <QMouseEvent>
#include <QHeaderView>
Table_cell_resizer::Table_cell_resizer(QTableView* view) :
QObject(view), m_view(view)
{
m_view->viewport()->installEventFilter(this);
m_view->viewport()->setMouseTracking(true);
m_sensibility = 5;
m_drag_in_progress = false;
}
bool Table_cell_resizer::eventFilter(QObject* object, QEvent* event) {
if (object == m_view->viewport()) {
QMouseEvent* mouse_event = dynamic_cast<QMouseEvent*>(event);
if (mouse_event) {
if (mouse_event->type() == QEvent::MouseMove) {
if (m_drag_in_progress) { // apply dragging
int delta;
QHeaderView* header_view;
if (m_drag_orientation == Qt::Vertical) {
delta = mouse_event->pos().y() - m_drag_previous_pos;
header_view = m_view->verticalHeader();
m_drag_previous_pos = mouse_event->pos().y();
} else if (m_drag_orientation == Qt::Horizontal) {
delta = mouse_event->pos().x() - m_drag_previous_pos;
header_view = m_view->horizontalHeader();
m_drag_previous_pos = mouse_event->pos().x();
}
//using minimal size = m_sensibility * 2 to prevent collapsing
header_view->resizeSection(m_drag_section,
qMax(m_sensibility * 2, header_view->sectionSize(m_drag_section) + delta));
return true;
} else { // set mouse cursor shape
if (index_resizable(mouse_event->pos(), Qt::Vertical).isValid()) {
m_view->viewport()->setCursor(Qt::SplitVCursor);
} else if (index_resizable(mouse_event->pos(), Qt::Horizontal).isValid()) {
m_view->viewport()->setCursor(Qt::SplitHCursor);
} else {
m_view->viewport()->setCursor(QCursor());
}
}
} else if (mouse_event->type() == QEvent::MouseButtonPress &&
mouse_event->button() == Qt::LeftButton &&
!m_drag_in_progress) { // start dragging
if (index_resizable(mouse_event->pos(), Qt::Vertical).isValid()) {
m_drag_in_progress = true;
m_drag_orientation = Qt::Vertical;
m_drag_previous_pos = mouse_event->y();
m_drag_section = index_resizable(mouse_event->pos(), Qt::Vertical).row();
return true;
} else if (index_resizable(mouse_event->pos(), Qt::Horizontal).isValid()) {
m_drag_in_progress = true;
m_drag_orientation = Qt::Horizontal;
m_drag_previous_pos = mouse_event->x();
m_drag_section = index_resizable(mouse_event->pos(), Qt::Horizontal).column();
return true;
}
} else if (mouse_event->type() == QEvent::MouseButtonRelease &&
mouse_event->button() == Qt::LeftButton &&
m_drag_in_progress) { // stop dragging
m_drag_in_progress = false;
return true;
}
}
}
return false;
}
QModelIndex Table_cell_resizer::index_resizable(QPoint mouse_pos, Qt::Orientation orientation) {
QModelIndex index = m_view->indexAt(mouse_pos - QPoint(m_sensibility + 1, m_sensibility + 1));
if (index.isValid()) {
if (orientation == Qt::Horizontal) {
if (qAbs(m_view->visualRect(index).right() - mouse_pos.x()) < m_sensibility &&
m_view->horizontalHeader()->sectionResizeMode(index.column()) == QHeaderView::Interactive) {
return index;
}
} else {
if (qAbs(m_view->visualRect(index).bottom() - mouse_pos.y()) < m_sensibility &&
m_view->verticalHeader()->sectionResizeMode(index.row()) == QHeaderView::Interactive) {
return index;
}
}
}
return QModelIndex();
}
Usage:
new Table_cell_resizer(ui->table);
User can now resize rows and columns using cell area in addition to header areas. You can hide headers if you wish. This implementation respects header resize modes, so make sure that resize mode is set to QHeaderView::Interactive for headers which you want to be resizable. For example, you can set horizontal header mode to Interactive and vertical header mode to Fixed, resulting in resizable columns and fixed rows.

Related

QWebEngineView scroll with touch

I have an application ported from an old Qt 4.7.4 to Qt5, and as I understood, QWebView became QWebEngineView, and with a QWebView I used FlickCharm, that was in Qt examples, it still works fine with QScrollArea (such as QListWidget, QTableWidget, ... and of course base QScrollArea), but no more with QWebEngineView, here is the code to activate FlickCharm, that was working on Qt4:
void FlickCharm::activateOn(QWidget *widget, QWidget* p_viewport)
{
QAbstractScrollArea *scrollArea = qobject_cast<QAbstractScrollArea*>(widget);
if (scrollArea) {
// Widget is a scroll area
QAbstractItemView *itemView = qobject_cast<QAbstractItemView*>(widget);
if(itemView)
{
itemView->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
itemView->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
}
QWidget *viewport = scrollArea->viewport();
if ( p_viewport )
{
viewport = p_viewport;
}
else
{
scrollArea->installEventFilter(this);
}
viewport->installEventFilter(this);
QHash<QWidget*, FlickData*>::iterator oldViewport;
if ( ( oldViewport = d->flickData.find(viewport) ) != d->flickData.end() )
{
delete oldViewport.value();
}
d->flickData.remove(viewport);
d->flickData[viewport] = new FlickData;
d->flickData[viewport]->widget = widget;
d->flickData[viewport]->state = FlickData::Steady;
d->flickData[viewport]->customViewPort = (viewport != scrollArea->viewport());
return;
}
QWebView *webView = qobject_cast<QWebView*>(widget);
if (webView) {
// Widget is a web view
webView->installEventFilter(this);
QHash<QWidget*, FlickData*>::iterator oldViewport;
if ( ( oldViewport = d->flickData.find(webView) ) != d->flickData.end() )
{
delete oldViewport.value();
}
d->flickData.remove(webView);
d->flickData[webView] = new FlickData;
d->flickData[webView]->widget = webView;
d->flickData[webView]->state = FlickData::Steady;
return;
}
qWarning() << "FlickCharm only works on QAbstractScrollArea (and derived classes)";
qWarning() << "or QWebView (and derived classes)";
}
And in FlickData class there is following function that does the scroll:
bool scrollWidget(const int dx, const int dy)
{
{
QAbstractScrollArea *scrollArea = qobject_cast<QAbstractScrollArea*>(widget);
if (scrollArea) {
const int x = scrollArea->horizontalScrollBar()->value();
const int y = scrollArea->verticalScrollBar()->value();
scrollArea->horizontalScrollBar()->setValue(x - dx);
scrollArea->verticalScrollBar()->setValue(y - dy);
return (scrollArea->horizontalScrollBar()->value() != x
|| scrollArea->verticalScrollBar()->value() != y);
}
}
{
QWebEngineView *webEngineView = qobject_cast<QWebEngineView*>(widget);
if (webEngineView) {
const QPointF position = webEngineView->page()->scrollPosition();
const QPointF newPosition = position - QPointF(dx, dy);
webEngineView->page()->runJavaScript(QString("window.scrollTo(%1, %2);").arg(newPosition.x()).arg(newPosition.y()));
return webEngineView->page()->scrollPosition() != position;
}
}
return false;
}
In Qt5 I tried applying directly to QWebEngineView as above:
QWebEngineView *webEngineView = qobject_cast<QWebEngineView*>(widget);
if (webEngineView) {
webEngineView->installEventFilter(this);
QHash<QWidget*, FlickData*>::iterator oldViewport;
if ( ( oldViewport = d->flickData.find(webEngineView) ) != d->flickData.end() )
{
delete oldViewport.value();
}
d->flickData.remove(webEngineView);
d->flickData[webEngineView] = new FlickData;
d->flickData[webEngineView]->widget = webEngineView;
d->flickData[webEngineView]->state = FlickData::Steady;
return;
}
And also tried to page view that I assume was viewport:
QWebEngineView *webEngineView = qobject_cast<QWebEngineView*>(widget);
if (webEngineView) {
QWidget *viewport = webEngineView->page()->view();
webEngineView->installEventFilter(this);
viewport->installEventFilter(this);
QHash<QWidget*, FlickData*>::iterator oldViewport;
if ( ( oldViewport = d->flickData.find(viewport) ) != d->flickData.end() )
{
delete oldViewport.value();
}
d->flickData.remove(viewport);
d->flickData[viewport] = new FlickData;
d->flickData[viewport]->widget = webEngineView;
d->flickData[viewport]->state = FlickData::Steady;
return;
}
In QWebEngineView::page() (that is a QWebEnginePage), there is a scrollPosition() function but this is from a Q_PROPERTY but with no write accessor function, but I found a code peace to scroll with javascript I tried using:
QWebEngineView *webEngineView = qobject_cast<QWebEngineView*>(widget);
if (webEngineView) {
const QPointF position = webEngineView->page()->scrollPosition();
const QPointF newPosition = position - QPointF(dx, dy);
webEngineView->page()->runJavaScript(QString("window.scrollTo(%1, %2);").arg(newPosition.x()).arg(newPosition.y()));
return webEngineView->page()->scrollPosition() != position;
}
But after adding some logs I see I never pass in scrollWidget for a QWebEngineView, and, as I have my own class that inherits QApplication that is instanciated, I can see this is not directly the QWebEngineView that is clicked but a RenderWidgetHostViewQtDelegateWidget (that has QWebEngineView as parent) that is not accessible from anywhere, I see this because I reimplemented MyApplication::notify to log on which widget clicks are made:
bool MyApplication::notify(QObject* p_object, QEvent* p_event)
{
[...]
if ( p_event->type() == QEvent::MouseButtonPress )
{
QWidget* widget = dynamic_cast<QWidget*>(p_object);
if (widget != 0)
{
qDebug().nospace() << "Mouse pressed on [" << (widget->isEnabled() ? "enabled" :"disabled") << "] widget: "
<< p_object
<< ", parent: " << p_object->parent();
}
else
{
qDebug().nospace() << "Mouse pressed on object: " << p_object
<< ", parent: " << p_object->parent();
}
}
else if ( p_event->type() == QEvent::MouseButtonRelease )
{
QWidget* widget = dynamic_cast<QWidget*>(p_object);
if (widget != 0)
{
qDebug().nospace() << "Mouse release on [" << (widget->isEnabled() ? "enabled" :"disabled") << "] widget: "
<< p_object
<< ", parent: " << p_object->parent();
}
else
{
qDebug().nospace() << "Mouse release on object: " << p_object
<< ", parent: " << p_object->parent();
}
}
[...]
}
Also, when I try to scroll on the QWebEngineView, text is selected instead.
Here is the full code of flickcharm: https://doc.qt.io/archives/qt-4.8/qt-demos-embedded-anomaly-src-flickcharm-cpp.html
Also, I've seen there is a solution to make a WebEngineView flickable but only for QtQuick QML: https://stackoverflow.com/a/42817245
Anyone knows how to have touch scrolling on a QWebEngineView ?
Thanks
Edit: since RenderWidgetHostViewQtDelegateWidget is a child of the QWebEngineView, I tried to access it (at least as a QWidget if it was possible) from webEngineView->children() but unsuccessfully since its only children are its QVBoxLayout (that is empty) and the FlickCharm.
I finally managed to do what I want, in FlickCharm::activateOn for QWebEngineView I put:
QWebEngineView *webEngineView = qobject_cast<QWebEngineView*>(widget);
if (webEngineView) {
QLayout* webEngineViewLayout = webEngineView->layout();
QWidget* webEngineViewChildWidget = 0;
for (int i = 0; i < webEngineViewLayout->count(); ++i)
{
webEngineViewChildWidget = qobject_cast<QWidget*>(webEngineViewLayout->itemAt(i)->widget());
if (webEngineViewChildWidget != nullptr)
{
// There should be only one QWidget under QWebEngineView, but break just in case
break;
}
}
if (webEngineViewChildWidget != nullptr)
{
// Install event filter on widget found in QWebEngineView layout
webEngineViewChildWidget->installEventFilter(this);
QHash<QWidget*, FlickData*>::iterator oldViewport;
if ( ( oldViewport = d->flickData.find(webEngineViewChildWidget) ) != d->flickData.end() )
{
delete oldViewport.value();
}
d->flickData.remove(webEngineViewChildWidget);
d->flickData[webEngineViewChildWidget] = new FlickData;
d->flickData[webEngineViewChildWidget]->widget = webEngineView;
d->flickData[webEngineViewChildWidget]->state = FlickData::Steady;
}
else
{
// Web engine view has not yet a page loaded, activate "again" when a page has been loaded
connect(webEngineView, &QWebEngineView::loadFinished, this, &FlickCharm::ReactToWebEngineViewLoaded);
}
return;
}
And FlickCharm::ReactToWebEngineViewLoaded() is:
void FlickCharm::ReactToWebEngineViewLoaded()
{
QWebEngineView* webEngineView = qobject_cast<QWebEngineView*>(sender());
if (webEngineView != nullptr)
{
// We need to pass only once there then we can disconnect now
disconnect(webEngineView, &QWebEngineView::loadFinished, this, &FlickCharm::ReactToWebEngineViewLoaded);
// Activate "again" so that view actually uses flick charm
activateOn(webEngineView);
}
else
{
LOG_ERROR("Web engine view is NULL");
assert(webEngineView != nullptr);
}
}
With of course void ReactToWebEngineViewLoaded() declared as slot in flickcharm.h.
And in FlickData::scrollWidget for QWebEngineView (this is tricky, and I don't like the qApp->processEvents calls but they are mandatory if we want to actually compare values for return of the function):
QWebEngineView *webEngineView = qobject_cast<QWebEngineView*>(widget);
if (webEngineView) {
webEngineView->page()->runJavaScript(QString("window.scrollBy(%1, %2);").arg(-dx).arg(-dy));
return true;
}

QTableView: interactive column resize without QHeaderView

I have a QTableView with a hidden horizontal header
table->horizontalHeader()->hide();
As you can see, the text in the central column is clipped because of the column width.
To view the text, the user would need to resize the column, but without a header, I am unable to do this.
What I would like to be able to do is hover my mouse over the edge of the column and have the normal resize icon appear, and then allow the user to drag the column wider.
Is this possible?
Got something to work using event filters and the following two classes...
/*
* Subclass of QTableView that provides notification when the mouse cursor
* enters/leaves a column boundary.
*/
class headerless_table_view: public QTableView {
using super = QTableView;
public:
explicit headerless_table_view (QWidget *parent = nullptr)
: super(parent)
, m_boundary_width(10)
, m_column_index(-1)
{
viewport()->setMouseTracking(true);
viewport()->installEventFilter(this);
}
/*
* #return The index of the column whose right hand boundary the cursor lies
* on or -1 if not on a boundary.
*/
int column_index () const
{
return(m_column_index);
}
protected:
virtual bool eventFilter (QObject *obj, QEvent *event) override
{
if (event->type() == QEvent::MouseMove) {
if (auto *e = dynamic_cast<QMouseEvent *>(event)) {
auto col_left = columnAt(e->pos().x() - m_boundary_width / 2);
auto col_right = columnAt(e->pos().x() + m_boundary_width / 2);
bool was_on_boundary = m_column_index != -1;
if (col_left != col_right) {
if (m_column_index == -1) {
if (col_left != -1) {
m_column_index = col_left;
}
}
} else {
m_column_index = -1;
}
bool is_on_boundary = m_column_index != -1;
if (is_on_boundary != was_on_boundary) {
entered_column_boundary(is_on_boundary);
}
}
}
return(super::eventFilter(obj, event));
}
/*
* Called whenever the cursor enters or leaves a column boundary. if
* `entered' is true then the index of the column can be obtained using
* `column_index()'.
*/
virtual void entered_column_boundary (bool entered)
{
}
private:
int m_boundary_width;
int m_column_index;
};
/*
* Subclass of headerless_table_view that allows resizing of columns.
*/
class resizable_headerless_table_view: public headerless_table_view {
using super = headerless_table_view;
public:
explicit resizable_headerless_table_view (QWidget *parent = nullptr)
: super(parent)
, m_dragging(false)
{
viewport()->installEventFilter(this);
}
protected:
virtual bool eventFilter (QObject *obj, QEvent *event) override
{
if (auto *e = dynamic_cast<QMouseEvent *>(event)) {
if (event->type() == QEvent::MouseButtonPress) {
if (column_index() != -1) {
m_mouse_pos = e->pos();
m_dragging = true;
return(true);
}
} else if (event->type() == QEvent::MouseButtonRelease) {
m_dragging = false;
} else if (event->type() == QEvent::MouseMove) {
if (m_dragging) {
int delta = e->pos().x() - m_mouse_pos.x();
setColumnWidth(column_index(), columnWidth(column_index()) + delta);
m_mouse_pos = e->pos();
return(true);
}
}
}
return(super::eventFilter(obj, event));
}
/*
* Override entered_column_boundary to update the cursor sprite when
* entering/leaving a column boundary.
*/
virtual void entered_column_boundary (bool entered) override
{
if (entered) {
m_cursor = viewport()->cursor();
viewport()->setCursor(QCursor(Qt::SplitHCursor));
} else {
viewport()->setCursor(m_cursor);
}
}
private:
bool m_dragging;
QPoint m_mouse_pos;
QCursor m_cursor;
};
I ended up splitting it across two classes as it seemed cleaner.
Anyway, simply replacing QTableView with resizable_headerless_table_view in some old example code I found seemed to have the desired effect -- the cursor sprite changes when the mouse is over a column boundary and the relevant boundary can be dragged.
Not sure how close it is to what you're after, but...

QDoubleSpinBox with leading zeros (always 4 digits)

I have a QDoubleSpinBox in range 0-7000, but want the value always displayed as 4 digits
(0-> 0000, 1 -> 0001 , 30 -> 0030, 3333 -> 3333).
I understand I can add a prefix, but a prefix is always added.
What are my options?
If you use integers, then QSpinBox will be enough.
You can simply inherit from QSpinBox and re-implement the textFromValue function:
class MySpinBox: public QSpinBox
{
Q_OBJECT
public:
MySpinBox( QWidget * parent = 0) :
QSpinBox(parent)
{
}
virtual QString textFromValue ( int value ) const
{
/* 4 - number of digits, 10 - base of number, '0' - pad character*/
return QString("%1").arg(value, 4 , 10, QChar('0'));
}
};
Filling QString this way does the trick.
Since prefix is not an option solution if you consider negative values, in my opinion the best and most elegant solution is defining your own custom spin box by deriving QAbstractSpinBox. Here is a small example:
Note that it is far from perfection and it serves just as an example on what could be done:
q4digitspinbox.h:
#ifndef Q4DIGITSPINBOX_H
#define Q4DIGITSPINBOX_H
#include <QAbstractSpinBox>
#include <QLineEdit>
class Q4DigitSpinBox : public QAbstractSpinBox
{
Q_OBJECT
public:
explicit Q4DigitSpinBox(QWidget *parent = 0);
StepEnabled stepEnabled() const;
double maximum() const;
double minimum() const;
void setMaximum(double max);
void setMinimum(double min);
void setRange(double minimum, double maximum);
double value() const;
public slots:
virtual void stepBy(int steps);
void setValue(double val);
signals:
void valueChanged(double i);
void valueChanged(const QString & text);
private:
double m_value;
double m_minimum;
double m_maximum;
QLineEdit m_lineEdit;
};
#endif // Q4DIGITSPINBOX_H
q4digitspinbox.h:
#include "q4digitspinbox.h"
Q4DigitSpinBox::Q4DigitSpinBox(QWidget *parent) :
QAbstractSpinBox(parent),
m_value(0),
m_minimum(-99),
m_maximum(99)
{
setLineEdit(&m_lineEdit);
setValue(0.0);
}
QAbstractSpinBox::StepEnabled Q4DigitSpinBox::stepEnabled() const
{
return StepUpEnabled | StepDownEnabled;
}
double Q4DigitSpinBox::maximum() const
{
return m_maximum;
}
double Q4DigitSpinBox::minimum() const
{
return m_minimum;
}
void Q4DigitSpinBox::setMaximum(double max)
{
m_maximum = max;
}
void Q4DigitSpinBox::setMinimum(double min)
{
m_minimum = min;
}
void Q4DigitSpinBox::setRange(double minimum, double maximum)
{
m_minimum = minimum;
m_maximum = maximum;
}
double Q4DigitSpinBox::value() const
{
return m_value;
}
void Q4DigitSpinBox::stepBy(int steps)
{
m_value += (double)steps / 10;
if (fabs(m_value - 0) < 0.00001)
{
m_value = 0;
}
if(m_value < m_minimum || m_value > m_maximum)
{
return;
}
int prefixNumberOfDigits = 4;
QString valueAsString = QString("%1").arg((int)m_value);
int numberOfDigits = valueAsString.length();
QString prefix;
prefixNumberOfDigits -= numberOfDigits;
if(prefixNumberOfDigits > 0)
{
while(prefixNumberOfDigits--)
{
prefix += "0";
}
}
QString value;
if(m_value < 0)
{
value = QString("-%1%2").arg(prefix).arg(-m_value);
}
else
{
value = QString("%1%2").arg(prefix).arg(m_value);
}
m_lineEdit.setText(value);
emit valueChanged(m_value);
emit valueChanged(value);
}
void Q4DigitSpinBox::setValue(double val)
{
if(val < m_minimum || val > m_maximum)
{
return;
}
int prefixNumberOfDigits = 4;
QString valueAsString = QString("%1").arg((int)val);
int numberOfDigits = valueAsString.length();
QString prefix;
prefixNumberOfDigits -= numberOfDigits;
if(prefixNumberOfDigits > 0)
{
while(prefixNumberOfDigits--)
{
prefix += "0";
}
}
QString value;
if(val < 0)
{
value = QString("-%1%2").arg(prefix).arg(-val);
}
else
{
value = QString("%1%2").arg(prefix).arg(val);
}
m_lineEdit.setText(value);
emit valueChanged(val);
emit valueChanged(value);
}
I didn't provide any commentary since I considered it pretty straight forward, but if needed I can add a few more explanations.
I hope this helps.

QLineEdit setSelection doesn't work

I created a custom QLineEdit for timecode (a timecode looks like this : hh:mm:ss:ff) edition.
It react with the keyboard and the mouse. If the user edit the timecode with the mouse (draging up/down a couple of digit) the highlighting is wrong : it select all the characters from the cursor to the end. (ie: If I drag mm the selection will be mm:ss:ff).
In order to get rid of that, I use setSelection(x,2) which select only the wanted digit (verified using qDebug() << selectedText()) but the highlighting is still wrong :
#include "PhTimecodeEdit.h"
PhTimeCodeEdit::PhTimeCodeEdit(QWidget *parent) :
QLineEdit(parent),
_tcType(PhTimeCodeType25)
{
connect(this, SIGNAL(textChanged(QString)), this, SLOT(onTextChanged(QString)));
this->installEventFilter(this);
_mousePressed = false;
_selectedIndex = 0;
}
bool PhTimeCodeEdit::isTimeCode()
{
PhFrame frame;
QString text;
frame = PhTimeCode::frameFromString(this->text(),_tcType);
text = PhTimeCode::stringFromFrame(frame, _tcType);
if(text == this->text())
return true;
else
return false;
}
bool PhTimeCodeEdit::eventFilter(QObject *, QEvent *event)
{
switch (event->type()) {
case QEvent::MouseButtonPress:
_mousePressed = true;
_mousePressedLocation = static_cast<QMouseEvent *>(event)->pos();
if(_mousePressedLocation.x() > 110 and _mousePressedLocation.x() < 145) {
_selectedIndex = 0;
}
else if(_mousePressedLocation.x() > 145 and _mousePressedLocation.x() < 190) {
_selectedIndex = 3;
}
else if(_mousePressedLocation.x() > 190 and _mousePressedLocation.x() < 230) {
_selectedIndex = 6;
}
else if(_mousePressedLocation.x() > 230 and _mousePressedLocation.x() < 270) {
_selectedIndex = 9;
}
return true;
case QEvent::MouseButtonRelease:
_mousePressed = false;
return true;
case QEvent::MouseMove:
{
if(_mousePressed) {
int y = static_cast<QMouseEvent *>(event)->pos().y();
PhFrame currentFrame = PhTimeCode::frameFromString(this->text(), _tcType);
if(_selectedIndex == 0) {
if(_mousePressedLocation.y() > y)
currentFrame += 25 * 60 * 60;
else
currentFrame -= 25 * 60 * 60;
}
else if(_selectedIndex == 3) {
if(_mousePressedLocation.y() > y)
currentFrame += 25 * 60;
else
currentFrame -= 25 * 60;
}
else if(_selectedIndex == 6) {
if(_mousePressedLocation.y() > y)
currentFrame += 25;
else
currentFrame -= 25;
}
else if(_selectedIndex == 9) {
if(_mousePressedLocation.y() > y)
currentFrame++;
else
currentFrame--;
}
_mousePressedLocation.setY(y);
this->setText(PhTimeCode::stringFromFrame(currentFrame, _tcType));
setSelection(_selectedIndex,2);
}
return false;
}
default:
return false;
}
}
What should I do to get it right ?
I'm not sure, but your eventFilter is strange. It conflicts with mouse processing of QLineEdit.
Try to return QLineEdit::eventFilter(); in default section.
You didn't implement mouse tracking. You should disable it with mouse capture and overloading mouse*Event();. Mouse events processing should be disabled while you track manually mouse.
Reason of bug: mouse moving, that processed by QLineEdit overrides text selection that you set in eventFilter. It happens on mouse release.
Possible hotfix (dirty): implement your own slot for set selection and call it via Qt::QueuedConnection. So your direct call of setSelection will be processed after releasing of mouse.

No Keyboard input if QLineEdit on frameless popup window

When a parent widget is defined with:
setWindowFlags(Qt::FramelessWindowHint | Qt::Popup);
Any edit box widget placed on top of it will not receive keyboard input. Mouse will work, right click, context menu, paste will work -- but not direct keyboard input. Any ideas why and how it can be fixed?
As I mentioned previously in a comment I had the same problem but now it is fixed with the following code:
// virtual override
void MyDialog::showEvent( QShowEvent* aShowEvent )
{
QDialog::showEvent( aShowEvent );
activateWindow();
}
After I added the activateWindow() function call I could use QLineEdit on my popup dialog.
I use Visual Studio 2013 and Qt 5.4.1 on Windows 8.1.
#include "StdAfx.h"
#include "qfindedit.h"
QFindEdit::QFindEdit(QWidget *parent)
: QLineEdit(parent)
, m_bEditFocus(true)
{
setPlaceholderText("please input find word");
m_stringListmodel = new QStringListModel(this);
m_pFindWnd = new QListView(this);
//m_pFindWnd->setWindowFlags(Qt::Popup);
m_pFindWnd->setEditTriggers(QAbstractItemView::NoEditTriggers);
m_pFindWnd->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
m_pFindWnd->setSelectionBehavior(QAbstractItemView::SelectRows);
m_pFindWnd->setSelectionMode(QAbstractItemView::SingleSelection);
m_pFindWnd->setParent(0, Qt::Popup);
m_pFindWnd->setFocusPolicy(Qt::NoFocus);
m_pFindWnd->setFocusProxy(this);
connect(this, SIGNAL(textEdited(const QString&)), this, SLOT(textEditedSlot(const QString&)));
QObject::connect(m_pFindWnd, SIGNAL(clicked(QModelIndex)),
this, SLOT(clickedSlot(QModelIndex)));
QObject::connect(this, SIGNAL(activated(QModelIndex)),
m_pFindWnd, SLOT(hide()));
this->installEventFilter(this);
m_pFindWnd->installEventFilter(this);
}
QFindEdit::~QFindEdit()
{
delete m_pFindWnd;
}
QStringList& QFindEdit::stringList()
{
return m_stringList;
}
void QFindEdit::showFindWnd(const QString& text)
{
QStringList sl;
foreach(QString word, m_stringList) {
if (word.contains(text)) {
sl << word;
}
}
if (sl.size() == 0)
{
hideFineWnd();
return;
}
m_stringListmodel->setStringList(sl);
m_pFindWnd->setModel(m_stringListmodel);
m_pFindWnd->resize(rect().width(), 200);
QPoint pTopleft = mapToGlobal(rect().bottomLeft());
m_pFindWnd->move(pTopleft.x(), pTopleft.y());
m_pFindWnd->show();
}
void QFindEdit::textEditedSlot(const QString& text)
{
QString strText = text.trimmed();
if (!strText.isEmpty())
{
showFindWnd(strText);
}
else
{
hideFineWnd();
}
}
void QFindEdit::clickedSlot(QModelIndex modelIndex)
{
setText(m_pFindWnd->model()->data(modelIndex).toString());
hideFineWnd();
}
void QFindEdit::hideFineWnd()
{
m_pFindWnd->hide();
}
bool QFindEdit::eventFilter(QObject *o, QEvent *e)
{
if (m_bEditFocus && (o == this) && e->type() == QEvent::FocusOut)
{
if (m_pFindWnd && m_pFindWnd->isVisible())
return true;
}
if (o != m_pFindWnd)
return __super::eventFilter(o, e);
switch (e->type())
{
case QEvent::KeyPress:
{
QKeyEvent *ke = static_cast<QKeyEvent *>(e);
QModelIndex curIndex = m_pFindWnd->currentIndex();
QModelIndexList selList = m_pFindWnd->selectionModel()->selectedIndexes();
const int key = ke->key();
if ((key == Qt::Key_Up || key == Qt::Key_Down) && selList.isEmpty() && curIndex.isValid() )
{
m_pFindWnd->setCurrentIndex(curIndex);
return true;
}
switch (key)
{
case Qt::Key_End:
case Qt::Key_Home:
if (ke->modifiers() & Qt::ControlModifier)
return false;
break;
case Qt::Key_Up:
if (!curIndex.isValid())
{
int rowCount = m_pFindWnd->model()->rowCount();
QModelIndex lastIndex = m_pFindWnd->model()->index(rowCount - 1, m_pFindWnd->modelColumn());
m_pFindWnd->setCurrentIndex(lastIndex);
return true;
}
else if (curIndex.row() == 0)
{
return true;
}
return false;
case Qt::Key_Down:
if (!curIndex.isValid())
{
QModelIndex firstIndex = m_pFindWnd->model()->index(0, m_pFindWnd->modelColumn());
m_pFindWnd->setCurrentIndex(firstIndex);
return true;
}
else if (curIndex.row() == m_pFindWnd->model()->rowCount() - 1)
{
return true;
}
return false;
}
m_bEditFocus = false;
this->event(ke);
m_bEditFocus = true;
if ( e->isAccepted() || !m_pFindWnd->isVisible()) {
if (!this->hasFocus())
hideFineWnd();
if (e->isAccepted())
return true;
}
switch (key)
{
case Qt::Key_Return:
case Qt::Key_Enter:
case Qt::Key_Tab:
hideFineWnd();
if (curIndex.isValid())
{
QString text = m_pFindWnd->currentIndex().data().toString();
setText(text);
}
break;
case Qt::Key_F4:
if (ke->modifiers() & Qt::AltModifier)
hideFineWnd();
break;
case Qt::Key_Backtab:
case Qt::Key_Escape:
hideFineWnd();
break;
default:
break;
}
return true;
}
case QEvent::MouseButtonPress:
if (!m_pFindWnd->underMouse())
{
hideFineWnd();
return true;
}
return false;
case QEvent::InputMethod:
case QEvent::ShortcutOverride:
QApplication::sendEvent(this, e);
break;
default:
return false;
}
return false;
}
Maybe try activating the window and/or setting widget focus?

Resources