Change row drag animation - qt

I create a QTableWidget subclass who can recive drop both from external file and his internal rows. While dragging a row to move it in a different position, a copy of the row appear anchored to the mouse cursor. There's a way to remove the anchored row copy while dragging?
Here is the class:
#include <QtGui>
#include <QDrag>
#include "dtablewidget.h"
#include "nofocusproxystyle.cpp"
DTableWidget::DTableWidget(QWidget *parent) : QTableWidget(parent) {
//set widget default properties:
setFrameStyle(QFrame::Sunken | QFrame::StyledPanel);
viewport()->setAcceptDrops(true); //set accept drop on viewport
setDragDropOverwriteMode(false); //set drag drop overwrite to false
setDropIndicatorShown(true); //show drop indicator on tag drop
setDragDropMode(QAbstractItemView::InternalMove); //enable internal drag drop on tablular dispaly
setSelectionBehavior(QAbstractItemView::SelectRows); //enable selection of entire row
setEditTriggers(QAbstractItemView::NoEditTriggers);//preventing editing
setAlternatingRowColors(true);
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
setShowGrid(false);
setAcceptDrops(true);
setWordWrap(false);
setStyleSheet("selection-background-color: yellow;"
"selection-color: #002041;"
"font-size: 75%;"
);
setStyle(new NoFocusProxyStyle(style()));
}
void DTableWidget::dragEnterEvent(QDragEnterEvent *event) {
event->acceptProposedAction();
}
void DTableWidget::dragMoveEvent(QDragMoveEvent *event) {
event->acceptProposedAction();
}
void DTableWidget::dropEvent(QDropEvent *event) {
event->acceptProposedAction();
if (event->mimeData()->urls().size() > 0) {
emit dropped(event->mimeData());
}
else {
QPoint old_coordinates = QPoint(-1,-1);
if(currentItem() != NULL) //Check if user is not accessing empty cell
{
old_coordinates = QPoint(currentItem()->row(), currentItem()->column());
}
QTableWidget::dropEvent(event);
if(this->itemAt(event->pos().x(), event->pos().y()) != NULL && old_coordinates != QPoint(-1, -1))
{
emit moved(old_coordinates.x(), itemAt( event->pos().x(), event->pos().y())->row());
}
}
}
void DTableWidget::dragLeaveEvent(QDragLeaveEvent *event) {
event->accept();
}
void DTableWidget::keyPressEvent(QKeyEvent *event) {
emit keyboard(event);
}

Override QAbstractItemView::startDrag() and don't set an image in QMimeData. You can base your implementation by looking at https://code.woboq.org/qt5/qtbase/src/widgets/itemviews/qabstractitemview.cpp.html#_ZN17QAbstractItemView9startDragE6QFlagsIN2Qt10DropActionEE

Related

Qt: How to display a text consistently on the cursor, not depending on cursor position?

I want a text to be displayed persistently on the curser event when the cursor is moving, not depending on the cursor position. I used Qtooltip for this purpose. This is the code to show the text:
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
// ...
}
bool Widget::event (QEvent *ev)
{
if (event->type() == QEvent::ToolTip) {
QHelpEvent *helpEvent = static_cast<QHelpEvent *>(ev);
QToolTip::showText(helpEvent->globalPos(), "Something got it");
return false;
}
return QWidget::event(ev);
}
But when I run this code the text is not displayed consistently and it shows up only sometimes, disappears while moving the cursor, and the whole window flickers.
You can probably achieve what you want by intercepting mouse move events rather than the tool tip notifications...
class tooltip_event_filter: public QLabel {
using super = QLabel;
public:
tooltip_event_filter ()
{
setWindowFlags(windowFlags()
| Qt::BypassWindowManagerHint
| Qt::FramelessWindowHint
);
}
protected:
virtual bool eventFilter (QObject *obj, QEvent *event) override
{
if (event->type() == QEvent::MouseMove) {
/*
* Note the QPoint(1, 0) offset here. If we don't do that then the
* subsequent call to qApp->widgetAt(QCursor::pos()) will return a
* pointer to this widget itself.
*/
move(QCursor::pos() + QPoint(1, 0));
if (const auto *w = qApp->widgetAt(QCursor::pos())) {
setText(QString("widget#%1").arg((qulonglong)w));
show();
} else {
hide();
}
}
return super::eventFilter(obj, event);
}
};
Then install an instance of tooltip_event_filter on the application instance...
tooltip_event_filter tooltip_event_filter;
qApp->installEventFilter(&tooltip_event_filter);
The example shown simply displays the address of the widget under the mouse pointer as it's moved.

Detect Drag on Drop over all Child QWidgets

I'm trying to implement Drag and Drop behavior, across my whole application, I would like to execute some action when a drop occurs, no mater where in the MainWindow. The Problem that I'm facing is that in my MainWindow, I have a Widget which contains a QGraphicsView, which again contains a QGraphicsScene, I can detect the drop anywhere in the application via EventFilter, except in the in the View and Scene. Is it possible to achieve some kind of global drag and drop behavior which I can detect from the MainWindow? I'm trying to avoid spreading the Logic across all my visible Widgets.
This is the drag and drop logic in my MainWindow, works, as said, for all drag and drops that happen not over the View/Scene:
void MainWindow::dropEvent(QDropEvent *event)
{
auto pixmap = QPixmap(event->mimeData()->urls().first().toString().remove("file://"));
if(!pixmap.isNull()) {
load(pixmap);
}
event->acceptProposedAction();
}
void MainWindow::dragEnterEvent(QDragEnterEvent *event)
{
if (event->mimeData()->hasUrls()) {
event->acceptProposedAction();
}
}
For the drag and drops over the View/Scene I've tried to install following event filter:
bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{
if(event->type() == QEvent::DragEnter) {
qDebug("Enter");
} else if(event->type() == QEvent::Drop) {
qDebug("Drop");
} else if(event->type() == QEvent::GraphicsSceneDrop) {
qDebug("Scene drop");
} else if (event->type() >= 159 && event->type() <= 168) {
qDebug("Any Scene Event");
}
return QObject::eventFilter(obj, event);
}
And apply it in the MainWindow ctor, the only thing that I detect is an enter when it enters the view.
...
setAcceptDrops(true);
mChildWidget->installEventFilter(this);
mChildWidget->setAcceptDrops(true);
...
It looks like I was facing two issues here. The first one is explained in this question here Accepting drops on a QGraphicsScene. The QGraphicsScene ignores DragAndDrops that are not happening over items. Basically you can enable drag and drop but only for evey item individually, which still leaves the arae that is not covered by an item. The solution to this issue seems to be to overwrite dragMoveEvent
void MyQGprahicsScene::dragMoveEvent(QGraphicsSceneDragDropEvent *event)
{
Q_UNUSED(event)
// Overriding default dragMoveEvent in order to accept drops without items under them
}
The second issue was that i was facing is that I was trying to apply the eventFilter to Child widget, but it looks like I had to apply it to qApp, after which everything seemed to work, probably because qApp is the top QObject where all events will land when not handled.
#include "DragAndDropHandler.h"
bool DragAndDropHandler::eventFilter(QObject *obj, QEvent *event)
{
if(event->type() == QEvent::DragEnter) {
handleDragEnter(dynamic_cast<QDragEnterEvent *>(event));
} else if(event->type() == QEvent::Drop) {
handleDrop(dynamic_cast<QDropEvent *>(event));
} else if(event->type() == QEvent::GraphicsSceneDrop) {
handleDrop(dynamic_cast<QGraphicsSceneDragDropEvent *>(event));
} else if (event->type() == QEvent::GraphicsSceneDragEnter) {
handleDragEnter(dynamic_cast<QGraphicsSceneDragDropEvent *>(event));
}
return QObject::eventFilter(obj, event);
}
void DragAndDropHandler::handleDragEnter(QDragEnterEvent *event)
{
if (event->mimeData()->hasUrls()) {
event->acceptProposedAction();
}
}
void DragAndDropHandler::handleDragEnter(QGraphicsSceneDragDropEvent *event)
{
if (event->mimeData()->hasUrls()) {
event->acceptProposedAction();
}
}
void DragAndDropHandler::handleDrop(QDropEvent *event)
{
auto path = getUrlFromMimeData(event->mimeData());
event->acceptProposedAction();
emit imageDropped(path);
}
void DragAndDropHandler::handleDrop(QGraphicsSceneDragDropEvent *event)
{
auto path = getUrlFromMimeData(event->mimeData());
event->acceptProposedAction();
emit imageDropped(path);
}
QString DragAndDropHandler::getUrlFromMimeData(const QMimeData *mimeData) const
{
return mimeData->urls().first().toString().remove("file://");
}
And, in the constructor of my MainWindow:
setAcceptDrops(true);
qApp->installEventFilter(mDragAndDropHandler);
connect(mDragAndDropHandler, &DragAndDropHandler::imageDropped, this, &MainWindow::loadImageFromFile);

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...

How to create a SIGNAL for QTableWidget from keyboard?

I have a table and move around inside with left, right, up, down buttons. Now I need to create a SIGNAL when I stay in a certain cell and press SPACE button. This SIGNAL should bring also the coordinate of that cell. I tried with standard signals of QTableWidget but it does not work. How can I solve this?
Create a separate header file i.e. "customtable.h" and then in the Designer you can Promote the existing QTableWidget to this class.
class customTable:public QTableWidget
{
Q_OBJECT
public:
customTable(QWidget* parent=0):QTableWidget(parent){}
protected:
void keyPressEvent(QKeyEvent *e)
{
if(e->key()==Qt::Key_Space)
{
emit spacePressed(this->currentRow(),this->currentColumn());
}
else { QTableWidget::keyPressEvent(e); }
}
signals:
spacePressed(int r, int c);
};
You can use an event filter to do this:
class TableSpaceWatcher : public QObject {
Q_OBJECT
bool eventFilter(QObject * receiver, QEvent * event) override {
auto table = qobject_cast<QTableWidget*>(receiver);
if (table && event->type() == QEvent::KeyPress) {
auto keyEvent = static_cast<QKeyEvent*>(event);
if (keyEvent->key() == Qt::Key_Space)
emit spacePressed(table->currentRow(), table->currentColumn());
}
return false;
}
public:
using QObject::QObject;
Q_SIGNAL void spacePressed(int row, int column);
void installOn(QTableWidget * widget) {
widget->installEventFilter(this);
}
};
QTableWidget table;
TableSpaceWatcher watcher;
watcher.installOn(&table);

How do I implement the ability to edit a QTableWidget's vertical and horizontal header text in-line from the GUI instead of programmatically?

I have a QTableWidget that is displayed in the user interface that I can add and remove rows and columns using buttons. The problem is, when I add a row or column, I can change the data in the actual cells, but I cannot name the row or column. The name is simply a static number.
Is there a way to allow the user of my program to perhaps double-click on a row/column header and edit the name in-line or something similar?
Thanks.
As far as I know there is no built-in way to do this. However this can be implemented manually. The main idea of the following code is to detect double clicks on header items, place QLineEdit over them and save edited text once it loses focus. The example is based on Qt generated Designer Form Class with a table named ui->tableWidget which can be either QTableWidget or QTableView.
class MainWindow : public QMainWindow {
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private:
Ui::MainWindow *ui;
QLineEdit* header_editor;
int editor_index;
bool eventFilter(QObject*, QEvent*);
};
Source:
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
header_editor = 0;
ui->setupUi(this);
ui->tableWidget->horizontalHeader()->viewport()->installEventFilter(this);
ui->tableWidget->verticalHeader()->viewport()->installEventFilter(this);
}
MainWindow::~MainWindow() {
delete ui;
}
bool MainWindow::eventFilter(QObject* object, QEvent* event) {
if ((object == ui->tableWidget->horizontalHeader()->viewport() ||
object == ui->tableWidget->verticalHeader()->viewport()) &&
event->type() == QEvent::MouseButtonDblClick) {
if (header_editor) { //delete previous editor just in case
header_editor->deleteLater();
header_editor = 0;
}
QMouseEvent* e = static_cast<QMouseEvent*>(event);
QHeaderView* header = static_cast<QHeaderView*>(object->parent());
int mouse_pos = header->orientation() == Qt::Horizontal ? e->x() : e->y();
int logical_index = header->logicalIndex(header->visualIndexAt(mouse_pos));
if (logical_index >= 0) { // if mouse is over an item
QRect rect; // line edit rect in header's viewport's coordinates
if (header->orientation() == Qt::Horizontal) {
rect.setLeft(header->sectionPosition(logical_index));
rect.setWidth(header->sectionSize(logical_index));
rect.setTop(0);
rect.setHeight(header->height());
} else {
rect.setTop(header->sectionPosition(logical_index));
rect.setHeight(header->sectionSize(logical_index));
rect.setLeft(0);
rect.setWidth(header->width());
}
rect.adjust(1, 1, -1, -1);
header_editor = new QLineEdit(header->viewport());
header_editor->move(rect.topLeft());
header_editor->resize(rect.size());
header_editor->setFrame(false);
//get current item text
QString text = header->model()->
headerData(logical_index, header->orientation()).toString();
header_editor->setText(text);
header_editor->setFocus();
editor_index = logical_index; //save for future use
header_editor->installEventFilter(this); //catch focus out event
//if user presses Enter it should close editor
connect(header_editor, SIGNAL(returnPressed()),
ui->tableWidget, SLOT(setFocus()));
header_editor->show();
}
return true; // filter out event
} else if (object == header_editor && event->type() == QEvent::FocusOut) {
QHeaderView* header = static_cast<QHeaderView*>(
header_editor->parentWidget()->parentWidget());
//save item text
header->model()->setHeaderData(editor_index, header->orientation(),
header_editor->text());
header_editor->deleteLater(); //safely delete editor
header_editor = 0;
}
return false;
}
Downsides of this method are that it's hacky, things go bad when headers are resized or table is scrolled. It's just an example that can be improved.
I have a feeling there has to be a simpler way. But Qt headers ignore Qt::ItemIsEditable flag and can't use delegates.

Resources