QTableView: interactive column resize without QHeaderView - qt

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

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.

How can we make a QRubberBand semi-transparent

I have already used
setOpacity();
setAttribute(Qt:WA_TranseculentBackground:)
even i have tied all the available solution nothing has effect.
this is my code
void Physician::mouseMoveEvent(QMouseEvent *e)
{
rubberBand->hide();
bottomRight = e->pos();
QRect rect = QRect(topLeft, bottomRight);
rubberBand->setGeometry(rect);//Area Bounding
QToolTip::showText(e->globalPos(), QString("%1,%2")
.arg(rubberBand->size().width())
.arg(rubberBand->size().height()), this);
}
void Physician::mousePressEvent(QMouseEvent *e)
{
rubberBand->hide();
if(e->x()<ui->videoShowLabel->x()||e->y()<ui->videoShowLabel->y())
{
selectWithInLabel.critical(0,"Error", "Select within the LABEL !");
selectWithInLabel.setFixedSize(500, 200);
}
else{
topLeft = e->pos();
myPoint = ui->videoShowLabel->mapFromGlobal(this->mapToGlobal(e->pos()));
}
}
void Physician::mouseReleaseEvent(QMouseEvent *e){
rubberBand->setWindowOpacity(0.5);
rubberBand->show();
}
void Physician::on_manualROIRadioButton_clicked()
{
rubberBand = new RubberBand(RubberBand::Rectangle, this);
}
What should i do to make rubberBand semiTransparent
I assume you sub classed QRubberBand (RubberBand).
After calling the setWindowopacity the paint event is generated (http://doc.qt.io/qt-5/qwidget.html#windowOpacity-prop)
So redefine the paint event in RubberBand class.
Inside the paint event call "initStyleOption" (given below)
http://doc.qt.io/qt-5/qrubberband.html#initStyleOption
By calling "initStyleOption" you can set the rubber band parameters for drawing.
The real issue with making the QRubberband semi-transparent is that mplayer is painting on a window without Qt having any knowledge of it. Hence Qt itself cannot act as a compositor to generate the required effect.
One possibility would be to make the QRubberBand a top level window. That way the compositing is the responsibility of the underlying graphics system rather than Qt.
With that in mind try the following. Firstly a utility base class to manage the geometry...
class abstract_rubber_band {
public:
virtual QRect get_geometry () const
{
return(QRect(m_parent->mapFromGlobal(widget().geometry().topLeft()), widget().size()));
}
virtual void set_geometry (const QRect &rect)
{
widget().setGeometry(map_rect(rect));
}
protected:
explicit abstract_rubber_band (QWidget *parent)
: m_parent(parent)
{}
/*
* #param point Coords relative to effective parent.
*/
QPoint map_point (QPoint point) const
{
if (point.x() < 0)
point.rx() = 0;
else if (point.x() >= m_parent->width())
point.rx() = m_parent->width() - 1;
if (point.y() < 0)
point.ry() = 0;
else if (point.y() >= m_parent->height())
point.ry() = m_parent->height() - 1;
point = m_parent->mapToGlobal(point);
return(point);
}
QRect map_rect (QRect rect) const
{
return(QRect(map_point(rect.topLeft()), map_point(rect.bottomRight())));
}
private:
QWidget &widget ()
{
return(dynamic_cast<QWidget &>(*this));
}
const QWidget &widget () const
{
return(dynamic_cast<const QWidget &>(*this));
}
QWidget *m_parent;
};
Now use the above as a base of the required rubber band class...
class rubber_band: public abstract_rubber_band,
public QRubberBand {
using super = QRubberBand;
public:
/*
* #param parent Note that this isn't actually used as the
* parent widget but rather the widget to which
* this rubber_band should be confined.
*/
explicit rubber_band (QWidget *parent)
: abstract_rubber_band(parent)
, super(QRubberBand::Rectangle)
{
setAttribute(Qt::WA_TranslucentBackground, true);
}
protected:
virtual void paintEvent (QPaintEvent *event) override
{
QPainter painter(this);
painter.fillRect(rect(), QColor::fromRgbF(0.5, 0.5, 1.0, 0.25));
QPen pen(Qt::green);
pen.setWidth(5);
painter.setPen(pen);
painter.setBrush(Qt::NoBrush);
painter.drawRect(rect().adjusted(0, 0, -1, -1));
/*
* Display the current geometry in the top left corner.
*/
QRect geom(get_geometry());
painter.drawText(rect().adjusted(5, 5, 0, 0),
Qt::AlignLeft | Qt::AlignTop,
QString("%1x%2+%3+%4").arg(geom.width()).arg(geom.height()).arg(geom.left()).arg(geom.top()));
}
};
The above rubber_band class should almost be a drop in replacement for QRubberBand. The main difference is that rather than reading/writing its geometry with geometry/setGeometry you must use get_geometry/set_geometry -- those will perform the mapping to/from global coordinates.
In your particular case create the rubber_band with...
rubberBand = new rubber_band(ui->videoShowLabel);

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);

QTreeView: column resize from columns and not from headers?

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.

Qt Undo/Redo for moving items

I am implementing undo/redo for move operation on QGraphicsItemGroup in my graphics scene. It works decently for point entity.
My command for move looks like:
class CommandMove : public QUndoCommand
{
public:
CommandMove(QGraphicsItemGroup *group, qreal fromX, qreal fromY,
qreal toX, qreal toY)
{
itemGroup = group;
mFrom = QPointF(fromX, fromY);
mTo = QPointF(toX, toY);
setText(QString("Point move (%1,%2) -> (%3,%4)").arg(fromX).arg(fromY)
.arg(toX).arg(toY));
}
virtual void undo()
{
itemGroup->setPos(mFrom);
}
virtual void redo()
{
itemGroup->setPos(mTo);
}
private:
QGraphicsItemGroup *itemGroup;
QPointF mFrom;
QPointF mTo;
};
My command is pushed to the undo stack as:
if (item.first->scenePos() != item.second)
{
mUndoStack->push(new CommandMove(item.first, item.second.x(),
item.second.y(), item.first->x(),
item.first->y()));
}
item is a QPair defined as:
typedef QPair<QGraphicsItemGroup *, QPointF> item;
Implemenatation for entities like line, circle etc. requires more information as compared to point. eg., start and end points for line. How do I define my command for moving my entities?
Edit
This is my implementation for line:
if (m1)
{
start_p = event->scenePos();
m1 = false;
m2 = true;
}
else if (!m1 && m2)
{
end_p = event->scenePos();
m3 = true;
m2 = false;
}
if (m3)
{
lineItem = new Line(start_p, end_p);
}
Here event is mousePressEvent.
Where do I use setPos to set the position of line?
I think you shouldn't care about all item's peculiarities. You can implement a move command that works well for any item or group of items. This is a modified version of your code.
class CommandMove : public QUndoCommand
{
public:
CommandMove(QGraphicsItem *item, qreal toX, qreal toY)
{
mItem = item;
mFrom = mItem->pos();
mTo = QPointF(toX, toY);
setText(QString("Point move (%1,%2) -> (%3,%4)").arg(mFrom.x()).arg(mFrom.y())
.arg(mTo.x()).arg(mTo,y()));
}
virtual void undo()
{
mItem->setPos(mFrom);
}
virtual void redo()
{
mItem->setPos(mTo);
}
private:
QGraphicsItem* mItem;
QPointF mFrom;
QPointF mTo;
};
I hope this helps.

Resources