I created a left QDockWidget, and added 2 QLabel into it.
I want a squared size for my labels (by default 100x100).
So when the user increases the width of the QDockWidget, how should I force my labels to keep a squared size?
I tried a lot of things, without any success :(
I think you shall overwrite the int QWidget::heightForWidth( int aWidth ) const function. So you need to create your own QLabel subclass.
class MyLabel
{
public:
virtual int heightForWidth( int aWidth ) const override
{
return aWidth; // So it will be square.
}
};
I think I have the solution.
I create this subclass :
class SquareLabel : public QLabel
{
public:
SquareLabel(QWidget* parent = 0) : QLabel(parent)
{
setMinimumSize(100, 100);
QSizePolicy pol(QSizePolicy::Preferred, QSizePolicy::Preferred);
pol.setHeightForWidth(true);
setSizePolicy(pol);
}
virtual int heightForWidth(int w) const { return w; }
virtual QSize sizeHint() const { int w = this->width(); return QSize(w, heightForWidth(w)); }
protected:
void resizeEvent(QResizeEvent* event) { }
};
All my labels are added in a QVBoxLayout, and I need this property :
layout->setAlignment(Qt::AlignTop);
Now, I need to resize my pixmap in my label.
I will post it before close this question.
Related
How i could create a click effect similar to push buttons on a QLabel?
QPixmap pixmap;
pixmap.load(":/files/hotkeys.png");
int w = 131;
int h = 71;
pixmap = pixmap.scaled(w, h, Qt::KeepAspectRatio);
// Label
ui.label->setGeometry(220, 220, w, h);
ui.label->setPixmap(pixmap);
// Button
QIcon icon(pixmap);
ui.toolButton->setIconSize(QSize(w, h));
ui.toolButton->setIcon(icon);
ui.toolButton->setStyleSheet("QToolButton { background-color: transparent }");
By click effect i mean like when you click on a push button containing a picture:
Ideally you'd just use a QToolButton or QPushButton, but if you must use a QLabel, you could do it by subclassing QLabel with a custom paintEvent() to give the desired effect, something like this:
class MyLabel : public QLabel
{
public:
MyLabel(const QPixmap & pm) : _isMouseDown(false) {setPixmap(pm);}
virtual void mousePressEvent( QMouseEvent * e) {_isMouseDown = true; update(); e->accept();}
virtual void mouseReleaseEvent(QMouseEvent * e) {_isMouseDown = false; update(); e->accept();}
virtual void paintEvent(QPaintEvent * e)
{
QPainter p(this);
const int offset = _isMouseDown ? 2 : 0;
p.drawPixmap(QPoint(offset, offset), *pixmap());
}
private:
bool _isMouseDown;
};
NOTE: it turned out that the problem was not due to the implementation of QStyledItemDelegate, but it was the fact that in the constructor of MyTreeWidget I was calling setUniformRowHeights(true). The code below and the solution posted by #scopchanov are valid and working
QTreeWidget has a protected method called itemFromIndex() and this is how I am making it accessible:
class MyTreeWidget : public QTreeWidget {
Q_OBJECT
public:
MyTreeWidget(QWidget *parent) : QTreeWidget(parent) {
setItemDelegate(new MyItemDelegate(this));
}
QTreeWidgetItem treeWidgetItemFromIndex(const QModelIndex& index) {
return itemFromIndex(index);
}
}
In my QStyledItemDelegate, I am storing a pointer to MyTreeWidget and then overriding its virtual sizeHint() method and based on the type of the QTreeWidgetItem adding a padding.
class MyItemDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
MyItemDelegate(QObject *parent) : QStyledItemDelegate(parent) {
_myTreeWidget = dynamic_cast<MyTreeWidget*>(parent);
}
QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const {
auto treeWidgetItem = _myTreeWidget->treeWidgetItemFromIndex(index);
QSize padding;
if (dynamic_cast<MyCustomTreeWidgetItem1*>(treeWidgetItem) {
padding = {0, 5};
} else if (dynamic_cast<MyCustomTreeWidgetItem2*>(treeWidgetItem) {
padding = {0, 10};
}
return QStyledItemDelegate::sizeHint(option, index) + padding;
}
}
This doesn't work, since sizeHint() of the delegate doesn't get called for every single QTreeWidgetItem.
So my text options to call setSizeHint() in the constructor of MyCustomTreeWidgetItem1, and that didn't seem to have any effect either. Is Qt ignoring it because there is a delegate?
Another option was to set a minimum height of a QWidget that is contained in MyCustomTreeWidgetItem which is made possible via the QTreeWidget::setItemWidget().
So it looks like the moment I use the delegate, I am confined to only size. Is my option to get rid of the delegate or there's something else I can try?
I know many people would say switch from a QTreeWidget to a QTreeView, but it's not possible at the moment.
Solution
I would approach this problem in a different (simpler) manner:
Define an enumeration for the different item sizes, e.g.:
enum ItemType : int {
IT_ItemWithRegularPadding,
IT_ItemWithBigPadding
};
When creating an item, set the desired size in its user data, depending on its type, e.g.:
switch (type) {
case IT_ItemWithRegularPadding:
item->setData(0, Qt::UserRole, QSize(0, 5));
break;
case IT_ItemWithBigPadding:
item->setData(0, Qt::UserRole, QSize(0, 10));
break;
}
In the reimplementation of sizeHint retrieve the desired size from the index's data, e.g.:
QSize sizeHint(const QStyleOptionViewItem &option,
const QModelIndex &index) const override {
return QStyledItemDelegate::sizeHint(option, index)
+ index.data(Qt::UserRole).toSize();
}
Example
Here is an example I wrote for you to demonstrate how the proposed solution could be implemented:
#include <QApplication>
#include <QStyledItemDelegate>
#include <QTreeWidget>
#include <QBoxLayout>
class Delegate : public QStyledItemDelegate
{
public:
explicit Delegate(QObject *parent = nullptr) :
QStyledItemDelegate(parent){
}
QSize sizeHint(const QStyleOptionViewItem &option,
const QModelIndex &index) const override {
return QStyledItemDelegate::sizeHint(option, index)
+ index.data(Qt::UserRole).toSize();
}
};
class MainWindow : public QWidget
{
public:
enum ItemType : int {
IT_ItemWithRegularPadding,
IT_ItemWithBigPadding
};
MainWindow(QWidget *parent = nullptr) :
QWidget(parent) {
auto *l = new QVBoxLayout(this);
auto *treeWidget = new QTreeWidget(this);
QList<QTreeWidgetItem *> items;
for (int i = 0; i < 10; ++i)
items.append(createItem(QString("item: %1").arg(i),
0.5*i == i/2 ? IT_ItemWithRegularPadding
: IT_ItemWithBigPadding));
treeWidget->setColumnCount(1);
treeWidget->setItemDelegate(new Delegate(this));
treeWidget->insertTopLevelItems(0, items);
l->addWidget(treeWidget);
resize(300, 400);
setWindowTitle(tr("Different Sizes"));
}
private:
QTreeWidgetItem *createItem(const QString &text, int type) {
auto *item = new QTreeWidgetItem(QStringList(text));
switch (type) {
case IT_ItemWithRegularPadding:
item->setData(0, Qt::UserRole, QSize(0, 5));
break;
case IT_ItemWithBigPadding:
item->setData(0, Qt::UserRole, QSize(0, 10));
break;
}
return item;
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
Note: This example sets the item's size depending on its index - odd or even. Feel free to change this by implementing the logic you need to differentiate between the items.
Result
The given example produces the following result:
The even and odd items are of a different height.
Because QTabWidget inherits QWidget we have setWindowModified() available.
But it seems it doesn't work for the tab title:
ui->tab1->setWindowTitle(QString("%1[*]").arg(tr("Tab title")));
ui->tab1->setWindowModified(true);
but it doesn't show the '*' nor changes the tab text.
Is there a way to handle this automatically instead of manually use setTabText()?
I don't think there's any way to get the tab text to follow the widget title by default. Having said that, it should be very easy to fix up by overriding QTabWidget::tabInserted.
class tab_widget: public QTabWidget {
using super = QTabWidget;
using this_class = tab_widget;
public:
using super::super;
protected:
virtual void tabInserted (int index) override
{
super::tabInserted(index);
if (auto *w = widget(index)) {
connect(w, &QWidget::windowTitleChanged, this, &this_class::handle_window_title_change);
}
}
virtual void tabRemoved (int index) override
{
super::tabRemoved(index);
if (auto *w = widget(index)) {
disconnect(w, &QWidget::windowTitleChanged, this, &this_class::handle_window_title_change);
}
}
private:
void handle_window_title_change (const QString &title)
{
if (auto *w = qobject_cast<QWidget *>(sender())) {
setTabText(indexOf(w), title);
}
}
};
Using the above class rather than QTabWidget should result in the tab text mirroring the title of the widget associated with that tab.
So, after debugging the program in Qt I realized that the debugger thinks I did not initialized the variables; however, I got the variables in and out from the private class, made the class a pointer and seems like nothing happens. Please let me know what am I missing, I have the same problem in other program but I don't know if is just me or the program.
The code is as follows:
main:
#include "selectionarea.h"
#include <QApplication>
#include <QtWidgets>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
// QMainWindow *win = new QMainWindow();
QSize winsize(500,500);
SelectionArea area;
area.setStartingLocX(0);
area.setStartingLocY(0);
area.setLength(300);
area.setWidth(300);
area.resize(winsize);
area.show();
return app.exec();
}
selectionarea.cpp
#include "selectionarea.h"
#include <QPainter>
#include <QDebug>
#include <QLabel>
SelectionArea::SelectionArea()
{
}
void SelectionArea::paintEvent(QPaintEvent *)
{
QRect rectangle(getStartingLocx(), getStartingLocy(),
getWidth(), getLength());
/*QRegion Constructs a paint event object with the
* region that needs to be updated. The region is
* specified by paintRegion.*/
QPainter painter(this);
painter.setPen(QPen(Qt::SolidPattern,
2.0,
Qt::SolidLine,
Qt::FlatCap,
Qt::MiterJoin));
painter.drawRect(rectangle);
}
void SelectionArea::setStartingLocX(int x)
{
x=StartingLocX;
qDebug() <<x<<" "<<StartingLocX;
}
int SelectionArea::getStartingLocx()
{
return StartingLocX;
}
void SelectionArea::setStartingLocY(int y)
{
y=StartingLocY;
qDebug() <<y<<" "<<StartingLocY;
}
int SelectionArea::getStartingLocy()
{
return StartingLocY;
}
void SelectionArea::setWidth(int w)
{
w=Width;
qDebug() <<w<<" "<<Width;
}
int SelectionArea::getWidth()
{
return Width;
}
void SelectionArea::setLength(int l)
{
l=Length;
}
int SelectionArea::getLength()
{
return Length;
}
and selectionarea.h
#ifndef SELECTIONAREA_H
#define SELECTIONAREA_H
#include <QPixmap>
#include <QLabel>
#include <QRect>
class SelectionArea : public QLabel
{
int StartingLocX;
int StartingLocY;
int Length;
int Width;
public:
SelectionArea();
~SelectionArea()
{
}
void setStartingLocX(int);
void setStartingLocY(int);
void setLength(int);
void setWidth(int);
int getStartingLocx();
int getStartingLocy();
int getWidth();
int getLength();
virtual void paintEvent(QPaintEvent *);
};
#endif // SELECTIONAREA_H
forgive my ambiguity.
UPDATE:
the application output is
0 0
0 0
0 0
and the window is displayed.
Making my comment the answer
Your setter function should actually look like:
void SelectionArea::setStartingLocX(int x)
{
StartingLocX = x;
}
because you initialize class member variable StartingLocX with the value of x (same for other setter functions). In your version of the function you do the opposite, so that your class member variables remain uninitialized.
You don't initialize the member variables when creating an instance of SelectionArea. You only set them afterwards via the setter functions.
You should initialize them in your constructor using an initializer list.
This might in this case be obsolete, yet it will prevent you from running into strange error cases.
This is very much non-idiomatic C++:
SelectionArea area;
area.setStartingLocX(0);
area.setStartingLocY(0);
area.setLength(300);
area.setWidth(300);
You should refactor the code to read:
SelectionArea area{0, 0, 300, 300};
or even
SelectionArea area{300, 300};
Furthermore, you really should make it a true QObject, not a half-made one. And don't shadow QWidget's own members (like witdth() etc.) with your own of similar names. Getters don't have to have a get prefix, it only adds to visual clutter and is not helpful.
The properties I've added below are likely overkill, just add the ones you're reasonably likely to use. It's still a good idea to simply keep the entire selection as a QRect - that's what it really is.
class SelectionArea : public QLabel
{
Q_OBJECT
Q_PROPERTY(QRect selection READ selection WRITE setSelection USER true)
Q_PROPERTY(QPoint selTopLeft READ selTopLeft WRITE setSelTopLeft STORED false)
Q_PROPERTY(int selX READ selX WRITE setSelX STORED false)
Q_PROPERTY(int selY READ sely WRITE setSelY STORED false)
Q_PROPERTY(QSize selSize READ selSize WRITE setSelSize STORED false)
Q_PROPERTY(int selWidth READ selWidth WRITE setSelWidth STORED false)
Q_PROPERTY(int selHeight READ selHeight WRITE setSelHeight STORED false)
QRect m_selection;
public:
SelectionArea(QWidget * parent = 0) : QLabel(parent) {}
SelectionArea(const QSize & size, QWidget * parent = 0) :
QLabel(parent), m_selection(QPoint(), size) {}
SelectionArea(const QPoint & startingLoc, const QSize & size,
QWidget * parent = 0) :
QLabel(parent), m_selection(startingLoc, size) {}
SelectionArea(int width, int height, QWidget * parent = 0) :
QLabel(parent), m_selection(0, 0, width, height) {}
SelectionArea(int x, int y, int width, int height, QWidget * parent = 0) :
QLabel(parent), m_selection(x, y, width, height) {}
void setSelection(const QRect & r) {
if (m_selection == r) return;
m_selection = r;
update();
}
void setSelTopLeft(const QPoint & p) {
if (m_selection.topLeft() == p) return;
m_selection.moveTo(p);
update();
}
void setSelX(int x) {
if (m_selection.x() == x) return;
m_selection.moveTo(x, m_selection.y());
update();
}
void setSelY(int y) {
if (m_selection.y() == y) return;
m_selection.moveTo(m_selection.x(), y);
update();
}
void setSelSize(const QSize & s) {
if (m_selection.size() == s) return;
m_selection.setSize(s);
update();
}
// etc.
QRect selection() const { return m_selection; }
QPoint selTopLeft() const { return m_selection.topLeft(); }
int selX() const { return m_selection.x(); }
int selY() const { return m_selection.y(); }
QSize selSize() const { return m_selection.size(); }
// etc.
protected:
void paintEvent(QPaintEvent *) Q_DECL_OVERRIDE;
};
It's also questionable whether you really want to derive from QLabel. It seems that you override the painting, and you don't really care for the text property directly. You'd be better off inheriting straight from a QFrame instead.
I'm trying to move a QGraphicsRectItem after I add it to the scene. It moves, but appears with a certain offset from the mouse pointer. I think it is simply adding the mouse pointer position to its original position. I am at a loss as to how to work around this.
Here is my code:
class ucFilter : public QGraphicsItem {
std::shared_ptr<QGraphicsRectItem> m_rect;
std::shared_ptr<QGraphicsTextItem> m_text;
std::shared_ptr<QString> m_name;
std::shared_ptr<QPointF> m_pos;
QGraphicsItem* selectedItem;
bool m_mouseGrabbed;
public:
static const int default_x = 80, default_y=40;
ucFilter::ucFilter(QString &name, QPointF &pos){
m_name = shared_ptr<QString>(new QString(name));
m_pos = shared_ptr<QPointF>(new QPointF(pos));
m_rect = shared_ptr<QGraphicsRectItem>( new QGraphicsRectItem(pos.x()-default_x, pos.y()-default_y, 2*default_x, 2*default_y ));
m_text = shared_ptr<QGraphicsTextItem>( new QGraphicsTextItem(name));
m_text->setPos(pos.x() - m_text->boundingRect().width()/2, pos.y()- 30);
selectedItem = NULL;
m_mouseGrabbed = false;
}
QGraphicsRectItem* getRect() { return m_rect.get(); }
QGraphicsTextItem* getText() { return m_text.get(); }
QString* getName() { return m_name.get(); }
QPointF* getPos() { return m_pos.get(); }
void setPos(QPointF newPos) { m_pos->setX(newPos.x()); m_pos->setY(newPos.y()); }
QRectF ucFilter::boundingRect() const
{
return m_rect->boundingRect();
}
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
Q_UNUSED(widget);
//QBrush brush(
if (!m_mouseGrabbed){ grabMouse(); m_mouseGrabbed = true; }
}
void mousePressEvent(QGraphicsSceneMouseEvent *event){
selectedItem = this;
QGraphicsItem::mousePressEvent(event);
}
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event){
selectedItem = NULL;
QGraphicsItem::mouseReleaseEvent(event);
}
void mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
if(NULL != selectedItem){
m_text->setPos(event->pos());
m_rect->setPos(event->pos());
}
QGraphicsItem::mouseMoveEvent(event);
}
};
the ucFilter object is created in the scene dropEvent:
void cGraphicsScene::dropEvent(QGraphicsSceneDragDropEvent * event){
QTreeView* source = static_cast<QTreeView*>(event->source());
string name = event->mimeData()->text().toUtf8().constData();
if(0 == name.length()){
event->acceptProposedAction();
return ; // nothing to do anymore
}
QPointF pos = event->scenePos ();
shared_ptr<ucFilter> newFilter = shared_ptr<ucFilter>(new ucFilter(event->mimeData()->text(),event->scenePos ()));
m_filters.push_back(newFilter);
this->addItem(newFilter->getRect());
this->addItem(newFilter->getText());
this->addItem(newFilter.get()); // also add the item to grab mouse events
event->acceptProposedAction();
}
Where could the problem be ?
Here a screenshot of what I'm actually seeing :
I would like the rectangle to be drawn where the mouse is..
You have several issues:
The use of shared pointers to hold everything is completely unwarranted. The scene acts as a container for items - just like QObject is a container for objects.
ucFilter doesn't have children. It holds pointers to other items, but that is unnecessary. The base item can be a rectangle itself, and it can have the text as a child. That way you don't need to handle positioning in a special fashion.
ucFilter can be movable. Don't reimplement that functionality yourself.
When you pass things by reference, pass them as const references unless you are intending to pass the modified value out. If you wish to change the value inside of the body of the function, you can pass it by value instead.
The mouse is already grabbed when you are dragging the item.
Let's start with the ucFilter item. It is really simple and does everything you need. Note that m_text is held by value, and is made a child of the rectangle parent.
// https://github.com/KubaO/stackoverflown/tree/master/questions/graphics-item-drop-32574576
#include <QtWidgets>
class ucFilter : public QGraphicsRectItem {
QGraphicsTextItem m_text;
public:
ucFilter(const QString &name, const QPointF &pos, QGraphicsItem * parent = 0) :
QGraphicsRectItem(parent),
m_text(this)
{
static const QRect defaultRect(0, 0, 160, 80);
setPos(pos);
setRect(QRect(-defaultRect.topLeft()/2, defaultRect.size()));
setFlags(QGraphicsItem::ItemIsMovable);
setName(name);
}
void setName(const QString & text) {
m_text.setPlainText(text);
m_text.setPos(-m_text.boundingRect().width()/2, -30);
}
QString name() const {
return m_text.toPlainText();
}
};
Since we're dropping from a convenience widget (a QListWidget), we need to decode the text from a application/x-qabstractitemmodeldatalist mime type:
const char * kMimeType = "application/x-qabstractitemmodeldatalist";
QVariant decode(const QMimeData* data, Qt::ItemDataRole role = Qt::DisplayRole) {
auto buf = data->data(kMimeType);
QDataStream stream(&buf, QIODevice::ReadOnly);
while (!stream.atEnd()) {
int row, col;
QMap<int, QVariant> map;
stream >> row >> col >> map;
if (map.contains(role)) return map[role];
}
return QVariant();
}
The scene creates an item as soon as a drag enters it, and moves the item during the drag.
Ownership of the items remains with the scene: we don't need to delete any items unless we want to explicitly remove them from the scene. The m_dragItem is used to refer to the currently dragged item simply to move it and add it to m_filters upon completion of the drop. It the drag leaves the scene (or is aborted), the item is simply deleted from the scene.
class cGraphicsScene : public QGraphicsScene {
QList<ucFilter*> m_filters;
ucFilter* m_dragItem;
public:
cGraphicsScene(QObject * parent = 0) : QGraphicsScene(parent), m_dragItem(nullptr) {}
void dragEnterEvent(QGraphicsSceneDragDropEvent * event) Q_DECL_OVERRIDE {
if (!event->mimeData()->hasFormat(kMimeType)) return;
auto name = decode(event->mimeData()).toString();
if (name.isEmpty()) return;
QScopedPointer<ucFilter> filter(new ucFilter(name, event->scenePos()));
addItem(m_dragItem = filter.take());
event->acceptProposedAction();
}
void dragMoveEvent(QGraphicsSceneDragDropEvent * event) Q_DECL_OVERRIDE {
if (!m_dragItem) return;
m_dragItem->setPos(event->scenePos());
event->acceptProposedAction();
}
void dropEvent(QGraphicsSceneDragDropEvent * event) Q_DECL_OVERRIDE {
if (!m_dragItem) return;
m_dragItem->setPos(event->scenePos());
m_filters << m_dragItem;
event->acceptProposedAction();
}
void dragLeaveEvent(QGraphicsSceneDragDropEvent * event) Q_DECL_OVERRIDE {
delete m_dragItem;
m_dragItem = nullptr;
event->acceptProposedAction();
}
};
The test harness is very simple: our scene, a view displaying it, and a list with two items that you can drag onto the scene.
int main(int argc, char ** argv) {
QApplication app{argc, argv};
QWidget w;
cGraphicsScene scene;
QGraphicsView view(&scene);
QListWidget list;
QHBoxLayout l(&w);
l.addWidget(&view);
l.addWidget(&list);
list.setFixedWidth(120);
list.addItem("Item1");
list.addItem("Item2");
list.setDragDropMode(QAbstractItemView::DragOnly);
view.setAcceptDrops(true);
w.resize(500, 300);
w.show();
return app.exec();
}
You can drag items from the list on the right to the scene on the left. You can also move the items within the scene, to relocate them.