Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Programming the Roma Widget Set (C# X11) - a zero dependency GUI application framework - Programming techniques

0.00/5 (No votes)
21 Oct 2015 1  
How to develop Linux/Unix (X11) GUI applications in C# efficiently without dependencies to GUI frameworks like GTK or KDE. Description of simple widgets.

Introduction

This article contains the description of some programming techniques concerning the Roma Widget Set (Xrw) - eiter used to extend the widget set or to apply it for application programming. It has been created because the whole topic grows beyond 50 print pages, and i decided to split it into three parts with and into four parts with . With i moved the complete API description to a separate HTML documentation (that is part of the Xrw project) and restructured all four articles to make reading more entertaining and exciting:

  • The origin one: Programming the Roma Widget Set (C# X11) - a zero dependency GUI application framework - Introduction. It contains a short explanation of the widget set's features. (Before  it was: ~ Basics. It contained general descriptions.) This atricle should always be the preferred starting point.
  • The first split-off: Programming the Roma Widget Set (C# X11) - a zero dependency GUI application framework - Widget set. Due to the constantly growing of 'boring' API documentation, the API documentation has been moved with  to a separate HTML documentation (that is part of the Xrw project) and the article has been focused on a briefly introduction of all widgets. (Before  it was: ~ Intrinsic widgets. It contained the API reference description of intrinsic widgets only.)
  • This second split-off: Programming the Roma Widget Set (C# X11) - a zero dependency GUI application framework - Programming techniques. Due to the constantly growing of 'boring' API documentation, the API documentation has been moved with  to a separate HTML documentation (that is part of the Xrw project) and this article has been focused on programming techniques. (Before  it was: ~ Simple widgets. It contained the API reference description of simple widgets only.)
  • The third split-off: Programming the Roma Widget Set (C# X11) - a zero dependency GUI application framework - MVVM/XAML support. Due to the constantly growing of 'boring' API documentation, the API documentation has been moved with  to a separate HTML documentation (that is part of the Xrw project) and this article has been focused on introducing the MVVM/XAML support of the Xrw. (Before  it was: ~ Composite widgets. It contains the API reference description of composite widgets only.)

All features, described later on, can be marked as:

  • available from version 0.1,
  • available from version 0.2,
  • available from version 0.3,
  • available from version 0.4,
  • available from version 0.5,
  • available from version 0.6,
  •  available from version 0.7,
  •  available from version 0.8,
  •  available from version 0.9 and
  • disabled with version 0.2,
  • disabled with version 0.3,
  • disabled with version 0.4,
  • disabled with version 0.5,
  • disabled with version 0.6,
  •  disabled with version 0.7,
  •  disabled with version 0.8,
  •  disabled with version 0.9.

Theme support

The framework has a generic theme support and these predefined themes:

  • XrwTheme.GeneralStyle.WinClassic (in and named XrwTheme.GeneralStyle.Win95; looks like Windows 95/98/Me/2K and a little bit like Motif - classic gray 3D),
  • XrwTheme.GeneralStyle.WinLuna (loocks similar to Windows XP / MS Office 2007),
  • XrwTheme.GeneralStyle.WinRoyale (loocks similar to Windows Vista/7 / MS Office 2010),
  • XrwTheme.GeneralStyle.WinMidori (loocks similar to Windows 8/8.1 / MS Office 2013) and
  • XrwTheme.GeneralStyle.Gtk2Clearlooks (looks like Gnome 2.30.0 Clearlooks - typical GTK 2).

The theme support covers colors, geometries and images.

To design an uncommon GUI for a specific application, almost all widget properties, that are influenced by a theme, can also be overruled by individual values. Or a new, alternatively XrwTheme.GeneralStyle can be created and applied.

Look & feel of XrwTheme.GeneralStyle.WinClassic demonstrated with the "File selection" dialog.

Look & feel of XrwTheme.GeneralStyle.Gtk2Clearlooks demonstrated with the "File selection" dialog.

See HTML cocumentation of XrwTheme class for details and X11Graphic class for supported stock items.

Font support

The default font of most Xfree86 installation is "-misc-fixed-medium-r-semicondensed--13-*-*-*-*-*-*". Hence the XrwTheme class predefines these default font specifications:

DefaultFontName        = "-misc-fixed-medium-r-semicondensed--13-*-*-*-*-*-*";
DefaultItalicFontName  = "-misc-fixed-medium-o-semicondensed--13-*-*-*-*-*-*";
DefaultBoldFontName    = "-misc-fixed-bold-r-semicondensed--13-*-*-*-*-*-*"; 

These fonts are almost guaranteed to be available at every X server but they are monospace bitmap fonts and do not look very smart.

The image shows a sample -misc-fixed- font output.

This version introduces convenience methods to change the predefined font specifications with user defined font specifications and ensures all widget's GC (graphics context) initialization with the DefaultFontName font specification.

  • TrySetDefaultFont set the DefaultFontName to the user defined font specification and returns true on success (font is available) or leaves the DefaultFontName unchanged and returns false otherwise.
  • TrySetDefaultItalicFont set the DefaultItalicFontName to the user defined font specification and returns true on success (font is available) or leaves the DefaultItalicFontName unchanged and returns false otherwise.
  • TrySetDefaultBoldFont set the DefaultBoldFontName to the user defined font specification and returns true on success (font is available) or leaves the DefaultBoldFontName unchanged and returns false otherwise.

It is recommended to set user defined font specifications after the creation of the application shell before the widget hierarchy is build up.

public static void Main ()
{
    XrwTheme.Style = XrwTheme.GeneralStyle.Gtk2Clearlooks;
    
    Point assignedPosition = new Point (0, 0);
    Size  assignedSize     = new Size  (353, 480);
    X11Window appWindow    = new X11Window(ref assignedPosition, ref assignedSize);
    
    // Set the preferred font before the widget hierarchy is build up,
    // but after application shell creation.
    XrwTheme.TrySetDefaultBoldFont (appWindow.Display, appWindow.GC,
        "-*-helvetica-bold-r-normal--12-*-*-*-*-*-*");
    XrwTheme.TrySetDefaultItalicFont (appWindow.Display, appWindow.GC,
        "-*-helvetica-medium-o-normal--12-*-*-*-*-*-*");
    XrwTheme.TrySetDefaultFont (appWindow.Display, appWindow.GC,
        "-*-helvetica-medium-r-normal--12-*-*-*-*-*-*");
    
    appWindow.Run ();
}

This version introduces -*-helvetica- fonts for the XrwTheme class predefinitions, because they look smarter than the -misc-fixed- fonts.

The image shows a sample -*-helvetica- font output.

Support for 16 Bit color model

The general approach to support 16 bit color model is to use an individual visual and colormap for the application. In this specific case it is based on a (virtually) 24 bit color model (hardware and X11 server have 24 bit color capabilities) while the X server is running in 16 bit color mode. There is no experience with a (physically) 16 bit color model (hardware and X11 server don't have 24 bit color capabilities).

The necessary code, especially XrwCore.InitializeApplicationShellWindow(), XrwCore.InitializeTransientShellWindow() and XrwCore.InitializeOverrideShellWindow() has been prepared already by . The following changes have been implemented with to provide support for this specific 16 bit color model case finally:

  • Reduction of the stock icon's color depth to 15 bit (not necessarily needed, but recommended).
  • Reduction of the application's icon color depth to 15 bit (required).
  • Correction of XrwApplicationFramework.SetWmShellIcon() (see "Fixed with " No. 3).
  • Correction of X11Graphic.CreateIndependentGraphicPixmap() (see "Fixed with " No. 4).

The /etc/X11/xorg.conf.d/50-screen.conf on my OPEN SUSE 11.3 Linux 32 bit EN has been modiefied like that, to test 16 bit color model support:

Section "Screen"
  Identifier "Default Screen"
  Device "Default Device"

  ## Doesn't help for radeon/radeonhd drivers; use magic in
  ## 50-device.conf instead
  Monitor "Default Monitor"

  # DefaultDepth 32 Chrashing!
  # DefaultDepth 24 # Running!
  DefaultDepth 16 # Running!
  # DefaultDepth 15 Chrashing!
  # DefaultDepth 8 # Running, but ugly!

  #############################################################################
  # Use one of these GRUB start options to repair a crashing X11 session:
  # - vga=ask: This option allows you to select the mode for the video adaptor.
  # - init=/bin/sh: Run the program /bin/sh (the shell) instead of init.
  #############################################################################
EndSection 

The ("X -configure" generated) /etc/X11/xorg.conf on my OPEN SUSE 12.3 Linux 64 bit DE has been modiefied like that, to test 16 bit color model support:

Section "Screen"
  ...

  # DefaultDepth 32 Chrashing!
  # DefaultDepth 24 # Running!
  DefaultDepth 16 # Running!
  # DefaultDepth 15 Running with Xfce only!
  # DefaultDepth 8 # Running, but ugly!
EndSection 

Internationalisation

Support for internationalized text output

There is a very good document about I18N. And Chapter 13.1 TWM -- usage of XFontSet instead of XFontStruct is the comprehensive guid for X11 application's internationalized text output implementation.

The first major change of the Roma Widget Set's internal architecture was to introduce the X11FontData structure, that supports XFontSet alternatively to XFontStruct and to provide X11Surface.TextBoundings() as single point for all text measurement as well as X11Surface.DrawString() as single point for all text output.

The second major change of the internal architecture was to provide XFontSet alternatively to XFontStruct during font allocation. Depending on the I18N capabilities of the X server and the C runtime, the XrwApplicationShell's UseFontSet property is set to true (or false) and XFontSet is used instead of XFontStruct (or isn't). Font allocation is realized by XrwCore's PrepareFont() method.

The story of the subsequent methods is quickly recounted: After a check of the prerequisits it is tested if FontData are to set to application's default fontset/font or current FontData is already up-to-date. Elsewise the requested fontset (if XrwApplicationShell's UseFontSet property is true) or font is loaded and assigned. To provide fast geometry calculation the fontsets/fonts maximum height, ascent and descent are provided with FontData as well.

XrwApplicationShell's font initialization:

/// <summary>Set a new default font (associate the indicated font with the graphics
/// context).</summary>
/// <param name="fontSpecification">The font specification to set as new default
/// font.<see cref="System.String"/></param>
/// <returns>True on success, or false otherwise.<see cref="System.Boolean"/></returns>
public virtual bool SetFont (string fontSpecification)
{    X11.X11FontData fontData = null;
     PrepareFont (fontSpecification, ref fontData);

    if (fontData == null)
        return false;
    _fontData = fontData;
    return true;
}

XrwCore's font preparation:

/// <summary>Prepare a new font for usage.</summary>
/// <param name="fontSpecification">The font specification to set as new font.<see cref="System.String"/></param>
/// <param name="fontData">The font data to set with the new font.<see cref="X11.X11FontData"/></param>
/// <returns>True on success, or false otherwise.<see cref="System.Boolean"/></returns>
protected bool PrepareFont (string fontSpecification, ref X11.X11FontData fontData)
{
    // Check prerequisits.
    if (_surface.Display == IntPtr.Zero)
    {
        SimpleLog.LogLine (TraceEventType.Error, CLASS_NAME +
            "::PrepareFont () Can not set a fontset/font to undefined display.");
        return false;
    }
    if (string.IsNullOrEmpty (fontSpecification))
    {
        SimpleLog.LogLine (TraceEventType.Error, CLASS_NAME +
            "::PrepareFont () Can not set a fontset/font with empty specification.");
        return false;
    }
    XrwApplicationShell appShell = ApplicationShell;
    if (appShell == null)
    {
        SimpleLog.LogLine (TraceEventType.Error, CLASS_NAME +
            "::PrepareFont () Can not set a fontset/font for a widget/gadget " +
            "that is not associated with an application shell.");
        return false;
    }
    
    // If fontset/font is equal to the current fontset/font, skip.
    if (fontData != null && fontData.FontSpecification == fontSpecification)
    {
        SimpleLog.LogLine (TraceEventType.Warning, CLASS_NAME +
            "::PrepareFont () Skip to reset fontset/font from '" + fontData.FontSpecification +
            "' to '" + fontSpecification + "'.");
        return true;
    }
    
    // If fontset/font is equal to the application's default fontset/font, assign a reference copy.
    if ((appShell.FontData != null ?
         appShell.FontData.FontSpecification == fontSpecification : false) == true)
    {
        fontData = appShell.FontData;
        return true;
    }
    
    // If fontset/font is not equal to the application's default fontset/font and
    // not equal to the current fontset/font, load and assign fontset/font.
    // Use fontset, if supported.
    return X11FontService.PrepareFont (fontSpecification, _surface.Display,
                                       appShell.UseFontset, ref fontData);
}

X11FontService's font preparation:

/// <summary>Prepare a font or fonteset for utilization with Xrw.</summary>
/// <param name="fontSpecification">The font specification, that identifies a font/fontset.<see cref="System.String"/></param>
/// <param name="x11display">The display pointer, that specifies the connection to the X server.<see cref="IntPtr"/></param>
/// <param name="useFontset">The flag defining whether to use a fontset or a single font.<see cref="System.Boolean"/></param>
/// <param name="fontData">The resulting font data on success, or null otherwise.<see cref="X11.X11FontData"/></param>
/// <returns>True on success, or false otherwise.<see cref="System.Boolean"/></returns>
public static bool PrepareFont (string fontSpecification, IntPtr x11display, bool useFontset, ref X11.X11FontData fontData)
{
    fontData = null;
    
    // Check font cache.
    foreach (KeyValuePair<FontDataKey, X11FontData> loadedFont in _loadedFonts)
    {
        if (loadedFont.Key.FontSpecification == fontSpecification &&
            loadedFont.Key.X11Display == x11display && loadedFont.Key.UseFontset)
        {
            fontData = loadedFont.Value;
            return true;
        }
    }
    
    FontDataKey key = new FontDataKey (fontSpecification, x11display, useFontset)
    ...

Start loading the requested fontset. If requested fontSpecification doesn't correspond to an existing fontset completely, make fontSpecification more fuzzy step by step, until an existing fontset corresponds. This starts with stretch, continued with weight and slant. In case of no success, load the font server's fallback fontset.

...
// Load fontset, if fontset isn't supported.
if (useFontset)
{
    IntPtr  missingCharsetList;
    TInt    missingCharsetCount;
    X11.XID fontsetResourceId = X11lib.XCreateFontSet (x11display, fontSpecification, out missingCharsetList,
                                                       out missingCharsetCount, IntPtr.Zero);
    // Check whether directly matching fontset has been loaded,
    // and - if not - load the most similar fontset (fuzzy).
    int fuzzyFactor = 0;
    string fuzzyFontSpecification = (fontsetResourceId == (X11.XID)0 ? fontSpecification : null);
    while (fontsetResourceId == (X11.XID)0 && fuzzyFactor < 3)
    {
        string lastFuzzyFontSpecification = fuzzyFontSpecification;
        if (fuzzyFactor == 0)
            fuzzyFontSpecification = X11FontData.ModifyFontSpecificationStretch (fuzzyFontSpecification, "*");
        if (fuzzyFactor == 1)
            fuzzyFontSpecification = X11FontData.ModifyFontSpecificationWieght  (fuzzyFontSpecification, "*");
        if (fuzzyFactor == 2)
            fuzzyFontSpecification = X11FontData.ModifyFontSpecificationSlant   (fuzzyFontSpecification, "*");

        fuzzyFactor++;
        // Safe time if no change has been made.
        if (lastFuzzyFontSpecification == fuzzyFontSpecification)
            continue;

        if (!string.IsNullOrEmpty(lastFuzzyFontSpecification) && lastFuzzyFontSpecification.Trim() != "")
        {
            fontsetResourceId = X11lib.XCreateFontSet (x11display, fuzzyFontSpecification,
                                                       out missingCharsetList,
                                                       out missingCharsetCount, IntPtr.Zero);
            if (fontsetResourceId != (X11.XID)0)
            {
                SimpleLog.LogLine (TraceEventType.Information, CLASS_NAME +
                                   "::PrepareFont () Fuzzy load fontset with specification '" +
                                   fuzzyFontSpecification + "' " +
                                   "instead of '" + fontSpecification + "' succeeded.");
            }
        }
    }

    // Check whether directly matching or most similar fontset has been loaded,
    // and - if not - load a fallback fontset.
    string extFontSpecification = null;
    if (fontsetResourceId == (X11.XID)0)
    {
        // Let the font server guess a fallback fontset.
        if (!string.IsNullOrEmpty(fontSpecification) && fontSpecification.Trim() != "" &&
            !fontSpecification.Trim().EndsWith (",*"))
        {
            extFontSpecification = fontSpecification + ",*";

            SimpleLog.LogLine (TraceEventType.Warning, CLASS_NAME +
                               "::PrepareFont () Can not load a fontset with specification '" +
                               fontSpecification + "'.");
            SimpleLog.LogLine (TraceEventType.Information, CLASS_NAME +
                               "::PrepareFont () Retry to load a fontset with specification '" +
                               extFontSpecification + "'.");

            fontsetResourceId = X11lib.XCreateFontSet (x11display, extFontSpecification,
                                                       out missingCharsetList,
                                                       out missingCharsetCount, IntPtr.Zero);
        }
        // The font specification already includs a joker to guess a fallback fontset.
        else
        {
            SimpleLog.LogLine (TraceEventType.Error, CLASS_NAME +
                               "::PrepareFont () Can not load a fontset with specification '" +
                               fontSpecification + "'.");

            // No success at all - even with a guess of a fallback fontset!
            return false;
        }
    }

    // Check whether matching fontset has been loaded.
    if (fontsetResourceId == (X11.XID)0)
    {
        SimpleLog.LogLine (TraceEventType.Error, CLASS_NAME +
                           "::PrepareFont () Can not load a fontset with specification '" +
                           extFontSpecification + "'.");

        // No success at all - even with a guess of a fallback fontset!
        return false;
    }
    ...

Report the finally loaded fontset and the missing character sets. Calculate some fontset attributes and create the fontData.

    ...
    if (!string.IsNullOrEmpty (extFontSpecification))
        SimpleLog.LogLine (TraceEventType.Information, CLASS_NAME +
                           "::PrepareFont () Successfully loaded best matching fontset for specification '" +
                           fontSpecification + "' " +
                           "using specification '" + extFontSpecification + "'.");
    else if (!string.IsNullOrEmpty (fuzzyFontSpecification))
        SimpleLog.LogLine (TraceEventType.Information, CLASS_NAME +
                           "::PrepareFont () Successfully loaded best matching fontset for specification '" +
                           fontSpecification + "' " +
                           "using specification '" + fuzzyFontSpecification + "'.");
    else
        SimpleLog.LogLine (TraceEventType.Information, CLASS_NAME +
                           "::PrepareFont () Successfully loaded best matching fontset for specification '" +
                           fontSpecification + "'.");

    for (int countCharSet = 0; countCharSet < (int)missingCharsetCount; countCharSet++)
    {
        IntPtr p = Marshal.ReadIntPtr (missingCharsetList, countCharSet * Marshal.SizeOf(typeof(IntPtr)));
        string s = Marshal.PtrToStringAuto (p);
        if (!string.IsNullOrEmpty (extFontSpecification))
            SimpleLog.LogLine (TraceEventType.Warning, CLASS_NAME +
                               "::PrepareFont () Fontset for specification '" +
                               extFontSpecification + "' is missing font for charset  '" + s + "'.");
        else if (!string.IsNullOrEmpty (fuzzyFontSpecification))
            SimpleLog.LogLine (TraceEventType.Warning, CLASS_NAME +
                               "::PrepareFont () Fontset for specification '" +
                                fuzzyFontSpecification + "' is missing font for charset  '" + s + "'.");
        else
            SimpleLog.LogLine (TraceEventType.Warning, CLASS_NAME +
                               "::PrepareFont () Fontset for specification '" +
                               fontSpecification + "' is missing font for charset  '" + s + "'.");
    }

    // Calculate maximum font height, ascent and descent.
    int                        ascent  = 0;
    int                        descent = 0;
    X11lib.XFontSetExtents     extents = X11lib.XExtentsOfFontSet (fontsetResourceId);

    X11lib.XFontStruct[]       fontStructArray;
    string[]                   fontNameArray;
    int                        maxFonts;

    maxFonts = X11lib.XFontsOfFontSet (fontsetResourceId, out fontStructArray, out fontNameArray);
    for (int countFonts = 0; countFonts < maxFonts; countFonts++)
    {
        if (ascent  < (int)fontStructArray[countFonts].ascent)
            ascent  = (int)fontStructArray[countFonts].ascent;
        if (descent < (int)fontStructArray[countFonts].descent)
            descent = (int)fontStructArray[countFonts].descent;
    }

    string finalFontSpecification = null;
    if (!string.IsNullOrEmpty (extFontSpecification))
        finalFontSpecification = extFontSpecification;
    else if (!string.IsNullOrEmpty (fuzzyFontSpecification))
        finalFontSpecification = fuzzyFontSpecification;
    else
        finalFontSpecification = fontSpecification;

    // Maximum font height, ascent and descent might be frequently used for calculation.
    fontData = X11FontData.NewFontSetData (finalFontSpecification, x11display, fontsetResourceId,
                                           (int)extents.max_logical_extent.height, ascent, descent);

    IntPtr gc = X11lib.XCreateGC (x11display, X11lib.XDefaultRootWindow (x11display), 0, IntPtr.Zero);
    if (gc != IntPtr.Zero)
    {
        fontData.SetTypicalCharWidth (AverageCharacterWidth(x11display, gc, fontData));
        X11lib.XFreeGC (x11display, gc);
    }
    _loadedFonts.Add (key, fontData);
    return true;
}
...

Start loading the requested font, if fontset isn't supported. If requested fontSpecification doesn't correspond to an existing font completely, make fontSpecification more fuzzy step by step, until an existing font corresponds. This starts with stretch, continued with weight and slant. In case of no success, load the font server's fallback font.

...
// Use font, if fontset isn't supported.
else // of (useFontset)
{
    // Load font and query font structure (to get maximum font height, ascent and descent).
    IntPtr fontStructure = X11lib.XLoadQueryFont (x11display, fontSpecification);

    // Check whether directly matching font has been loaded,
    // and - if not - load the most similar font (fuzzy).
    int fuzzyFactor = 0;
    string fuzzyFontSpecification = (fontStructure == IntPtr.Zero ? fontSpecification : null);
    while (fontStructure == IntPtr.Zero && fuzzyFactor < 3)
    {
        string lastFuzzyFontSpecification = fuzzyFontSpecification;
        if (fuzzyFactor == 0)
            fuzzyFontSpecification = X11FontData.ModifyFontSpecificationStretch (fuzzyFontSpecification, "*");
        if (fuzzyFactor == 1)
            fuzzyFontSpecification = X11FontData.ModifyFontSpecificationWieght  (fuzzyFontSpecification, "*");
        if (fuzzyFactor == 2)
            fuzzyFontSpecification = X11FontData.ModifyFontSpecificationSlant   (fuzzyFontSpecification, "*");

        fuzzyFactor++;
        // Safe time if no change has been made.
        if (lastFuzzyFontSpecification == fuzzyFontSpecification)
            continue;

        if (!string.IsNullOrEmpty(lastFuzzyFontSpecification) && lastFuzzyFontSpecification.Trim() != "")
        {
            fontStructure = X11lib.XLoadQueryFont (x11display, lastFuzzyFontSpecification);
            if (fontStructure != IntPtr.Zero)
            {
                SimpleLog.LogLine (TraceEventType.Information, CLASS_NAME +
                                   "::PrepareFont () Fuzzy load font with specification '" +
                                   fuzzyFontSpecification + "' " +
                                   "instead of '" + fontSpecification + "' succeeded.");
            }
        }
    }

    // Check whether directly matching or most similar font has been loaded,
    // and - if not - load a fallback font.
    string extFontSpecification = null;
    if (fontStructure != IntPtr.Zero)
    {
        // Let the font server guess a fallback fontset.
        if (!string.IsNullOrEmpty(fontSpecification) && fontSpecification.Trim() != "" &&
            !fontSpecification.Trim().EndsWith (",*"))
        {
            extFontSpecification = fontSpecification + ",*";

            SimpleLog.LogLine (TraceEventType.Warning, CLASS_NAME +
                               "::PrepareFont () Can not load a fontset with specification '" +
                               fontSpecification + "'.");
            SimpleLog.LogLine (TraceEventType.Information, CLASS_NAME +
                               "::PrepareFont () Retry to load a fontset with specification '" +
                               extFontSpecification + "'.");

            fontStructure = X11lib.XLoadQueryFont (x11display, extFontSpecification);
        }
        // The font specification already includs a joker to guess a fallback fontset.
        else
        {
            SimpleLog.LogLine (TraceEventType.Error, CLASS_NAME +
                               "::PrepareFont () Can not load a font with specification '" +
                               fontSpecification + "'.");

            // No success at all - even with a guess of a fallback font!
            return false;
        }
    }

    // Check whether matching font has been loaded.
    if (fontStructure == IntPtr.Zero)
    {
        SimpleLog.LogLine (TraceEventType.Error, CLASS_NAME +
                           "::PrepareFont () Can not load a font with specification '" +
                           fontSpecification + "'.");
        // No success at all - even with a guess of a fallback font!
        return false;
    }
    ...

Report the finally loaded font. Create the fontData.

        ...
        if (!string.IsNullOrEmpty (extFontSpecification))
            SimpleLog.LogLine (TraceEventType.Information, CLASS_NAME +
                               "::PrepareFont () Successfully loaded best matching font for specification '" +
                               fontSpecification + "' " +
                               "using specification '" + extFontSpecification + "'.");
        else if (!string.IsNullOrEmpty (fuzzyFontSpecification))
            SimpleLog.LogLine (TraceEventType.Information, CLASS_NAME +
                               "::PrepareFont () Successfully loaded best matching font for specification '" +
                               fontSpecification + "' " +
                               "using specification '" + fuzzyFontSpecification + "'.");
        else
            SimpleLog.LogLine (TraceEventType.Information, CLASS_NAME +
                               "::PrepareFont () Successfully loaded best matching font for specification '" +
                               fontSpecification + "'.");
        
        X11lib.XFontStruct    fs  = (X11lib.XFontStruct)Marshal.PtrToStructure (fontStructure,
                                                                                typeof(X11lib.XFontStruct));

        string finalFontSpecification = null;
        if (!string.IsNullOrEmpty (extFontSpecification))
            finalFontSpecification = extFontSpecification;
        else if (!string.IsNullOrEmpty (fuzzyFontSpecification))
            finalFontSpecification = fuzzyFontSpecification;
        else
            finalFontSpecification = fontSpecification;
        
        // Maximum font height, ascent and descent might be frequently used for calculation.
        fontData = X11FontData.NewSingleFontData (finalFontSpecification, x11display, fs.fid,
                                                  (int)fs.ascent + (int)fs.descent,
                                                  (int)fs.ascent, (int)fs.descent);
        
        IntPtr gc = X11lib.XCreateGC (x11display, X11lib.XDefaultRootWindow (x11display), 0, IntPtr.Zero);
        if (gc != IntPtr.Zero)
        {
            fontData.SetTypicalCharWidth (AverageCharacterWidth(x11display, gc, fontData));
            X11lib.XFreeGC (x11display, gc);
        }
        _loadedFonts.Add (key, fontData);
        return true;
    }
}

Anatomy of an application or dialog window

Application windows are based on an XrwApplicationShell, dialog windows are based on an XrwDialogShell. Both are derived from abstract XrwWmShell, and XrwWmShell is derived from XrwComposite - the base class for containers, that manage an arbitary number of child widgets. XrwWmShell provides interaction with the windows manager (move, resize, close, ... of a window). Since XrwComposite has no integrated layout management, it is recommended to assign one XrwBox, XrwGridForm, XrwUniformGrid or XrwDockPanel child to each XrwApplicationShell or XrwDialogShell instance, that manages the layout of the shell's grandchildren.

Since XrwWmShell derivatives set the XSetWindowAttributes attribute bit_gravity to NorthWestGravity and the window's background color to XrwTheme.GeneralBackgroundColor, the flickering effects during the redraw procedure (as concequence of ConfigureNotify events - to observe on window resize operations) of a shell's layout manager widget/gadget are already minimized: Most of the flickering effects come from the black (undrawn) shell background and the time delay between shell background cleaning and manager widget/gadget redrawing.

There are some more reasons for flickering effects - see Chapter Specific aspects of event processing.

The sample code shows how to use a XrwBox as manager gadget of a dialog shell.

public class XrwBitmapAndVectorFontSelectionDialog : XrwDialogShell
{
    // Define constants and member attributes.
    ...
 
    // Implement the constructor.
    public XrwBitmapAndVectorFontSelectionDialog (XrwApplicationShell parent,
                                                  ref Point assignedPosition,
                                                  ref Size assignedSize, string title)
        : base (parent, ref assignedPosition, ref assignedSize)
    {
        // Initialize member attributes.
        ...
 
        // Create shell's primary layout manager.
        XrwBox vboxMain = XrwBox.NewVBoxGadget (this);
        vboxMain.BorderWidth = XrwTheme.DlgShellPrimaryChildBorderWidth;
        vboxMain.BorderColor = _backgroundColorPixel;
        vboxMain.VertSpacing = XrwTheme.DlgShellPrimaryChildSpacing;
        AddChild (vboxMain);
 
        // Create shell's grandchildren.
        ...
    }
 
    // Implement the destructor, properties and methods.
    ...   
} 

A dialog window typically contains an action area with action buttons - e.g. Cancel and OK.

The next sample code shows how to create an action area, extracted from a dialog constructor.

{

    ...

    // ---- Begin "Action" area.

    // Create a HBox to group the action buttons.
    XrwBox hboxActionArea = XrwBox.NewHBoxGadget (vboxMain);
    hboxActionArea.BorderWidth = 2;
    hboxActionArea.ChildAlign = 1.0F;
    hboxActionArea.HorzSpacing = XrwTheme.DlgShellPrimaryChildSpacing;
    hboxActionArea.BorderColor = hboxActionArea.BackgroundColorDark;
    vboxMain.AddChild (hboxActionArea);

    // Create and register Cancel button.
    X11Graphic cancelGraphic = XrwTheme.GetGraphic (_surface.Display, _surface.ScreenNumber,
                                                    X11Graphic.StockIcon.Cancel16);
    XrwCommand cbCancel = XrwCommand.NewCommandWidget (hboxActionArea, "Cancel", cancelGraphic,
                                                       true, null, false);
    cbCancel.ExpandToMaxSiblingWidth = true;
    cbCancel.HorzTextAlign = 0.5F;
    cbCancel.Clicked += HandleCancelButtonClicked;
    hboxActionArea.AddChild (cbCancel);

    // Register "Cancel" button ation as input receiver.
    ICommand cmd0 = new RelayCommand (ProcessCancelButtonAction);
    Xrw.Utils.KeyGestureBinding kgb0 = new Xrw.Utils.KeyGestureBinding (cmd0,
        X11lib.XKeySym.XK_Escap,    System.Windows.Input.ModifierKeys.None);
    base._inputReceiver.Add (kgb0);
            
    // Create and register OK button.
    X11Graphic okGraphic = XrwTheme.GetGraphic (_surface.Display, _surface.ScreenNumber,
                                                X11Graphic.StockIcon.Ok16);
    XrwCommand cbOk = XrwCommand.NewCommandWidget (hboxActionArea, "OK", okGraphic,
                                                   true, null, false);
    cbOk.ExpandToMaxSiblingWidth = true;
    cbOk.HorzTextAlign = 0.5F;
    cbOk.Clicked += HandleOkButtonClicked;
    hboxActionArea.AddChild (cbOk);
                
    // Register "OK" button action as input receiver.
    ICommand cmd1 = new RelayCommand (ProcessOkButtonAction);
    Xrw.Utils.KeyGestureBinding kgb1 = new Xrw.Utils.KeyGestureBinding (cmd1,
        X11lib.XKeySym.XK_Return,   System.Windows.Input.ModifierKeys.None);
    Xrw.Utils.KeyGestureBinding kgb2 = new Xrw.Utils.KeyGestureBinding (cmd1,
        X11lib.XKeySym.XK_Num_Enter, System.Windows.Input.ModifierKeys.None);
    base._inputReceiver.Add (kgb1);
    base._inputReceiver.Add (kgb2);
            
    // ---- End "Action" area.

    ...

}

This is what it looks like:

The button callbacks are connected to the Clicked event.

/// <summary> Handle the ButtonPress event. </summary>
/// <param name="source"> The widget, the ButtonPress event is assigned
/// to. <see cref="XrwRectObj"/> </param>
void HandleOkButtonClicked (XrwRectObj source)
{
    if ((source is XrwCommand) && !(source as XrwCommand).Focused)
        return;
    
    ProcessOkButtonAction (null);
}

/// <summary>The default 'action' implementation for XrwCommand to execute if triggered.</summary>
/// <param name="o">A parameter to use for any purpose.</param>
private void ProcessOkButtonAction (object o)
{
    _result = System.Windows.MessageBoxResult.OK;
    this.DefaultClose ();
    this.OnEnd (_result);
}

/// <summary> Handle the ButtonPress event. </summary>
/// <param name="source"> The widget, the ButtonPress event is assigned
/// to. <see cref="XrwRectObj"/> </param>
void HandleCancelButtonClicked (XrwRectObj source)
{
    if ((source is XrwCommand) && !(source as XrwCommand).Focused)
        return;
    
    ProcessCancelButtonAction (null);
}

/// <summary>The default 'action' implementation for XrwCommand to execute if triggered.</summary>
/// <param name="o">A parameter to use for any purpose.</param>
private void ProcessCancelButtonAction (object o)
{
    _result = System.Windows.MessageBoxResult.Cancel;
    this.DefaultClose ();
    this.OnEnd (_result);
}

Since this version introduces key gesture binding, the ultimate Clicked event processing isn't part of the event handler, but swaped out to the corresponding action method. This provides the feasibility to use the action methos a second time. Namely as keyboard input receiver using the Xrw.Utils.KeyGestureBinding. Thereby the dialog can be closed with [Escape] key (that is equivalent to the dialog's Cancel button), [Enter] or [Return] key (that is equivalent to the dialog's OK button).

Dialogs are based on transient shells and often take over the (infinite) message loop processing from the application shell. To fully clean up such active dialogs by closing the application window, they must override the transient shell's  DefaultClose() method to stop it's (infinite) message loop processing. Here is a closer look on this: The XrwTransientShell implements these two message handler.

OnWmClose() is called only if shell closing is invoked via the window decoration's close button. It's only purpose is to provide event forwarding to registered delegates. Even if it is virtual, additional functionality should be implemented via delegates rather than via overwriting. This, besides, promotes code reuse.

DefaultClose() should always be called, no matter whether closing is invoked via the window decoration's close button, a widget/gadget inside the dialog (like the [OK] or [Cancel] button) or a keyboard shortcut (like [Return] or [Escape]). Derived classes can use the override to prepare return values and terminate their (infinite) message loop processing.

#region Event handler

/// <summary>Handle the ClientMessage event.</summary>
/// <param name="e">The event data.<see cref="XrwClientMessageEvent"/></param>
/// <remarks>Set XawClientMessageEvent.Result to nonzero to stop further event processing.</remarks>
internal virtual void OnWmClose (XrwClientMessageEvent e)
{
    WmShellCloseDelegate wmShellClose = WmShellClose;
    if (wmShellClose != null)
        wmShellClose (this, e);
}

/// <summary>Default processing for the Close event.</summary>
/// <returns>True to continue closing, false otherwise.</returns>
/// <remarks>This method should be overwritten, if transient shell calls the message loop
/// within Run(). Otherwise the message loop continues to run and the garbage collector
/// can't clean the application.</remarks>
public virtual bool DefaultClose ()
{
    if (_disposed == true)
        return true;
    
    // Attention: Avoid double dispose by calling Dispose() a second time within derived classes!

    this.ApplicationShell.RemoveTransientShell (this);
    Unrealize ();
    Dispose ();
    
    // Don't disconnect from X server - application shell is still running.
    
    return true;
}

#endregion

A XrwTransientShell derived class, e. g. XrwFileSelectionDialog, should override the DefaultClose() to prepare return values and terminate it's (infinite) message loop processing. And it should register and implement HandleDialogClose() to provide a consistent behaviour on closing, no matter whether closing is invoked via the window decoration's close button, via a dialog's [Cancel] button or the [Escape] key shortcut.

WmShellClose += HandleDialogClose;
#region Overwritten methods (XrwTransientShell)

/// <summary>Default processing for the Close event.</summary>
/// <param name="source">The widget, the Close event is assigned to.<see cref="XrwTransientShell"/></param>
/// <returns>True to continue closing, false otherwise.</returns>
/// <remarks>This method should be overwritten, if transient shell calls the message loop.</remarks>
public override bool DefaultClose ()
{
    // FIRST: Ensure the message loop will be released.
    _result = System.Windows.MessageBoxResult.Cancel;
    
    // SECOND: Standard transient shell behaviour is appropriate.
    return base.DefaultClose ();
}

#endregion Overwritten methods (XrwTransientShell)

#region Event handler

/// <summary> Application specific processing of the ApplicationClose event. </summary>
/// <param name="source"> The widget, the WmShellClose event is assigned to. <see cref="XrwRectObj"/> </param>
/// <param name="e"> The event data. <see cref="XawClientMessageEvent"/> </param>
/// <remarks> Set XawClientMessageEvent.Result to nonzero to stop further event processing. </remarks>
void HandleDialogClose (XrwRectObj source, XrwClientMessageEvent e)
{
    this.DefaultClose ();

    // Stop event processing here!
    e.Result = 1;
    
    this.OnEnd (_result);
}

...

Usage of popup menus

Popup menus can be created very easily. These are the steps:

  • Create a popup menu shell XrwSimpleMenuShell.
  • Add menu entries of XrwSme class to the menu shell and register the callbacks to the menu entries.
  • Force shell's geometry management.
  • Create a menu button XrmMenuButton and add it to the parent composite.
  • Register the menu shell to the menu button.

There is no additional code to process pop up, layout, pop down or selection required.

The image shows a simple XrwMenuButton with left and right (transparent multicolor) bitmap - including it's poped up menu, based on a XrwDialogShell containing two XrwSme with left and right (transparent multicolor) bitmap.

The sample code shows how to create the simple pop up menu, illustrated by the previous image.

XrwSimpleMenu _fileMenuShell = null;

...

// ---- Create popup menu.
Point origin   = new Point (20, 20);
Size  initSize = new Size  (-1, -1);
_fileMenuShell = new XrwSimpleMenu (this, ref origin, ref initSize);

// ---- Add menu entries.
X11Graphic menuEntryGraphicA = XrwTheme.GetGraphic ( _display,_screenNumber,
    X11Graphic.StockIcon.Information16);
X11Graphic menuEntryGraphicB = XrwTheme.GetGraphic ( _display,_screenNumber,
    X11Graphic.StockIcon.Question16);
XrwSme menuEntry1 = XrwSme.NewSmeGadget (_fileMenuShell, "File menu entry 1",
    menuEntryGraphicA, true, menuEntryGraphicB, true);
menuEntry1.ButtonRelease += HandleMenuEntry1ButtonRelease;
_fileMenuShell.AddChild (menuEntry1);
XrwSme menuEntry2 = XrwSme.NewSmeGadget (_fileMenuShell, "File menu entry 2",
    menuEntryGraphicA, true, menuEntryGraphicB, true);
menuEntry2.ButtonRelease += HandleMenuEntry2ButtonRelease;
_fileMenuShell.AddChild (menuEntry2);

// ---- Beautify popup menu.
_fileMenuShell.CalculateChildLayout ();
_fileMenuShell.SetFixedWidth  (_fileMenuShell.AssignedSize.Width);
_fileMenuShell.SetFixedHeight (_fileMenuShell.AssignedSize.Height);

// ---- Create menu button.
X11Graphic cbw0GraphicA = XrwTheme.GetGraphic ( _display,_screenNumber,
    X11Graphic.StockIcon.FileGeneric16);
X11Graphic cbw0GraphicB = XrwTheme.GetGraphic ( _display,_screenNumber,
    X11Graphic.StockIcon.FolderClose16);
XrwMenuButton commandFileMenu = XrwMenuButton.NewMenuButtonWidget
    (hboxFileRibbon, "File", cbw0GraphicA, true, cbw0GraphicB, true);
commandFileMenu.FrameType = XrwTheme.StaticFrameType;
commandFileMenu.FrameWidth = XrwTheme.StaticFrameWidth;
commandFileMenu.ExpandToAvailableHeight = true;

// ---- Register menu to menu button and insert menu button into parent widget.
commandFileMenu.Menu = _fileMenuShell;
hboxFileRibbon.AddChild (commandFileMenu); 

Closing an application Window

To provide a convenient API to the application developer, some things have to be prepared framework internally:

1. To clean up the XrwApplicationShell, a primary shell's close delegate is registered:

// Register close event.
this.WmShellClose += HandleApplicationShellCloseDefault;

...

/// <summary> Application specific processing of the WmShellClose event. </summary>
/// <param name="source"> The widget, the ApplicationClose event is
/// assigned to. <see cref="XrwRectObj"/> </param>
/// <param name="e"> The event data. <see cref="XawClientMessageEvent"/> </param>
/// <remarks> Set XawClientMessageEvent.
/// Set result to nonzero to stop further event processing. </remarks>
void HandleApplicationShellCloseDefault (XrwRectObj source, XrwClientMessageEvent e)
{
     ...

     Dispose ();

     // Disconnect from X server.
     X11lib.XCloseDisplay (_surface.Display);
     _surface.SetDisplay (IntPtr.Zero);

     e.Result = 0;
}

2. Since the OnClose delegate is implemented to invoke registered handler in reverse order, the clean up process always processes from derived classes back to the base class - and the base class handler must be the last besause it disconnects from X11 server.

/// <summary> Handle the ClientMessage 'Close' event. </summary>
/// <param name="e"> The event data. <see cref="XawClientMessageEvent"/> </param>
/// <remarks> Set XawClientMessageEvent result to nonzero to stop further event processing. </remarks>
public void OnClose (XrwClientMessageEvent e)
{
    // Call the close delegates in reverse order!
    object[]    param = new object[] {this, e};
    Delegate[]    delegates = WmShellClose.GetInvocationList();
    for (int i=delegates.Length-1;i>=0;i--)
        delegates[i].DynamicInvoke (param);
        
    //WmShellCloseDelegate wmShellClose = WmShellClose;
    //if (wmShellClose != null)
    //    wmShellClose (this, e);
}

This enables resource deallocation without memory leaks.

Usage of standard dialogs

Curently these standard dialogs are available:

  • XrwMessageBox for a notification only dialog (no input except the choice between OK and Cancel).
  • XrwFileSelectionDialog for single file selection.
  • XrwBitmapAndVectorFontSelectionDialog to select a font using all X11 font information.

This is an image of the XrwMessageBox with plain text and with markup text, introduced with .

   

This is an image of the XrwFileSelectionDialog.

This is an image of the XrwBitmapAndVectorFontSelectionDialog.
(This dialog is suitable for non-internationalized text output using XLoadFont and XDrawString or XDrawString16 but not for I18N text output using font set.)

Now these additional standard dialogs are available:

These are images of the XrwColorSelectionDialog, using 16 predefined colors in 8 columns and 2 rows or 2 columns and 8 rows with color names.

Now this additional standard dialog is available:

These are images of the XrwColorChooseDialog, using one notebook page for standard colors and one notebook page for custom colors.

Now this additional standard dialog is available:

This is an image of the XrwFontSelectionDialog.

(This dialog is suitable for internationalized text output using font set, XCreateFontSet and XwcDrawString.)

The creation of XrwFileSelectionDialog, XrwBitmapAndVectorFontSelectionDialog, XrwColorSelectionDialog, XrwColorChoseDialog and XrwFontSelectionDialog is straight forward.

The sample code shows how to use the XrwFileSelectionDialog.

/// <summary> Handle the Clicked event. </summary>
/// <param name="source"> The widget, the Clicked event is assigned to. <see cref="XrwRectObj"/> </param>
void HandleFileSelectionDialogButtonClicked (XrwRectObj source)
{
    XrwFileSelectionDialog fileDialog = XrwFileSelectionDialog.
        NewFileSelectionDialog (this, "Mono Develop - File selection", Environment.CurrentDirectory);
    fileDialog.SetMinimumSize (fileDialog.AssignedSize);

    // This call has been required before version 0.6.
    // Starting with version 0.6 the dialog's constructor sets the shell icon.
    // ApplicationFramework.SetWmShellIcon (fileDialog, AppIconFilePath);
 
    // This call has been required before version 0.6.
    // Starting with version 0.6 the dialog's Run() method registers the transient shell to the
    // application by this.AddTransientShell (fileDialog);
    XrwDialogShell.DialogResult result = fileDialog.Run ();

    if (result == XrwDialogShell.Result.OK)
    {
        if (XrwApplicationSettings.VERBOSE_OUTPUT_TO_CONSOLE)
            Console.WriteLine ("VERBOSE: " + CLASS_NAME +
                               "::HandleFileSelectionDialogButtonClicked() " +
                               "File dialog closed with: OK, File selected is: " + fileDialog.SelectedFile);
        ApplicationFramework.WriteStatus ("File selection dialog closed with: OK");
    }
    else
    {
        if (XrwApplicationSettings.VERBOSE_OUTPUT_TO_CONSOLE)
            Console.WriteLine ("VERBOSE: " + CLASS_NAME +
                               "::HandleFileSelectionDialogButtonClicked() " +
                               "File dialog closed with: Cancel");
        ApplicationFramework.WriteStatus ("File selection dialog closed with: Cancel");
    }
}

The next sample code shows how to use the XrwBitmapAndVectorFontSelectionDialog.

/// <summary> Handle the Clicked event. </summary>
/// <param name="source"> The widget, the Clicked event is assigned to. <see cref="XrwRectObj"/> </param>
void HandleFontDialogButtonClicked (XrwRectObj source)
{
    XrwBitmapAndVectorFontSelectionDialog fontDialog = XrwBitmapAndVectorFontSelectionDialog.
        NewBitmapAndVectorFontSelectionDialog (this, "Mono Develop - Font selection");
    fontDialog.SetMinimumSize (fontDialog.AssignedSize);
 
    // This call has been required before version 0.6.
    // Starting with version 0.6 the dialog's constructor sets the shell icon.
    // ApplicationFramework.SetWmShellIcon (fontDialog, APPICON_FILEPATH);
 
    // This call has been required before version 0.6.
    // Starting with version 0.6 the dialog's Run() method registers the transient shell to the
    // application by this.AddTransientShell (fontDialog);
 
    XrwDialogShell.DialogResult result = fontDialog.Run ();
 
    if (result == XrwDialogShell.Result.OK)
    {
        if (XrwApplicationSettings.VERBOSE_OUTPUT_TO_CONSOLE)
            Console.WriteLine ("VERBOSE: " + CLASS_NAME + "::HandleFontDialogButtonClicked() " +
                               "Font dialog closed with: OK");
        ApplicationFramework.WriteStatus ("Font selection dialog closed with: OK");
    }
    else
    {
        if (XrwApplicationSettings.VERBOSE_OUTPUT_TO_CONSOLE)
            Console.WriteLine ("VERBOSE: " + CLASS_NAME + "::HandleFontDialogButtonClicked() " +
                               "Font dialog closed with: Cancel");
        ApplicationFramework.WriteStatus ("Font selection dialog closed with: Cancel");
    }
}

The next sample code shows how to use the XrwColorSelectionDialog.

/// <summary> Handle the Clicked event. </summary>
/// <param name="source"> The widget, the Clicked event is assigned to. <see cref="XrwRectObj"/> </param>
void HandleColorSelectionDialogButtonClicked (XrwRectObj source)
{
    XrwColorSelectionDialog colorDialog = XrwColorSelectionDialog.NewColorSelectionDialog8x2 (this,
                                              "Mono Develop - Color selection", 0x00ffffff);
    colorDialog.SetMinimumSize (colorDialog.AssignedSize); 
    // This call has been required before version 0.6.
    // Starting with version 0.6 the dialog's constructor sets the shell icon.
    // ApplicationFramework.SetWmShellIcon (colorDialog, APPICON_FILEPATH);    
 
    // This call has been required before version 0.6.
    // Starting with version 0.6 the dialog's Run() method registers the transient shell to the
    // application by this.AddTransientShell (colorDialog); 
 
    XrwDialogShell.DialogResult result = colorDialog.Run ();    
    if (result == XrwDialogShell.Result.OK)
    {
        SimpleLog.LogLine (TraceEventType.Verbose, CLASS_NAME +
            "::HandleColorSelectionDialogButtonClicked () Color dialog closed with: OK, " +
            "Color selected is: #{0:X000000}", colorDialog.SelectedColor);
        ApplicationFramework.WriteStatus ("Color selection dialog closed with: OK");
    }
    else
    {
        SimpleLog.LogLine (TraceEventType.Verbose, CLASS_NAME +
            "::HandleColorSelectionDialogButtonClicked () Color dialog closed with: Cancel");
        ApplicationFramework.WriteStatus ("Color selection dialog closed with: Cancel");
    }
}

The next sample code shows how to use the XrwColorChooseDialog.

/// Handle the ButtonRelease event.
/// <param name="source" />The widget, the ButtonRelease event is assigned to. <see cref="XrwRectObj"/>
/// <param name="e" />The event data. <see cref="XawButtonEvent"/>
/// <remarks>Set XawButtonEvent.result to nonzero to stop further event processing. </remarks>
void HandleColorChooseDialogButtonRelease (XrwRectObj source, XrwButtonEvent e)
{
    XrwColorChooseDialog colorDialog = XrwColorChooseDialog.NewColorCooseDialog (this,
        "Mono Develop - Color choose", 0x00ffffff);
    colorDialog.SetMinimumSize (colorDialog.AssignedSize); 
    // This call has been required before version 0.6.
    // Starting with version 0.6 the dialog's constructor sets the shell icon.
    // ApplicationFramework.SetWmShellIcon (colorDialog, APPICON_FILEPATH);    
 
    // This call has been required before version 0.6.
    // Starting with version 0.6 the dialog's Run() method registers the transient shell to the
    // application by this.AddTransientShell (colorDialog); 
 
    XrwDialogShell.DialogResult result = colorDialog.Run ();    
    if (result == XrwDialogShell.Result.OK)
    {
        SimpleLog.LogLine (TraceEventType.Verbose, CLASS_NAME +
            "::HandleColorChooseDialogButtonRelease () Color dialog closed with: OK, " +
            "Color selected is: #{0:X000000}", colorDialog.SelectedColor);
        ApplicationFramework.WriteStatus ("Color selection dialog closed with: OK");
    }
    else
    {
        SimpleLog.LogLine (TraceEventType.Verbose, CLASS_NAME +
            "::HandleColorChooseDialogButtonRelease () Color dialog closed with: Cancel");
        ApplicationFramework.WriteStatus ("Color selection dialog closed with: Cancel");
    }
}

The next sample code shows how to use the XrwFontSelectionDialog.

/// <summary> Handle the ButtonRelease event. </summary>
/// <param name="source"> The widget, the ButtonRelease event is assigned to.<see cref="XrwRectObj"/> </param>
/// <param name="e"> The event data. <see cref="XawButtonEvent"/> </param>
/// <remarks> Set XawButtonEvent. Set result to nonzero to stop further event processing. </remarks>
void HandleVectorFontDialogButtonRelease (XrwRectObj source, XrwButtonEvent e)
{
    XrwFontSelectionDialog fontDialog =
        XrwFontSelectionDialog.NewFontSelectionDialog (this, "Mono Develop - GTK/Windows " +
                                                             "compatible font selection");
    fontDialog.Font = new Xrw.FontInfo ("Adobe Helvetica", 14, System.Drawing.FontStyleEx.Italic);
        
    System.Windows.MessageBoxResult result = fontDialog.Run ();
    
    if (result == System.Windows.MessageBoxResult.OK)
    {
        SimpleLog.LogLine (TraceEventType.Verbose, CLASS_NAME +
                        "::HandleVectorFontDialogButtonRelease () Font dialog closed with: OK");
        ApplicationFramework.WriteStatus ("Font selection dialog closed with: OK");
        
        // Get the font specification, that alows to load a specific font from X11 font server.
        // string fs = fontDialog.FontSpecification;
        // Get the font info to determine Strikeout and Underline.
        Xrw.FontInfo font = fontDialog.Font;
        if (font == null)
            return;
        
    }
    else
    {
        SimpleLog.LogLine (TraceEventType.Verbose, CLASS_NAME +
                           "::HandleVectorFontDialogButtonRelease () " +
                           "Font dialog closed with: Cancel");
        ApplicationFramework.WriteStatus ("Font selection dialog closed with: Cancel");
    }

    e.Result = 1;
}

After the dialog instantiation this call is recommended:

  • dialog.SetMinimumSize(dialog.AssignedSize) to prevent size underflow.

All dialogs calculate their initial size in a way that it is the smallest size where all controls are displayed properly. To set the initial size as the minimum size prevents the dialog from resizing to an unsuitable display.

Previous versions of Xrw recommended to call these methods after the dialog instantiation as well:

  • ApplicationFramework.SetWmShellIcon(dialog, APPICON_FILEPATH) to set the shell icon.
  • this.AddTransientShell(dialog) to register the dialog to the application's transient shell list.

But these calls are not necessary any longer, because they are called framework internally now.

XrwFileSelectionDialog, XrwBitmapAndVectorFontSelectionDialog, XrwColorSelectionDialog and XrwFontSelectionDialog are implemented as application modal dialogs. Hence the call XrwDialogShell.DialogResult result = dialog.Run() waits for the dialog end and the result can be evaluated subsequently.

Now all standard dialogs support key gesture binding. Thereby the dialogs can be closed with [Escape] key (if dialog contains an Cancel button), [Enter] or [Return] key (that are equivalent to the dialog's OK button).

Specific aspects of event processing

Redraw performance issues and/or flickering

During the window resize process a plenty of ConfigureNotify events are emitted by the windows manager. The application recalculates the layout of it's child widgets for every ConfigureNotify event. New sizes in turn lead to corresponding Expose events. Due to the asynchronous drawing model of X11 and - especially for complex layouts due to the layout recalculation time - redraw performance issues and/or flickering are the result.

To avoid this, Athena and Motif provide event compression flags with their wigets (compress_motion, compress_exposure, compress_enterleave). But this is no satisfying solution, because the events to compress must already be queued and immediate sequential. My tests with this approach didn't show a significant improvement.

The sample code shows a snippet of the XrwApplicationShell's DoEvent() method, that illustrates this event compression approach.

if (xevent.type == XEventName.ConfigureNotify)
{
    // ***************************************************************************************
    // Why does the GUI flicker and is there a way out?
    // http://fixunix.com/xwindows/556305-resizing-motif-xaw-xlib-apps-vs-resizing-gtk-qt-
    // apps-how-tospeed-up-xlib-app.html
    // ***************************************************************************************

    // This compression approach requires, that compressible events are ALREADY in the queue.
    // This assumption is NOT very realistic!!!
    X11EventHelper.Matches = 0;
    X11lib.XCheckIfEvent (_display, ref X11EventHelper.Event,
                          X11EventHelper.CountConfigureMatchesProcPtr,
                          xevent.ConfigureEvent.window);
    if (X11EventHelper.Matches > 0)
    {
        Console.WriteLine ("INFORMATION: " + CLASS_NAME + "::DoEvent () // CONFIGURE " +
                           "found subsequent configure events for window " +
                           xevent.ConfigureEvent.window.ToString("x") + " and skip this event.");
        return true;
    }

    ...
}
else if (xevent.type == XEventName.Expose)
{
    if (xevent.ExposeEvent.count > 0)
        return true;
    
    // ***************************************************************************************
    // Why does the GUI flicker and is there a way out?
    // http://fixunix.com/xwindows/556305-resizing-motif-xaw-xlib-apps-vs-resizing-gtk-qt-
    // apps-how-tospeed-up-xlib-app.html
    // ***************************************************************************************

    // This compression approach requires, that compressible events are ALREADY in the queue.
    // This is assumption NOT very realistic!!!
    X11EventHelper.Matches = 0;
    X11lib.XCheckIfEvent (_display, ref X11EventHelper.Event,
                          X11EventHelper.CountExposeMatchesProcPtr, xevent.ExposeEvent.window);
    if (X11EventHelper.Matches > 0)
    {
        Console.WriteLine ("INFORMATION: " + CLASS_NAME + "::DoEvent () // EXPOSE " +
                           "found subsequent expose events for window " +
                           xevent.ExposeEvent.window.ToString("x") + " and skip this event.");
        return true;
    }

    ...
}

The best way i've found to avoid redraw performance issues and/or flickering is to suspend every ConfigureNotify event for some milli-seconds and to compress all ConfigureNotify evens emmitted during this suspension. This causes a redrawing latency equal to the suspension interval, but the entire impression during a resizing process is much smoother.

The sample code shows a snippet of the XrwApplicationShell's DoEvent() method, that illustrates the event suspension approach.

/// <summary>Define the suspension interval in milli-seconds.</summary>
private ulong           _compressConfigureBySuspend = 350;
    
/// <summary>Remember the point in time the last configuration took place.</summary>
private ulong           _lastConfigureShell = 0;
    
/// <summary>Keep the latest configuration event to process it after suspension.</summary>
private XConfigureEvent _lastConfigureEvent = new XConfigureEvent();

...


/// <summary> Process the topmost event and remove it from the event queue. </summary>
/// <returns> True if event processing must contionue, false otherwise. </returns>
public bool DoEvent()
{
    // Prevent event processing *** after dispose *** but *** before destruction ***.
    if (_display == IntPtr.Zero)
        return false;
    
    XEvent xevent = new XEvent ();
    
    // Ensure all events are queued.
    X11lib.XFlush (_display);
    
    // Check for suspended ConfigureEvent and process it after the suspension interval.
    if (X11lib.XQLength (_display) == 0 && _compressConfigureBySuspend > 0)
    {
        DateTime dt        = DateTime.Now;
        ulong timeStamp    = (ulong)(dt.Millisecond + dt.Second * 1000 + dt.Minute * 60000 +
                             dt.Hour * 3600000) + (ulong)dt.Day * (ulong)86400000;
        
        if (timeStamp - _lastConfigureShell > _compressConfigureBySuspend &&
            _lastConfigureEvent.window != IntPtr.Zero)
        {
            XrwConfigureEvent e = new XrwConfigureEvent (ref _lastConfigureEvent);
            OnConfigure (e);
            _lastConfigureEvent.window = IntPtr.Zero;
            _lastConfigureShell = timeStamp;
        }
        return true;
    }


    ...
}

The _compressConfigureBySuspend interval can be adjusted to a certain use case to find the optimum between latency and flickering. A value of 0 completely suppresses event suspension.

The only drawback i've found is, that XrwPaned doen't benefit from this approach.

Undoubtedly the best approach to avoid redraw performance issues and/or flickering is to use double buffering. This approach is saved for later use, because it breaks the Xrw zero dependency promise.

Key gesture binding

With this version key gesture binding is supported. It can be used to register global keyboard shortcuts to any XrwShell (typically application shell or dialog shell) targeting any GUI element. This technique can e. g. be used to improve the Accessibility of an application (or dialog). Typically the preferred targets of a key gesture binding are menus and ribbons. The sample code shows how to bind key gestures to a XrwRibbon and select a specific XrwRibbonTab.

...

    // -- Start connection of application global key gesture bindings.

    System.Windows.Input.ICommand      activateRibbonTab0_Action  = new RelayCommand
        (ActivateRibbonTab0_Action);
    Xrw.Utils.KeyGestureBinding        activateRibbonTab0_Gesture = new Xrw.Utils.KeyGestureBinding
        (activateRibbonTab0_Action, null, null, X11lib.XKeySym.XK_D,
         System.Windows.Input.ModifierKeys.Alt);
    ApplicationShell.InputReceiver.Add (activateRibbonTab0_Gesture);

    System.Windows.Input.ICommand      activateRibbonTab1_Action  = new RelayCommand
        (ActivateRibbonTab1_Action);
    Xrw.Utils.KeyGestureBinding        activateRibbonTab1_Gesture = new Xrw.Utils.KeyGestureBinding
        (activateRibbonTab1_Action, null, null, X11lib.XKeySym.XK_R,
         System.Windows.Input.ModifierKeys.Alt);
    ApplicationShell.InputReceiver.Add (activateRibbonTab1_Gesture);

    System.Windows.Input.ICommand      activateRibbonTab2_Action  = new RelayCommand
        (ActivateRibbonTab2_Action);
    Xrw.Utils.KeyGestureBinding        activateRibbonTab2_Gesture = new Xrw.Utils.KeyGestureBinding
        (activateRibbonTab2_Action, null, null, X11lib.XKeySym.XK_S,
     System.Windows.Input.ModifierKeys.Alt);
    ApplicationShell.InputReceiver.Add (activateRibbonTab2_Gesture);

    // -- End connection of application global key gesture bindings.

...

/// <summary>Activate ribbon tab 0 action to execute on kex gesture binding.</summary>
/// <param name="parameter">Data used by the command. If the command does not require data
/// to be passed, this object can be set to null.<see cref="System.Object"/></param>
public void ActivateRibbonTab0_Action (object parameter)
{
    ribbon.SetSelectedTab (0);
}
/// <summary>Activate ribbon tab 1 action to execute on kex gesture binding.</summary>
/// <param name="parameter">Data used by the command. If the command does not require data
/// to be passed, this object can be set to null.<see cref="System.Object"/></param>
public void ActivateRibbonTab1_Action (object parameter)
{
    ribbon.SetSelectedTab (1);
}
/// <summary>Activate ribbon tab 2 action to execute on kex gesture binding.</summary>
/// <param name="parameter">Data used by the command. If the command does not require data
/// to be passed, this object can be set to null.<see cref="System.Object"/></param>
public void ActivateRibbonTab2_Action (object parameter)
{
    ribbon.SetSelectedTab (2);
}

...

To indicate the keyboard shortcuts for a GUI element, the markup syntax should be used.

...

    XrwRibbonTab dialogtestTab = XrwRibbonTab.NewRibbonTabGadget
        (ribbon, "<markup><u>D</u>ialog test</markup>");

...

    XrwRibbonTab radiotoggletestTab  = XrwRibbonTab.NewRibbonTabGadget
        (ribbon, "<markup><u>R</u>adio & toggle test</markup>");

...

    XrwRibbonTab splitTab  = XrwRibbonTab.NewRibbonTabGadget
        (ribbon, "<markup><u>S</u>plit test</markup>");

...

This is what the result looks like. The ribbon tabs can be secected via key combinations [Alt]+[d], [Alt]+[r] or [Alt]+[s].

Event management policy in comparison

This table takes a closer look to the pointer events and actions that are invoked. Consistent behaviour is a very important fact for user acceptance. Different behaviour is highlighted in red.

Action Windows 8.1 GTK 2 clear looks Xrw Remark on Xrw
open a menu
drop-down
button 1 press
on menu button
any button press
on menu button
any button press
on menu button
 
close a menu
drop-down
delayed button 1
press on appropriate
menu button
delayed any button
release on appropriate
on menu button
delayed any button
press on appropriate
menu button
press is more
common
close a menu
drop-down
any button press
outside the menu
drop-down
any button release
outside the menu
drop-down
any button press
outside the menu
drop-down
press is more
common
select a menu item button 1 release
on menu item
any button release
on menu item
any button release
on menu item
currently no
context help
open a combo box button 1 press
on combo box
button 1 press
on combo box
any button press
on combo box
 
close a combo box delayed button 1
press on combo
box drop-down
delayed button 1
release on combo
box drop-down
delayed any button
press on combo
box drop-down
press is more
common
close a combo box any button press
outside the combo
box drop-down
any button release
outside the combo
box drop-down
any button press
outside the combo
box drop-down
press is more
common
select a combo box
item
button 1 release
on combo box item
button 1 release
on combo box item
button 1 release
on combo box item
currently no
context help
open the ribbon's
application menu
button 1 press
on menu button
- any button press
on menu button
 
close the ribbon's
application menu
delayed button 1
press on appropriate
menu button
- delayed any button
press on appropriate
menu button
 
close the ribbon's
application menu
any button press
outside the menu
drop-down
- any button press
outside the menu
drop-down
 
select a ribbon's
application menu
item
button 1 release
on menu item
- any button release
on menu item
currently no
context help
open the ribbon's
split button menu
button 1 press
on split button
- any button press
on split button
 
close the ribbon's
split button menu
delayed button 1
press on appropriate
split button
- delayed any button
press on appropriate
split button
 
close the ribbon's
split button menu
any button press
outside the split
button drop-down
- any button press
outside the split
button drop-down
 
select a ribbon's
split button menu
item
button 1 release
on menu item
- any button press currently no
context help
tab selection button 1 press button 1 press any button press  
ribbon selection button 1 press - any button press  
in-place text
editor invocation
delayed button 1
press after item
selection
via context menu
of the selected item
delayed  any button
press after item
selection
delayed button
press is more
common
in-place text
editor leave
any button press
outside the in-
place editor
any button press
outside the in-
place editor
any button press
outside the in-
place editor
 

Clipboard usage

Currently the only supported clipboard data type ist STRING (atom _XA_STRING). See XrwApplicationShell's attribute region for more atom definitions, if other clipboard data types are to support.

To copy or paste text data (between applications) via the clipboard any widget (but not a gadget) can provide data via XrwApplicationShell.ProvideClipboardText() or request data via XrwApplicationShell.RequestClipboardText(). An XrwText widget's clipboard support looks like this:

{
    ...
    // XrwText widget: Provide text data (_selectionStart and _selectionEnd are character positions).
    XrwApplicationShell app = this.ApplicationShell;
    if (_selectionStart != _selectionEnd && app != null)
    {
        SetCopyBuffer ();
        app.ProvideClipboardText (_surface, e.Event.time);
    }
    ...
}

...

{
    ...
    // XrwText widget: Request test data.
    XrwApplicationShell app = this.ApplicationShell;
    if (app != null)
    {
        app.RequestClipboardText (_surface, e.Event.time);
    }
    ...
}

...

{
    ...
    // XrwApplicationShell: What happens inside the application's event loop to inject result?
    s = Marshal.PtrToStringAuto (data);
    if (target as XrwText != null)
    {
        (target as XrwText).Paste (s);
    }
    ...
}

Beside the 'legacy X11 compatible' methods XrwApplicationShell.ProvideClipboardText() and XrwApplicationShell.RequestClipboardText(), now methods of Windows compatible names but different argument lists support an alternatively 'convenient MS Windows similar' behaviour. To achieve this, the 'legacy X11 compatible' funftionality is wraped by the System.Windows.Clipboard's methods SetText() and GetText().

Attention: Even if the namespace System.Windows.Clipboard of the 'convenient MS Windows similar' behaviour implies an application-independent clipboard, X11 doesn't provide an application-independent clipboard - it must be implemented by the windows manager to perform well, not by the application or application programming framework. Therefore a successful copy/paste operation always requires the provider widget and the receiver widget to be alife.

Finally besause of the distributed storage of clipboard data (any widget can be a clipboard data provider) and asynchronous processing of clipboard data (inter-application message processing) on X11 systems (compared to the centralized/monolithic Windows clipboard system) a delegate to inject the result into the requestor must be provided for GetText().

{
    ...
    // XAML TextBox control: Provide text data.
    XrwApplicationShell app = this.ApplicationShell;
    if (_selectionStart != _selectionEnd && app != null)
    {
        Clipboard.SetText (Entry.Text);
    }

...

    // XAML TextBox control: Request test data.
    XrwApplicationShell app = this.ApplicationShell;
    if (app != null)
    {
        // Besause of the asynchronous processing of clipboard data(inter-application
        // message processing) a delegate to inject the result must be provided.
        Clipboard.GetText (this.ProcessClipboardPasteToEntry);
    }
    ...
}

...

/// <summary>Handle the ClipboardGetResult event.</summary>
/// <param name="result">The clipboard get text result.<see cref="System.Object"/></param>
private void ProcessClipboardPasteToEntry (object result)
{
    if (result != null)
    {
        Entry.Text = result.ToString ();
    }
}

...

{
    ...
    // XrwApplicationShell: What happens inside the application's event loop to inject result?
    s = Marshal.PtrToStringAuto (data);
    if (target as XrwApplicationShell != null)
    {
        System.Windows.ClipboardGetResultDelegate clipboardGetResult =
            System.Windows.Clipboard.ClipboardGetResult;
        if (clipboardGetResult != null)
            clipboardGetResult (s);
    }
    ...
}

History

This article has been split-off from the article Programming the Roma Widget Set (C# X11) - a zero dependency GUI application framework - Part 1, Basics with the fourth public version of the Roma widget set, version from 13. May 2014.
The fifth public version of the Roma widget set is version  from 15. August 2014.
The sixth public version of the Roma widget set is version from 05. October 2014.
The seventh public version of the Roma widget set is version  from 14. December 2014.
The eighth public version of the Roma widget set is version  from 8. March 2015.
The eighth public version of the Roma widget set is version  from 21. October 2015.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here