Change Toolbutton Icon in gtkmm Toolbar after Toolbutton was clicked - c++

I want to change the Icon of a Toolbutton after the Toolbutton was clicked.
My problem is, when i use the Toolbutton method set_icon_widget() , the current Icon disappears, but the new one doesn't show up. The Toolbutton is still there, but it has no Icon anymore.
Here is my Code:
#include <gtkmm.h>
class MainWindow : public Gtk::Window{
public:
MainWindow();
private:
void clicked();
Gtk::Box m_vbox;
Gtk::Image image;
Gtk::Image image_clicked;
Gtk::Toolbar toolbar;
Gtk::ToolButton icon;
Gtk::ToolButton connected;
};
MainWindow::MainWindow() :
image(Gdk::Pixbuf::create_from_file( "network-transmit-receive.svg")),
image_clicked(Gdk::Pixbuf::create_from_file("network-offline.svg")){
//Window Configuration
set_title("Tool Button Icon Test");
set_default_size(400, 200);
set_position(Gtk::WIN_POS_CENTER);
icon.set_icon_widget(image);
connected.set_icon_widget(image_clicked);
icon.signal_clicked().connect( sigc::mem_fun(*this, &MainWindow::clicked));
toolbar.set_toolbar_style(Gtk::TOOLBAR_ICONS);
toolbar.set_icon_size(Gtk::ICON_SIZE_SMALL_TOOLBAR);
toolbar.set_vexpand_set(false);
toolbar.add(icon);
m_vbox.set_orientation(Gtk::ORIENTATION_VERTICAL);
m_vbox.pack_start(toolbar, Gtk::PACK_SHRINK, 0);
add(m_vbox);
show_all_children();
}
void MainWindow::clicked(){
icon.set_icon_widget(image_clicked);
}
int main (int argc, char *argv[])
{
Glib::RefPtr<Gtk::Application> app = Gtk::Application::create(argc, argv, "de.example.Toolbutton-Test");
MainWindow mainwindow;
//Shows the window and returns when it is closed.
return app->run(mainwindow);
}
I also tried to remove the current ToolButton and to add a new one with the different Icon, but than the current Toolbutton is removed and the new one is not drawn :-/
Can somebody help me please?

I just found the answer by myself. I can change the icon of the MenuButton by setting a new image to the image object of the MenuButton.
The clicked Method looks like this now:
void MainWindow::clicked(){
image.set(Gdk::Pixbuf::create_from_file("network-offline.svg"));
}

Related

How to forward all MouseEvents from QGraphicsView to its QFrame to its QMainWindow? (C++, Qt5)

I have a widget with this structure:
main->QMainWindow->QFrame->QGraphicsView->QScene->QGraphicsPixMapItem->QPixmap
I had to do it this way because im not using Qt creator or QML, just widgets. Anyway I added an event filter to my QMainindow to be movable when clicking and dragging whenever side of the window And it worked. But due to the QGraphicsView implementation if I try to drag it doesnt work but still receives input (a menu opens when i click). What makes QGraphicsview so stubborn and how do i make the window to be draggable when i click and drag on the QGraphics view, Even installing the eventFilter on the view and frame but with no results. Thanks in advance and this is my code.
This is the problem, the red square is the QGrapicsView, the white one is the MainWindow/Qframe. See how i still can make right clic on the red one and the menu still appears. I made a debug and see i get 2 objs when i click, the mainwindow and the Qgraphicview so i dont know why the move functionality just doesn work.
enter image description here
Main.cpp
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
MainWindow w;
w.show();
return QApplication::exec();
}
MainWindow.h
protected:
bool eventFilter(QObject *obj, QEvent *event) override{
if (event->type() == QEvent::MouseButtonPress ) {
qDebug() << event;
auto *ev = (QMouseEvent *) event;
if (ev->button() == Qt::RightButton) {
//Opens menu, it works well when i click on the QGraphics as well
auto *menu = new QMenu(this);
auto *idle = new QAction("Idle", this);
auto *close = new QAction("Quit", this);
menu->addAction(idle);
menu->popup(ev->globalPos());
connect(close, &QAction::triggered, [](){QCoreApplication::quit();});
connect(idle, &QAction::triggered, [this](){changeDance(0);});
}
else{
// "grab" the mainWindow
this->oldPos = ev->globalPos();
}
}
if (event->type() == QEvent::MouseMove) {
//drag the window
auto *ev = (QMouseEvent *) event;
const QPoint delta = ev->globalPos() - oldPos;
move(x() + delta.x(), y() + delta.y());
oldPos = ev->globalPos();
}
}
MainWindow.cpp
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent) {
setWindowTitle("waifu"); //waifu
setWindowFlags(Qt::Dialog | Qt::MSWindowsFixedSizeDialogHint);
setFixedSize(screenWidth, screenHeight);
//setAttribute(Qt::WA_TranslucentBackground, true);
setWindowFlags(Qt::Widget | Qt::FramelessWindowHint);
setWindowFlags(Qt::BypassGraphicsProxyWidget);
//mainWindow->frame->view->scene->pixmap
installEventFilter(this);
frame = new Frame(this);
setCentralWidget(frame);
view = new View(frame);
view->setFixedSize(50, 50);
scene = new Scene(view);
view->setScene(this->scene); // That connects the view with the scene
frame->layout()->addWidget(view);
myItem = new QGraphicsPixmapItem(*new QPixmap());
//scene->setBackgroundBrush(QBrush(Qt::yellow));
scene->addItem(myItem);
view->show();
Frame.h
class Frame : public QFrame {
Q_OBJECT
public:
explicit Frame(QMainWindow *parent = 0) : QFrame(parent) {
setMouseTracking(true);
setStyleSheet("background-color: red;"); //delete
setLayout(new QVBoxLayout);
layout()->setContentsMargins(0, 0, 0, 0);
setWindowFlags(Qt::FramelessWindowHint); //No windowing
setAttribute(Qt::WA_TranslucentBackground); // No background
setStyleSheet("background-color: transparent;");
};
};
QGraphicsView.h
class View : public QGraphicsView {
Q_OBJECT
public:
explicit View(QFrame *parent) : QGraphicsView(parent) {
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setMouseTracking(true);
};
protected:
private:
};
Tried to install the event filter located in the main window, in the Qframe and QGraphicsView class. with the parameter event filter being *parent.

QPushButton not executing connected function on click

I have a class called UserInterface. In this class there are different functions. build_start_screen() adds all the widgets (labels, buttons,...) for the initial startscreen. build_option_a_screen() removes everything from the startscreen and adds all the widgets needed for the screen when the users click on the button for option A, and so on. The class is stripped down for the sake of this question.
Now I have declared a button in build_start_screen() and connected it to a simple MessageBox.exec() so it should pop-up on click.
However, nothing happens when the button gets clicked.
What am I doing wrong? Has it something to do with the lifetime of variables expiring after the function finishes?
#include <QApplication>
#include <QPushButton>
#include <QAbstractButton>
#include <QLabel>
#include <QFont>
#include <QVBoxLayout>
#include <QMessageBox>
//Class handling all the UI in this Application
class UserInterface {
public:
//Build the initial UI the user sees
void build_start_screen(QWidget& window) {
//Make new QVBoxLayout for this startscreen UI
this->layout = new QVBoxLayout(&window);
//Test messagebox
QMessageBox msgBox;
msgBox.setText("Button test.");
//Button to go to Option A-screen
QPushButton* showMsgBox = new QPushButton("Show pop-up");
QAbstractButton::connect(showMsgBox, SIGNAL (clicked()), &window, SLOT (msgBox.exec()));
//Add labels and button to QVBoxLayout
layout->addWidget(showMsgBox);
}
private:
//Properties
QVBoxLayout* layout;
};
int main(int argc, char **argv) {
QApplication app (argc, argv);
//Initialize Window
QWidget Window;
Window.resize(400, 250);
//Create new UserInterface object
//This will allow us to create different user-interfaces
//depending on the function we call
UserInterface* ui = new UserInterface();
ui->build_start_screen(Window);
Window.show();
return app.exec();
}
And what if I'd like to do the same, but instead of calling a messageBox I'd like to call another function?
#include <QApplication>
#include <QPushButton>
#include <QAbstractButton>
#include <QLabel>
#include <QFont>
#include <QVBoxLayout>
#include <QMessageBox>
//Class handling all the UI in this Application
class UserInterface {
public:
//Build the initial UI the user sees
void build_start_screen(QWidget& window) {
//Make new QVBoxLayout for this startscreen UI
this->layout = new QVBoxLayout(&window);
//Test messagebox
QMessageBox msgBox;
msgBox.setText("Button test.");
//Button to go to Option A-screen
QPushButton* showMsgBox = new QPushButton("Show pop-up");
QAbstractButton::connect(showMsgBox, SIGNAL (clicked()), &window, SLOT (build_option_a_screen()));
//Add labels and button to QVBoxLayout
layout->addWidget(showMsgBox);
}
void build_option_a_screen(QWidget& window) {
//Do stuff here with window
//e.g
window.resize(500, 500);
}
private:
//Properties
QVBoxLayout* layout;
};
int main(int argc, char **argv) {
QApplication app (argc, argv);
//Initialize Window
QWidget Window;
Window.resize(400, 250);
//Create new UserInterface object
//This will allow us to create different user-interfaces
//depending on the function we call
UserInterface* ui = new UserInterface();
ui->build_start_screen(Window);
Window.show();
return app.exec();
}
Your code has 2 problems:
The window "object" does not have slot "msgBox.exec()" as pointed out by the error:
QObject::connect: No such slot QWidget::msgBox.exec() in ../main.cpp:23
Correcting the above, the solution would be:
QObject::connect(showMsgBox, &QPushButton::clicked, &msgBox, &QMessageBox::exec);
but now the problem is that "msgBox" is a local variable that will be destroyed and cannot be displayed.
So the solution is to make msgBox a member of the class or a pointer (in the case of the pointer you must manage the dynamic memory to avoid memory leaks):
//Class handling all the UI in this Application
class UserInterface {
public:
//Build the initial UI the user sees
void build_start_screen(QWidget& window) {
//Make new QVBoxLayout for this startscreen UI
this->layout = new QVBoxLayout(&window);
msgBox.setText("Button test.");
//Button to go to Option A-screen
QPushButton* showMsgBox = new QPushButton("Show pop-up");
QObject::connect(showMsgBox, &QPushButton::clicked, &msgBox, &QMessageBox::exec);
//Add labels and button to QVBoxLayout
layout->addWidget(showMsgBox);
}
private:
//Properties
QVBoxLayout* layout;
QMessageBox msgBox;
};
Plus:
It is recommended not to use the old connection syntax as it has limitations and hides the problems.
It is recommended not to use the old connection syntax as it has limitations and hides the problems.
If you want to connect to a method of some kind that is not a QObject (for example X as you want the OP) then the solution is to use a lambda method:
//Class handling all the UI in this Application
class UserInterface {
public:
//Build the initial UI the user sees
void build_start_screen(QWidget& window) {
//Make new QVBoxLayout for this startscreen UI
this->layout = new QVBoxLayout(&window);
//Button to go to Option A-screen
QPushButton* showMsgBox = new QPushButton("Show pop-up");
QObject::connect(showMsgBox, &QPushButton::clicked, [this, &window](){
build_option_a_screen(window);
});
//Add labels and button to QVBoxLayout
layout->addWidget(showMsgBox);
}
void build_option_a_screen(QWidget& window) {
//Do stuff here with window
//e.g
window.resize(500, 500);
}
private:
//Properties
QVBoxLayout* layout;
};

No items showing up in menu

I'm currently haveing a bit of a problem with a Gtk popup menu.
I have a MenuButton set with a Menu which has a MenuItem.
When clicked the Menu pops up but only a tiny bit of it, as if there are no items and doesn't show the Preferences item.
Is there a function I'm not calling or is the MenuItem not being added. As I took a look at program in the Inspector and it doesn't show a Menu anywhere.
I don't want to use the Gtk Builder with Glade just to create a popup menu.
Not sure if this is needed but this is under Msys2 with Gtk 3.22.
#include <gtkmm.h>
class WindowTest : public Gtk::Window {
public:
WindowTest() {
set_titlebar(mHeaderBarApp);
mBoxContainer.set_orientation(Gtk::Orientation::ORIENTATION_VERTICAL);
mBoxContainer.pack_start(mHeaderBarApp, false, true, 0);
mHeaderBarApp.set_show_close_button(true);
mHeaderBarApp.pack_end(mMenuButtonApp);
mMenuButtonApp.set_image(mImageApp);
mImageApp.set_from_icon_name("open-menu-symbolic", Gtk::BuiltinIconSize::ICON_SIZE_BUTTON);
mMenuButtonApp.set_popup(mMenuApp);
mMenuApp.append(mMenuItemPreferences);
mMenuItemPreferences.set_label("Preferences");
add(mBoxContainer);
show_all_children();
};
virtual ~WindowTest() {};
protected:
Gtk::Box mBoxContainer;
Gtk::HeaderBar mHeaderBarApp;
Gtk::MenuButton mMenuButtonApp;
Gtk::Image mImageApp;
Gtk::Menu mMenuApp;
Gtk::MenuItem mMenuItemPreferences;
};
int main(int argc, char *argv[]) {
Glib::RefPtr<Gtk::Application> app = Gtk::Application::create(argc, argv, "com.site.test");
WindowTest windowTest;
windowTest.set_default_size(600, 400);
return app->run(windowTest);
}

Move a window by clicking an internal widget instead of title bar

In Windows when I create a QMainWindow I can move it around the screen by clicking the title bar and dragging it.
In my application I've hidden the title bar by using setWindowFlags(Qt::CustomizeWindowHint) and I'm trying to build a custom title bar using a widget and setting it in the menu space with setMenuWidget(myWidget).
Now I want to reproduce the original behaviour: I want to click on my MyWidget widget inside the QMainWindow and, while mouse is pressed, dragging the mouse moves the window.
Is there a way to do it?
This is an example on how to implement a fake title bar, that has standard buttons (minimize, maximize, close), and can be dragged to move the whole window (this is based on the approach in #Kevin's answer).
#include <QtWidgets>
class FakeTitleBar : public QWidget{
Q_OBJECT
public:
explicit FakeTitleBar(QWidget* parent= nullptr):QWidget(parent){
label.setSizePolicy(QSizePolicy::Expanding,
QSizePolicy::Expanding);
layout.addWidget(&label);
layout.addWidget(&buttonMinimize);
layout.addWidget(&buttonMaximize);
layout.addWidget(&buttonClose);
//connecting buttons' signals to slots
connect(&buttonMinimize, &QPushButton::clicked,
this, &FakeTitleBar::MinimizeWindow);
connect(&buttonMaximize, &QPushButton::clicked,
this, &FakeTitleBar::MaximizeWindow);
connect(&buttonClose, &QPushButton::clicked,
this, &FakeTitleBar::CloseWindow);
//setting vertical fixed size policy
//so that the title bar does not take up any additional space
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
//a bit of styling
setStyleSheet("QPushButton {margin:0px; padding:5px;}"
"QWidget {background-color:blue; color:white;}");
}
public slots:
//slots for corresponding buttons
void MinimizeWindow(){
window()->showMinimized();
}
void MaximizeWindow(){
if(!window()->isMaximized())
window()->showMaximized();
else
window()->showNormal();
}
void CloseWindow(){
window()->close();
}
protected:
void mousePressEvent(QMouseEvent* event){
//save the press position (this is relative to the current widget)
pressPos= event->pos();
isMoving= true;
}
void mouseMoveEvent(QMouseEvent* event){
//isMoving flag makes sure that the drag and drop event originated
//from within the titlebar, because otherwise the window shouldn't be moved
if(isMoving){
//calculate difference between the press position and the new Mouse position
//(this is relative to the current widget)
QPoint diff= event->pos() - pressPos;
//move the window by diff
window()->move(window()->pos()+diff);
}
}
void mouseReleaseEvent(QMouseEvent* /*event*/){
//drag and drop operation end
isMoving= false;
}
//double-clicking on the title bar should maximize the window
void mouseDoubleClickEvent(QMouseEvent* /*event*/){
MaximizeWindow();
}
//in order for the style sheet to apply on this custom widget
//see https://doc.qt.io/qt-5/stylesheet-reference.html#qwidget-widget
void paintEvent(QPaintEvent *)
{
QStyleOption opt;
opt.init(this);
QPainter p(this);
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
}
private:
QHBoxLayout layout{this};
QLabel label{"Fake Title Bar"};
QPushButton buttonMinimize{"-"};
QPushButton buttonMaximize{"M"};
QPushButton buttonClose{"X"};
QPoint pressPos;
bool isMoving{false};
};
//sample usage
class Widget : public QWidget{
public:
explicit Widget(QWidget* parent= nullptr):QWidget(parent){
setWindowFlags(Qt::CustomizeWindowHint);
layout.addWidget(&titleBar);
layout.addWidget(&label);
layout.setContentsMargins(0, 0, 0, 0);
label.setAlignment(Qt::AlignCenter);
//default size for the window
resize(320,240);
}
~Widget(){}
private:
QVBoxLayout layout{this};
FakeTitleBar titleBar;
QLabel label{"this is a sample window"};
};
int main(int argc, char* argv[]) {
QApplication app(argc, argv);
Widget w;
w.show();
return app.exec();
}
#include "main.moc"
You just need to implement the necessary mouse event handling by overwriting MyWidget's mousePressEvent(), mouseMoveEvent() and mouseReleaseEvent() handlers.
Detect the mouse down, get current mouse position
While moving, get current mouse position, calculate difference, save new position, move window by diff
You can get the window (top level widget) from inside MyWidget through the window() method.

QGraphicsView possible bug?

The example code is from my project. I've tried to make it as short as possible and to the point.
The overlay is used to draw over all the other widgets in the app. This works for most widgets, but today I've started to notice that QAbstractScrollArea subclasses are giving me a hard time. The problem is that the overlay appears not on top, and whatever drawing that happens is blocked.
#include <QtGui/QApplication>
#include <QtGui/QVBoxLayout>
#include <QtGui/QGraphicsView>
#include <QtGui/QPushButton>
class View : public QGraphicsView{
public:
View(){
//delete viewport(); setViewport(new QWidget);
}
};
class Widget : public QWidget{
QWidget* overlay_;
public:
Widget(){
resize(512, 512);
QVBoxLayout* layout = new QVBoxLayout;
QPushButton* button = new QPushButton(" Click Me! ");
layout->addWidget(button);
layout->addWidget(new View);
overlay_ = new QWidget(this);
overlay_->installEventFilter(this);
connect(button, SIGNAL(clicked()),
overlay_, SLOT(show()));
overlay_->hide();
setLayout(layout);
}
bool eventFilter(QObject* target, QEvent* event){
if(target == overlay_){
if(event->type() == QEvent::Paint && overlay_->isVisible()){
overlay_->resize(size());
QPainter painter(overlay_);
painter.setPen(QPen(QColor(1, 102, 192, 255), 1, Qt::SolidLine,
Qt::FlatCap, Qt::MiterJoin));
painter.drawRect(rect().adjusted(60, 0, -60, 0));
return true;
}
}
}
};
int main(int argc, char *argv[]){
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
To fix this in this example and have overlay go on top of View, you'll need to uncomment the commented line at the top. So my question is this: why do I need to delete and assign a new viewport widget in the constructor in order for overlay not get overdrawn?
This isn't a bug with QGraphicsView, it will happen if you use a standard QScrollArea as well.
The issue, I think, is the order in which Qt draws child widgets. Sibling widgets are drawn in the order they are added to the parent (although you can't rely on this).
The reason that resetting the viewport "solved" the problem is because when you do that you create a new QWidget that has no background to be the viewport. The QGraphicsView is still being drawn over the overlay_, it just has a transparent viewport. Notice how it's still drawn behind the pushbutton, however.
If you want to draw an overlay only over the QGraphicsView, you can override QGraphicsView::paintEvent() and do it there. If you want to draw the overlay over your entire widget, I would embed your layout inside a second QWidget and then try using QWidget::raise() to force the overlay visually to the top.