gansm / finalcut

A text-based widget toolkit
https://github.com/gansm/finalcut/wiki/First-steps#first-steps-with-the-final-cut-widget-toolkit
GNU Lesser General Public License v3.0
981 stars 52 forks source link

Problem with FLabel drawing double #109

Closed wimstockman closed 1 year ago

wimstockman commented 1 year ago

scr1 Hi , I'm tinkering to create a kind of subform with rows of fields. But when i add labels on top it is like they are printed twice and some letters stay on the canvas.

Here is my code:

#include <final/final.h>
#include <fstream>

using finalcut::FPoint;
using finalcut::FSize;
using std::endl; using std::ofstream; using std::string;
using namespace finalcut;

class SubForm : public FScrollView
{
    public:
        class row : public FWidget{
            public:
                explicit row (FWidget* parent) : FWidget{parent}
                {
            vField.push_back(&ArtikelCode);
            vField.push_back(&Aantal);
            vField.push_back(&StukPrijs);
            vField.push_back(&TotPrijs);
            initLayout();
                }

            FLineEdit ArtikelCode{this};
            FLineEdit Aantal{this};
            FLineEdit StukPrijs{this};
            FLineEdit TotPrijs{this};
            int RowId;
            std::vector<FLineEdit*> vField;
            void initLayout() override {
            ArtikelCode.setGeometry(FPoint{1,1},FSize{20,3});
            ArtikelCode.unsetShadow();
            Aantal.setGeometry(FPoint{24,1},FSize{10,3});
            Aantal.unsetShadow();
            StukPrijs.setGeometry(FPoint{36,1},FSize{10,3});
            StukPrijs.unsetShadow();
            TotPrijs.setGeometry(FPoint{48,1},FSize{10,3});
            TotPrijs.unsetShadow();
            }

            };
        explicit SubForm (FWidget* parent) : FScrollView{parent}
        {
        }
        explicit SubForm (const FString& txt, FWidget* parent) : FScrollView{parent}
        {
        }
        std::vector<row*> vRow;
        std::vector<std::string> vCaption{"ArtikelCode","Aantal","E.Prijs","TotPrijs"};
        unsigned int InitLines{5};
    void initLayout() override {
        int posy=2;
        int posx=1;
        for(unsigned int i=0;i<InitLines;i++){
            posy+=2;
            row* r = new row{this};
            this->vRow.push_back(r);
            r->setGeometry(FPoint{1,posy},FSize{100,10});
            // First Row add Caption above the Fields
            if (i==0) {
                for(long unsigned int j=0;j<vCaption.size();j++){
                    posx = r->vField[j]->getPos().getX();
                    long unsigned int width = r->vField[j]->getSize().getWidth();
                    FLabel* l = new FLabel{this};
                    l->setGeometry(FPoint{posx,1},FSize{width,1});
                    l->setText(vCaption[j]);
                    l->setAlignment(Align::Center);
                    posy-=1;
                }
            }
        }
        this->setText("Detail");
        this->setPos(FPoint{1,2});
        this->setSize(FSize{80,30});
        this->setScrollSize(FSize{70,100});
        this->setColor(FColor::Blue,FColor::White);
        this->clearArea();
        }       
};
class Form : public FDialog
{
    public:
        explicit Form (FWidget *parent):FDialog{parent} {
    }
    SubForm S{"Detail",this};
    void initLayout() override{
    S.setGeometry(FPoint{5,5},FSize{50,15});
    }
};

int main (int argc, char* argv[])
{
  // Create the application object
  finalcut::FApplication app{argc, argv};
  app.initTerminal();
  Form dgl{&app};
    dgl.setText("SubForm");
    dgl.setGeometry(FPoint{1,1},FSize{100,50});
    dgl.redraw();
  // Set dialog object as main widget
  FWidget::setMainWidget(&dgl);
  // Show and start the application
  dgl.show();
  return app.exec();
}

Can you spot the problem. Kind Regards, Wim

gansm commented 1 year ago

Hello Wim, you cannot create an object/widget in initLayout(). The initLayout() method is called dynamically, and you do not influence on when and how often. I added some logging entries (std::clog ...) to your application to document the function calls.

#include <final/final.h>
#include <fstream>

using finalcut::FPoint;
using finalcut::FSize;
using std::endl;
using std::ofstream;
using std::string;
using namespace finalcut;

class SubForm : public FScrollView
{
  public:
    class row : public FWidget
    {
      public:
        explicit row (FWidget* parent) : FWidget{parent}
        {
          vField.push_back(&ArtikelCode);
          vField.push_back(&Aantal);
          vField.push_back(&StukPrijs);
          vField.push_back(&TotPrijs);
          initLayout();
        }

        FLineEdit ArtikelCode{this};
        FLineEdit Aantal{this};
        FLineEdit StukPrijs{this};
        FLineEdit TotPrijs{this};
        int RowId;
        std::vector<FLineEdit*> vField;

        void initLayout() override
        {
          std::clog << "row::initLayout()\n";
          ArtikelCode.setGeometry(FPoint{1, 1}, FSize{20, 3});
          ArtikelCode.unsetShadow();
          Aantal.setGeometry(FPoint{24, 1},FSize{10, 3});
          Aantal.unsetShadow();
          StukPrijs.setGeometry(FPoint{36, 1},FSize{10, 3});
          StukPrijs.unsetShadow();
          TotPrijs.setGeometry(FPoint{48, 1},FSize{10, 3});
          TotPrijs.unsetShadow();
          FWidget::initLayout();
        }

    };

    explicit SubForm (FWidget* parent)
      : FScrollView{parent}
    { }

    explicit SubForm (const FString& txt, FWidget* parent)
      : FScrollView{parent}
    { }

    std::vector<row*> vRow;
    std::vector<std::string> vCaption{"ArtikelCode", "Aantal", "E.Prijs", "TotPrijs"};
    unsigned int InitLines{5};

    void initLayout() override
    {
      std::clog << "SubForm::initLayout()\n";
      int posy = 2;
      int posx = 1;

      for (unsigned int i = 0; i < InitLines; i++)
      {
        posy += 2;
        row* r = new row{this};
        this->vRow.push_back(r);
        r->setGeometry(FPoint{1, posy}, FSize{100, 10});

        // First Row add Caption above the Fields

        if ( i == 0 )
        {
          for (long unsigned int j = 0; j < vCaption.size(); j++)
          {
            posx = r->vField[j]->getPos().getX();
            long unsigned int width = r->vField[j]->getSize().getWidth();
            FLabel* l = new FLabel{this};
            std::clog << "Create new Label \"" << vCaption[j] << "\"\n";

            if ( j == 3 )
            {
              l->setBackgroundColor(FColor::Yellow);
            }

            l->setGeometry(FPoint{posx, 1 + 8 + int(j)}, FSize{width, 1});
            l->setText(vCaption[j]);
            l->setAlignment(Align::Center);

            if ( j == 3 ) 
            {
              std::clog << "pos(x y) = (" << l->getPos() << ");"
                        << " text = \"" << l->getText() << "\"\n";
            }

            posy -= 1;
          }
        }

        FScrollView::initLayout();
      }

      this->setText("Detail");
      this->setPos(FPoint{1, 2});
      this->setSize(FSize{80, 30});
      this->setScrollSize(FSize{70, 100});
      this->setColor(FColor::Blue,FColor::White);
      this->clearArea();
    }    
};

class Form : public FDialog
{
  public:
    explicit Form (FWidget *parent) : FDialog{parent}
    { }

    SubForm S{"Detail", this};

    void initLayout() override
    {
      std::clog << "Form::initLayout()\n";
      S.setGeometry(FPoint{5, 5}, FSize{50, 15});
      FDialog::initLayout();
    }
};

int main (int argc, char* argv[])
{
  // Create the application object
  finalcut::FApplication app{argc, argv};
  app.initTerminal();
  Form dgl{&app};
  dgl.setText("SubForm");
  dgl.setGeometry(FPoint{1, 1}, FSize{100, 50});
  dgl.redraw();

  // Set dialog object as main widget
  FWidget::setMainWidget(&dgl);

  // Show and start the application
  dgl.show();
  return app.exec();
}

To prevent the log lines from being written to the terminal, you have to start your program with ./yourapp --log-file=/tmp/fc.log and watch the output live in a second terminal with tail -f /tmp/fc.log.

You will see the following output:

Thu, 22 Dec 2022 23:40:28 +0100 [INFO] Form::initLayout()
Thu, 22 Dec 2022 23:40:28 +0100 [INFO] SubForm::initLayout()
Thu, 22 Dec 2022 23:40:28 +0100 [INFO] row::initLayout()
Thu, 22 Dec 2022 23:40:28 +0100 [INFO] Create new Label "ArtikelCode"
Thu, 22 Dec 2022 23:40:28 +0100 [INFO] Create new Label "Aantal"
Thu, 22 Dec 2022 23:40:28 +0100 [INFO] Create new Label "E.Prijs"
Thu, 22 Dec 2022 23:40:28 +0100 [INFO] Create new Label "TotPrijs"
Thu, 22 Dec 2022 23:40:28 +0100 [INFO] pos(x y) = (39 12); text = "TotPrijs"
Thu, 22 Dec 2022 23:40:28 +0100 [INFO] row::initLayout()
Thu, 22 Dec 2022 23:40:28 +0100 [INFO] row::initLayout()
Thu, 22 Dec 2022 23:40:28 +0100 [INFO] row::initLayout()
Thu, 22 Dec 2022 23:40:28 +0100 [INFO] row::initLayout()
Thu, 22 Dec 2022 23:40:28 +0100 [INFO] row::initLayout()
Thu, 22 Dec 2022 23:40:28 +0100 [INFO] row::initLayout()
Thu, 22 Dec 2022 23:40:28 +0100 [INFO] row::initLayout()
Thu, 22 Dec 2022 23:40:28 +0100 [INFO] row::initLayout()
Thu, 22 Dec 2022 23:40:28 +0100 [INFO] row::initLayout()
Thu, 22 Dec 2022 23:40:28 +0100 [INFO] SubForm::initLayout()
Thu, 22 Dec 2022 23:40:28 +0100 [INFO] row::initLayout()
Thu, 22 Dec 2022 23:40:28 +0100 [INFO] Create new Label "ArtikelCode"
Thu, 22 Dec 2022 23:40:28 +0100 [INFO] Create new Label "Aantal"
Thu, 22 Dec 2022 23:40:28 +0100 [INFO] Create new Label "E.Prijs"
Thu, 22 Dec 2022 23:40:28 +0100 [INFO] Create new Label "TotPrijs"
Thu, 22 Dec 2022 23:40:28 +0100 [INFO] pos(x y) = (48 12); text = "TotPrijs"
Thu, 22 Dec 2022 23:40:28 +0100 [INFO] row::initLayout()
Thu, 22 Dec 2022 23:40:28 +0100 [INFO] row::initLayout()
Thu, 22 Dec 2022 23:40:28 +0100 [INFO] row::initLayout()
Thu, 22 Dec 2022 23:40:28 +0100 [INFO] row::initLayout()
Thu, 22 Dec 2022 23:40:28 +0100 [INFO] row::initLayout()
Thu, 22 Dec 2022 23:40:28 +0100 [INFO] row::initLayout()
Thu, 22 Dec 2022 23:40:28 +0100 [INFO] row::initLayout()
Thu, 22 Dec 2022 23:40:28 +0100 [INFO] row::initLayout()
Thu, 22 Dec 2022 23:40:28 +0100 [INFO] row::initLayout()
Thu, 22 Dec 2022 23:40:28 +0100 [INFO] row::initLayout()
Thu, 22 Dec 2022 23:40:28 +0100 [INFO] row::initLayout()
Thu, 22 Dec 2022 23:40:28 +0100 [INFO] row::initLayout()
Thu, 22 Dec 2022 23:40:28 +0100 [INFO] row::initLayout()
Thu, 22 Dec 2022 23:40:28 +0100 [INFO] row::initLayout()
Thu, 22 Dec 2022 23:40:28 +0100 [INFO] row::initLayout()
Thu, 22 Dec 2022 23:40:28 +0100 [INFO] row::initLayout()
Thu, 22 Dec 2022 23:40:28 +0100 [INFO] row::initLayout()
Thu, 22 Dec 2022 23:40:28 +0100 [INFO] row::initLayout()
Thu, 22 Dec 2022 23:40:28 +0100 [INFO] row::initLayout()
Thu, 22 Dec 2022 23:40:28 +0100 [INFO] row::initLayout()
Thu, 22 Dec 2022 23:40:28 +0100 [INFO] row::initLayout()
Thu, 22 Dec 2022 23:40:28 +0100 [INFO] row::initLayout()
Thu, 22 Dec 2022 23:40:28 +0100 [INFO] row::initLayout()
Thu, 22 Dec 2022 23:40:28 +0100 [INFO] row::initLayout()

The row::initLayout() method is called twice! Thus, you create 8 instead of 4 FLabel widgets. The 4th input field is not initialized when SubForm::initLayout() is first called. It returns an x value of 39. On the second call, it returns the initialized x-value 48.

To prevent the labels from overlapping, I increased the y-value by one line at a time:

grafik

wimstockman commented 1 year ago

Hi , Thanks for looking at it. I moved the creation of the widgets out of the initLayout and now it does what it should do. Thanks for the std::clog example. Maybe we should put it in the Wiki . I was debugging with gdb , but this is way easier.

Here is what I come so far to easily create a subform on a form.

#include <final/final.h>
#include <fstream>

using finalcut::FPoint;
using finalcut::FSize;
using std::endl; using std::ofstream; using std::string;
using namespace finalcut;

class SubForm : public FScrollView
{
public:
    //Helper Struct to create the LineEdits
    struct Field 
        { 
        long unsigned int width;
        bool numeric;
        Align align;
        };
    //Row Class to manage the rows of the SubForm
    class Row : public FWidget{
        public:
            explicit Row (FWidget* parent) : FWidget{parent}
            {
            }
        int RowId;
        std::vector<FLineEdit*> vField;
        void initLayout() override 
            {
            int px=1;
            for(unsigned int i=0;i<vField.size();i++)
                {
                vField[i]->setPos(FPoint{px,1});
                px+=vField[i]->getWidth()+2;
                vField[i]->unsetShadow();
                }
            }
        void makeFields(std::vector<Field*> vF) {
            for(unsigned int i=0;i<vF.size();i++)
                {
                FLineEdit* f = new FLineEdit(this);
                f->setSize(FSize{vF[i]->width,1});
                f->setAlignment(vF[i]->align);
                if (vF[i]->numeric == 1)  {f->setInputFilter("[.0-9]");}
                vField.push_back(f);
                }
            }

        void onChildFocusOut (FFocusEvent * out_ev) override
        {
            const auto focus_Widget = FWidget::getFocusWidget();
            if ( out_ev->getFocusType() == FocusTypes::NextWidget )
            {   
                const auto& last_widget = getLastFocusableWidget(getChildren());
                if ( focus_Widget == last_widget )
                {
                    out_ev->accept();
                    emitCallback("end-of-row");
                }
            }
            else if ( out_ev->getFocusType() == FocusTypes::PreviousWidget )
            {
                const auto& first_widget = getFirstFocusableWidget(getChildren());
                if ( focus_Widget == first_widget )
                {
                    out_ev->accept();
                    focusPrevChild();
                }
            }
        }
};// End of Row Class

    std::vector<Row*> vRow;
    std::vector<FLabel*> vLabel;
    std::vector<std::string> vCaption;
    std::vector<Field*> vField;
    unsigned int InitLines{5};

    explicit SubForm (FWidget* parent) : FScrollView{parent} { init(); }
    explicit SubForm (const FString& txt, FWidget* parent) : FScrollView{parent} { init(); } 

    void createField(std::string FieldCaption,long unsigned int width ,bool numeric=false , Align align=Align::Left)

        {
        Field* F = new Field;
        F->width = width;
        F->align = align; 
        vField.push_back(F);
        vCaption.push_back(FieldCaption);
        FLabel* l = new FLabel(FieldCaption,this);
        vLabel.push_back(l);
        }
    void createRow()
        {
        Row* r = new Row{this};
        r->setGeometry(FPoint{1,posy},FSize{100,1});
        r->makeFields(vField);
        r->addCallback("end-of-row",this,&SubForm::cb_rowchange);
        this->vRow.push_back(r);
        this->posy+=2;
        }

    void cb_rowchange()
    {
     auto lastfield = vRow[0]->vField.size()-1;
     for (long unsigned int i=0;i<vRow.size();i++)
        {
        if (vRow[i]->vField[lastfield]->hasFocus())
            { 
             if (i == vRow.size()-1) {(*vRow[0]->vField.begin())->setFocus(); break;}
              else {(*vRow[++i]->vField.begin())->setFocus(); break;}
            }
        } 
    this->redraw();
    }

    //Init function creates the dynamic objects
    void init()
        {
        //Create the amount of columns you want to have
        // with Column Caption,width ,numeric field or not , alignment
        // Defaults for numeric = alphanumeric , default alignment is left
        this->createField("ArtikelCode",20,0,Align::Center);
        this->createField("Aantal",10,1,Align::Right);
        this->createField("E.Prijs",10,1,Align::Right);
        this->createField("TotPrijs",10,1,Align::Right);

        //Create the amount of rows you want
        this->InitLines = 20;
        for(unsigned int i=0;i<InitLines;i++){this->createRow();}
        }
    //InitLayout sets the layout how the objects look -- !!! don't create new objects here !!! ---
    void initLayout() override 
        {
        int posx=2;
        for(long unsigned int j=0;j<vLabel.size();j++)
            {
            posx = vRow[0]->vField[j]->getPos().getX();
            long unsigned int width = vRow[0]->vField[j]->getSize().getWidth();
            vLabel[j]->setGeometry(FPoint{posx,1},FSize{width,1});
            vLabel[j]->setText(vCaption[j]);
            vLabel[j]->setAlignment(Align::Center);
            }
        this->setText("Detail");
        this->setPos(FPoint{1,2});
        this->setSize(FSize{80,30});
        this->setScrollSize(FSize{70,100});
        this->setColor(FColor::Blue,FColor::White);
        this->clearArea();
        }       
private:
    int posy=2;
};//End of SubForm Class

class Form : public FDialog
{
public:
    explicit Form (FWidget *parent):FDialog{parent} { }
    SubForm S{"Detail",this};
    void initLayout() override{
    S.setGeometry(FPoint{5,5},FSize{50,15});
    }
};

int main (int argc, char* argv[])
{
  // Create the application object
  finalcut::FApplication app{argc, argv};
  app.initTerminal();
  Form dgl{&app};
    dgl.setText("SubForm");
    dgl.setGeometry(FPoint{1,1},FSize{100,50});
    dgl.redraw();
  // Set dialog object as main widget
  FWidget::setMainWidget(&dgl);
  // Show and start the application
  dgl.show();
  return app.exec();
}

Have a nice Christmas Kind Regards, Wim Stockman