Qt, offset when restore geometry - c++

With Qt 5.11, I have this code to save end restore geometry of a QDialog subclass :
class HlgRunoffEditorDialog:public QDialog
{
some code
}
void HlgRunoffEditorDialog::show()
{
if (isVisible())
return;
ReosSettings settings;
restoreGeometry(settings.value(QStringLiteral("RunoffEditorDialog/geometry")).toByteArray());
ui->splitter->restoreState(settings.value(QStringLiteral("RunoffEditorDialog/splitter/geometry")).toByteArray());
QDialog::show();
}
void HlgRunoffEditorDialog::closeEvent(QCloseEvent *event)
{
updateSettings();
QDialog::closeEvent(event);
}
void HlgRunoffEditorDialog::updateSettings()
{
ReosSettings settings;
settings.setValue(QStringLiteral("RunoffEditorDialog/geometry"),saveGeometry());
settings.setValue(QStringLiteral("RunoffEditorDialog/splitter/geometry"),ui->splitter->saveState());
}
The problem is when the show() method is called, the windows has an offset with the position before the close event. A picture is more explicit than text :
The red line is the position before the close event.
It seems like the new position is set the top left point of the widget excluding the windows frame before close event is set on the the top left point of the widget including the windows frame after show() method id called.
Any idea ?
Edit 1
This offset above is on Windows. When I run on Linux KDE, it seems like the offset is in the other direction ....
Edit 2 :
This offset appear anly when the QDialog is close with the cross on the top right. When the QDialog is closed with the close button (connect to the close() slot), the offset is not here ...

For correct resoring visibility states of widgets (as windows) you must follow this call order:
// 1) show the widget
widget->show();
// 2) set status bar visibility (if exists)
//widget->statusBar->setVisible(booleanValueFromSettings);
// 3) set margins (if use it)
//widget->setContentsMargins(a, b, c, d);
// 4) geometry
widget->restoreGeometry(byteArrayFromSettings);
// 5) state (can be use it and for you splitter)
widget->restoreState(byteArrayFromSettings);

I remember a project where I had to calculate a bit, something like this:
QRect ng = w->normalGeometry();
QRect frame = w->frameGeometry();
ng.adjust(-frame.x(), -frame.y(), -frame.x(), -frame.y());
and saving the latter values

Related

Closing Popup and setting button label

I'm writing a C++ wxWidgets calculator application. I want to compress trigonometric function buttons into a single one to save on space, using what's basically a split button. If you left click on it, the current option is used. If you right click, a popup menu is opened, which contains all the buttons; when you click on one of them, it is used and the big button changes.
I've been suggested to use wxComboBox and other stuff for this job, but I preferred using wxPopupTransientWindow because this way I can display the buttons in a grid, making everything - in my opinion - neater.
Problem is, when I choose an option from the menu, the main button's ID changes (because when I reopen the menu the previously clicked button is light up as its ID and the big button's ID match), but the label does not. Furthermore, the popup is supposed to close itself when you left click on one of the buttons, but it does not.
This is the code for the custom button in the popup which is supposed to do all that stuff:
void expandButton::mouseReleased(wxMouseEvent& evt)
{
if (pressed) {
pressed = false;
paintNow();
wxWindow* mBtn = this->GetParent()->GetParent();
mBtn->SetLabel(this->GetLabel());
mBtn->SetId(this->GetId());
this->GetParent()->Close(true);
}
}
This is the code for the custom button in the main frame which opens up the popup (temporary setup just to test if the whole thing is working):
void ikeButton::rightClick(wxMouseEvent& evt) // CREA PANNELLO ESTENSIONE
{
if (flags & EXPANDABLE)
{
std::vector<expandMenuInfo> buttons;
buttons.push_back(expandMenuInfo(L"sin", 3001));
buttons.push_back(expandMenuInfo(L"cos", 3002));
buttons.push_back(expandMenuInfo(L"tan", 3003));
buttons.push_back(expandMenuInfo(L"arcsin", 3004));
buttons.push_back(expandMenuInfo(L"arccos", 3005));
buttons.push_back(expandMenuInfo(L"arctan", 3006));
wxPoint p = this->GetScreenPosition();
size_t sz = this->GetSize().GetHeight() / 1.15;
expandMenu* menu = new expandMenu(this, buttons, sz, wxPoint(
p.x, p.y + this->GetSize().GetHeight() + 2));
menu->SetPosition(wxPoint(
menu->GetPosition().x - ((menu->GetSize().GetWidth() - this->GetSize().GetWidth()) / 2),
menu->GetPosition().y));
menu->Popup();
}
}
Let me know if I need to show more code.
This is probably a terrible way of doing this, but this is basically the first "serious" application I'm creating using the wxWidgets framework. Any help is appreciated.
when I choose an option from the menu, the main button's ID changes
(because when I reopen the menu the previously clicked button is light
up as its ID and the big button's ID match), but the label does not.
If you're creating the popup menu like in your previous post, you had a popup window with a panel as its child and the buttons were then children of the panel layed out with a sizer.
If that's still the case, this->GetParent() and should be the panel, this->GetParent()->GetParent() should be the popup. So this->GetParent()->GetParent()->GetParent() should be the trig function button (assuming you created the popup with the trig function button as the parent).
So I think the line wxWindow* mBtn = this->GetParent()->GetParent(); should be changed to wxWindow* mBtn = this->GetParent()->GetParent()->GetParent();.
Or slightly shorter wxWindow* mBtn = this->GetGrandParent()->GetParent();;
the popup is supposed to close itself when you left click on one of
the buttons, but it does not.
It looks like wxPopupTransientWindow has a special Dismiss method that is supposed to be used to close it.
So I think the line this->GetParent()->Close(true); should be changed to this->GetGrandParent()->Dismiss(); (Assuming as above that the buttons in the popup are children pf a panel).
Alternately, if you want a solution that will work regardless of the parentage of the controls in the popup window, you could have a utility function to find the popup ancestor which would look something like this:
wxPopupTransientWindow* FindPopup(wxWindow* w)
{
wxPopupTransientWindow* popup = NULL;
while ( w != NULL )
{
popup = wxDynamicCast(w, wxPopupTransientWindow);
if ( popup )
{
break;
}
w = w->GetParent();
}
return popup;
}
This uses the wxDynamicCast function which is slightly different from the c++ dynamic_cast expression. wxDynamicCast uses wxWidgets' RTTI system to check if the given pointer can be converted to the given type.
Then the mouseReleased method could use this utility function something like this:
void expandButton::mouseReleased(wxMouseEvent& evt)
{
if (pressed) {
pressed = false;
paintNow();
wxPopupTransientWindow* popup = FindPopup(this);
if (popup ) {
wxWindow* mBtn = popup->GetParent();
mBtn->SetLabel(this->GetLabel());
mBtn->SetId(this->GetId());
mBtn->Refresh();
popup->Dismiss();
}
}
}
I'm not sure why you're setting trig function button to have a new id, but I assume you have a reason.
To make the SetLabel method work in your custom button class, I think the easist thing is to call the SetLabel() method in the button's constructor. This will store the string passed to the constructor in the button's internal label member.
Based on other questions, I think the ikeButton constructor looks something like this:
ikeButton::ikeButton(wxFrame* parent, wxWindowID id, wxString text,...
{
...
this->text = text;
To store the label, you would need to change the line this->text = text; to
SetLabel(text);
And when you draw the button, I think the method you use looks like this:
void ikeButton::render(wxDC& dc)
{
...
dc.DrawText(text, ...);
}
You would need to change, the line dc.DrawText(text, ...); to
dc.DrawText(GetLabel(), ...);
Likewise, any other references to the button's text member should be changed to GetLabel() instead.
Finally, when you set the label in the expandButton::mouseReleased method, it might be a good idea to call button's Refresh() method to force the button to redraw itself. I added that my suggestion for the mouseReleased method above.

Qt5: How to solve this trouble. Custom QWidget with custom paintEvent() vs. QWidget move() function

first post here, I also need it to help others.
I'm just new at QT/c++, but with years of hobby making programming things, I'm progressing relatively fast.
I'm trying to learn it by making a game, so on, I made, besides other things, a custom sprite class that inherits from QWidget.
I reimplemented, as a common practice, the paintEvent function, using some tricks, qpixmap/qpainter, json files, spritesheets, codehelpers made by my self, to succefully play an animation with stances like idle/walking/attacking.
All went perfect until I tried to move it while walking by the function move(int x, int y) from QWidget.
The problem, I think, is that every time I call "move" function it updates/refresh/repaint the QWidget by calling paintEvent function again, chopping my animation, generating several issues, etc. etc.
I can't to figure out how to move it without repainting or waiting the animation to finish, or what I'm missing. It seems there are no other ways to move a QWidget.
I belive the problem is in another event function that's being called by a emitted signal, probably moveEvent, but I reimplemented it keeping it empty and the result was the same.
Do you have any idea? Should I remake it all in another way to skip this trouble? Or is it as simple as I can't see the coin opposite to me?
Ask for code if needed.
Thank you so much, Luna.
EDIT 1:
customsprite::customsprite(QWidget *parent, QString spriteName) : QWidget(parent)
{
// qpm is a QPixmap
qpm_idle.load(":/Assets/sprites/" + spriteName + ".png");
qpm_walk.load(":/Assets/sprites/" + spriteName + "_walk.png");
...
// here goes a custom class to load json files for each sprite sheet
// the json contains x,y,h,w info to draw the animation
...
//some int,bools,qstrings
...
// fps is QTimer
fps.setInterval(33);
connect(&fps,SIGNAL(timeout()),this,SLOT(update()));
fps.start();
}
//the paintEvent:
void customsprite::paintEvent(QPaintEvent *){
//
QPainter qp(this);
QRect targetR(xMove,0,spriteW,spriteH);
QRect sourceR(jhV[status].getFrameX(jhV[status].namesV[spriteFg]),0,spriteW*sizeDivisor,spriteH*sizeDivisor);
// things abouts consulting jsonhelper info
switch (status){ // status is to know if should draw idle or walk animation
case 0:
qp.drawPixmap(targetR,qpm_idle,sourceR);
break;
case 1:
xMove = xMove + 1;
targetR = QRect(xMove,0,spriteW,spriteH);
qp.drawPixmap(targetR,qpm_walk,sourceR);
break;
default:
break;
}
// all this works fine
if(!reversingAnimation && shouldAnimate){ // simple bool options
spriteFg++; // an int counter refering to which frame should be drawed
if(spriteFg == jhV[status].getSSSize()){ // this consults the json files by a custom jsonhelper, it works fine
if(reversible){
reversingAnimation = true;
spriteFg = jhV[status].getSSSize() - 1;
}else if(!reversible){
spriteFg = 0;
emitSignals();
if(animateOnce == true){
shouldAnimate = false;
}
}
}
}
//here goes a similar the above code to to handle the animation playing in reverse, nothing important
}
Then in my generic QMainWindow I add an instance of my customsprite class, set its parent as QMainWindow, it appears, play the animation.
The problem is when I try to use:
//cs is customsprite
cs->move(cs->x()+1,cs->Y());
It moves, but interrumping the animation several times.
I have tried also putting it with a "isWalking" bool filter inside the paintEvent, but it's the same.
EDIT 2:
The "xMove" thing there, is a trick used to succefully and smoothly move the animation, but it need to increase the width of the QWidget to being seen.
Not a good solution.

QDockWidget::restoreGeometry not working correctly when QMainWindow is maximized

I have a number of QDockWidgets, all docked in a single QMainWindow.
I have overridden the showEvent, and after passing the event on to the base class I am restoring the dock widget's geometry
void DockWidget::showEvent(QShowEvent* ev)
{
QDockWidget::showEvent(ev);
QByteArray byte_array = settings_.value(id + ".geometry").toByteArray();
LOG("rest: %s", QString(byte_array.toHex()));
QDockWidget::restoreGeometry(byte_array);
}
In my QMainWindow::closeEvent I am calling saveSettings for each of my dock widgets
void MainWindow::closeEvent(QCloseEvent* ev)
{
QList<DockWidget*> dock_widgets = findChildren<DockWidget*>();
for (DockWidget* dock_widget : dock_widgets)
dock_widget->saveSettings();
QMainWindow::closeEvent(ev);
}
In my dock widget's saveSettings function I write the result of saveGeometry to disk:
void DockWidget::saveSettings()
{
QByteArray byte_array = QDockWidget::saveGeometry();
LOG("save: %s", QString(byte_array.toHex()));
settings_.setValue(id + ".geometry", byte_array);
settings_.sync();
}
Whilst this does work when my QMainWindow is not maximized, when it is maximized the widgets aren't restored correctly.
In this image I have arranged my widgets just prior to closing. (linked because inline the image will be too large)
In this image I reload my app and the widgets' geometry is incorrectly loaded.
You can see in my functions above I log the geometry string being saved and loaded.
I have proven to myself that the settings are correctly saved and restored again, but somehow it's not working correctly
close the app; save the state:
save: 01d9d0cb000200000000053f000000640000077f000001a00000053f000000640000077f000001a000000000000000000780
open the app; restore the state: (the hex data here matches the saved one exactly)
rest: 01d9d0cb000200000000053f000000640000077f000001a00000053f000000640000077f000001a000000000000000000780
close the app again, having touched nothing: (the hex data is different now, as the geometry is different, see marks below)
save: 01d9d0cb000200000000053f000000640000077f0000014e0000053f000000640000077f0000014e00000000000000000780
^ ^
This doesn't happen when the window is not maximized.
Is this a bug in Qt, or am I not using the functionality correctly?
I am using Qt 5.5 on Ubuntu 16.04.
This is a bug in Qt. Specifically, QTBUG-46620 and possibly also QTBUG-16252.
The bug report for QTBUG-46620 details a work-around, which you should try. To begin with, ensure that the main window geometry and main window state are saved when you close the application (note that you should not have to save the geometry of each dock window separately):
void MainWindow::closeEvent(QCloseEvent* ev)
{
settings_.setValue("geometry", saveGeometry());
settings_.setValue("state", saveState());
}
Then, restore the geometry as follows:
restoreGeometry(settings.value("geometry").toByteArray());
if (isMaximized())
{
setGeometry( QApplication::desktop()->availableGeometry(this) );
}
restoreState(settings.value("windowState").toByteArray());
If the you have trouble with the above work-around, you may also have to save the maximized state of the window:
void MainWindow::closeEvent(QCloseEvent* ev)
{
settings_.setValue("geometry", saveGeometry());
settings_.setValue("state", saveState());
settings_.setValue("maximized", isMaximized());
}
Then restore as follows:
restoreGeometry(settings.value("geometry").toByteArray());
if (settings.value("maximized").toBool())
{
showMaximized();
setGeometry( QApplication::desktop()->availableGeometry(this) );
}
restoreState(settings.value("windowState").toByteArray());
Note that these work-arounds may cause some warning messages to be generated on some platforms.

Qt QDialog moves location slightly each time it is closed and showed again

I have a QDialog and it pops up as a top level window in the center of my main window the first time it is opened(great, exactly what I want). However, each time I hit the "X" to close the window, when I trigger an event to cause the QDialog to pop up again(I use show() function for this)...the widget has slightly moved down and to the right(maybe 10 pixels each way, each time). Does anyone know what is causing this behavior/have a solution? Ideally I would like it to always pop up in the center of my main window(like it does the first time it opens).
Thanks in advance.
commandTimeWindow = new QDialog();
commandTimeWindow->resize(390, 180);
commandTimeWindow->setWindowTitle("Command In Progress");
commandTimeWindow->setStyleSheet("background-color: white;");
commandTimeWindow->setWindowFlags(Qt::WindowTitleHint | Qt::WindowCloseButtonHint);
commandTimeWindow->setWindowIcon(QIcon(""));
commandTimeWindow->installEventFilter(this);
commandTimeWindow->close();
commandTimeWindow->show();
Also the standard "X" button closes the window...but I didnt write any code on this end.
You can use the widget's move(x, y) method or replace resize with setGeometry.
I was able to solve my issue using the code below:
void MyMainWindow::moveEvent(QMoveEvent* event)
{
const QPoint global = this->mapToGlobal(rect().center());
waitDialog->move(global.x() - waitDialog->width() / 2, global.y() - waitDialog->height() / 2);
}

How to remove focus from QLineEdit

I am developing a cpp/Qt tool.When I click on a QLineEdit field, its frame turns to a different color and the cursor starts blinking.When I type on Return in the field, I want its cursor to stop blinking and its frame color to default back to normal.I can intercept the Return Pressed, but when I then start the clearFocus() command, the keyboard input does not come anymore to the QLineEdit field (which is the behaviour I am expecting), but its frame doesn't go back to the default color and the cursor continues blinking. How to really remove focus from the field (i.e.: No cursor blinking anymore and frame back to default color) ?
=== EDIT ===
Here is the code:
void myQLineEditClass::keyPressEvent(QKeyEvent *e)
{
if(e->text().length()>0)
{
int asciiVal = e->text().at(0).toAscii();
if (asciiVal==3||asciiVal==13)
{
MGlobal::displayInfo(MQtUtil::toMString(QString().sprintf("Focus cleared"))); // -> this is properly displayed
clearFocus();
}
}
QLineEdit::keyPressEvent(e);
}
Thanks.