Partage
  • Partager sur Facebook
  • Partager sur Twitter

[Qt] Besoin de quelques conseils

    9 janvier 2019 à 18:00:50

    Bonsoir, bonsoir

    Je me suis décidé hier à me faire un petit programme d'édition de map 2D mais cela faisait un sacré bout de temps que je n'avais pas codé sous Qt, du coup quelques questions me taraudent.

    Pour l'instant je n'ai rien fait de mirobolant, juste une fenêtre principale contenant une Vue et une Scene ainsi qu'une classe Dialog aidant lors de la création d'une nouvelle Map.

    MainWindow :

    #ifndef MAINWINDOW_H
    #define MAINWINDOW_H
    
    #include "newmapdialog.h"
    
    #include <map>
    
    #include <QMainWindow>
    #include <QDebug>
    #include <QMessageBox>
    #include <QCloseEvent>
    #include <QGridLayout>
    #include <QGraphicsView>
    #include <QGraphicsScene>
    #include <QPushButton>
    #include <QStatusBar>
    #include <QMenuBar>
    
    class MainWindow : public QMainWindow
    {
        Q_OBJECT
    
    public:
        MainWindow(QWidget *parent = nullptr);
        ~MainWindow() override;
    
    protected:
        void closeEvent(QCloseEvent *event) override;
    
    private slots:
        void newMap();
        void about();
    
    private:
        void createActions();
        void createView();
        void createScene(/*const std::map<QString, int>& values*/);
        void createGridLayout();
        void createStatusBar();
        void maybeSave();
    
    private:
        QWidget        *m_centralWidget{nullptr};
        QGridLayout    *m_mainGridLayout{nullptr};
        QGraphicsView  *m_mapView{nullptr};
        QGraphicsScene *m_mapScene{nullptr};
        QPushButton    *m_testButton{nullptr};
    };
    
    #endif // MAINWINDOW_H
    #include "mainwindow.h"
    
    MainWindow::MainWindow(QWidget *parent)
        : QMainWindow{parent},
          m_centralWidget{new QWidget{this}},
          m_mainGridLayout{new QGridLayout{this}},
          m_mapView{new QGraphicsView{this}},
          m_mapScene{new QGraphicsScene{m_mapView}},
          m_testButton{new QPushButton{this}}
    
    {
        setCentralWidget(m_centralWidget);
        m_centralWidget->setLayout(m_mainGridLayout);
    
        m_testButton->setText(tr("Test"));
    
        createActions();
        createView();
        createGridLayout();
        createStatusBar();
    }
    
    MainWindow::~MainWindow()
    {
    
    }
    
    void MainWindow::closeEvent(QCloseEvent *event)
    {
        // In the future, it will be used to check if doc was saved or not.
        // If not, the user will be invite to save, else the window will be closed
        const QMessageBox::StandardButton ret = QMessageBox::warning(this,
                                                                     tr("Application"),
                                                                     tr("Do you really want to quit ?"),
                                                                     QMessageBox::Ok | QMessageBox::Cancel);
        if(ret == QMessageBox::Ok)
            event->accept();
        else
            event->ignore();
    }
    
    void MainWindow::newMap()
    {
        // TODO : Before process new map, check if save is needed and if user want it
    
        NewMapDialog dial{this};
        // NewMapDialog::processDial return a map<Qstring, int> that contains values :
        // ["tileWidth"]  => val(int)
        // ["tileHeight"] => val(int)
        // ["totalRows"]  => val(int)
        // ["totalCols"]  => val(int)
        auto values = dial.processDial();
        // createScene(values);
    }
    
    void MainWindow::about()
    {
        QMessageBox message{};
        message.setIcon(QMessageBox::Information);
        message.setText("Mapox2D was created in 2019");
        message.exec();
    }
    
    void MainWindow::createActions()
    {
        // File Menu
        QMenu *fileMenu = menuBar()->addMenu(tr("&File"));
        QAction *newAct = new QAction{tr("&New map"), this};
        newAct->setShortcut(QKeySequence::New);
        newAct->setStatusTip(tr("Create a new map"));
        connect(newAct, &QAction::triggered, this, &MainWindow::newMap);
        fileMenu->addAction(newAct);
    
        // Help Menu
        QMenu *helpMenu = menuBar()->addMenu(tr("&Help"));
        QAction *aboutAct = new QAction{tr("&About"), this};
        aboutAct->setStatusTip(tr("About this software"));
        connect(aboutAct, &QAction::triggered, this, &MainWindow::about);
        helpMenu->addAction(aboutAct);
    }
    
    void MainWindow::createView()
    {
        m_mapView->setScene(m_mapScene);
        m_mapView->setMinimumWidth(640);
        m_mapView->setMinimumHeight(360);
    }
    
    void MainWindow::createScene(/*const std::map<QString, int>& values*/)
    {
        // ...
    }
    
    void MainWindow::createGridLayout()
    {
        m_mainGridLayout->addWidget(m_mapView, 0, 0);
        m_mainGridLayout->addWidget(m_testButton, 0, 1, Qt::AlignTop);
    }
    
    void MainWindow::createStatusBar()
    {
        // Status Bar is automatically created the first time statusBar() is called
        // This function return a pointer to the main window's qstatusbar
        statusBar()->showMessage(tr("Ready !"));
    }
    
    void MainWindow::maybeSave()
    {
    
    }

    NewMapDialog :

    #ifndef NEWMAPDIALOG_H
    #define NEWMAPDIALOG_H
    
    #include <map>
    #include <QDebug>
    #include <QDialog>
    #include <QIntValidator>
    #include <QFormLayout>
    #include <QLineEdit>
    #include <QLabel>
    #include <QDialogButtonBox>
    
    class NewMapDialog : public QDialog
    {
        Q_OBJECT
    
    public:
        NewMapDialog(QWidget *parent = nullptr);
    
    public:
        std::map<QString, int> processDial();
    
    private:
        void createForm();
        void createButtonBox();
        bool validInput();
    
    private:
        QIntValidator                 m_validatorSizeTile{0, 999};
        QIntValidator                 m_validatorCount{0, 9999};
        QFormLayout                   m_form{this};
        std::map<QString, QLineEdit*> m_fields{};
        QDialogButtonBox             *m_buttonBox{nullptr};
    };
    
    #endif // NEWMAPDIALOG_H
    #include "newmapdialog.h"
    
    NewMapDialog::NewMapDialog(QWidget *parent)
        : QDialog{parent, Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowCloseButtonHint}
    {
        setWindowTitle(tr("Create a new map"));
        createForm();
    }
    
    std::map<QString, int> NewMapDialog::processDial()
    {
        QLabel *invalidFieldInfo = new QLabel{this};
        // Exec dialog while input are not valid except if dial is canceled
        do{
            exec();
            if(!validInput() && result() == QDialog::Accepted){
                if(invalidFieldInfo->text().isEmpty()){
                    invalidFieldInfo->setText(tr("A field cannot be empty !"));
                    m_form.addRow(invalidFieldInfo);
                }
            }
        }while(!validInput() && result() == QDialog::Accepted);
    
    
        std::map<QString, int> values{};
        // Ok, we can process if all input are good and user say Ok
        if(result() == QDialog::Accepted){
            for(auto it = std::begin(m_fields); it != std::end(m_fields); ++it){
                values[it->first] = it->second->text().toInt();
            }
        }
        return values;
    }
    
    void NewMapDialog::createForm()
    {
        QLineEdit *lineEditTileWidth = new QLineEdit{this};
        lineEditTileWidth->setValidator(&m_validatorSizeTile);
        m_form.addRow(QString{tr("Tile Width : ")}, lineEditTileWidth);
        m_fields["tileWidth"] = lineEditTileWidth;
    
        QLineEdit *lineEditTileHeight = new QLineEdit{this};
        lineEditTileHeight->setValidator(&m_validatorSizeTile);
        m_form.addRow(QString{tr("Tile Height : ")}, lineEditTileHeight);
        m_fields["tileHeight"] = lineEditTileHeight;
    
        QLineEdit *lineEditTotalRows = new QLineEdit{this};
        lineEditTotalRows->setValidator(&m_validatorCount);
        m_form.addRow(QString{tr("Total  Rows : ")}, lineEditTotalRows);
        m_fields["totalRows"] = lineEditTotalRows;
    
        QLineEdit *lineEditTotalCows = new QLineEdit{this};
        lineEditTotalCows->setValidator(&m_validatorCount);
        m_form.addRow(QString{tr("Total  Cols : ")}, lineEditTotalCows);
        m_fields["totalCols"] = lineEditTotalCows;
    
        createButtonBox();
    }
    
    void NewMapDialog::createButtonBox()
    {
        m_buttonBox = new QDialogButtonBox{QDialogButtonBox::Ok | QDialogButtonBox::Cancel,
                                   Qt::Horizontal, this};
        m_form.addRow(m_buttonBox);
        connect(m_buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
        connect(m_buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
    }
    
    bool NewMapDialog::validInput()
    {
        bool valid{true};
        for(auto it = std::begin(m_fields); it != std::end(m_fields); ++it){
            if(it->second->text().isEmpty()){
                valid = false;
                break;
            }
        }
        return valid;
    }

    Avant d'aller plus loin j'aurai donc quelques question :

    • Au niveau du constructeur de MainWindow, est-ce une bonne chose d'initialiser directement les pointeurs dans la liste d'initialisation ? Ou est-ce que je devrai plutôt le faire dans des fonctions dédiées que j'appelerai ensuite dans le corps du constructeur ?
    • Au niveau de la prise en charge des pointeurs par Qt, on est bien d'accord qu'ici je n'ai rien à faire ? Leur libération est bien automatique ? Sinon y-a-t-il une fuite de mémoire quelque part ?
    • Y-a-t-il des choses immondes qui vous sautent aux yeux ? :D

    Par avance merci.

    -
    Edité par Guit0Xx 9 janvier 2019 à 18:07:10

    • Partager sur Facebook
    • Partager sur Twitter

    ...

      10 janvier 2019 à 1:03:20

      Au niveau du constructeur de MainWindow, un peu de réalisme ;) Supposons qu'un new échoue, que tu sois dans le corps du constructeur ou bien dans la liste d'initialisation, trapper l'exception ne te permettra (probablement) pas de rattraper le coup, ça va donc probablement se finir par un abort, donc ça n'a pas beaucoup d'importance... A tout prendre, autant utiliser la liste d'initialisation, à défaut de t'apporter quoi que ce soit, c'est uniforme avec le code non Qt.

      Pour ta deuxième question, oui, si tu assignes un parent, tu lui transfères la responsabilité. Cependant, il faut bien garder à l'esprit, que cela ne concerne que les dérivées de QObject/QWidget, pour le reste, Qt ne s’occupe de rien, c'est à toi de gérer, donc les bonnes vielles règles s'appliquent ;)

      Pour les horreurs, deux petits détails:

      - Quel est l'intérêt de garder un membre sur le layout?

      - Pareil pour le ButtonBox?

      Si on prévoit un service rendu par la classe, alors on en aura besoin, sinon quel est l'intérêt? Dans ton exemple l'intérêt est nul (au vu de ton sample de code). Utiliser Qt ne te dispense pas, d'oublier les règles de base de C++ ;)

      -
      Edité par int21h 10 janvier 2019 à 1:12:25

      • Partager sur Facebook
      • Partager sur Twitter
      Mettre à jour le MinGW Gcc sur Code::Blocks. Du code qui n'existe pas ne contient pas de bug
        10 janvier 2019 à 1:37:49

        int21h a écrit:

        Au niveau du constructeur de MainWindow, un peu de réalisme ;) Supposons qu'un new échoue, que tu sois dans le corps du constructeur ou bien dans la liste d'initialisation, trapper l'exception ne te permettra (probablement) pas de rattraper le coup, ça va donc probablement se finir par un abort, donc ça n'a pas beaucoup d'importance... A tout prendre, autant utiliser la liste d'initialisation, à défaut de t'apporter quoi que ce soit, c'est uniforme avec le code non Qt.

        Effectivement, on va rester sur la liste d'initialisation alors ^^.

        int21h a écrit:

        Cependant, il faut bien garder à l'esprit, que cela ne concerne que les dérivées de QObject/QWidget, pour le reste, Qt ne s’occupe de rien, c'est à toi de gérer, donc les bonnes vielles règles s'appliquent ;)

        Ah super ! Je garde ça en tête :)

        int21h a écrit:

        Pour les horreurs, deux petits détails:

        - Quel est l'intérêt de garder un membre sur le layout?

        - Pareil pour le ButtonBox?

        Je dois avouer que je ne m'étais pas posé la question, je me la poserai à l'avenir. Bon j'ai changé ça du coup, c'est plus propre maintenant ;).

        Merci pour ces conseils.

        • Partager sur Facebook
        • Partager sur Twitter

        ...

          21 janvier 2019 à 23:30:35

          Bonsoir,

          Je reviens vers vous car j'ai un petit souci d'implémentation. J'en suis arrivé à l'étape du Copy/Cut/Paste et je ne suis pas sûr de l'utilisation de la classe QClipboard.

          Dans un premier temps, je me demande si je dois intégrer un membre QClipboard à ma classe view perso ou utiliser le clipboard de l'application (via qApp->clipboard()) ?

          Ensuite, au niveau des données, au départ je pensais créer une seule QPixmap puis l'enregistrer dans le clipboard mais finalement j'ai besoin de plus d'informations (image/position/nom de la texture) pour pouvoir "recoller" ça comme il se doit. Du coup j'ai un peu de mal à voir comment traiter ces données en une seule fois pour les transmettre au clipboard.

          Est-ce que je dois d'abord transposer les données sous forme de QString puis de QByteArray pour les remettre ensuite au clipboard sous forme de QMimeData ?

          Pour l'instant je n'ai pas de classe "custom" pour les items graphiques, j'utilise simplement QGraphicsRectItem. Donc pour le moment, les seules informations utiles que je peux récupérer sont la position, la taille et l'image de chaque items.

          Typiquement ça donne ça :

          Et avec un Ctrl+C je récupère la position et la taille (et éventuellement l'image avec le nom par le biais de ma classe scene custom) :

          Item  1  | pos( -0.5 ,  -0.5 ) | size( 33 ,  33 )
          Item  2  | pos( -0.5 ,  31.5 ) | size( 33 ,  33 )
          Item  3  | pos( 31.5 ,  31.5 ) | size( 33 ,  33 )
          Item  4  | pos( 31.5 ,  -0.5 ) | size( 33 ,  33 )

          Bon par la suite j'implémenterai le Cut et le Paste via une classe "commande" héritant de QUndoCommand (le Copy lui est inutile pour le undo/redo). Mais pour l'instant j'aurai juste besoin de savoir comment traiter les données pour les transférer au clipboard comme il se doit ^^.

          Si quelqu'un a une piste, je suis preneur :).

          -
          Edité par Guit0Xx 21 janvier 2019 à 23:49:41

          • Partager sur Facebook
          • Partager sur Twitter

          ...

          [Qt] Besoin de quelques conseils

          × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
          × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
          • Editeur
          • Markdown