I'm using Qt3D. I'm adding QObjectPicker to my 3D entities like this:
Qt3DRender::QObjectPicker *MyScene::createObjectPickerForEntity(Qt3DCore::QEntity *entity)
{
if (!entity)
return nullptr;
picker = new Qt3DRender::QObjectPicker(entity);
picker->setHoverEnabled(true); // I set this correctly
picker->setDragEnabled(true); // Also required!
picker->setObjectName(QStringLiteral("__internal ") + entity->objectName());
entity->addComponent(picker);
connect(picker, &Qt3DRender::QObjectPicker::pressed, this, &MyScene::handlePickerPress);
connect(picker, &Qt3DRender::QObjectPicker::moved, this, &MyScene::handlePickerMove);
return picker;
}
The mouse press events are fired, and corresponding slot logs the name of pressed entity and global coordinate correctly:
void MyScene::handlePickerPress(Qt3DRender::QPickEvent *event)
{
Qt3DCore::QEntity *pressedEntity = qobject_cast<Qt3DCore::QEntity *>(sender()->parent());
if (!pressedEntity && !pressedEntity->isEnabled())
return;
qDebug() << "Pressed Entity Name: "<< pressedEntity->objectName();
qDebug() << "Global Coord: " << event->worldIntersection();
}
However, to my surprise, the mouse move (hover) events are not working. The following slot function is NOT logging anything. Also, when I place a debugger break-point at this slot, I see that it is NOT invoked at all:
void MyScene::handlePickerMove(Qt3DRender::QPickEvent *event)
{
qDebug() << "Hover Intersection:" << event->worldIntersection();
}
Am I missing something?
My Qt3D rendering settings are:
m_renderSettings = new Qt3DRender::QRenderSettings();
m_renderSettings->pickingSettings()->setPickMethod(Qt3DRender::QPickingSettings::TrianglePicking);
m_renderSettings->pickingSettings()->setPickResultMode(Qt3DRender::QPickingSettings::NearestPick);
m_renderSettings->setObjectName(QStringLiteral("__internal Scene frame graph"));
m_renderer = new Qt3DExtras::QForwardRenderer();
m_renderer->setClearColor(QColor("#c8c8c8"));
m_renderSettings->setActiveFrameGraph(m_renderer);
m_renderSettings->setRenderPolicy(Qt3DRender::QRenderSettings::RenderPolicy::OnDemand);
m_rootEntity->addComponent(m_renderSettings);
m_rootEntity->addComponent(new Qt3DInput::QInputSettings());
UPDATE
Relates to this report.
The description of the moved signal says: "This signal is emitted when the bounding volume defined by the pickAttribute property intersects with a ray on a mouse move with a button pressed".
So this signal is not emitted on hover/mouse move, but on mouse move while pressing a mouse button.
Related
I'm trying to allow for selecting other indices while editing the content of a cell in a (custom) QTableView. My QTableView uses a custom delegate.
In order to do this, since clicking another cell causes the delegate to close its editor, I've tried to add an event filter to my QTableView.
// Constructor
viewport()->installEventFilter(this);
bool Table::eventFilter(QObject *obj, QEvent *ev)
{
auto mouseEvent = dynamic_cast<QMouseEvent*>(ev);
if (!mouseEvent)
return false;
if (mouseEvent->type() == QEvent::MouseMove)
return false;
// editing_ is a state attribute that describes whether the user is editing a cell
if (editing_)
{
if (mouseEvent->button() == Qt::MouseButton::LeftButton)
{
selectedCell = model()->data(indexAt(mouseEvent->pos()), Qt::UserRole).value<SpreadsheetCell*>();
qDebug() << "User selected" << selectedCell->id();
return true;
}
}
}
Unfortunately, things don't happen in the right order, and I only get to catch the event after the editor has been closed. See:
void MyDelegate::commitAndCloseEditor()
{
// ...
qDebug() << "Closing editor";
// ...
}
My debug output:
Creating editor
User selected "D4" <- This is triggered when the editor has just been opened, not when the editor is closing
Closing editor
It seems like my event filter catches the events after they've already been propagated to children, which kind of kills the purpose... Please suggest.
So I want to create the gui interface in Qt/cpp for the server which can be in many different states, and depending on its state the buttons in gui need to be set differently ex:
GUI:
button1 - unchecked and enabled to click
button2 - disabled(grayed out)
button3 - disabled
button3 - disabled
Then after click button1
GUI:
button1 - checked
button2 - enabled to click
button3 - enabled to click
button3 - enabled to click
But for example if server is in different state and you connect via gui the buttons should look like this:
GUI:
button1 - checked
button2 - enabled to click
button3 - disabled to click
button3 - disabled to click
Is there some established pattern/way of handling that intuitively? The biggest problem here is that if the server has a lot of different states that need the buttons to be set in a lot of different configurations. The only thing I can come up with is mapping the state of all buttons to the specific state but well... there's a lot of buttons and a lot of states.
You could try using flags, the idea is that when an event happens and you want the GUI to change you set a flag which in turn is recalled in a loop. Below you can see the general idea and concept.
If you change the state of the flag you will get a different output and it will loop over and over listening for events just write the GUI code for each in the different states.
#include <iostream>
using namespace std;
int getserverstatusfunction() {/*your code to check server status returns 0,1 or 2*/
return 0;
}
UI.button.click(true) { getresult = 1; }; //change the UI state when the button is clicked
int main() {
bool running;
while (running){
int getresult = getserverstatusfunction();
if (getresult == 0)
{
cout << "Draw flag one interface code\n";
}
else if (getresult == 1)
{
cout << "Draw flag two interface code\n";
}
else {
cout << "Draw flag three interface code\n";
}
system("pause");
return 0;
}
I've found the best way to do this is just to have a single slot-method (e.g. UpdateAllButtonStates() that updates all of your buttons and checkboxes, e.g.:
void MyWindow::UpdateAllButtonStates() // Qt-slot method
{
const bool shouldButton1BeEnabled = [...];
button1->setEnabled(shouldButton1BeEnabled);
const bool shouldButton2BeEnabled = [...];
button2->setEnabled(shouldButton2BeEnabled);
[... and so on for all enables and checked/unchecked states in your GUI]
}
... then anytime your program's internal state has changed in any way that might require an update to one or more buttons/checkboxes in the GUI, call this method explicitly, or set up a signal/slot connection that will call it for you.
The advantage of doing it this way is the simplicity -- with this approach, it's trivial to guarantee that your GUI widgets will be in updated to the expected state after any internal-state-change, because there is only one code-path to write and debug. The alternative (trying to come up with the correct transitional behavior for every possible state-change in your program) quickly leads to an intractable amount of complexity, and endless debugging and hair-pulling.
You might think the downside is inefficiency -- after all, we are updating all the buttons even though in any cases, only one of them may have changed -- but Qt's code is smart enough that calling setEnabled(false) on a button that is already disabled is a no-op (likewise calling setEnabled(true) on a button that is already enabled, and so on), so the heavyweight code of redrawing a widget's pixels will only be executed when the widget's state has actually changed.
The logic inside UpdateAllButtonStates() that calculates shouldButton1BeEnabled, etc, does get executed a lot, but it usually ends up being pretty trivial logic so that turns out not to be important. However, if for some reason that logic turns out to be expensive, you have the option of reducing the frequency at which UpdateAllButtonStates() gets executed, by using asynchronous execution and a boolean "dirty-bit", e.g.:
void MyWindow::ScheduleUpdateAllButtonStates() // Qt-slot method
{
if (_dirtyBit == false)
{
_dirtyBit = true;
QTimer::singleShot(0, this, SLOT(UpdateAllButtonStates()));
}
}
void MyWindow::UpdateAllButtonStates()
{
if (_dirtyBit == false) return;
_dirtyBit = false;
// Update button enables/checked states as previously, here
}
... then have all your internal-state-change code call ScheduleUpdateAllButtonStates() rather than calling UpdateAllButtonStates() directly; the advantage is that even if ScheduleUpdateAllButtonStates() gets called 500 times in a row, it will only result in UpdateAllButtonStates() getting called once, during the next iteration of Qt's event loop.
Enabling/disabling button UI logic can be very dirty and difficult to manage and trace. Also, sometimes we want to be in particular state and want to make a minor change like changing state of just one button. Here is an approach. It's generic but you will have to adrop it accordingly with your UI.
#include <iostream>
class UIState
{
protected:
bool btn1;
bool btn2;
bool btn3;
public:
UIState()
{
btn1 = false;
btn2 = false;
btn3 = false;
}
virtual void setBtn1State(bool new_state)
{
btn1 = new_state;
std::cout << btn1 << btn2 << btn3 << std::endl;
};
virtual void setBtn2State(bool new_state)
{
btn2 = new_state;
std::cout << btn1 << btn2 << btn3 << std::endl;
};
virtual void setBtn3State(bool new_state)
{
btn3 = new_state;
std::cout << btn1 << btn2 << btn3 << std::endl;
};
};
class UIStateAllEnabled : public UIState
{
public:
UIStateAllEnabled()
{
btn1 = true;
btn2 = true;
btn3 = true;
std::cout << btn1 << btn2 << btn3 << std::endl;
}
};
class UIStateAllDisabled : public UIState
{
public:
UIStateAllDisabled()
{
btn1 = false;
btn2 = false;
btn3 = false;
std::cout << btn1 << btn2 << btn3 << std::endl;
}
};
class UI
{
UIState * currentState;
public:
UI()
{
currentState = NULL;
}
~UI()
{
if (currentState != NULL)
{
delete currentState;
std::cout << "deleted current state" << std::endl;
}
}
void setState(UIState * new_state)
{
// should also check for if already current state?
UIState * prevState = currentState;
currentState = new_state;
if (prevState != NULL)
{
delete prevState;
std::cout << "deleted previous state" << std::endl;
}
}
void setBtn1State(bool new_state)
{
currentState->setBtn1State(new_state);
};
void setBtn2State(bool new_state)
{
currentState->setBtn2State(new_state);
};
void setBtn3State(bool new_state)
{
currentState->setBtn3State(new_state);
};
};
int main()
{
UI ui;
// enable all buttons
ui.setState(new UIStateAllEnabled);
// Now say you want to change state of a particular button within this state.
ui.setBtn1State(false);
ui.setBtn3State(false);
// switch to a completely new state, disable all buttons
ui.setState(new UIStateAllDisabled);
// customize within that sate
ui.setBtn3State(true);
return 0;
}
I have a QListWidget to store usernames, and I use this signal to detect if username is being changed:
connect(listWidget, &QListWidget::itemChanged, this, &MainWindow::changeUserName);
void MainWindow::changeUserName(QListWidgetItem *editItem)
{
qDebug() << "Name:" << editItem->text();
}
And this is how I make QListWidget editable in another function:
connect(listWidget, &QListWidget::itemDoubleClicked, this, &MainWindow::makeListEditable);
void MainWindow::makeListEditable()
{
QListWidgetItem *editItem = listWidget->currentItem();
editItem->setFlags(editItem->flags() | Qt::ItemIsEditable);
qDebug() << "Name edit";
}
But which confused me is, whenever I double clicked the list widget, the itemChanged signal will be triggered once, and when I input a new username, the signal will be triggered again. Why would this happen?
This is the debug output, when I double clicked the list, it says:
Name: "Testing name_1"
Name edit
after I entered a new name and hit the enter, it says:
Name: "Testing name_2" //a new name I changed to
And what if I only want a signal be triggered once whenever I input a new name and hit the enter, what should I do to achieve this?
Thanks
You can use the item delegate commitData signal, this way:
QObject::connect(listWidget->itemDelegate(), SIGNAL(commitData(QWidget*)), this, SLOT(dataCommited(QWidget*)));
the slot is like this:
void dataCommited(QWidget * w)
{
QString data = (static_cast<QLineEdit *>(w))->text();
//...
}
The signal will be emitted at the end of the editing (i.e. enter key pressed or focus lost etc.)
As #Rafalon said, calling setFlags calls your slot changeUserName, and the signal currentTextChanged is emitted when the current item changed, it is the same as currentItemChanged except it gives you the text instead of the item.
What you can do is, make your item editable as you instantiate it:
QListWidgetItem* pItem = new QListWidgetItem();
pItem->setText("Testing name_1");
pItem->setFlags(pItem->flags() | Qt::ItemIsEditable);
listWidget->addItem(pItem);
or you can activate/deactivate the connection when you need it:
void MainWindow::makeListEditable(QListWidgetItem *editItem)
{
editItem->setFlags(editItem->flags() | Qt::ItemIsEditable);
connect(ui->listWidget, SIGNAL(itemChanged(QListWidgetItem *)), this, SLOT(changeUserName(QListWidgetItem *)));
qDebug() << "Name edit";
}
void MainWindow::changeUserName(QListWidgetItem *editItem)
{
qDebug() << "Name:" << editItem->text();
disconnect(ui->listWidget, SIGNAL(itemChanged(QListWidgetItem *)), this, SLOT(changeUserName(QListWidgetItem *)));
}
but you will have to make another connection when the current element is not modified after a double click, maybe with the signal currentItemChanged.
I'm attempting to forward all key press events from my QGraphicsView to a widget that is currently on the scene.
My QGraphicsView looks like this:
Character_controller::Character_controller(Game_state * game_state) : Game_base_controller(game_state) {
this->character = new Character(this->game_state);
this->scene->addWidget(this->character);
connect(this, SIGNAL(keyPress(QKeyEvent *)), this->character, SLOT(update()));
}
And then, my character which subclasses QWidget, which should recieve all keypress events
Character::Character(Game_state * game_state) : Base_object(game_state) {
}
Character::~Character() {
}
void Character::update() {
cout << "HELLO FROM TIMER CONNECTED ITEM" << endl;
}
For some reason, this isn't working. How can I forward all keypress events from the view to my character?
The error I get is this:
Object::connect: No such signal game::Character_controller::keyPress(QKeyEvent *) in implementation/game_controllers/character_controller.cpp:21
keyPress(QKeyEvent*) doesn't exist as a signal, hence the error message that you're getting. As such, you can't do this:
connect(this, SIGNAL(keyPress(QKeyEvent *)), this->character, SLOT(update()));
In order to capture key press events in your graphics view, you will need to override the keyPressEvent function:
void Character_controller::keyPressEvent(QKeyEvent* event)
{
// Call functions on your character here.
switch (event->key())
{
case Qt::Key_A:
character->moveLeft(); // For example
break;
case Qt::Key_D:
character->moveRight(); // For example
break;
...
}
// Otherwise pass to QGraphicsView.
QGraphicsView::keyPressEvent(event);
}
You could just pass the QKeyEvent to the character to manage its own key presses, but you might find it difficult to ensure that different items in your scene don't rely on the same key(s) if you don't keep all your key press handling code in one place.
You have to override the keyPressEvent event to capture key press events
I have an application which draws lines based on different data from cars. I want my application to be able to select the lines drawn, and then make the corresponding item selected in a list on the left as well. The problem is that the mousePressEvent is only called when I press the mousebutton in the leftmost quarter of the scene. When it is called the curveSelected() function works as well, but I can't figure out why I can't invoke the mousePressEvent from the other areas on the scene.
First of all I have a mousePressEvent.
void DrawingScene:::mousePressEvent ( QGraphicsSceneMouseEvent * event ){
event->ignore();
bool leftbutton = (event->button() == Qt::LeftButton);
if(leftbutton)
{
qDebug() << "leftbutton";
emit leftButtonPress(event->scenePos());
}
QGraphicsScene::mousePressEvent(event);
}
Later connected:
connect(d_scene, SIGNAL(leftButtonPress(QPointF)), this, SLOT(curveSelected(QPointF)));
leftButtonPress is the signal emitted. Then I have the function which selects the item in the list. This method seems to work just fine. The problem exists without this function as well.
void CurveDrawer::curveSelected(QPointF pos){
QMapIterator<QPair<unitID, QString>, carData*> it(dataMap);
while(it.hasNext()){
it.next();
QPainterPath curPath = it.value()->pathItem->path();
if(curPath.contains(pos)){
for (int i = 0; i < list->count(); ++i) {
QListWidgetItem* curItem = list->item(i);
if(curItem == it.value()->listItem){
qDebug() << "curveSelected";
curItem->setSelected(true);
}
}
}
}
}
Anyone experienced something similar, or may see some obvious mistakes in my code?
EDIT:
How can i achieve that the mousePressEvent is called every time I click inside the scene? This is basically what I want it to do. Now it is only called when I click in certain area.
I tried to implement it with void DrawGraphicsView;;mousePressEvent(QMouseEvent *event) now, and the same problem existed there. The event just got invoked from certain areas in the scene.
The strange thing for me is that when a certain place in the scene is in the left of the viewport it is not possible to invoke the mousepressEvent, but when I scroll the same place to the right in the viewport, then it is suddenly possible to invoke the mousepressEvent. Does this make the problem clearer?