QGraphicsItem loses focus after using QComboBox - c++

I am doing a small game in Qt and try to use WASD to move my protagonist(QGraphicsPixmapItem). The map is quite big so I use a QComboBox to change the scale of the scene.
The game looks like this:
The simple game
How I built the protagonist:
protagonist = new MyProtagonist();
protagonist->setFlag(QGraphicsItem::ItemIsFocusable);
protagonist->setFocus();
scene->addItem(protagonist);
How I built the combox:
sceneScaleCombo = new QComboBox;
QStringList scales;
scales << tr("1%")<<tr("10%") << tr("20%") <<tr("50%") << tr("100%") <<tr("200%");
sceneScaleCombo->addItems(scales);
sceneScaleCombo->setCurrentIndex(4);
connect(sceneScaleCombo,SIGNAL(currentIndexChanged(QString)),this,SLOT(sceneScaleChanged(QString)));
void MainWindow::sceneScaleChanged(const QString &scale)
{
double newScale = scale.left(scale.indexOf(tr("%"))).toDouble() / 100;
QMatrix oldMatrix = view->matrix();
view->resetMatrix();
view->translate(oldMatrix.dx(),oldMatrix.dy());
view->scale(newScale,newScale);
protagonist->setFocus();
}
Everything worked well at the beginning. However, after I clicking the combobox, my protagonist cannot be controlled by keyboard any more. I need to click my protagonist to make it focused again.
Is there any way to set it focused automatically?
Thanks a lot.

By clicking QComboBox the QGraphicsView looses the focus. You attempt to return it back by calling setFocus(); on the QGraphicsPixmapItem. This approach doesn't work as you expect, because it sets the focus item inside the view, but the view itself still does not have focus. To fix that in MainWindow::sceneScaleChanged instead of protagonist->setFocus(); write view->setFocus();.

Related

Why is my QStyledItemDelegate not visible?

I'm trying to create a custom item delegate for a list of objects, but when I run my app the list view is blank. I know my model is populated, and it displays as expected with the standard default delegate. I can also tell that my custom delegate is being rendered in some sense, since space is taken up in the list, and the tooltips and statustips that I set work, but the contents are not visible (the listview appears to be blank white).
My code is based on this example: https://doc.qt.io/archives/qt-5.12/qtwidgets-itemviews-stardelegate-example.html
Qt 5.12 is the version that I'm stuck with, and I'm also stuck using visual studio without the normal Qt debugging tools, do to factors outside of my control.
Anyway, I create the widgets that should be displayed in the constructor, with default values for testing:
EntityListDelegate::EntityListDelegate(QWidget* parent /*= nullptr*/) : QStyledItemDelegate(parent)
{
ui = new QWidget(parent);
ui->setMinimumSize(QSize(200, 40));
QHBoxLayout* hLayout = new QHBoxLayout(ui);
iconLabel = new QLabel(ui);
iconLabel->setMinimumSize(QSize(20, 20));
iconLabel->setMaximumSize(QSize(40, 40));
iconLabel->setPixmap(QPixmap(":/icons/Placeholder.png"));
hLayout->addWidget(iconLabel);
QVBoxLayout* vLayout = new QVBoxLayout(ui);
nameLabel = new QLabel(ui);
nameLabel->setMinimumSize(QSize(150, 20));
nameLabel->setText("Unknown entity");
vLayout->addWidget(nameLabel);
typeLabel = new QLabel(ui);
typeLabel->setMinimumSize(QSize(150, 16));
typeLabel->setText("Unknown type");
vLayout->addWidget(typeLabel);
hLayout->addLayout(vLayout);
ui->setLayout(hLayout);
}
Note that each item should have a thick black border, which is not being rendered. (Edit: I changed the QFrame back to a QWidget)
I also implemented paint, where the real values should get set based on info in the model:
void EntityListDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
nameLabel->setText(index.data(Qt::DisplayRole).toString());
switch (qvariant_cast<int>(index.data(UserRoles::Type)))
{
case EntityType::Foo:
{
iconLabel->setPixmap(QPixmap(":/icons/foo.png"));
typeLabel->setText("Foo");
break;
}
case EntityType::Bar:
{
iconLabel->setPixmap(QPixmap(":/icons/bar.png"));
typeLabel->setText("Bar");
break;
}
case EntityType::Baz:
{
iconLabel->setPixmap(QPixmap(":/icons/baz.png"));
typeLabel->setText("Baz");
break;
}
default:
break;
}
}
The other functions are just stubs, since the user shouldn't be able to edit this data.
Am I missing some step in my setup?
Update: I tried making the pointer to the delegate that gets passed into setItemDelegate() a member of the class that owns the the ListView. I now get one item on the list that renders correctly, in the top position in the list. I can sometimes get it to display another item by clicking where that item should be, but it's still in the same location.
The answer to the question as asked is that if you're trying to alter the default display, not the editor, you have to reimplement paint and basically draw the whole thing manually. This is such a massive PITA as to make QStyledItemDelegate functionally unusable for this use case, IMO. Use QtQuick if you can, but that's not an option in my case.
My actual solution, for the curious, was to use QSortFilterProxyModel and override data() to return "EntityName\nEntityType". That gets me ~80% of what I was trying to achieve, and hopefully I can get a little closer with styling when I get to that part.

Call button click function from grandchild

I'm creating my first C++ wxWidgets application. I'm trying to create some kind of split button where the options are displayed in a grid. I have a custom button class which, when right-clicked on, opens a custom wxPopupTransientWindow that contains other buttons.
When I click on the buttons in the popup, I want to simulate a left click on the main button. I'm trying to achieve this through events, but I'm kinda confused.
void expandButton::mouseReleased(wxMouseEvent& evt)
{
if (pressed) {
pressed = false;
paintNow();
wxWindow* mBtn = this->GetGrandParent();
mBtn->SetLabel(this->GetLabel());
mBtn->Refresh();
wxCommandEvent event(wxEVT_BUTTON);
event.SetId(GetId());
event.SetEventObject(mBtn);
mBtn-> //make it process the event somehow?
wxPopupTransientWindow* popup = wxDynamicCast(this->GetParent(), wxPopupTransientWindow);
popup->Dismiss();
}
}
What is the best way to do this?
You should do mBtn->ProcessWindowEvent() which is a shorter synonym for mBtn->GetEventHandler()->ProcessEvent() already mentioned in the comments.
Note that, generally speaking, you're not supposed to create wxEVT_BUTTON events from your own code. In this particular case and with current (and all past) version(s) of wxWidgets it will work, but a cleaner, and guaranteed to also work with the future versions, solution would be define your own custom event and generate it instead.

How to set QListWidget in particular position

How do i set QListWidget in particular position say i have window size of(1000,1000) and i want to set QListWidget at position (200,200).
widget = new QWidget();
setCentralWidget(widget);
list1->setFixedSize(200,150);
list1->addItem("Surya TV");
list1->addItem("Sony TV");
list1->addItem("Zee TV");
vertical->addWidget(list1);
widget->setLayout(vertical);
You can use :
void move ( int x, int y )
Something like :
p = new QListWidget(this);
p->move(200,200);
In my case "this" is the QMainWindow.
Does it helps?
Please use spacers with predefined sizes to pad your list if you are going for layouts. It is better to go for layouts if you have more widgets along with the listWidget in your main form.
If you are unsure, please use Designer to build your forms. This will allow you to have the kind of control you wish achieve in terms of fine grained positioning.
Personally, I do not prefer designer though.

How to make a context menu disappear without crashing the app?

Please take a look at this picture:
You can see a QTableView with some stupid content and a context menu in the center of it. My issue is if I click the table (no matter which button was pressed) view when that context menu is on (and I expect the context menu to disappear as it happens in Windows program and then appear back in a new place if the right button was pressed) my program immediately crash. I create it like this:
connect(tableView, SIGNAL(customContextMenuRequested(const QPoint&)),
this, SLOT(showContextMenu(const QPoint&)));
void MainWindow :: showContextMenu(const QPoint &_point)
{
QPoint pos = tableView->mapToGlobal(_point);
QModelIndex index = tableView->currentIndex();
int row = index.row();
QMenu menu;
menu.addAction("Test 1");
menu.addAction("Test 2");
QAction *action = menu.exec(pos);
QString text = action->text();
if (text == "Test 1")
qDebug("Test 1");
else
if (text == "Test 2")
qDebug("Test 2");
else
qDebug("Vzdroch");
}
I have no idea why it crashes. There's no such thing as debugger in QtCreator, i.e. it is but installing it is as complicated as launching a rocket to space. What I need is just to handle mouse clicks beyond context menu area as I normally do.
I understand that it might be really difficult for you to find out why it crashes, so I'm gonna ease my question a bit. Is there a way to make that context menu disappear when the mouse goes beyond its area? There's a signal named hovered() in Qt. It is emitted when user mouse is over a widget, so I was searching for a signal, let's call it unhovered(), emitted when user takes mouse off a widget. Unfortunately I failed to find such a signal. Is there a way to let my program know that mouse is off?
Hope I've described my issue fully.
QMenu::exec returns 0 if no menu item was selected.
You need to check action before you dereference it, otherwise you'll dereference a null pointer which leads to undefined behavior.
QAction *action = menu.exec(pos);
if (!action) {
qDebug() << "no menu selected";
} else {
QString text = action->text();
...
}

QDockWidget Draggable Tabs

I am using QDockWidgets and placing two of them on the left side of my application so that tabs can be used to select between them. However, Qt's default behavior for this looks horrible and is unintuitive. Instead of being able to drag the tabs to move the widgets, it places another bar below the selected tab (with the same name) that must be dragged instead. As a user, it would be hard to figure this out.
(My QDockWidgets are "Attributes" and "Library")
Is there a way to get rid of this second bar and make it so I can move my QDockWidgets by dragging the tabs themselves?
If you are adding QTabWidgets to a main window derived from QMainWindow, you can try tabifyDockWidget.
It tabifies two QDockWidgets just like you wanted and of course you are able to drag them.
dockWidget1 = new QDockWidget("Tab1") ;
dockWidget2 = new QDockWidget("Tab2") ;
this->addDockWidget(Qt::LeftDockWidgetArea , dockWidget1 );
this->addDockWidget(Qt::LeftDockWidgetArea , dockWidget2 );
this->tabifyDockWidget(dockWidget1,dockWidget2);
I think, Tom was not too far away from a solution:
You can set your own Widget as title bar:
myDockingWidget->setTitleBarWidget(myTitleBar)
If you design this widget to not show the dock window title, you have it. Via the signal QDockWidget::topLevelChanged your docking widget can even become informed, when it gets floating, so you could then enable the title in myTitleBar again.
As far as I can see from QDockWidget::mousePressEvent implementation in src/gui/widgets/qdockwidget.cpp dragging the dockwidgets using tabs is NOT possible:
QDockWidgetLayout *dwLayout
= qobject_cast<QDockWidgetLayout*>(layout);
if (!dwLayout->nativeWindowDeco()) {
QRect titleArea = dwLayout->titleArea();
if (event->button() != Qt::LeftButton ||
!titleArea.contains(event->pos()) ||
// check if the tool window is movable... do nothing if it
// is not (but allow moving if the window is floating)
(!hasFeature(this, QDockWidget::DockWidgetMovable) && !q->isFloating()) ||
qobject_cast<QMainWindow*>(parent) == 0 ||
isAnimating() || state != 0) {
return false;
}
initDrag(event->pos(), false);
....
As you can see from the implementation one of the things that the QDockWidget checks before allowing undocking is whether the mouse press event has come from title bar or not.
have you tried:
myDockingWidget->setTitleBarWidget(0)
edit:
QWidget* titleWidget = new QWidget(this);
mUi.dockWidget->setTitleBarWidget(titleWidget);
where 'this' is a QMainWindow
this will remove the title bar, though im not sure how to make the QDockWidget draggable from the tabs
Edited:
Please do not use this method. It introduces problems rather than soloves them.
Maybe you can try this wierd way, that is move the QWidget in the dock widget area to the title bar.
I modify the demo in folder
C:\Qt\Qt5.12.9\Examples\Qt-5.12.9\widgets\mainwindows\dockwidgets
to show how it works:
In "void MainWindow::createDockWindows()"
QDockWidget *dock = new QDockWidget(tr("Customers"), this);
dock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
//make a panel to hold your widgets
QWidget *p = new QWidget(dock);
QVBoxLayout *l = new QVBoxLayout(p);
p->setLayout(l);
customerList = new QListWidget(p);
l->addWidget(customerList);
customerList->addItems(QStringList()
<< "John Doe, Harmony Enterprises, 12 Lakeside, Ambleton"
<< "Jane Doe, Memorabilia, 23 Watersedge, Beaton"
<< "Tammy Shea, Tiblanka, 38 Sea Views, Carlton"
<< "Tim Sheen, Caraba Gifts, 48 Ocean Way, Deal"
<< "Sol Harvey, Chicos Coffee, 53 New Springs, Eccleston"
<< "Sally Hobart, Tiroli Tea, 67 Long River, Fedula");
dock->setWidget(new QWidget());//hide the real dock area
dock->setTitleBarWidget(p); //use the titlebar erea to show the content
The demo:
Drag the edge of the panel to move, actually you can drag the empty area (no child widget area). The widget on this panel still functional properly.
I also think that setTitleBarWidget() really does the trick. I remember seeing it being used for a similar purpose in the source code of the Amarok music player. Amarok has a QMainWindow which only contains dock widgets. You might want to have a look at the source code there.
It looks like you've set your dock tab position to be on the top. The default is for it to be on the bottom. Then it's not as visually jarring to have the tab text right next to the title bar text.
I don't think there's any way to do what you're proposing in Qt (eliminate the QDockWidget title bar and drag from the tab), at least not with the standard widgets. You could probably write a lot of custom code to make it happen, but that's probably not worth it.
Instead, I'd suggest moving the tabs to the bottom (see QMainWindow::setTabPosition) or possibly one of the sides.