Adding a right-click menu for specific items in QTreeView - qt

I'm writing a Qt desktop application in c++ with Qt Creator.
I declared in my main window a treeView, and a compatible model.
Now, I would like to have a right-click menu for the tree item. Not for all of the items, but for a part of them, for example: for the tree elements with an even index.
I tried adding a simple context menu with the following code:
in the .h file:
QStandardItemModel* model;
QMenu* contextMenu;
QAction* uninstallAction;
private slots:
void uninstallAppletClickedSlot();
and in the .cpp file:
in the constructor:
ui->treeView->setModel(model);
contextMenu = new QMenu(ui->treeView);
ui->treeView->setContextMenuPolicy(Qt::ActionsContextMenu);
uninstallAction = new QAction("Uninstall TA",contextMenu);
ui->treeView->addAction(uninstallAction);
connect(uninstallAction, SIGNAL(triggered()), this, SLOT(uninstallAppletClickedSlot()));
and a slot:
void MainWindow::uninstallAppletClickedSlot()
{
}
this code gives me a context menu with the wanted action, but do you have any idea how can I add this action only for the QStandardItems with the even indexes??
BTW, I'm adding items to the treeView by the following way:
void MainWindow::AddItem(QString name)
{
QStandardItem *parentItem = model->invisibleRootItem();
QStandardItem *app = new QStandardItem(name);
parentItem->appendRow(app);
}
I googled a lot, but found nothing :(
thanks in advance!

I would do this in the following way:
Configure the context menu
ui->treeView->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ui->treeView, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onCustomContextMenu(const QPoint &)));
Implement the context menu handling
void MainWindow::onCustomContextMenu(const QPoint &point)
{
QModelIndex index = ui->treeView->indexAt(point);
if (index.isValid() && index.row() % 2 == 0) {
contextMenu->exec(ui->treeView->viewport()->mapToGlobal(point));
}
}

Related

Geometry information of QRubberBand giving SIGSEGV

I am designing a Qt application using Qt Creator. As a part of the application, I need to be able to get position, height and width information of QRubberBand before I hide() it. For that purpose I tried to use the following logic, which is given in the documentation of QRubberBand:
void Widget::mousePressEvent(QMouseEvent *event)
{
origin = event->pos();
if (!rubberBand)
rubberBand = new QRubberBand(QRubberBand::Rectangle, this);
rubberBand->setGeometry(QRect(origin, QSize()));
rubberBand->show();
}
void Widget::mouseMoveEvent(QMouseEvent *event)
{
rubberBand->setGeometry(QRect(origin, event->pos()).normalized());
}
void Widget::mouseReleaseEvent(QMouseEvent *event)
{
rubberBand->hide();
// determine selection, for example using QRect::intersects()
// and QRect::contains().
}
And defined rubberBand as below in the private section of the header file:
QRubberBand *rubberBand;
After doing that it works well. To go to the next step I defined the following integers as well (private section of the header):
int rubX;
int rubY;
int rubWidth;
int rubHeight;
And I tried to get geometry information before hiding rubberBand in mouseReleaseEvent similar to the following:
void Widget::mouseReleaseEvent(QMouseEvent *event)
{
rubX = rubberBand->x();
rubY = rubberBand->y();
rubHeight = rubberBand->height();
rubWidth = rubberBand->width();
rubberBand->hide();
}
When I added those codes, the program runs, but when I try drawing the rubberBand, the program crashes giving SIGSEGV.
So here are my questions:
Why this is happening?
Is it possible to accomplish my goal by slightly editing the code?
What should I do to get what I want?
I know that I have done a foolish mistake, but I have not found it yet. Do not hesitate to comment if you want to get more information about the question.
From the code and comments you appear to be assuming that the rubberBand member will automatically be initialized to nullptr. However, it's actually uninitialized making the following...
if (!rubberBand)
rubberBand = new QRubberBand(QRubberBand::Rectangle, this);
result in undefined behaviour.
Change your Widget constructor to include an explicit initialization...
Widget::Widget ()
: rubberBand(nullptr)
{
...
}
Better still, just make rubberBand a non-pointer member...
QRubberBand rubberBand;
and initialize it in the constructor with...
Widget::Widget ()
: rubberBand(QRubberBand::Rectangle, this)
{
...
}

Qt - Not correctly adding widgets to QHBoxLayout after clearing the layout

I have a QHBoxLayout in which I added some widgets. I need to be able to refresh the layout dynamically so I use this to clear the layout :
void ClearLayout(QLayout* layout)
{
if (!layout)
return;
QLayoutItem* item;
while ((item = layout->takeAt(0)) != nullptr)
{
delete item->widget();
ClearLayout(item->layout());
}
}
This indeed removes all widgets and layouts. After this layout->isEmpty() returns true and layout->count() returns 0.
However, when I try to add new widgets (same type of other previously added but new instance) It does not work !
AddWidget()
{
// DeviceWidget inherits QWidget
DeviceWidget* deviceWidget = new DeviceWidget;
deviceWidget->setFixedSize(150, 200);
connect(deviceWidget->GetSignalObject(), &DeviceObject::Selected, this,
&DeviceLayout::SelectedDevice);
layout->addWidget(deviceWidget, 0, Qt::AlignCenter);
}
This is the same function used previously to add the widgets to the layout and worked the first time at Construction:
MainLayout(QWidget* parent) : QHBoxLayout(parent)
{
layout = new QHBoxLayout;
addLayout(layout);
uint32 nb = GetDeviceNumber(); // returns 2
for (uint32 i = 0; i < deviceNb; ++i)
AddDeviceWidget();
}
After trying to add 2 widgets I have layout->isEmpty() returns true and layout->count() returns 2 so I'm confused …
thanks for any help provided :)
EDIT:
The problem seems to be comming from my DeviceWidget class since trying to add a simple QLabel to the cleared layout worked. Here's the DeviceWidget Constructor:
DeviceWidget::DeviceWidget(QWidget* parent) : QWidget(parent)
{
QVBoxLayout* vLayout = new QVBoxLayout;
QLabel* deviceIcon = new QLabel("DeviceIcon", this);
deviceIcon->setFixedSize(128, 128);
deviceIcon->setPixmap(QPixmap::fromImage(QImage("Resources/Icons/device.png")));
deviceIcon->setObjectName("DeviceIcon");
// StatusWidget inherits QWidget
// Just override paintEvent to display a colored filled disk
m_status = new StatusWidget(20, Status::Close, this);
m_status->setObjectName("DeviceStatus");
vLayout->addWidget(deviceIcon, 0, Qt::AlignCenter);
vLayout->addWidget(m_status, 0, Qt::AlignCenter);
// DeviceObjct inherits from QObject an add a signal
m_object = new DeviceObject(size());
// Function clearing the stylesheet background-color
Clear();
setLayout(vLayout);
installEventFilter(this);
setObjectName(QString("DeviceWidget"));
}
Commenting installEventFilter(this) make it work so I think I need to add an event filter to make it work but I don't know which one
As said in the Edit, The problem is coming from DeviceWidget added in the layout that override eventFilter. There is probably a way to add a case in eventFilter to make it work but in my case it was best either (1) or (2):
1. Remove eventFilter from class DeviceWidget and put it in class DeviceObject: m_object is present to emit a signal according to event:
DeviceObject.h:
DeviceObject(QObject* parent);
bool eventFilter(QObject* obj, QEvent* event) override;
signals:
void Select(uint32 i);
Then in class DeviceWidget still call installEventFilter but with m_object as parameter: installEventFilter(m_object);
For the other event (Enter/Leave) I overrode void enterEvent(QEvent* event) and void leaveEvent(QEvent* event) for class DeviceWidget. That's what lead me to the second option that seems better.
2. Completely remove eventFilter and installEventFilter as it is only used to emit a signal when widget is clicked and do things when cursor hovers the widget. Instead override enterEvent and leaveEvent for class DeviceWidget like said before for hover event.
Then in class DeviceObjecy override void mousePressEvent(QMouseEvent*) for clicked event.

Qt - requiring new model rows to be non-empty

I'm making a program where the user can add multiple people (participants) in a list. When the "Add" button is clicked, a new row is added and "edit" is called for the name field. All is well so far, but there is a thing I'd like to implement, and I can't seem to figure out how: when the user closes the editing field (presses enter or escape, clicks elsewhere, etc.) and if the name field remains empty, I'd like the row to be deleted. In other words, a name has to be filled in. Here is what I have so far:
void MainWindow::addParticipant()
{
QList<QStandardItem *> newRow;
newRow << new QStandardItem()
<< new QStandardItem();
participantModel->appendRow(newRow);
participantView->edit(participantModel->index(participantModel->rowCount()-1, 0));
}
Here participantModel is a QStandardItemModel and participantView is a QTreeView. I tried using signals and slots to detect when a row is empty and to delete it, but it hasn't worked and the syntax is elusive to me.
Ideally I'd be able to detect when the name field is not being edited anymore, so that I can delete the row if need be.
Here is ugly but working solution: subclass from QItemDelegate and check input data inside setModelData member function. As far setModelData has a const qualifier you can not modify model inside it, so you need some trick: in the following example the model is modified inside handler of closeEditor signal.
class MainWidget : public QWidget
{
Q_OBJECT
public:
MainWidget ()
{
QStandardItemModel * model = new QStandardItemModel ();
ItemDelegate * delegate = new ItemDelegate ();
table->setItemDelegate (delegate);
connect (delegate, & ItemDelegate::closeEditor, [=](){
if (isEmpty) {
model->removeRow (emptyRow);
isEmpty = false;
emptyRow = -1;
}
});
connect (delegate, & ItemDelegate::cellEdited, [=](const int row){
isEmpty = true;
emptyRow = row;
});
}
bool isEmpty;
int emptyRow;
};
class ItemDelegate : public QItemDelegate
{
Q_OBJECT
signals:
void cellEdited (int) const;
public:
void setModelData (QWidget * widget, QAbstractItemModel * model, const QModelIndex & index) const override
{
if (0 == index.column () ) {
if (QLineEdit * cellWidget = qobject_cast <QLineEdit *> (widget) ) {
if (cellWidget->text ().isEmpty () ) {
emit cellEdited (index.row () );
return;
}
}
}
QItemDelegate::setModelData (widget, model, index);
}
};
Complete example available at GitLab.
The comments/answers posted thus far have urged me to look more into item delegates. Quite embarrassingly, after relatively little googling I found the following solution for my problem:
void MainWindow::addParticipant()
{
QStyledItemDelegate *participantDelegate = new QStyledItemDelegate;
participantView->setItemDelegateForColumn(0, participantDelegate);
QList<QStandardItem *> newRow;
newRow << new QStandardItem()
<< new QStandardItem();
participantModel->appendRow(newRow);
connect(participantDelegate, SIGNAL(closeEditor(QWidget*,QAbstractItemDelegate::EndEditHint)), this, SLOT(checkRow()));
participantView->edit(participantModel->index(participantModel->rowCount()-1, 0));
}
Apparently the closeEditor signal (only available to delegates) is exactly what I was looking for. When the editor is closed, the slot checkRow() checks if the name field of the participant is empty and decides whether or not to delete the row.

Qt custom context menu

Am creating a Qt Application which consists of a tree view and a webview. when a item from a tree view is clicked it should load the corresponding url. It Works Fine. when am right clicking on the item a custom context menu will appear n it will open it in a new webview. This is also working. But my problem is when am Right clicking on the treeview item my context menu comes and if am clicking it outside the pop up menu the url of that item gets loaded. how to solve this.. Help me friends..
Here's my coding:
QStandardItem *rootItem = new QStandardItem("Google");
QStandardItem *stackItem = new QStandardItem("Stack Overflow");
QStandardItem *yahooItem = new QStandardItem("Yahoo");
rootItem->appendRow(stackItem);
standardModel->appendRow(rootItem);
standardModel->appendRow(yahooItem);
***// private slot for loading the url if a treeview item is clicked:***
void MainWindow::treeViewClicked(const QModelIndex &index)
{
str = index.data().toString();
if(!(str.isEmpty()) && str=="Google")
{
url = "http://www.google.com";
}
else if (!(str.isEmpty()) && str == "stack Overflow")
{
url = "http://www.stackoverflow.com";
}
else if (!(str.isEmpty()) && str == "Yahoo")
{
url = "http://www.yahoo.com";
}
WebView *wv = dynamic_cast<WebView *>(ui->tabWidget->currentWidget());
wv->load(QUrl(url));
ui->tabWidget->setTabText(ui->tabWidget->currentIndex(),str);
treeView->setModel(standardModel);
**//Creating custom context menu for QtreeView:**
void MainWindow::showContextMenu(const QPoint& point)
{
QList<QAction *> actions;
if(treeView->indexAt(point).isValid())
{
actions.append(m_treeViewAction);
}
else if(actions.count() > 0)
{
QMenu::exec(actions, MainWindow::treeView->mapToGlobal(point));
QModelIndex index = treeView->indexAt(point);
QStandardItem *item = standardModel->itemFromIndex(index);
treeView->setCurrentIndex(index);
treeViewClicked(index);
}
}
For what I know, the situation you describe is standard with context menus in views: When you right click, the item is also selected.
If you want another behaviour, you must implement the mousePressEvent and implement the behaviour you want to achieve.
Here is a hint:
void MyTreeView::mousePressEvent ( QMouseEvent * event )
{
if (event->button() == Qt::LeftButton) {
// set the current item based on event->pos() / deselect if no item
}
else if (event->button() == Qt::RightButton) {
// show context menu for the item / different context menu if no item
}
}
Yes, you must derive the QTreeView class and make one of your own.
I've done this long time ago, and I remember this as the starting point. I don't remember now if I had to reimplement all four basic mouse events: press, release, move and doubleclick, as they are internally related.

How to add multiple tables dynamically in scroller in qt

I am adding three tables dynamically in widget containing table widget and labels, but nothing shows on screen, I have tried to do it with vertical layout but it does not expand if i add a new row, so not scrolling.
Is there any other way to get all three tables on a same page with scrolling.
QScrollArea *m_scrollArea =ui->scrollArea_Stats;
m_scrollArea->setWidgetResizable(true);
QWidget *area = new QWidget;
QVBoxLayout *vlay = new QVBoxLayout(m_scrollArea);
area->setLayout(vlay);
StatsWidget *objStatsWidget;
for(int i=0;i<2;i++)
{
objStatsWidget=new StatsWidget(ui->scrollArea_Stats);
vlay->addWidget(objStatsWidget);
}
m_scrollArea->setWidget(area);
here StatsWidget is my custom widget containing 2 lables at top and a table widget
I am adding three tables dynamically but page is not scrolling, vlay is not showing all tables it is just showing what it can show in a page without scrolling.
try rewrite the code as this:
m_scrollArea->setWidgetResizable(true);
QVBoxLayout *vlay = new QVBoxLayout;
StatsWidget *objStatsWidget;
for(int i=0;i<2;i++)
{
objStatsWidget=new StatsWidget(ui->scrollArea_Stats);
vlay->addWidget(objStatsWidget);
}
QWidget *area = new QWidget(m_scrollArea);
area->setLayout(vlay);
m_scrollArea->setWidget(area);
EDIT: i made something like what you are trying to do some time ago..
so: create a custom QWidget with a QVBoxLayout as member.let's call this object "widgetList". then reimplement all method that you need, as addWidget, takeAt etc.. using your layout as a list
finally set widgetList as widget for your scroll area..
let me know..
I made all this because QWidgetList was not enough easy to use and i needed something else that i have omitted here..
I found my piece of code:
class WidgetList : public QWidget
{
Q_OBJECT
public:
WidgetList(QWidget *parent = 0);
~WidgetList();
void addWidget(QWidget*);
void removeWidget(QWidget*);
QList<QWidget*> getListWidget() const;
QWidget* takeAt(int) const;
int count() const;
private:
QVBoxLayout* layout_;
};
.cpp
WidgetList::WidgetList(QWidget *parent)
: /**/QWidget(parent)
/**/,layout_(new QVBoxLayout(this))
{
this->setLayout(layout_);
}
void WidgetList::removeWidget(QWidget* widget)
{
layout_->removeWidget(widget);
}
void WidgetList::addWidget(QWidget* widget)
{
layout_->addWidget(widget);
}
QWidget* WidgetList::takeAt(int index) const
{
return layout_->takeAt(index)->widget();
}
int WidgetList::count() const
{
return layout_->count();
}
this will be your new Widget with layout where to insert your custom widget..
then i put widgetList as widget of QScrollArea:
QScrollArea* scrollArea = new QScrollArea;
widgetList* list = new widgetList(scrollArea);
scrollArea->setWidget(list);
everything works for me..
EDIT 2: i post my main that works good with my previous code:
QScrollArea* scroll = new QScrollArea;
WidgetList* w = new WidgetList(scroll);
QLabel * label = new QLabel("Label1");
QLabel* label2 = new QLabel("label2");
QTableWidget* table = new QTableWidget(10,10);
w->addWidget(label);
w->addWidget(label2);
w->addWidget(table);
scroll->setWidget(w);
scroll->setWidgetResizable(true);
scroll->show();

Resources