conditions



Hi,

I'd like to propose the following extension to Gtkmm.

Currently, all non-trivial programs do something like this many times:

    entry->signal_changed ().connect (sigc::mem_fun (*this, &Windows::update_sensitivity));
    check_button->signal_toggled ().connect (sigc::mem_fun (*this, &Windows::update_sensitivity));

    // Set initial state.
    update_sensitivity ();  

    ...

    void
    update_sensitivity ()
    {
      button->set_sensitive (entry->get_text_length () > 0 && check_button->get_active ());
    }

With conditions it can be simplified to

    button->set_sensitive (entry->get_not_empty_condition () & check_button->get_active_condition ());

This gives two advantages: 1) less coding, and 2) less error probability
since you don't have to keep in sync state computation and change tracking,
it is done automatically.  (I.e. in the example above update_sensitivity()
function must be connected to appropriate signals and if the function itself
changes, the signal set might change too; with conditions this is done
automatically.)

Downsides:

- Gtkmm must support Gtk::Widget::set_sensitive (condition) methods (few
  methods, like set_sensitive(), set_visible() etc.) and, more importantly,
  standard conditions like Gtk::ToggleButton::get_actve_conditions() (more
  methods in various classes.)  This also involves a few fields for each
  widget class for an efficient implementation.

- Somewhat less efficient than standard approach, but this is not important
  in GUI.

I have a working example of this, which is attached.  Note that I couldn't
build methods into Gtkmm, so condition creation is more ugly in the
example.  Instead of the one line above it is

    controller = new SensivityController (button,
                                          (condition (new EntryNotEmptyCondition (entry))
                                           & condition (new ToggleButtonActiveCondition (check_button))));

In case you are interested, I can implement it in Gtkmm.

Paul


#include <sigc++/signal.h>
#include <gtkmm.h>


// sigc++ part.

class condition;


class condition_impl : public sigc::trackable
{
  friend  class condition; 


  bool				  state;
  int				  reference_counter;
  sigc::signal <void, condition>  state_changed;


  void
  reference ()
  {
    ++reference_counter;
  }

  bool
  unreference ()
  {
    if (!--reference_counter)
      delete this;
  }


protected:

  explicit
  condition_impl (bool initial_state)
    : state		(initial_state),
      reference_counter (0)
  { }

  virtual
  ~condition_impl ()
  { }

  void  set_state (bool new_state);
};


class condition
{
  condition_impl*  implementation;


public:

  explicit
  condition (condition_impl* implementation)
    : implementation (implementation)
  {
    implementation->reference ();
  }

  condition (const condition& other_condition)
    : implementation (other_condition.implementation)
  {
    implementation->reference ();
  }

  ~condition ()
  {
    implementation->unreference ();
  }


  operator bool () const
  {
    return implementation->state;
  }

  sigc::signal <void, condition>&
  signal_state_changed ()
  {
    return implementation->state_changed;
  }
};


class conjunction : public condition_impl
{
  const condition  first;
  const condition  second;


public:

  conjunction (condition first, condition second);


private:

  void  update (condition condition);
};


void
condition_impl::set_state (bool new_state)
{
  if (state != new_state)
    {
      state = new_state;
      state_changed (condition (this));
    }
}


conjunction::conjunction (condition first, condition second)
  : condition_impl (first && second),
    first	   (first),
    second	   (second)
{
  first.signal_state_changed  ().connect (sigc::mem_fun (*this, &conjunction::update));
  second.signal_state_changed ().connect (sigc::mem_fun (*this, &conjunction::update));
}


inline  condition
operator& (condition first, condition second)
{
  return condition (new conjunction (first, second));
}


void
conjunction::update (condition condition)
{
  set_state (first && second);
}


// Gtkmm part.

class EntryNotEmptyCondition : public condition_impl
{
  const Glib::RefPtr <Gtk::Entry>  entry;


public:

  explicit
  EntryNotEmptyCondition (Gtk::Entry* entry)
    : condition_impl (entry->get_text_length () > 0),
      entry	     (entry)
  {
    entry->signal_changed ().connect (sigc::mem_fun (*this, &EntryNotEmptyCondition::update_condition));
  }


  void
  update_condition ()
  {
    set_state (entry->get_text_length () > 0);
  }
};


class ToggleButtonActiveCondition : public condition_impl
{
  const Glib::RefPtr <Gtk::ToggleButton>  toggle_button;


public:

  explicit
  ToggleButtonActiveCondition (Gtk::ToggleButton* toggle_button)
    : condition_impl (toggle_button->get_active ()),
      toggle_button  (toggle_button)
  {
    toggle_button->signal_toggled ().connect (sigc::mem_fun (*this, &ToggleButtonActiveCondition::update_condition));
  }


  void
  update_condition ()
  {
    set_state (toggle_button->get_active ());
  }
};


class SensivityController
{
  Glib::RefPtr <Gtk::Widget>  widget;
  condition		      sensitivity;


public:

  SensivityController (Gtk::Widget* widget, condition sensitivity)
    : widget      (widget),
      sensitivity (sensitivity)
  {
    sensitivity.signal_state_changed ().connect
      (sigc::mem_fun (*this, &SensivityController::update_sensivity));
    update_sensivity (sensitivity);
  }


  void
  update_sensivity (condition condition)
  {
    widget->set_sensitive (condition);
  }
};


// Program part.

class Windows
{
  Gtk::Window*          window;
  Gtk::VBox*	        vbox;
  Gtk::Entry*           entry;
  Gtk::CheckButton*     check_button;
  Gtk::Button*          button;
  SensivityController*  controller;


public:

  Windows ()
  {
    window       = new Gtk::Window ();
    vbox	 = manage (new Gtk::VBox (false, 12));
    entry	 = manage (new Gtk::Entry ());
    check_button = manage (new Gtk::CheckButton ("Check me"));
    button       = manage (new Gtk::Button (Gtk::Stock::CLOSE));

    window->add (*vbox);
    vbox->add (*entry);
    vbox->add (*check_button);
    vbox->add (*button);

    window->set_title ("With Conditions");

    controller = new SensivityController (button,
					  (condition (new EntryNotEmptyCondition (entry))
					   & condition (new ToggleButtonActiveCondition (check_button))));

    window->show_all ();

    window       = new Gtk::Window ();
    vbox	 = manage (new Gtk::VBox (false, 12));
    entry	 = manage (new Gtk::Entry ());
    check_button = manage (new Gtk::CheckButton ("Check me"));
    button       = manage (new Gtk::Button (Gtk::Stock::CLOSE));

    window->add (*vbox);
    vbox->add (*entry);
    vbox->add (*check_button);
    vbox->add (*button);

    window->set_title ("Without Conditions");

    entry->signal_changed ().connect (sigc::mem_fun (*this, &Windows::update_sensitivity));
    check_button->signal_toggled ().connect (sigc::mem_fun (*this, &Windows::update_sensitivity));

    // Set initial state.
    update_sensitivity ();

    window->show_all ();
  }


private:

  void
  update_sensitivity ()
  {
    button->set_sensitive (entry->get_text_length () > 0 && check_button->get_active ());
  }
};


int
main (int argc, char** argv)
{
  Gtk::Main  main_loop (argc, argv);
  Windows    windows;

  main_loop.run ();

  return 0;
}


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]