3

I'm making an application that deals with floorplans and so wish to have a Layer dialogue which allows the user to turn on/off the different layers in the floorplan.

I would like to have a pop up window which therefore has a checkbox for every layer in the floorplan loaded at runtime. Each check box would be a widget in its own right which I only have experience of specifying/creating at compile time. However, here I would not know how many check boxes I would need until the particular floor plan was loaded.

My question moving forward is how I would create such functionality in FLTK (what I'm using). I can imagine creating the check box widgets in a loop in a callback when the layer dialogue is brought up (each possessing a generic callback function themselves), but I don't know how to instruct FLTK where to put the check box widgets. Ie how would I, at runtime, instruct FLTK that the layer dialogue window should be the parent widget? Alternatively I could dynamically create the whole window, but I am concerned about ensuring the window was destroyed (deleted) whenever it was hidden.

N.B. I would imagine deleting the widgets when the window was closed (hidden). On this point I am also unclear: do I delete the widget in the usual new/delete c++ manner or do I use the Fl::delete_widget() dialogue (http://www.fltk.org/doc-1.3/group__fl__del__widget.html#ga609413ac47ba433d1e7da8678a27164f)

OR: is there a much better way of doing all of this which side steps all of these issues?

user3353819
  • 911
  • 2
  • 8
  • 21
  • 1
    Re widget deletion: all the widgets get destroyed when the dialog is closed. If you try deleting the widget and don't tell the dialog about it, it too will try deleting and consequently crash the application. If you have data attached to the widget, just delete you data and let FLTK deal with the widgets. – cup Sep 26 '14 at 19:48

1 Answers1

3

Here is a noddy example of how to do the sizing based on the number of layers. The main thing to note is that if you use a vector, it reallocates as the vector grows so keeping pointers to display data in a vector can result in rubbish on the screen.

If you set the sizes of the widgets in a const file somewhere, you can use it for sizing.

#include <FL/Fl.H>
#include <FL/fl_draw.H>
#include <FL/Fl_Window.H>
#include <FL/Fl_Box.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_Check_Button.H>
#include <iostream>
#include <sstream>
#include <string>
#include <list>
#include <vector>

const int hsep = 10, vsep = 10, btnwid = 100, btnhgt = 20;

// The canvas layer
class Canvas : public Fl_Box
{
    Fl_Offscreen& m_buffer;
public:
    Canvas(int x, int y, int w, int h, Fl_Offscreen& buffer)
        : Fl_Box(x, y, w, h)
        , m_buffer(buffer)
    {
    }

    void Clear()
    {
        fl_begin_offscreen(m_buffer);
        fl_color(FL_WHITE);
        fl_rectf(0, 0, w(), h());
        fl_end_offscreen();
    }
    void draw()
    {
        fl_copy_offscreen(x(), y(), w(), h(), m_buffer, 0, 0);
    }
};

// This structure exists because there is no callback that takes
// two client parameters.  Also, the tag must exist for the life of
// the dialog.
class LayerWin;
class LayerCheck
{
public:
    int m_layer;
    std::string m_tag;
    LayerWin* m_parent;
};

// The layer window
class LayerWin
{
    int m_layers;
    Fl_Window* m_dlg;
    std::string m_title;
    std::list<LayerCheck> m_tag;
    Canvas* m_canvas;
    Fl_Offscreen m_buffer;
    std::vector<bool> m_layer;
public:
    LayerWin(int layers)
        : m_layers(layers)
        , m_dlg(0)
    {
    }
    ~LayerWin()
    {
        delete m_dlg;
    }

    void Create()
    {
        int dlgwid = 400, dlghgt = 300;
        std::ostringstream oss;

        oss << m_layers << " Layers";
        m_title = oss.str();
        m_dlg = new Fl_Window(dlgwid, dlghgt, m_title.c_str());
        m_dlg->set_modal();
        int x = hsep, y = vsep;
        for (int ll = 0; ll < m_layers; ++ll)
        {
            oss.str("");
            oss << "Layer " << ll + 1;
            LayerCheck dummy;
            m_tag.push_back(dummy);
            LayerCheck& actual = m_tag.back();
            actual.m_tag = oss.str();
            actual.m_layer = ll;
            actual.m_parent = this;
            Fl_Check_Button* btnCheck = new Fl_Check_Button(x, y, btnwid, btnhgt, actual.m_tag.c_str());
            btnCheck->callback(_LayerCB, &actual);
            m_layer.push_back(false);
            y += vsep + btnhgt;
        }

        x = hsep + btnwid + hsep;
        y = vsep;
        int canw = dlgwid - x - 2 * hsep;
        int canh = dlghgt - 3 * vsep;
        m_buffer = fl_create_offscreen(canw, canh);
        m_canvas = new Canvas(x, y, canw, canh, m_buffer);
        m_canvas->Clear();

        Fl_Button* btnClose = new Fl_Button(hsep, dlghgt - vsep - btnhgt, btnwid, btnhgt, "Close");
        btnClose->callback(_CloseCB, this);
        m_dlg->end();
        m_dlg->show();

        Fl::wait();
    }

    static void _CloseCB(Fl_Widget* w, void* client)
    {
        LayerWin* self = (LayerWin*)client;
        delete self;
    }
    static void _LayerCB(Fl_Widget* w, void* client)
    {
        Fl_Check_Button* btn = (Fl_Check_Button*)w;
        LayerCheck* check = (LayerCheck*)client;
        check->m_parent->LayerCB(check->m_layer, !!btn->value());
    }
    void LayerCB(int layer, bool set)
    {
        std::cout << "Layer " << layer+1 << "=" << set << std::endl;
        m_layer[layer] = set;

        // Redo the canvas
        m_canvas->Clear();
        fl_begin_offscreen(m_buffer);
        for (int ll = 0; ll < m_layer.size(); ++ll)
        {
            if (m_layer[ll])
            {
                switch (ll)
                {
                case 0:
                    fl_color(FL_RED);
                    fl_circle(20, 40, 100);
                    break;
                case 1:
                    fl_color(FL_DARK_GREEN);
                    fl_rectf(50, 70, 20, 40);
                    break;
                case 2:
                    fl_color(FL_BLUE);
                    fl_circle(100, 100, 50);
                    break;
                case 3:
                    fl_color(FL_DARK_MAGENTA);
                    fl_circle(200, 200, 70);
                    break;
                case 4:
                    fl_color(FL_DARK_CYAN);
                    fl_rectf(100, 200, 60, 90);
                    break;
                default:
                    std::cout << "Don't know what to do for layer " << ll + 1;
                    break;
                }
            }
        }
        fl_end_offscreen();
        m_canvas->redraw();
    }
};

void LayerCB(Fl_Widget* w, long layers)
{
    // Create a modal dialog with layers checkbuttons
    LayerWin* dlg = new LayerWin(layers);
    dlg->Create();
}

int main()
{
    // Work out the sizes
    int scrwid = btnwid + 2 * hsep, scrhgt = 2 * vsep + 3 * (vsep + btnhgt);

    // Create the main dialog
    Fl_Window mainwin(scrwid, scrhgt, "Layers");
    int x = hsep;
    int y = vsep;
    Fl_Button* button = new Fl_Button(x, y, btnwid, btnhgt, "3 layers");
    button->callback(LayerCB, 3);

    y += vsep + btnhgt;
    button = new Fl_Button(x, y, btnwid, btnhgt, "4 layers");
    button->callback(LayerCB, 4);

    y += vsep + btnhgt;
    button = new Fl_Button(x, y, btnwid, btnhgt, "5 layers");
    button->callback(LayerCB, 5);
    mainwin.end();
    mainwin.show();
    return Fl::run();
}

Good luck!

yaobin
  • 2,436
  • 5
  • 33
  • 54
cup
  • 7,589
  • 4
  • 19
  • 42
  • 1
    Thanks. I ended up doing something similar, though perhaps not so elegant. Funnily enough I did run into the exact thing you pointed out immediately: watching out for addresses to vectors which change when it resizes. The problem arose with keeping pointers to data for the callbacks of the arbitrary number of check buttons rather than the pointers to buttons themselves. I ended up making sure I did no more allocating before using the address to it, but maybe iterators would be a better approach? – user3353819 Sep 30 '14 at 17:35
  • 1
    It is always a problem with callbacks when you need more than one bit of data. Sometimes you can infer it from the value() of the widget. You can also save the data in the user_data member of the widget. Slightly more elegant than my technique of creating a structure and passing it to the callback. – cup Sep 30 '14 at 19:38
  • 1
    Just going through things in detail trying to remove memory leaks. I'm not sure, but because the user can close the window without clicking btnClose (instead using the main close button at the top of the window) I think you need to add m_dlg->callback(_CloseCB, this); Otherwise the user can keep creating instances of LayerWin and close it without the memory being freed. – user3353819 Oct 01 '14 at 11:14
  • 1
    Yes, adding m_dlg->callback would have to be added for those who like pressing X. I normally do it in production code but not in proof of concept type experiments like the one above. – cup Oct 01 '14 at 19:40