Approach to Refactoring Tomboy for Platform Independence

Motivation

Tomboy is awesome. So awesome in fact, that people on Windows, Maemo, and other platforms are dying to run it. As a Mono program, it is inherently portable, yet we restrict ourselves to running on GNOME through dependencies on gnome-sharp, gconf-sharp, X11, etc.

My goal is to do some fairly simple refactoring to make it easier to bring Tomboy to platforms that already have Mono and gtk-sharp.

Disclaimer: I'm really trying to have a stable and supported Windows port, so the refactoring is biased towards that end.

General Approach

Assume it's only OK to depend on cross-platform Mono stuff and gtk-sharp. Any code that doesn't fit that definition is refactored. "Platform-specific" code gets moved into the Tomboy.Platform namespace. When a piece of code in the Tomboy namespace (eg Preferences) needs something platform-specific (eg a native IPreferencesClient), it calls a factory method in a class that resides in Tomboy.Platform. It is a general goal to encapsulate all #ifdef statements related to platform dependence in the factory class (eg #ifdef ENABLE_GCONF return new GConfPreferencesClient(); #else return new XmlPreferencesClient(); #endif).

Offending Code

Panel Applet

  • Depends directly on GNOME.

Refactoring

  • I haven't really looked at this too much yet, but I figure that in addition to offering a Gtk.StatusIcon for Tomboy, we could offer more platform-y things like the GNOME applet, and maybe a SWF.NotifyIcon for Windows, a Menu Bar thingy in OS X, etc. Anyway, from a refactoring standpoint, this needs to be broken out to its own conditionally-compiled file, since it depends directly on GNOME.

libtomboy

  • This is a C library that we should be looking to replace if we can anyway, just for the sake of maintenance. Individual pieces of it are addressed throughout this page.

Preferences (checked in to branch)

  • Depends directly on GConf, which is not available on all platforms.

Refactoring

  • Make Preferences class depend on a new interface called IPreferencesClient, which is designed to mimic the GConf.Client members.
  • Provide a GConfPreferencesClient that implements IPreferencesClient and forwards all requests to a GConf.Client instance.
  • Replace use of GConf.NotifyEventHandler with Tomboy.Platform.NotifyEventHandler. Unfortunately, this means that each T.P.NotifyEventHandler delegate must be wrapped by something that provides a GConf.NotifyEventHandler before calling GConf.Client.AddNotify.

  • Notes: This approach might be overkill. See alternatives.

Alternatives

  • Continue depending on GConf. On platforms where GConf does not exist, provide a mock implementation of GConf.Client (etc) that fits into that platform.
    • Pros: Minimal impact to current code-base. No need to wrap notification delegates or have a proxy to GConf.

    • Cons: Although a GConf mock is easy to maintain, it sets a bad design precedent for this refactoring, as other mocks are much more painful to maintain.

Keybindings (checked in to branch)

  • Depends on code in libtomboy that depends directly on X11. In the future, it might depend on GNOME configurable keybindings.

Refactoring

  • Rename TomboyGConfXKeybinder to TomboyPrefsKeybinder. Rename GConfXKeybinder to PrefsKeybinder.

  • Have PrefsKeybinder depend on a new interface called INativeKeybinder (instead of the XKeybinder class, which has dependencies on libtomboy).

  • INativeKeybinder is designed to mimic the old XKeybinder class, so make XKeybinder implement that interface and relocate it to Tomboy.Platform.
  • Notes: Works well. Excellent test for GConfPreferencesClient.

ForcedPresentWindow

  • Depends on libtomboy (specifically, tomboy_window_present_hardcore) to provide equivalent of PresentWithTime that was introduced in GTK+ 2.8. However, getting the current event time sometimes requires calling tomboy_keybinder_get_current_event_time, which is also in libtomboy.

Refactoring

  • Bump gtk-sharp dependency to at least 2.8.
  • Replace use of tomboy_window_present_hardcore with PresentWithTime method (now lots of libtomboy can be deleted).

  • (Maybe) let the Keybinder handle the case where you need to call tomboy_keybinder_get_current_event_time

Process Management (checked in to branch)

  • The Tomboy.Application class depends on libc (for process renaming), Mono.Unix.Native, and various pieces from GNOME.

Refactoring

  • Tomboy.Application retains same public interface, except for SetProcessName (remember, this is supposed to be a low-impact refactoring). It forwards the calls to an instance of a new interface called INativeApplication (which can do things like renaming the process internally if it makes sense).

  • INativeApplication takes care of starting/stopping Tomboy, etc. The GnomeApplication implementation does all the same stuff that Tomboy.Application used to do.

Remote Control

  • Depends directly on DBUS, which is not available on all platforms.

Refactoring

  • This is already #ifdef'd, so I'm in no rush to do anything. But we could easily provide other remote control interfaces through .NET remoting, or COM, or XML-RPC, or whatever makes sense.

Spell Checking

  • Depends on gtkspell. Last I checked, that didn't work correctly on Windows. Anyway, it's already #ifdef'd. (Wonder if gtkspell-sharp works?)

Other Plugins

  • Most other plugins with GNOME dependencies only make sense on GNOME (Evolution, Sticky Notes Import, Galago Presence, etc).

Considerations

Unfortunately, gtk-sharp-2.10 is not readily available on all platforms. (situation may be better now in 2008)

Apps/Tomboy/Win32/Refactoring (last edited 2013-08-09 00:14:59 by WilliamJonMcCann)