How to automatically stack FLTK's Fl_Input-objects one below the other

278 views Asked by At

I have created a custom-made object based on FLTK's Fl_Input-class that draws to screen an input box and automatically places it below the previous box that was declared. This was implemented by using an "external" (in the sense of "external to the class implementation") counter that is used to compute the y-coordinate of the box.

Here's a reduced version of the class:

// Example.h
#ifndef EXAMPLE_H
#define EXAMPLE_H

#include <FL/Fl.H>
#include <FL/Fl_Input.H>

class Example : public Fl_Input {
public:
  Example(const char * str); // constructor
private:
  int ex_y(); // compute y-coordinate of current box based on the previous ones
};

#endif // EXAMPLE_H

And here are the member function and the constructor:

// Example.cpp
#include "Example.h"

int inbox_y1;       // y-coord of first box; an example of “external” constants
int inbox_num;      // counter: number of input boxes declared; used to compute their y-coord

Example::Example(const char * str)
  : Fl_Input(200, ex_y(), 200, 25, str)
{
  ++inbox_num;
}

int Example::ex_y()
{
  return inbox_y1 + (25+25)*inbox_num;;
}

and finally the main file (in which I wrapped the declarations of the objects in a custom function):

// main.cpp
#include <FL/Fl.H>
#include <FL/Fl_Input.H>
#include <FL/Fl_Window.H>
#include <string>
#include <iostream>
#include "Example.h"

void place_inboxes()
{
  int inbox_y1 = 25; // not used by the following objects: the first one is attached to the upper window
  int inbox_num = 0; // the counter is not incremented by the following declarations

  Example * ex1 = new Example("First Input Box");
  std::cout << "First call: " << inbox_num << '\n';
  Example * ex2 = new Example("Second Input Box");
  std::cout << "Second call: " << inbox_num << '\n';
  Example * ex3 = new Example("Third Input Box");
  std::cout << "Third call: " << inbox_num << '\n';
}

int main()
{
  Fl_Window * win = new Fl_Window(600, 600, "Test of 'Example' class");
  {
    place_inboxes();
  }
  win->end();
  win->show();
  return Fl::run();
}

This compiles and links successfully, but (even though the boxes are indeed stacked) the result is not what I expected:

  1. The first box is attached to the upper part of the window, while it should be below it at a distance defined by the ("external") constant inbox_y1. This leads me to think that the declarations of Example-type objects do not take into account such constant.
  2. After each new object declaration the incremented value of the counter is printed, but actually it is unaffected.

The question: How can I make it work correctly? (I have the impression I'm making a mess in declaring the constants in the various source files...)

(Optional) Side question: Is defining a class that both builds an object and places it considered bad practice? I mean, is it bad practice to make an object do "too many" things at once? (I also thought to write a function that places the objects, but I wouldn't even know where to start about that.)

Final note: The code I use at the moment actually works well, but it has one single source file (where the constants I called "external" are global constants). The problem came up when dividing it into multiple source files.

1

There are 1 answers

0
cup On BEST ANSWER

Method 1: use statics in the class. This is not a very good method since it only knows about itself and not about anything else. The problem is if you have a second window which uses Example, it starts from the position where the last one stopped, so you need a reset function to put everything back to the start. The changes to your code are

// Example.h
class Example : public Fl_Input {
public:
    Example(const char * str); // constructor
    static int inbox_y1, inbox_num;
    static void Reset();
...

// Example.cpp
int Example::inbox_y1, Example::inbox_num;
void Example::Reset()
{
    inbox_y1 = 25;
    inbox_num = 0;
}
...

// main.cpp
// call Example::Reset() before you call place_inboxes

Method 2: ask the parent. The parent keeps track of where the last child was. This is slightly better in that the parent controls the position of the children. All the child needs to do is to ask the parent where it is.

// Example.h
#ifndef EXAMPLE_H
#define EXAMPLE_H

#include <FL/Fl.H>
#include <FL/Fl_Input.H>
#include <FL/Fl_Window.H>

class ExParent: public Fl_Window
{
public:
   ExParent(int x, int y, const char* name);
   int child_x();
   int child_y();
   void next_child_y();
private:
   int cy;
};

class Example : public Fl_Input {
public:
   Example(ExParent* parent, const char * str); // constructor
};

#endif // EXAMPLE_H


// Example.cpp
#include "Example.h"

Example::Example(ExParent* parent, const char * str)
  : Fl_Input(parent->child_x(), parent->child_y(), 200, 25, str)
{
}

ExParent::ExParent(int x, int y, const char* name)
: Fl_Window(x, y, name)
, cy(25)
{
}

int ExParent::child_x() { return 200; }

int ExParent::child_y()
{
   return cy;
}

void ExParent::next_child_y()
{
   cy += 50;
}

int main()
{
  ExParent * win = new ExParent(600, 600, "Test of 'Example' class");
  Example * ex1 = new Example(win, "First Input Box");
  win->next_child_y();
  Example * ex2 = new Example(win, "Second Input Box");
  win->next_child_y();
  Example * ex3 = new Example(win, "Third Input Box");
  win->end();
  win->show();
  return Fl::run();
}