QGraphicsItem selectedItems() is empty - qt

I'm trying to get items but I can't get to work with selectedItems(). With the following code, the qDebug returns "()" or crash of course if I use selectedItems.last().
I don't understand what I'm doing wrong, I even added a foreach to be sure items were selected but nothing. The following code comes from my View class.
if (event->button() == Qt::LeftButton) {
foreach(auto item, items(event->pos())) {
item->setSelected(true);
}
qDebug()<< scene->selectedItems();
Ask me if I can provide you further details, I have a lot of code and don't really know what could be concerned by my problem.
Moreover, if I change the foreach with :
foreach(auto item, items(event->pos())) {
scene->removeItem(item);
}
the item WILL BE deleted, so it kinda select the item. I don't get why setSelected(true) then selectedItems() doesn't return me items.

I found out that my item wasn't selectable by default. It's a QGraphicsEllipseItem by the way.
My selectedItems is working with the following flag :
item->setFlag(QGraphicsItem::ItemIsSelectable);
Final code :
foreach(auto item, items(event->pos())) {
item->setFlag(QGraphicsItem::ItemIsSelectable);
item->setSelected(true);
}

Related

allowsSelectionDuringEditing property for NSTableView

Application I'm trying to develop is heavily built around being able to have editable TableViews. I'm beginning to conclude that most if not all of my problems stem from the fact that I do not see allowsSelectionDuringEditing available for NSTableView as it is for UITableView.
First, would like to get insight into why.
Second, how do I go about implementing one in my NSTableView class?
Finally, I have a NSSegmentedControl in one of my columns. So I need to implement allowSelectiongWhenFocused property somewhere, because while I have focus on the button and allowing user to use <- and -> with spacebar to select switch(es), I don't want mouse / keyboard from changing the selected row.
As an aside, while I now know how to write custom UI classes and hook them into Interface Builder, I'm struggling on when/whether to customize NSTableView, NSTableRowView, NSTableCellView or NSSegmentedControl. I've tried to understand how refuseFirstResponder works as well. Trial and error is not getting me anywhere - I fix something and break something somwehre else. If someone can suggest any other reading besides Apple documentation (sometimes I suspect if it is in English) would really appreciate it.
Here's what I would do:
Subclass NSTableRowView and override hitTest(_:). If the row is not selected then the row view returns itself instead of a control. The first click in a row selects the row and doesn't change the value of a control.
override func hitTest(_ point: NSPoint) -> NSView? {
let view = super.hitTest(point)
if view == nil || self.isSelected {
return view
}
else {
return self
}
}
Implement NSTableViewDelegate's tableView(_:rowViewForRow:) to use the custom row view:
func tableView(_ tableView: NSTableView, rowViewForRow row: Int) -> NSTableRowView? {
return MyTableRowView()
}
Implement NSTableViewDelegate's selectionShouldChange(in:) so a row can't be deselected if the values aren't valid or other conditions aren't met.
func selectionShouldChange(in tableView: NSTableView) -> Bool {
let row = tableView.selectedRow
if (row >= 0) {
return self.validateRow(row)
}
else {
return true
}
}

How to set signals for each action in QMenu?

for(auto s :listofPossibleValues){
// item =s;
action = myMenu.addAction("Set Value to "+s);
connect(action,SIGNAL(triggered(bool)),this,SLOT(menuClicked()));
}
void MainWindow::menuClicked(){
value = new QStandardItem(item);
model->setItem(mainindex->row(),mainindex->column(),value);
}
I add actions and connect signals to the slot to my menu using the code above. Previously I was using the item to be the text. But it will only work for last item.
Does anyone at least know how to get the action that I clicked on?
How can I make it work for each individual item rather than just the last one?
Use the triggered signal of QMenu:
connect(menu, SIGNAL(triggered(QAction*)), this, SLOT(menuClicked(QAction*)));
Then, in menuClicked():
void MainWindow::menuClicked(QAction *action) {
// do something with action
}

How to make QComboBox popup upwards?

my QComboBox-derived class lives in a QGraphicsScene at the bottom end of the (visible) screen - but it pops up downwards, thus out of view.
(How) is it possible to force the popup to open above the widget?
I've tried re-implementing showPopup like this:
void MyComboBox::showPopup()
{
QAbstractItemView *popupView = view();
popupView->move(0,-100);
//popupView->window->move(0,-100);
QComboBox::showPopup();
}
The result is, that the content seems to be shifted, but not the underlying popup object.
I think it might be possible to find a solution with styles as indicated in
this article, but I can't find any Styles control that might be helpful here. I am rather new to C++ as well as Qt, so I might be missing something obvious.
I'd appreciate any help on this matter!
Best regards,
Sebastian
With the information found here, I was able to get it done this way:
void SteuerQComboBox::showPopup() {
QComboBox::showPopup();
QWidget *popup = this->findChild<QFrame*>();
popup->move(popup->x(),popup->y()-this->height()-popup->height());
}
Note that it's crucially important to call the base classes "showPopup" first.
Thanks to everybody who was reading my question and thinking about it!
user1319422's solution isn't bad, but it has two problems.
If your platform has GUI animation, the listbox will animate opening downwards, then is moved above the text box.
If you disable combobox animation (or you don't have it), the call to QComboBox::showPopup() still makes the GUI element start to appear on the screen already. So, moving it there would cause it to flicker as it appears in the first place and moves to the next.
So, to address the first problem, I just switched off animation:
void MyComboBox::showPopup()
{
bool oldAnimationEffects = qApp->isEffectEnabled(Qt::UI_AnimateCombo);
qApp->setEffectEnabled(Qt::UI_AnimateCombo, false);
QComboBox::showPopup();
qApp->setEffectEnabled(Qt::UI_AnimateCombo, oldAnimationEffects);
}
Then, for the second problem, I moved the frame in the Show event:
bool MyComboBox::eventFilter(QObject *o, QEvent *e)
{
bool handled = false;
if (e->type() == QEvent::Show)
{
if (o == view())
{
QWidget *frame = findChild<QFrame*>();
//For some reason, the frame's geometry is GLOBAL, not relative to the QComboBox!
frame->move(frame->x(),
mapToGlobal(lineEdit()->geometry().topLeft()).y() - frame->height());
}
}
/*else if other filters here*/
if (!handled)
handled = QComboBox::eventFilter(o, e);
return handled;
}
if you want to force popup to open above only when it is out of view you can do this:
void SteuerQComboBox::showPopup() {
QComboBox::showPopup();
QWidget *popup = this->findChild<QFrame*>();
if((popup->y() + popup->height()) > this->window()->height())
popup->move(popup->x(),popup->y()-this->height()-popup->height());
}

How to iterate through a menu's actions in Qt?

I'm working in a project where I need to open (show or popup) automatically the items in the QMenuBar.
Let's say I have the next menu bar:
File Edit Help
-op1 -op1 -op1
-op2 -op2 -op2
To set an action (show the menu associated with that action) I use:
menuBar->setActiveAction(mymenuactionpointer);
As I know, I can use one of the following to get a list of pointers to the elements of QMenuBar:
QMenuBar::actions();
or
QList<Object*> lst1 = QMenuBar::findChildren<QObject*>();
QList<Object*> lst2 = QMenuBar::findChildren<QAction*>();
When I use QMenuBar::findChildren<QAction*>() or MenuBar::actions() I got a list of the menus in menubar, I mean, I got "File, Edit, Help" from my QMenuBar, the size of the QList in this case is 3.
When I use QMenuBar::findChildren<QObject*>() I got a list of QObject of size 6, which is the correct number of items in the menu bar. However, I have tried cast to QAction*
QAction *a = (QAction *)lst1.at(0);
QAction *a = qobject_cast<QAction*>(lst1.at(0));
QAction *a = dynamic_cast<QAction*>(lst1.at(0));
In all this cases a is not NULL, but when I try to get the action name QAction::title() it always causes me segmentation fault.
I have been searching and I found here that after getting the menubar actions list, one can ask to QAction::menu() (which returns a valid QMenu pointer if the item is a menu) to know if the item is a QMenu, if yes, one can repeat getting the actions list of that menu, and continue iterating. But this does not work for me, I expected that for
QList<Object*> lst2 = QMenuBar::findChildren<QAction*>();
each element "File, Edit Help" QAction::menu() returns a valid menu pointer, so I could get the list of the actions of each menu, but this does not work at all for me.
The correct way to enumerate a QMenu is to use the actions() functions, but there is a catch - some of the actions are submenus, and they need to be iterated recursively. In fact, each QMenu is associated with a QAction, and they both hold pointers to each other - see QMenu::menuAction() and QAction::menu().
It is crucial to understand that each QMenu is also associated with a QAction. So knowing that, the proper implementation is the following:
void enumerateMenu(QMenu *menu)
{
foreach (QAction *action, menu->actions()) {
if (action->isSeparator()) {
qDebug("this action is a separator");
} else if (action->menu()) {
qDebug("action: %s", qUtf8Printable(action->text()));
qDebug(">>> this action is associated with a submenu, iterating it recursively...");
enumerateMenu(action->menu());
qDebug("<<< finished iterating the submenu");
} else {
qDebug("action: %s", qUtf8Printable(action->text()));
}
}
}
Below is how to iterate through every menu item in the menu bar, it will also seek out any menus underneath so there is not need for recursive calling here.
// assuming you have a private QActionGroup* actions; defined in the header..
// ...and a slot named 'onAction(QAction*)' as well... this should work:
QList<QMenu*> lst;
lst = ui->menuBar->findChildren<QMenu*>();
actions = new QActionGroup(this);
foreach (QMenu* m, lst)
{
foreach (QAction* a, m->actions())
{
actions->addAction(a);
}
}
connect(actions,SIGNAL(triggered(QAction*)),this,SLOT(onAction(QAction*)));
As you can see, you can then connect a master slot to handle the various events an action might bring up (i just showed triggered here but you get the idea). Hope this helps.. someone..
Notes
I used the QActionGroup for example purposes on using the list you might iterate through, but you really shouldnt use that unless you are dealing with radio groups since thats what its for. Secondly, if you want the actions because you plan to link them into a single method to handle all items, i suggest you use QMenu's triggered/hovering signals or if you need to know when a menu is about to pop up, you'll need QMenuBar's aboutToShow() signal. I cant think of a reason (for me anyway) that you cant do what you need in those signals since you are passed the QAction* in the slot. But if you MUST do it the otherway, you can do it the way I showed above, you just might not want to use the QActionGroup because of radio grouping is what it is designed for. (you can work around that by not adding items that are 'checkable' into the group.
The reason the qobject_cast is failing is that there are only three QActions with the QMenuBar as the parent. The other three are different QObjects (my guess is the three QMenus), so the cast fails. The QActions associated with those menus are then under those, not the root QMenuBar. I fail to see why you can't build a master list of QActions by recursively iterating through the QMenus.
You may be able to just use the QAction pointer from your UI definition if you are after a known menu, that might not trigger the parent menus. If you are trying to automate testing, trigger() on your desired QAction is probably as detailed as you need. If you are trying to do things in response to user actions, modifying toolbars is probably a better means of contextual feedback, as it doesn't break focus. Some more details on what you're actually trying to accomplish would help.
this puts it all together:
template <class Function>
class QMenuBarIterator {
QMenuBar *i_barP;
void iterate_sub(Function f, size_t tabsL, QMenu* m) {
foreach (QAction *action, m->actions()) {
f(tabsL, action);
if (action->menu()) {
iterate_sub(f, tabsL + 1, action->menu());
}
}
}
public:
QMenuBarIterator(QMenuBar *barP) : i_barP(barP) {}
virtual void operator()(size_t levelL, QAction *actionP) {
}
void iterate(Function f) {
QList<QMenu *> menuBar = i_barP->findChildren<QMenu *>();
foreach (QMenu* m, menuBar) {
f(0, m->menuAction());
iterate_sub(f, 1, m);
}
}
};
/***************************************************************************/
class CMenuLogger {
public:
void operator()(size_t tabsL, QAction *action) {
SuperString tabStr(GetIndentString(tabsL)); // returns a string with tabsL tab characters in it
if (action->isSeparator()) {
qDebug("%s-------------------", tabStr.utf8Z());
} else {
qDebug("%s%s (%s)",
tabStr.utf8Z(),
qUtf8Printable(action->text()),
qUtf8Printable(action->objectName()));
}
}
};
then in your main:
{
QMenuBarIterator<CMenuLogger> bar(ui->menuBar);
bar.iterate(CMenuLogger());
}

QListView & QStandardItemModel - Prevent duplicates

What is the way to go to prevent duplicates in a QListView which uses QStandardItemModel as its model? Data is added with drag & drop, so I tried to override QStandardItemModel::dropMimeData, which seems kind of odd since I need to override QStandardItemModel::mimeData (and reimplement encodeData/decodeData) as well. This has to be easier!
Well, I managed to solve this by overriding QListView::dataChanged, which checks if there is more than one item with the same data for Qt::DisplayRole in the model after dropping and deletes one of them if there is. It basically looks like that:
void MyListView::dataChanged(QModelIndex topLeft, QModelIndex bottomRight)
{
// there can be only one item dragged at once in my use case
if(topLeft == bottomRight)
{
QStandardItemModel* m = static_cast<QStandardItemModel*>(model());
// if theres already another item with the same DisplayRole...
if(m->findItems(topLeft.data().toString()).count() > 1)
{
// ... we get rid of it.
model()->removeRow(topLeft.row());
}
}
else
{
// let QListView decide
QListView::dataChanged(topLeft, bottomRight);
}
}
It's by far not perfect (e.g. if you can drop more than one item at once) but it works for that simple use case.
The easiest way I can see would be to create your own proxy model.
See http://doc.qt.io/qt-5/qabstractproxymodel.html

Resources