Here's a half-baked idea.  I'm sure there are issues, but maybe
it's a start.

Good luck,
Stan

#ifndef CHOICE_H
#define CHOICE_H

// Choice.h - Choice
//   Yet another choice widget
//
// Much of this was stolen from Fl_Input_Choice

#include "FL/Fl_Group.H"
#include "FL/Fl_Widget.H"
#include "FL/Fl.H"
#include "FL/Fl_Button.H"
#include "FL/Fl_Output.H"
#include "FL/Fl_Select_Browser.H"
#include "FL/fl_draw.H"

class Choice : public Fl_Group {
private:
  // This is the button that pops up a "menu"
  struct DownButton : public Fl_Button {
    DownButton(int x, int y, int w, int h, char const* label = 0)
      : Fl_Button(x, y, w, h, label)
    {;}
    void draw()
    {
      draw_box(FL_UP_BOX, color());
      fl_color(active_r() ? labelcolor() : fl_inactive(labelcolor()));
      int xc = x() + w()/2, yc = y() + h()/2;
      fl_polygon(xc - 5, yc - 3, xc + 5, yc - 3, xc, yc + 3);
      if(Fl::focus() == this) draw_focus();
    }
  };
  // The "menu"
  class Choices : public Fl_Double_Window {
  public:
    Choices(int x, int y, int w, int h)
      : Fl_Double_Window(x, y, w, 200)
      , browser_(new Fl_Select_Browser(0, 0, w, 200))
      , selection_(0)
      , selected_(false)
      , selectCallback_(Fl_Widget::default_callback)
      , userData_(0)
    {
      end();
      browser_->callback(select_cb, this);
      box(FL_BORDER_BOX);
      labeltype(FL_NO_LABEL);
      clear_border();
    }

    // Call when a selection is made
    // Will be called as c(this, ud)
    void selectCallback(Fl_Callback* c, void* ud)
    { selectCallback_ = c; userData_ = ud; }

    // Access to callback
    Fl_Callback* selectCallback() const
    { return selectCallback_; }
    // Access to user data arg for callback
    void* selectUserData() const
    { return userData_; }

    // Call the select callback
    void doSelectCallback()
    { selectCallback_(this, userData_); }

    // Pop up at specified location in window
    void popup(Fl_Window* window, int x, int y)
    { position(window->x() + x, window->y() + y); show(); Fl::grab(this); }

    // Go Away
    void dismiss()
    { Fl::grab(0); hide(); }

    // Change size
    void size(int w, int h)
    {  Fl_Double_Window::size(w, h); browser_->size(w, h); }

    // Return most recent selection, or null if nothing selected
    char const* selection() const
    { return selection_; }

    // Test whether something is selected
    bool selected() const
    { return selected_; }

    // Add a menu choice
    void add(char const* const choice)
    { browser_->add(choice); }

  private:
    Fl_Select_Browser* browser_;

    // Internal state
    char const* selection_;
    bool selected_;

    // Callback for selection
    Fl_Callback* selectCallback_;
    void* userData_;

    // Make a selection
    void select()
    {
      int v = browser_->value();
      selected_ = v ? true : false;
      selection_ = selected_ ? browser_->text(v) : 0;
      dismiss();
      doSelectCallback();
    }

    static void select_cb(Fl_Widget* w, void* self)
    { Choices* ch = static_cast<Choices*>(self); ch->select(); }

    int handle(int event)
    {
      if(event == FL_PUSH && !Fl::event_inside(browser_)) {
          dismiss(); return 1;
      }
      return Fl_Double_Window::handle(event);
    }

    // Not Implemented. No Copies
    Choices(Choices const&);
    Choices& operator=(Choices const&);
  };

public:

  Choice(int x, int y, int w, int h, char const* label)
    : Fl_Group(x, y, w,  h, label)
  {
    Fl_Group::box(FL_DOWN_BOX);
    display_ = new Fl_Output(disp_x(), disp_y(), disp_w(), disp_h());
    btn_ = new DownButton(btn_x(), btn_y(), btn_w(), btn_h());
    end();
    align(FL_ALIGN_LEFT);
    btn_->box(FL_FLAT_BOX);
    display_->box(FL_FLAT_BOX);
    btn_->callback(btn_cb, this);
    Fl_Group* curr = Fl_Group::current();
    Fl_Group::current(0);
    choices_ = new Choices(window()->x() + x, window()->y() + y, w, h);
    Fl_Group::current(curr);
  }
  ~Choice()
  { delete choices_; }

  // "Menu" selection, may be null
  char const* value() const
  { return display_->value(); }

  // Add a "menu choice"
  void add(char const* const choice)
  { choices_->add(choice); }

  // Change menu size. Default is w x 300
  void menu_size(int w, int h)
  { choices_->size(w, h); }

  // Forwarding functions omittted for brevity.  Most
  // of the Fl_Widget interface should be forwarded to display_

private:
  Fl_Output* display_;
  DownButton* btn_;
  Choices* choices_;

  void value(char const* const txt)
  { display_->value(txt); }

  // Callbacks
  static void btn_cb(Fl_Widget*, void* self)
  { Choice* ch = static_cast<Choice*>(self); ch->btn(); }
  static void select_cb(Fl_Widget*, void* self)
  { Choice* ch = static_cast<Choice*>(self); ch->select(); }
  void select()
  {
    value(choices_->selected() ? choices_->selection() : 0);
    set_changed();
  }
  void btn()
  {
    choices_->selectCallback(select_cb, this);
    choices_->popup(window(), btn_->x(), y() + h());
  }

  // Custom resize behavior -- input stretches, menu button doesn't
  //   stolen from Fl_Input_Choice
  int disp_x() { return(x() + Fl::box_dx(box())); }
  int disp_y() { return(y() + Fl::box_dy(box())); }
  int disp_w() { return(w() - Fl::box_dw(box()) - 20); }
  int disp_h() { return(h() - Fl::box_dh(box())); }

  int btn_x() { return(x() + w() - 20 - Fl::box_dx(box())); }
  int btn_y() { return(y() + Fl::box_dy(box())); }
  int btn_w() { return(20); }
  int btn_h() { return(h() - Fl::box_dh(box())); }

  void resize(int x, int y, int w, int h)
  {
    Fl_Group::resize(x, y, w, h);
    display_->resize(disp_x(), disp_y(), disp_w(), disp_h());
    btn_->resize(btn_x(), btn_y(), btn_w(), btn_h());
  }
};

#endif

_______________________________________________
fltk mailing list
[email protected]
http://lists.easysw.com/mailman/listinfo/fltk

Reply via email to