Discussion:
FLTK-1.3 group button menus in wrong place whe rearranged
Duncan Gibson
2013-04-08 07:33:10 UTC
Permalink
I've been playing around with this because I want to create a
set of buttons that change to indicate where the user is in the
program, instead of using a status bar. Maybe a little odd, but
sounded interesting.
I'd appreciate any ideas.
You could always set up a series of different panes in Fl_Wizard
where each pane has a status label and the set of buttons that
are relevant in the current program context, and switch between
panes as part of a button callback.

D.

This message and any attachments are intended for the use of the addressee or addressees only. The unauthorised disclosure, use, dissemination or copying (either in whole or in part) of its content is not permitted. If you received this message in error, please notify the sender and delete it from your system. Emails can be altered and their integrity cannot be guaranteed by the sender.

Please consider the environment before printing this email.
MacArthur, Ian (Selex ES, UK)
2013-04-08 10:27:56 UTC
Permalink
Oh! I was mid-reply, and I see Albrecht has already said most of what I was going to say...
So it appears that when a previous button in the group is removed, the
button's menu position doesn't get updated.
As Albrecht said, the way Fl_Pack behaves in practice is probably not what you want for your use case.

I have to confess I don't much care for Fl_Pack in general; every time I tried to use it, I ended up in difficulties.
Albrecht suggests deriving your own container from Fl_Group - this is probably the best bet; looks like more work initially but in practice it will be easier as it gives you so much more control over the way your widgets are positioned, sized and redrawn...

Albrecht also suggests a workaround based on timers. I think this might work out pretty well, though I'd consider having my own derived Fl_Pack (or similar container widget) that, in its derived draw() method, sets a flag to tell whether the draw has actually been enacted (and hence the sizes properly recalculated) and have that flag cleared down by the callback that triggers the timer.

The timer can then "poll" until it sees that the widget has been drawn (the flag is re-set) and thence that the sizes ought to be correct, for popping up the menus now...

Well, something like that...




Also, some entirely spurious critique of the example code, for no other reason than I was part way through rewriting it anyway...
static Fl_Font LFONT = FL_BOLD;
Would be better to say:

static Fl_Font LFONT = FL_HELVETICA | FL_BOLD;

Which is actually the same thing, but makes it explicit: It is (kinda) just chance that FL_BOLD on its own works (since FL_HELV... == 0). Really it is meant as a "modifier", not a name in its own right!
class Sbut : public Fl_Menu_Button
{
Sbut (int i);
~Sbut ();
static void onButton ( Fl_Widget* w, void* v);
int handle ( int event );
int i() { return _i; }
int _i;
/// char* _cstr; // not really needed
};
//cccccccccccccccccccccccccc
Sbut::Sbut (int id )
: Fl_Menu_Button( 0, 0, 100, 50)
{
// cout << "Sbut(s) 0: " << id << endl;
_i = idx;
callback(onButton);
//_cstr = new char[sizeof(int)]; // not needed, and maybe too small anyway?
char _cstr[16]; // use a stack automatic temp instead, cheaper than calling new
sprintf(_cstr, "%i", id);
// label(_cstr);
copy_label(_cstr); // use copy_label so the widget manages the label string for you
//cccccccccccccccccccccccccc
Sbut::~Sbut ()
{
// delete _cstr;
}
//***********************
int main (int argc, char **argv)
{
WIN->end();
// WIN->resizable(G1);
// With a pack, it may be that...
WIN->resizable(WIN); // might work out better...

// Passing argc, argv to show() ensures that fltk picks up
// the system default settings... Useful on some host systems.
WIN->show(argc, argv);
return Fl::run();
}
Selex ES Ltd
Registered Office: Sigma House, Christopher Martin Road, Basildon, Essex SS14 3EL
A company registered in England & Wales. Company no. 02426132
********************************************************************
This email and any attachments are confidential to the intended
recipient and may also be privileged. If you are not the intended
recipient please delete it from your system and notify the sender.
You should not copy it or use it for any purpose nor disclose or
distribute its contents to any other person.
********************************************************************
Albrecht Schlosser
2013-04-08 10:34:11 UTC
Permalink
.. at least ..
(1) I recommend not to use Fl_Pack, ...
(2) I don't recommend this one, but it should give you a clue
...
(3) You might also recalculate the button positions in your
callbacks/functions to modify the group, i.e. clearto() and/or
onMenu(), but you would have to do it in the same way as Fl_Pack
would do it later in its draw() method. This way, you'd have the
popup() work depending on the new positions, as you expect. The
point why I don't recommend this either is because you'd have to
*know* how Fl_Pack calculates the positions. However, this is not
trivial, but could work with your simple buttons. But if you do
this anyway, why not create an own group class? You could also
overwrite the resize() method and rearrange your buttons while
the window/group is being resized. Then you would be able to call
resize() after you rearranged the group's buttons, and that's it.
I like this more than recalculating positions in draw(), because
(a) it's less overhead, and (b) you avoid the problems you see
in your program.

So I still recommend (1).
Here the menu pops up, but Fl_Pack still "knows" of the old
button position(s), hence the unexpected position.
In fact, Fl_Pack is not involved here. It's the button widget
that still "knows" its old position, because Fl_Pack didn't
rearrange the children yet.

Albrecht
Albrecht Schlosser
2013-04-08 10:52:47 UTC
Permalink
Ian, I agree with you 100%, but want to add one comment.
Post by MacArthur, Ian (Selex ES, UK)
Albrecht also suggests a workaround based on timers. I think this might work out pretty well, though I'd consider having my own derived Fl_Pack (or similar container widget) that, in its derived draw() method, sets a flag to tell whether the draw has actually been enacted (and hence the sizes properly recalculated) and have that flag cleared down by the callback that triggers the timer.
The timer can then "poll" until it sees that the widget has been drawn (the flag is re-set) and thence that the sizes ought to be correct, for popping up the menus now...
Well, something like that...
Yep, this was suggested as a workaround only, either to see how it
can be done (just for learning), or if one really wants to use
Fl_Pack. Deriving from Fl_Pack (as I assume you meant above) for
another container widget and do the polling makes things even worse,
but I assume that you only wrote it for the same reasons as I did.

My personal opinion is that users should NOT try to resize widgets
in their draw methods, since this will lead to problems earlier or
later. The problems with Fl_Pack result just from the fact that it
tries to "allow" its children to resize in their respective draw()
methods, and hence it *must* recalculate positions after calling
the children's draw() methods - thus the only way to do it is in
its own draw() method.

My suggestion in my other posting about resizing/rearranging
children in resize() seems to be much better for user code, and
I'm using it in my apps successfully.

Albrecht
marty moore
2013-04-07 23:27:12 UTC
Permalink
Hi all,
I'm playing around with dynamic buttons in a group. I'm using FLTK-1.3, gcc-4.4.5, emacs 23, on a debian 6.5(64bit) system.

An example program, which demonstrates the problem,is included below.

What's supposed to happen:
1. click on a button, and a menu pops up with "add" and "delete" options.
2. click on "delete", and all other buttons disappear, and another menu for the clicked button appears(same options).
3. click on "add", and another button is added, and a menu pops up for the new button.

What happens:
1. When the first button is clicked, and the "delete" option is selected, the other buttons disappear, but the menu is where the clicked button used to be, not where the button is actually located.
2. If the "add" option is selected from the popped up menu, the menu is located on top of the new button.
3. Continue selecting "add", and the all of the menus appear over the first added button.
4. If "escape" is pressed, or the button is clicked again to cancel the menu, the buttons and menus seem to act properly again.

So it appears that when a previous button in the group is removed, the button's menu position doesn't get updated.

Is there any way to update the menu position, other than manually with
popup(x,y....) or pulldown(x,y,w,h....)

I've tried init_sizes() and sizes(), to no avail.
I've alse tried using activate to no avail.

I've been playing around with this because I want to create a set of buttons that change to indicate where the user is in the program, instead of using a status bar. Maybe a little odd, but sounded interesting.

I'd appreciate any ideas.
Marty

the example code:
#include <iostream>
#include <string>
#include <cstdio>
#include <cstdlib>

#include <FL/Fl.H>
#include <FL/Fl_Box.H>
#include <FL/Fl_Menu_Button.H>
#include <FL/Fl_Menu_Item.H>
#include <FL/Fl_Pack.H>
#include <FL/Fl_Window.H>

using std::cout;
using std::endl;
using std::string;

Fl_Pack* G1;
Fl_Window* WIN;

int idx = 0;

static Fl_Color LBG = FL_DARK_BLUE;
static Fl_Color LFG = FL_WHITE;
static Fl_Color LSC = FL_BLUE;;
static Fl_Color LTC = FL_WHITE;
static Fl_Font LFONT = FL_BOLD;
static Fl_Fontsize FSIZE = 30;
static int LSPC = 5;

enum eop { DEL=-1, ADD=1 };

class Sbut : public Fl_Menu_Button
{
public:
Sbut (int i);
~Sbut ();

static void onButton ( Fl_Widget* w, void* v);
int handle ( int event );
int i() { return _i; }

int _i;
char* _cstr;
};

//ffffffffffffffffffffffffffff
void clearTo ( int idx )
{
cout << "clearto " << idx << ": ";
Fl_Widget* w;

int last = G1->children()-1;
if ( last > idx )
cout << " removing ";
{
// remove last to idx
for ( int i=last; i>-1; i-- )
{
if ( i != idx )
{
cout << i << " ";
w = G1->child(i);
G1->remove(w);
delete w;
}
}
}
WIN->redraw();
cout << endl;
// cout << "popping menu" << endl;
// Sbut* sb = (Sbut*)G1->child(0);
// sb->popup();
}

//hhhhhhhhhhhhhhhhhhhhhhhhhhhh
void onMenu (Fl_Widget* w, void* v)
{
eop op = (eop)fl_intptr_t(v);
Sbut* sb;

if ( op == ADD )
{
cout << "onMenu: add" << endl;
sb = new Sbut(idx);
G1->add(sb);
idx++;
WIN->redraw();
cout << "onMenu 1: popping menu" << endl;
sb->show();
sb->popup();
}
else if ( op == DEL )
{
cout << "onMenu 2: deleting" << endl;
int i = G1->find(w);
clearTo(i);
WIN->redraw();
cout << "onMenu 3: poping menu" << endl;
sb = (Sbut*)G1->child(0);
sb->popup();
}

}

//mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm
Fl_Menu_Item xMenu[] = {
{"add button to end, and popup menu",
0, onMenu, (void*)ADD},
{"delete other buttons, and popup menu",
0, onMenu, (void*)DEL},
{0}
};


//cccccccccccccccccccccccccc
Sbut::Sbut (int id )
: Fl_Menu_Button( 0, 0, 100, 50)
{
// cout << "Sbut(s) 0: " << id << endl;
_i = idx;
callback(onButton);
_cstr = new char[sizeof(int)];
sprintf(_cstr, "%i", id);
label(_cstr);
// cout << "label = " << label() << endl;
color( LBG, LSC);
labelcolor(LFG);
labelfont(LFONT);
labelsize(FSIZE);
menu(xMenu);
if ( menu() )
{
Fl_Menu_Item* mi; // non-const pointer
mi = (Fl_Menu_Item*)menu();
int sz = mi->size();
for ( int j=0; j<sz; j++ )
{
// cout << "set: " << j << endl;
color(LBG, LSC);
mi->labelsize(FSIZE);
mi->labelfont(LFONT);
mi->labelcolor(LFG);
mi = mi->next();
}
}
}

//hhhhhhhhhhhhhhhhhhhhhhhhhH
int Sbut::handle ( int event )
{
if ( event == FL_PUSH )
{
cout << "Sbut handle 1: PUSH " << endl;
do_callback();
return 1;
}
return 0;
}

//hhhhhhhhhhhhhhhhhhhhhhhhhH
void Sbut::onButton ( Fl_Widget* w, void* v)
{
int i = G1->find(w);
cout << "Sbut onButton 0: " << i << endl;
Sbut* sb = (Sbut*)w;
sb->popup();
}

//cccccccccccccccccccccccccc
Sbut::~Sbut ()
{
delete _cstr;
}

//***********************
int main ()
{
Sbut* sb;
WIN = new Fl_Window(0, 0, 200, 300);
G1 = new Fl_Pack(0, 0, 150, 150);
G1->begin();
for ( int i=0; i<5; i++ )
{
sb = new Sbut(i);
idx++;
}
G1->end();
WIN->end();
// WIN->resizable(G1);
WIN->show();
return Fl::run();
}
Albrecht Schlosser
2013-04-08 09:58:56 UTC
Permalink
Post by marty moore
1. When the first button is clicked, and the "delete" option is selected, the other buttons disappear, but the menu is where the clicked button used to be, not where the button is actually located.
2. If the "add" option is selected from the popped up menu, the menu is located on top of the new button.
3. Continue selecting "add", and the all of the menus appear over the first added button.
4. If "escape" is pressed, or the button is clicked again to cancel the menu, the buttons and menus seem to act properly again.
So it appears that when a previous button in the group is removed, the button's menu position doesn't get updated.
Is there any way to update the menu position, other than manually with
popup(x,y....) or pulldown(x,y,w,h....)
Your problem lies buried in Fl_Pack. This is not designed to be
updated dynamically the way you try to do it, or at least this
doesn't work as expected. The reason why this is so is because
Fl_Pack rearranges its children only when it is draw(), but this
doesn't happen when you might expect it to do.
Post by marty moore
else if ( op == DEL )
{
cout << "onMenu 2: deleting" << endl;
int i = G1->find(w);
clearTo(i);
WIN->redraw();
Here you ask the window to be drawn, but this doesn't happen
until the event loop is reached again, either by returning
from the callback or ... (see below).
Post by marty moore
cout << "onMenu 3: poping menu" << endl;
sb = (Sbut*)G1->child(0);
sb->popup();
Here the menu pops up, but Fl_Pack still "knows" of the old
button position(s), hence the unexpected position. But this
popup() call also includes running the event loop again, until
the menu will be closed. Now you will see the new button
position(s), but the menu stays at the "old" position.

There are two ways to overcome this:

(1) I recommend not to use Fl_Pack, but instead calculate the
new button positions yourself (use a class derived from Fl_Group),
and everything ought to work as expected if you calculate the
positions before calling popup(). Note that calling init_sizes()
has nothing to do with Fl_Pack's and/or Fl_Groups position
calculations, unless you resize() the group or window. Hence it
would be useful to call init_sizes() after re-positioning the
buttons in the group.

(2) I don't recommend this one, but it should give you a clue
whether I'm right with my assumption. ;-) Instead of calling
popup() in the callback, as you did above, start a timer with
a short time, say 0.3 seconds, and popup() the menu in this
timer callback. If you did this, then the window's draw() would
happen before the timer callback runs, and therefore Fl_Pack's
draw() method would have adjusted the positions already. Although
this might look as the easier part, I don't consider this good
style, and if you extend your program later, Fl_Pack will maybe
not be the widget/group you want to use because of its restricted
features.


Albrecht

Loading...