Mapping a position within a QHeaderView to the context of the scrolled Viewport - qt

I have a QHeaderView that does a few things when clicking either to the left or right of the center of a section header.
For this, I need to know the position of the click in relation to the (possibly scrolled) content of the QHeaderView's viewport.
The actual click position, however, refers only to the QHeaderView (which is always fixed).
I tried variants of mapTo/From, but can't find the correct way to do it.
Here's simplified code:
void MyTableHeader::headerSectionClicked(int section_index, int click_pos)
{
int section_size = sectionSize(0); // All sections in a header are equally sized
int section_center = (section_size * (section_index+ 1)) - (section_size / 2); // Center of the clicked section
if (section_index>= 0)// if mouse is over an item
{
if (orientation() == Qt::Horizontal)
{
QPoint x_pos = QPoint(click_pos, 0);
int mapped_offset = viewport()->mapFrom(this, x_pos).x();
if (mapped_offset != -1)
{
// If the click was to the right of the center, iterate on the index
if (mapped_offset >= section_center)
{
section_index++;
}
}
}
else
{
// Same thing for the Y-dimension
}
}
// Neat stuff after this
}
The part where the problem occurs, is where I want to find out on which side of the section the click happened.
// If the click was to the right of the center, iterate on the index
if (mapped_offset >= section_center)
{
in_index++;
}
This mapped_offset does not properly refer to the same context as the section center.

The following solution might give you an idea on what to do.
MyHeaderView.h
#pragma once
#include <QHeaderView>
class MyHeaderView : public QHeaderView
{
Q_OBJECT
public:
MyHeaderView(Qt::Orientation orientation, QWidget* parent = nullptr);
};
MyHeaderView.cpp
#include "MyHeaderView.h"
#include <QDebug>
MyHeaderView::MyHeaderView(Qt::Orientation orientation, QWidget* parent) : QHeaderView(orientation, parent)
{
setSectionsClickable(true);
connect(this, &MyHeaderView::sectionClicked, [this](int section)
{
QRect currentSectionRect;
currentSectionRect.setRect(sectionViewportPosition(section), 0, sectionSize(section), viewport()->height());
auto pos = QCursor::pos();
auto localPos = mapFromGlobal(pos);
qDebug() << currentSectionRect << localPos;
qDebug() << currentSectionRect.contains(localPos); //"Always true!"
});
}
main.cpp
#include "MyHeaderView.h"
#include <QApplication>
#include <QTableView>
#include <QStandardItemModel>
int main(int argc, char **args)
{
QApplication app(argc, args);
auto model = new QStandardItemModel;
auto view = new QTableView;
view->setModel(model);
model->setRowCount(4);
model->setColumnCount(4);
view->setHorizontalHeader(new MyHeaderView(Qt::Horizontal));
view->show();
app.exec();
}

Related

How to simulate a drag and drop action using QTest

In order to create a test case for a drag and drop bug in the QTreeView widget I tried to simulate a drag and drop mouse movement behavior.
I basically select the first element in the QTreeView and want it to drag and drop on the third element. I did this by using a combination of QTest::mousePress and QTest::mouseMove. At the end there should be of course a QTest::mouseRelease, but I already failed in reproducing the mousePress and mouseMove.
These are steps necessary to reproduce:
User moves mouse on center of first item
User presses the left mouse button
User holds the left mouse button pressed and moves the mouse to the center of the third item
User releases the left mouse button
If one does these action as described I can see, that the QTreeView Widgets reacts appropriately, and indicating special highlights and vertical lines, in case the item will be move between to items.
Unfortunately, my automatized test fails to reproduce this behavior. It seems that calling QTest::mousePress in sequence does something different. Also using a pair of QTest::mousePress and QTest::mouseMove is something differently.
This is my code:
main.cpp
#include "TestObject.h"
#include <QTest>
QTEST_MAIN(TestObject)
TestObject.h
#include <QtTest/QtTest>
class TestObject : public QObject
{
Q_OBJECT
private slots:
void dragAndDrop();
};
TestObject.cpp
#include "TestObject.h"
#include "TestObject.moc"
#include <QTest>
#include <QTreeView>
#include <QStandardItemModel>
#include <QPropertyAnimation>
#include "MouseMover.h"
void TestObject::dragAndDrop() {
qDebug() << "Hello";
QStandardItemModel model;
QTreeView view;
view.setModel(&model);
view.show();
view.setHeaderHidden(true);
view.setDragDropMode(QAbstractItemView::DragDropMode::DragDrop);
view.setDefaultDropAction(Qt::DropAction::MoveAction);
view.setColumnHidden(1, true);
for (auto rowIter = 0; rowIter < 3; rowIter++) {
QList<QStandardItem*> items;
for (auto colIter = 0; colIter < 2; colIter++) {
items << new QStandardItem(QString("%1-%2").arg(rowIter).arg(colIter));
}
model.appendRow(items);
}
MouseMover mover;
mover.setWidget(view.viewport());
QPropertyAnimation anim(&mover, "mousePosition");
QTimer::singleShot(0, [&]() {
auto startValue = view.visualRect(model.index(0, 0)).center();
auto endValue = view.visualRect(model.index(2, 0)).center();
QTest::mousePress(view.viewport(), Qt::MouseButton::LeftButton, Qt::KeyboardModifier::NoModifier, startValue);
anim.setStartValue(startValue);
anim.setEndValue(endValue);
anim.setDuration(500);
anim.start();
});
qApp->exec();
}
MouseMover.h
#pragma once
#include <QObject>
#include <QTest>
class MouseMover : public QObject {
Q_OBJECT
public:
Q_PROPERTY(QPoint mousePosition READ mousePosition WRITE setMousePosition MEMBER mMousePosition)
void setWidget(QWidget* widget) {
mWidget = widget;
}
QPoint mousePosition() const {
return mMousePosition;
}
void setMousePosition(const QPoint& pos) {
mMousePosition = pos;
if (mWidget) {
QTest::mousePress(mWidget, Qt::MouseButton::LeftButton, Qt::KeyboardModifier::NoModifier, mMousePosition);
QTest::mouseMove(mWidget, mMousePosition);
}
}
private:
QPoint mMousePosition;
QWidget* mWidget{ nullptr };
};
MouseMover.cpp
#include "MouseMover.h"
mouseMoveEvent() handler starts and blocks at QDrag.exec(). In order to simulate the drop, you have to schedule it e.g. with QTimer:
def complete_qdrag_exec():
QTest.mouseMove(drop_target)
QTest.qWait(50)
QTest.mouseClick(drop_target, Qt.MouseButton.LeftButton)
QTimer.singleShot(1000, complete_qdrag_exec)
QTest.mousePress(drag_source, Qt.MouseButton.LeftButton, Qt.KeyboardModifier.NoModifier, QPoint(10, 10))
QTest.mouseMove(drag_source, QPoint(50, 50)) # Ensure distance sufficient for DND start threshold
Above is plain Python (PyQt6/PySide6). I assume it's similar for C++ ...

Qt: How to link hand coded UI with UI builder?

I am looking for an easy way to link hand coded design to the UI for widgets applications in Qt. I plan to use the UI builder to easily adjust the layouts and obtain proper spacing, which I find hard to do without the UI builder.
I want to create a 3x3 grid of buttons for which I plan to use QVector< QVector<QPushButton*> > (I am not sure how I would do this in UI builder.)
Here is what I have tried, Why are the buttons not displayed even when I set the parent of each button to the widget ?
main.cpp
#include "window.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Window w;
w.show();
return a.exec();
}
window.h
#ifndef WINDOW_H
#define WINDOW_H
#include <QWidget>
#include <QPushButton>
namespace Ui {
class Window;
}
class Window : public QWidget
{
Q_OBJECT
public:
explicit Window(QWidget *parent = 0);
~Window();
private:
Ui::Window *ui;
static const int tiles = 50, height = 600, width = 500;
QVector< QVector<QPushButton*> > cells;
};
#endif // WINDOW_H
window.cpp
#include "window.h"
#include "ui_window.h"
Window::Window(QWidget *parent) :
QWidget(parent),
ui(new Ui::Window)
{
ui->setupUi(this);
this->resize(width, height);
int j = 0;
for(auto &row : cells)
{
int i = 0;
for(auto &col : row)
{
col = new QPushButton(this);
col->setGeometry(i, j, tiles, tiles);
i += tiles;
}
j += tiles;
}
}
Window::~Window()
{
delete ui;
for(auto &row : cells)
{
for(auto &col : row)
{
delete col;
}
}
}
Any help would be appreciated.
The vectors are empty, so you iterate over nothing. Instead of managing these manually, you could leverage the grid layout.
Alas, you're complicating things unnecessarily with all the manual memory management and geometry management. It's unnecessary. All you need to do is to add widgets to the layout you allude to. And even then, I don't see how relegating the layout to a .ui file helps you since there the layout must be empty. So yes: you can set spacings, but you won't see them until you run the code. So it seems like a pointless exercise, unless you have other elements you're not telling us about (why aren't you - you went so far already).
Below is a minimum example that simplifies it as much as practicable, but see this answer and the links therein for more idea.
// https://github.com/KubaO/stackoverflown/tree/master/questions/button-grid-43214317
#include <QtWidgets>
namespace Ui { class Window {
public:
// Approximate uic output
QGridLayout *layout;
void setupUi(QWidget * widget) {
layout = new QGridLayout(widget);
}
}; }
class Window : public QWidget
{
Q_OBJECT
Ui::Window ui;
QPushButton * buttonAt(int row, int column) {
auto item = ui.layout->itemAtPosition(row, column);
return item ? qobject_cast<QPushButton*>(item->widget()) : nullptr;
}
public:
explicit Window(QWidget *parent = {});
};
Window::Window(QWidget *parent) : QWidget(parent) {
ui.setupUi(this);
for (int i = 0; i < 5; ++i)
for (int j = 0; j < 6; ++j)
{
auto b = new QPushButton(QStringLiteral("%1,%2").arg(i).arg(j));
ui.layout->addWidget(b, i, j);
}
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Window w;
w.show();
return a.exec();
}
#include "main.moc"

Programmatic scrolling with QGraphicsView and QGraphicsItem?

I would like to programmatically scroll a scene to the left / right, but I am not sure how to do that properly. Note that I do not want to have (visible) scroll bars.
I use a standard QGraphicsView + QGraphicsScene + QGraphicsItem setup. I have downsized it to the minimum, with one single QGraphicsItem (a QGraphicsRectItem) in the scene.
I have managed to achieve programmatic scrolling by setting my view like this:
// view setup
view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
and then, in another part of the code:
// programmatic scrolling
QScrollBar* const sb = view->horizontalScrollBar();
sb->setRange(0, 1000); // some values for experimenting
sb->setValue(sb->value() + 100 or -100); // some increment for experimenting
This works, but... scrolling through invisible scrollbars doesn't feel right.
I tried this more straightforward approach:
// programmatic scrolling - doesn't quite work
view->viewport()->scroll(100 or -100, 0); // some increment for experimenting
This code does scroll, but when the rectangle goes off the left edge of the view, and I reverse the scrolling direction (increment changed from 100 to -100 in the call to scroll()), the uncovered part of the rectangle is not repainted. The reason is that QGraphicsRectItem::paint() is not called in that case (it is called when using the scrollbar method).
So, is there a way to get viewport()->scroll() work? Or some other simple way to achieve programmatic scrolling? Or is the artificial scrollbar method just the way to go?
Moving the view assumes that it's smaller than its scene. If they're the same size, it won't move.
QGraphicsView can be set to centerOn any position in scene coordinates. Use a timer to call centerOn to move the view one frame at a time.
Here's a working example: -
#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsRectItem>
#include <QTimer>
class MyView : public QGraphicsView
{
private:
public:
MyView(QGraphicsScene* pScene)
: QGraphicsView(pScene, NULL)
{}
void AnimateBy(int x)
{
float updateFrequency = (1000/30.0); // ~30 frames per second
QPointF currScenePos = sceneRect().center();
int curX = currScenePos.x();
int endPos = curX + x;
int distanceToAnimate = (endPos - curX);
// speed = dist / time
float updatePosInterval = (float)distanceToAnimate / updateFrequency;
printf("updatePosInterval: %f \n", updatePosInterval);
static float newXPos = sceneRect().center().x();
QTimer* pTimer = new QTimer;
QObject::connect(pTimer, &QTimer::timeout, [=](){
newXPos += updatePosInterval;
centerOn(newXPos, sceneRect().center().y());
// check for end position or time, then....
if(newXPos >= endPos)
{
pTimer->stop();
pTimer->deleteLater();
}
});
pTimer->start(updateFrequency);
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsScene scene(0, 0, 10000, 20000);
MyView* view = new MyView(&scene);
QGraphicsRectItem* pRect = new QGraphicsRectItem(0, 0, 100, 100);
pRect->setPos(scene.width()/2, scene.height()/2);
scene.addItem(pRect);
// timer to wait for the window to appear, before starting to move
QTimer* pTimer = new QTimer;
pTimer->setSingleShot(true);
QObject::connect(pTimer, &QTimer::timeout,[=](){
view->centerOn(pRect); // centre on the rectangle
view->AnimateBy(100);
pTimer->deleteLater();
});
pTimer->start(1000);
view->show();
return a.exec();
}
So, we create the animation by moving the view frame-by-frame using the call to centerOn.
For simplicity, the code just deals with moving in one axis. To move in 2 axis, use 2D vector maths to calculate the interval position.
Try to change the view transformation with the QGraphicsView::translate() or QGraphicsView::setTransform().
But keep in mind that you can't move the viewport "outside" the scene, so make sure that your scene rectangle is large enough.
If I got your question correctly, there is a dojo classes library with such class as PanWebView that allow QWebView to scroll smoothly with mouse without any scrollbars. Take a look at sources. It supports panning and can be suitable for mobile apps, but maybe it'll help you too.
PanWebView class looks like this
#include <QWebView>
#include <QWebFrame>
#include <QMouseEvent>
#include <QApplication>
class PanWebView : public QWebView
{
Q_OBJECT
private:
bool pressed;
bool scrolling;
QPoint position;
QPoint offset;
QList<QEvent*> ignored;
public:
PanWebView(QWidget *parent = 0): QWebView(parent), pressed(false), scrolling(false) {
QWebFrame *frame = page()->mainFrame();
frame->setScrollBarPolicy(Qt::Vertical, Qt::ScrollBarAlwaysOff);
frame->setScrollBarPolicy(Qt::Horizontal, Qt::ScrollBarAlwaysOff);
}
protected:
void mousePressEvent(QMouseEvent *mouseEvent) {
if (ignored.removeAll(mouseEvent))
return QWebView::mousePressEvent(mouseEvent);
if (!pressed && !scrolling && mouseEvent->modifiers() == Qt::NoModifier)
if (mouseEvent->buttons() == Qt::LeftButton) {
pressed = true;
scrolling = false;
position = mouseEvent->pos();
QWebFrame *frame = page()->mainFrame();
int x = frame->evaluateJavaScript("window.scrollX").toInt();
int y = frame->evaluateJavaScript("window.scrollY").toInt();
offset = QPoint(x, y);
QApplication::setOverrideCursor(Qt::OpenHandCursor);
return;
}
return QWebView::mousePressEvent(mouseEvent);
}
void mouseReleaseEvent(QMouseEvent *mouseEvent) {
if (ignored.removeAll(mouseEvent))
return QWebView::mouseReleaseEvent(mouseEvent);
if (scrolling) {
pressed = false;
scrolling = false;
QApplication::restoreOverrideCursor();
return;
}
if (pressed) {
pressed = false;
scrolling = false;
QMouseEvent *event1 = new QMouseEvent(QEvent::MouseButtonPress,
position, Qt::LeftButton,
Qt::LeftButton, Qt::NoModifier);
QMouseEvent *event2 = new QMouseEvent(*mouseEvent);
ignored << event1;
ignored << event2;
QApplication::postEvent(this, event1);
QApplication::postEvent(this, event2);
QApplication::restoreOverrideCursor();
return;
}
return QWebView::mouseReleaseEvent(mouseEvent);
}
void mouseMoveEvent(QMouseEvent *mouseEvent) {
if (scrolling) {
QPoint delta = mouseEvent->pos() - position;
QPoint p = offset - delta;
QWebFrame *frame = page()->mainFrame();
frame- >evaluateJavaScript(QString("window.scrollTo(%1,%2);").arg(p.x()).arg(p.y()));
return;
}
if (pressed) {
pressed = false;
scrolling = true;
return;
}
return QWebView::mouseMoveEvent(mouseEvent);
}
};
And usage:
PanWebView web;
web.setUrl(QUrl("http://news.google.com"));
web.setWindowTitle("Web View - use mouse to drag and pan around");
web.show();
Also did you check this and this topics? I think it can be usefull.

Qt : Child graphic items hover event

I have two items of type QGraphicsRectItem. One over the other.
The first one is a custom class called Wall. Inside the wall there are windows and doors.
In fact, i have a list of Doors and Windows inside this custom Wall class.
The Doors are Items too, and are drawn inside the wall.
When i move the mouse over the door, the hover function of the wall is emited, but the hover of the door is not. Both of them correctly copied one from each other as virtual void protected.
Why is that happening? How can i make the door and window items realize about the hover?.
I have tried with a custom QGraphicsItem instead of a custom QGraphicsRectItem. It seems the hover event handler is successfully called for both Wall and Door. This is happening when explicitly setting QGraphicsItem with setAcceptHoverEvents(true). This is not tested with custom QGraphicsRectItem.
#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsItem>
#include <QPainter>
class Item : public QGraphicsItem
{
QBrush m_brush;
public:
explicit Item(bool nested = true, QGraphicsItem* parent = 0) : QGraphicsItem(parent), m_brush(Qt::white)
{
if (nested) {
Item* item = new Item(false, this);
item->setPos(10,10);
m_brush = Qt::red;
}
setAcceptHoverEvents(true);
}
QRectF boundingRect() const
{
return QRectF(0,0,100,100);
}
void hoverEnterEvent(QGraphicsSceneHoverEvent *)
{
m_brush = Qt::red;
update();
}
void hoverLeaveEvent(QGraphicsSceneHoverEvent *)
{
m_brush = Qt::white;
update();
}
void paint(QPainter *p, const QStyleOptionGraphicsItem *, QWidget *)
{
p->setBrush(m_brush);
p->drawRoundRect(boundingRect());
}
};
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QGraphicsScene scene;
scene.addItem(new Item);
QGraphicsView view;
view.setScene(&scene);
view.setMouseTracking(true);
view.show();
return app.exec();
}

Qt - several QTextBlock inline

Is it possible to arrange several QTextBlocks in QTextDocument in one horizontal line?
I need to know which block of text was clicked and QTextBlock would be nice to use due to its method setUserState(int), which can be used to hold id of particular block. Are there better approaches?
Not sure if I got your question right, but I am taking a shot at it (some three years after the question was asked.....)
In principle you can put QTextBlocks in a horizontal line using a QTextTable. If you then create a class which inherits from QTextEdit you can catch the mouse events and find out which text block was clicked.
I post some code below where I have a very simply dialog that only has a textedit in it (of the derived class mentioned above). I create a table laying out three text blocks in a horizontal line and set their user state to the column number. Then I have the text edit class only with the overloaded mouseEvent method which only prints the userState of whatever text block it is in to the command line, just to show the principle.
Let me know if this is of any help or if misunderstood your question.
dialog.h
#ifndef MYDIALOG_H
#define MYDIALOG_H
#include "ui_dialog.h"
class MyDialog : public QDialog, public Ui::Dialog
{
public:
MyDialog(QWidget * parent = 0, Qt::WindowFlags f = 0);
void createTable();
};
#endif
dialog.cpp
#include "dialog.h"
#include <QTextTable>
#include <QTextTableFormat>
MyDialog::MyDialog(QWidget * parent, Qt::WindowFlags f) :
QDialog(parent,f)
{
setupUi(this);
}
void MyDialog::createTable()
{
QTextCursor cursor = textEdit->textCursor();
QTextTableFormat tableFormat;
tableFormat.setCellPadding(40);
tableFormat.setBorderStyle(QTextFrameFormat::BorderStyle_None);
QTextTable* table=cursor.insertTable(1,3,tableFormat);
for( int col = 0; col < table->columns(); ++col ) {
cursor = table->cellAt(0, col).firstCursorPosition();
cursor.insertBlock();
cursor.block().setUserState(col);
cursor.insertText(QString("Block in Column ")+QString::number(col));
}
}
mytextedit.h
#ifndef MYTEXTEDIT_H
#define MYTEXTEDIT_H
#include <QTextEdit>
class MyTextEdit : public QTextEdit
{
public:
MyTextEdit(QWidget * parent = 0);
void mousePressEvent(QMouseEvent *event);
};
#endif
mytextedit.cpp
#include "mytextedit.h"
#include <QMouseEvent>
#include <QTextBlock>
#include <QtCore>
MyTextEdit::MyTextEdit(QWidget * parent) :
QTextEdit(parent)
{
}
void MyTextEdit::mousePressEvent(QMouseEvent *event)
{
if(event->button() == Qt::LeftButton) {
qDebug() << this->cursorForPosition(event->pos()).block().userState();
}
}
main.cpp (just for completeness)
#include <QApplication>
#include "dialog.h"
int main(int argc, char** argv)
{
QApplication app(argc,argv);
MyDialog dialog;
dialog.show();
dialog.createTable();
return app.exec();
}

Resources