Saturday, November 04, 2006

Clever Design or Fancy Hack? You Decide.

When I started designing the Zero Debugger Engine a couple of years ago, I also slapped together a Gtk-based user interface plugin. The engine has an API for extending the debugger with plugins.

The purpose of my UI plugin was to drive the design of the API that the engine should expose. It was intended to be a throw away. There wasn't much design behind the UI, I thought of it more in terms of a test bed than even a prototype.

Yet as it happens so many many times in software (and in life), at some point I got stuck with it. Once I was ok with the engine API, I should've started working on the "real" UI plugin. Clean room design, right?

In the process of developing the engine and its C++ API which I dubbed the Zero Development Kit, it became evident that for some simple customizations C++ was overkill, and a scripted language API would be easier and more intuitive for most users. With this idea in mind, and with all the lessons learned from the "mock" UI, I started drafting a design of a Python-written UI.

But because Zero is my witching hours project, and most of the time I get distracted by annoying things such as having a day job, the Python UI had not seen the light of day until the last few months (see my previous post).

Meanwhile it was essential for the debugger to have an UI, to appeal to users as an interesting proposition... So the "throw away" UI project outlived its initial scope. But there was a problem:

The C++ written UI was based on gtk-- 1.2 (the ancestor of Gtkmm, a C++ wrapper for the Gtk library). I should've started with the newer technology, Gtkmm 2.x, but I was hoping to get someone interested in a business proposition, and their company was running on RedHat 7. But I also needed to show something that worked on the more modern Linux distros.

To cut to the chase, I decided to write some adaptation layers so that my initial code would compile both on gtk-- and Gtkmm. For classes such as Gtk::CTree, that have been completely replaced with Gtk::TreeView, Gtk::TreeModel and such, I just created a minimal implementation of CTree.

But there are also classes that have retained most of their interface in between the 1.2 and 2.x versions of the Gtkmm, give or take a couple of deprecated methods and some new ones. For example: the Gtk::Notebook class has a new family of overloaded methods, append_page. Concise and self-documenting, they allow one to write code such as:

void TabLayoutStrategy::add_registers_view(Widget& w)
{
rightBook_->append_page(w, "Registers");
}

To achieve the same effect in the old code I had to say:

void TabLayoutStrategy::add_registers_view(Widget& w)
{
rightBook_->pages().add(w);
rightBook_->pages().back()->set_tab_text("Registers");
}

Very verbose, and the simple intent to append a page with a given tab label text kind of gets lost...

The new append_page method can be implemented in the terms of the older API, like this:

struct Notebook_Adapt : public Gtk::Notebook
{
void append_page(Gtk::Widget& w, const Gtk::nstring& text)
{
this->add(w);
this->pages().back()->set_tab_text(text);
}
// ...
};


Now only if I could replace Gtk::Notebook with Notebook_Adapt in the gtkmm 1.2 compilation... and here's where the hack comes in: Notebook_Adapt does not add any member data to the Gtk::Notebook, just non-virtual methods. So a Notebook_Adapt class instance could be overlayed on existing Notebook instances, hmmm.
Enter the adapting pointer:

template<typename S, typename T >
class APtr
{
S* p_;

public:
explicit APtr(S* p) : p_(p) { }

T* operator->() const
{
BOOST_STATIC_ASSERT(sizeof(S) == sizeof(T));
return static_cast(p_);
}

S& operator*() { assert(p_); return *p_; }
const S& operator*() const { assert(p_); return *p_; }

operator const void*() const { return p_; }
};


Armed with this template, and a..., yuck, yeah, some precompiler help, I can now say:

#ifdef GTKMM_2
typedef Gtk::Notebook* NotebookPtr;
#else
typedef APtr NotebookPtr;
#endif
//
// ...
//
class TabLayoutStrategy : public LayoutStrategy
{
// ...
// ...
private:
// old:
// Gtk::Notebook* rightBook_;
// replaced with:
NotebookPtr rightBook_;
};


Inside of the class above, calls to:

rightBook_->append_page(widget, labelText);

now compile (i.e. are source-level compatible) with either gtk-- 1.2 or Gtkmm 2.x

Voila.

No comments: