Skip navigation

OpenVista CIS

5 Posts tagged with the openvista_cis_internals tag
0

One of the features of OpenVista CIS is that it is extensible. Without modifying the core of CIS, you can drop in additional functionality, such as new application tabs, new image format rendering support, new preferences tabs, etc.

 

In order to make OpenVista CIS extensible, it uses Mono.Addins for a plugin system. This allows developers to dynamically add more functionality to CIS without having to change its internals. Mono.Addins allows developers to specify "extension points" and then inspect and load all extensions that attach to that extension point. Here is a simple example showing one of the CIS extension points, and a simple extension that implements it.

The Extension Point

Let's look at one of the core UI extension points in CIS, the IApplicationTab. This extension point is used to provide tabs visible in the main window of CIS. Here is the relevant code from the definition of that extension point interface:

using Gtk;

[Mono.Addins.TypeExtensionPoint ("/Interface/ApplicationTab")]
public interface IApplicationTab
{
     Widget LabelWidget {
         get;
     }
     Widget Widget {
          get;
     }
     .
     .
     .
}

 

This extension node is found in the Medsphere.OpenVista.Core.UI.dll assembly, and must define an "AddinRoot" in order to let this extension point be used. The following is from the AssemblyInfo.cs file in that assembly:

using Mono.Addins;

[assembly:AddinRoot ("Medsphere.OpenVista.Core.UI", "1.0")]

A Simple Extension

 

We are going to create a new assembly that contains our application tab extension. We need to specify both our addins dependencies, as well as advertise it the addin itself, in the AssemblyInfo.cs file:

using Mono.Addins;

[assembly: Addin]
[assembly: AddinDependency ("Medsphere.OpenVista.Core.UI", "1.0")]

 

Next we will define our simple hello world extension in HelloWorldSheet.cs:

using System;
using Gtk;
using Medsphere.OpenVista.Core.UI;

[Mono.Addins.Extension ("/Interface/ApplicationTab")]
public class HelloWorldSheet : IApplicationTab
{
        public Widget LabelWidget {
                 get { return _label; }
        }

        public Widget Widget {
                 get { return _widget; }
        }
        .
        .
        .
        public HelloWorldSheet ()
        {
                 _label = new Label ("Hello\nWorld");
                 _widget = new Label ("Hello World!");
        }

        Label _label, _widget;
}

We can now compile our addin with the following command line (of course, this could just as easily be done in Visual Studio, MonoDevelop, etc):

gmcs -r:Mono.Addins.dll -r:Medsphere.OpenVista.Core.UI.dll -pkg:gtk-sharp-2.0 \
     -t:library -out:Medsphere.OpenVista.CIS.Demo.dll HelloWorldSheet.cs AssemblyInfo.cs

 

Now when we run CIS, we see that our hello world application tab has been automatically loaded:

 

openvista-cis-hello-world-plugin.png

 

Conclussion

 

For further reading on Mono.Addins, check out the Mono.Addins page on http://www.mono-project.com/. If you want to get started on extending CIS, grab the CIS source code! The full source code for the simple CIS tab plugin is attached to this document, so you can see the full details.

673 Views 0 Comments 0 References Permalink Tags: c#, cis, mono_addins, openvista_cis_internals, openvista_cis, open_source
0

P/Invoke, short term for Platform Invoke, is the mechanism provided by the .NET framework to invoke unmanaged code (methods or functions from libraries written in C or C++ programming languages) from managed code. As an example let's look at some code from the OpenVistaCIS code base.

 

Let's look at the code that brings spelling support. If you have CIS's source code available go ahead and read the file src/Medsphere.OpenVista.Spelling/AspellSpeller.cs. The basic P/Invoke suport is based on the use of the DllImport attribute. What the DllImport attribute does is to provide the information needed to call a function from unmanaged code, which is described by the method's signature (which involves the name, return type and parameters types).

 

In order to create an Aspell dictionary on OpenVistaCIS we have two external libraries definitions that take care of what's the platform we are running on, either GNU/Linux or MS Windows

[DllImport ("aspell-15.dll", EntryPoint="new_aspell_speller")]
private static extern IntPtr new_aspell_speller_win32 (IntPtr config);
 
[DllImport ("libaspell")]
private static extern IntPtr new_aspell_speller (IntPtr config);

 

The first parameter for the DllImport attribute is the library name, that for the Win32 version is aspell-15.dll but for the Linux version is libaspell, we have to define two external method declarations due to that fact, then for the Win32's signature comes a named argument called EntryPoint that let us map the declaration to the original name of the method in the unmanaged library, so in that case we map new_aspell_speller_win32 to new_aspell_speller. Another important thing that we must highlight is the use of the extern method's modifier to "mark" the method as being implemented externally (that allow us to just use semi-colon as the method's body. And finally, we must highlight the use of the IntPtr type to represent a pointer or handle that's coming back from the unmanaged library.

 

Now, the way we use them inside the managed code is as follows:

public AspellSpeller (AspellConfig config)
{
    if (IsWindows) {
        error = new AspellCanHaveError (new_aspell_speller_win32 (config.Handle));
    } else {
        error = new AspellCanHaveError (new_aspell_speller (config.Handle));
    }
    .
    .
    .
}

 

Where the IsWindows property is defined as:

private static bool IsWindows {
    get { return ((int)Environment.OSVersion.Platform <= 3); }
}

 


The types used in an external method declaration can go from very simple Int32, String or more complicated types that need explicit marshalling. One very detailed article about is the one found in the Mono project's Interop with Native Libraries. Check it out for more details about P/Invoke.

653 Views 0 Comments Permalink Tags: cis, linux, openvista_cis, openvista_cis_internals
2

Internationalization.

 

I really like the way the internationalization concept is presented in the Gettext1 manual so I am going to quote them:

"By internationalization, one refers to the operation by which a program, or a set of programs turned into a package, is made aware of and able to support multiple languages. This is a generalization process, by which the programs are untied from calling only English strings or other English specific habits, and connected to generic ways of doing the same, instead."

What that means is that an internationalized application is designed in such a way that as soon as the proper values for a specific locale is provided, the application is able adapt to those values and change it's behavior accordingly. For example an internationalized application that's given a proper translation package for a different language should be able to display the new strings/messages and modify the text orientation in case is needed depending on the locale.


In particular for applications written with Gtk# we want to be able to have the capability of displaying the different shapes depending on the actual locale. As a consequence of building OpenVistaCIS with Gtk+ which uses Pango as the layout and rendering engine for text we gained all that for free.

 

 

 

Localization.

 

 

 

I really like the way the localization concept is explained in the Gettext  manual so I am going to quote them again:

"By localization, one means the operation by which, in a set of programs already internationalized, one gives the program all needed information so that it can adapt itself to handle its input and output in a fashion which is correct for some native language and cultural habits. This is a particularization process, by which generic methods already implemented in an internationalized program are used in specific ways."

In general what a locale must describe is the kind of characters and codesets, the currency, how dates are displayed, the type of number representation, and the last but no least the messages.

The way we ensure that OpenVistaCIS is easily translatable is making us of Gettext's routines everytime we want to show or display a message. The class that contains such methods is named Catalog and can be found in the file src/Medsphere.OpenVista.Shared/utilities/Catalog.cs.

The important pieces of that class are the methods GetString and GetPluralString

 

public class Catalog {     public static string GetString (string s)     {     }     public static string GetPluralString (string s, string p, int n)     {     } }

 

 

That we use it as follows:

 

TreeViewColumn primary = FactorsTreeView.AppendColumn (Catalog.GetString ("Primary"), toggle, new TreeCellDataFunc (FactorsListRenderer))

 

or

 

protected override string MessagePrefix {     get {        return Catalog.GetPluralString ("The surrogate settings cannot be set because of the following error",                                        "The surrogate settings cannot be set because of the following errors",                                        validator_group.InvalidCount);    } }

 

Now, the interesting part comes when third party contributor want to help translation OpenVistaCIS. The way all the pieces come together is the following. In our source code tree we have a subdir called po, you'll find various incomplete translation files de_DE.po, es_AR.po, pt_BR.po, and th.po. There must be on .po file per language.

In case you want to contribute to any of the languages that we already have translations for do the following:

Go to the po/ subdir and type:

 

make update-po

 

That command will update the current translation files from the source code.

Then you should edit the .po file for the language you want to contribute to, after that perform the following command:

 

intltool-update --dist language-you-edited.po

 

And send us the language-you-edited.po file so we can update the translation files.

 

In case you want to create a translation for a new language do the following:

 

msginit --locale=ll_CC

 

where ll_CC is formed with ll being a language code and CC should be a country code. For example for a Mexican spanish translation you would perform the following command:

 

msginit --locale es_MX

 

and a new file named es_MX.po should get created.

Now that you have your favorite language's template you can go crazy translating the strings contained in it.

When you run penVistaCIS you can change the language that OpenVistaCIS uses by setting an environment variable:

 

export LANG=ll_CC

 

For example, the code below would make OpenVistaCIS use the german language:

 

export LANG=de_DE

 

1 http://www.gnu.org/software/gettext/

1,061 Views 2 Comments Permalink Tags: i18n, l10n, openvista_cis, openvista_cis_internals, translation, translate, multilingual
0

One of the major benefits of OpenVista CIS is that it empowers users to choose the platform to run on.  At the core, we use Microsoft .NET on Windows platforms and Mono on others.  The user interface is developed using GTK+, which is portable across Windows, Linux (and other Unix variants such as OpenSolaris and FreeBSD), and MacOS X.  This is one of the major reasons that it was chosen as the development platform for Medsphere OpenVista client applications.  I'll try to give a brief introduction to the platform stack here, as many new CIS developers may be coming from a different development background.

 

GTK+ is written entirely in C, and has binding layers to other languages such as C++, Ruby, and Perl.  Medsphere uses the Gtk# binding to develop OpenVista CIS using GTK in C#.  The Gtk# binding layer gives us the cross-platform user interface toolkit but with the power and ease of C#; it connects the Mono/.NET garbage collector with GTK's reference counting system for memory management, maps GLib containers that are used by GTK+ to System.Collections (the container class library used by .NET and Mono), maps between the events and delegates in Mono/.NET and the signal slots and C function callbacks used in unmanaged land, and generally allows us to almost[1] seamlessly work with the GObject memory space as though it were part of the .NET object system.

 

The GTK+ stack is split into various components:

  • GLib provides general utility functions and cross-platform suppport.  It provides the mainloop that GTK+'s event processing is built on top of, and it provides GObject.  GObject is the basis for the object system on which GTK+ is written.  Yes, GTK+ is written in C and is also object-oriented.
  • ATK is the accessibility toolkit.  Essentially, this provides the groundwork for alternate input and interface methods, which allowed the application to be more easily accessible for impaired users (such as vision-impaired users, for example).  It is also the basis for how Medsphere's automated testing framework, Strongwind, is able to automate actions on the user interface.
  • Pango is the font and text rendering library.  It supports a lot of fairly advanced things that you may never need, but they're available.  For example, Pango supports bidirectional text layout for languages such as Hebrew and Arabic.
  • Cairo is the vector graphic renderer that GTK uses for a lot of its graphics.  We also use this directly for things such as the Graph widget.  It allows us to render really beautiful scalable vector graphics with high-quality antialiasing.
  • Gdk is the low-level windowing side of GTK+, and is the place that abstracts between the different windowing systems of Win32, MacOSX, and the X-Window System that is used by Linux.  This deals with the basic input and output of the whole user interface.
  • Gtk is the main widget library that handles such things as buttons, labels, and treeviews.  Note that Gtk typically does not use platform native controls (the one exception is the print dialog on Win32).  It implements all the controls using its own widget system and then optionally uses a theme engine to make them look like native controls.  This has a distinct advantage for us over using native controls in that we get the same behavior across different platforms, and we get a consistent API to develop with across them all.
  • The theme engine is what styles the widgets to look the way they do.  For example, on Win32 platforms the theme engine uses the native parts and states Win32 APIs to draw the GTK widgets to look like native Windows controls.

 

Medsphere adds a few more open-source components to the CIS stack, on top of GTK+ itself:

  • libglade allows us to design dialogs that are saved in XML files and loaded at runtime.
  • poppler# is a C# binding for the poppler PDF renderer
  • Mono.Addins is used for loading plugins at runtime

 

[1].  Note that the one thing we can't yet treat seamlessly between the GObject system and the .NET object model are interfaces.  Like .NET, GObject supports single inheritance and interfaces, but the glue between them is not yet in a place where we can use it.

586 Views 0 Comments 0 References Permalink Tags: openvista_cis, openvista_cis_internals, gtk
0

Overview

CIS includes an assembly consisting of core functionality, called Medsphere.OpenVista.Core. In this assembly, our core "service" infrastructure, consists of ServiceManager, IService, and the attributes ServiceReferenceAttribute and LazyServiceAttribute. A service is any persistent object/model you want accessed by any other part of the software. It loosely fills the role of "Singleton" but providing a mechanism for abstracting things out behind interfaces, to allow for platform/application dependant implementation of certain services, and to aid in unit testing. Services are accessed via ServiceManager, and can be registered into ServiceManager one of two ways:

  1. Manually using ServiceManager.RegisterService (IService service)

  2. Dynamically via Mono.Addins by decorating the IService subclass with [Mono.Addins.Extension ("/Core/Services")]

IService

IService is the base interface for all services. It is extremely simple, as of this writing:

public interface IService
{       
        /// Initialize any resources needed by the service.
        /// @return Used to indicate success/failure in intializing any needed resources.
        bool Initialize ();
}

Creating an IService Subclass

At a minimum, you need to implement the IService interface in a subclass, and manually register the service with ServiceManager.RegisterService (IService service). After the service is registered, it can be accessed via IService ServiceManager.GetService (Type type). Here's a rough psuedo-C# example:

public class MyService : IService
{
        public string Name { get { return "MyService"; } }

        public bool Initialize () { return true; }
}

// Create the service instance.
IService my_service = new MyService ();

// Services registered dynamically are automatically initialized,
// but since we're doing it manually, we'll do it by hand here.
my_service.Initialize ();

// Register the service.
ServiceManager.RegisterService (my_service);

// Fetch the service back, and verify it's the right service.
IService my_service_2 = ServiceManager.GetService (typeof (MyService));
Assert.AreEqual (my_service, my_service_2);

Dynamically Registered Service

If you would like CIS to automatically register your service into ServiceManager at start-up, via Mono.Addins, you do the following:

[Mono.Addins.Extension ("/Core/Services")]
public class MyService : IService
{
        public string Name { get { return "MyService"; } }

        public bool Initialize () { return true; }
}

Lazy Loaded Services

NOTE: The following section only applies to services dynamically registered via Mono.Addins.

You can also make dynamically registered services be "lazy loaded", that is, they will not be created nor initialized until someone attempts to fetch references to them. This improves application start time by avoiding having to load all services when the application is first run. To make you service lazy-loaded, simply decorate it with the LazyServiceAttribute. Here's an example:

[LazyService]
[Mono.Addins.Extension ("/Core/Services")]
public class MyService : IService
{
        public string Name { get { return "MyService"; } }

        public bool Initialize () { return true; }
}

When the service is needed, because of a ServiceDependencyAttribute, a ServiceReferenceAttribute or when requted via ServiceManager.GetService, the service, and any services it depends on, will be loaded and initialized.

Conventions

There are a few conventions and best practices that can aid you in writing services.

  1. Most services should use an intermediary interface that defines the public API of the service. E.g. MyService -> IMyService -> IService. This will aid in unit testing code that consumes those services.

  2. Service sub-interfaces and subclasses should have names ending in 'Service', e.g. IFooService and FooService.

Service Dependencies

If your service depends on other services being loaded first (either to consume/use them, or due to potential load order issues), you can use ServiceDependencyAttribute on your service class. Here's an example:

[ServiceDependency (typeof (IMyOtherService))]
[Mono.Addins.Extension ("/Core/Services")]
public class MyService : IService
{
        public string Name { get { return "MyService"; } }

        public bool Initialize () { return true; }
}

This will ensure that IMyOtherService is loaded and initialized before MyService.

Fetching Services, Two Approaches

There are two approaches to fetching references to services from ServiceManager. One, as previously mentioned is to use IService ServiceManager.GetService (Type type). The other way is to use the ServiceReferenceAttribute applied to fields on your class.

IService ServiceManager.GetService (Type type)

The direct way to fetch a service is with IService ServiceManager.GetService (Type type). Here is an example:

IMyService my_svc = ServiceManager.GetService (typeof (IMyService)) as IMyService;

ServiceManager will return null if the service is not found, so often you will want to add a null check to ensure you got the service you wanted from ServiceManager.

ServiceReferenceAttribute and ServiceManager.BindFields (object obj, Type type)

The other way to get references to services registered into ServiceManager is to decorate your fields with the ServiceReferenceAttribute, e.g.:

public class MyServiceConsumer
{
        [ServiceReference]
        private IMyService my_svc;

        public MyServiceConsumer ()
        {
                ServiceManager.BindFields (this, typeof (MyServiceConsumer));
        }
}

You'll notice that in order to have the fields actually set to the references of the service, the class must call ServiceManager.BindFields (object obj, Type type). This call makes ServiceManager iterate over all the fields in the class, and assign values to the ones decorated with the ServiceReferenceAttribute.

NOTE: For IService subclasses that are dynamically registered via Mono.Addins, you do not need to use ServiceManager.BindFields. The fields will automatically be bound by ServiceManager after your service is created, and before the IService.Initialize () method is called on your service. This convenience allows you to write services that consume other services very easily.

Conclusion

The ServiceManager functionality provided in Medsphere.OpenVista.Core provides a powerful way to create persistent, shared models between parts of CIS, CIS plugins, etc. When writing CIS plugins, or enhancing the base CIS code, it is the prefered way to achieve a singleton pattern, with all the benefits that come from interface abstraction, etc. Some example services you can see in CIS are:

  • Medsphere.OpenVista.Shared.UI.FontService

  • Medsphere.OpenVista.Shared.UI.UserIdleService

  • Medsphere.OpenVista.Shared.UI.MessageService

691 Views 0 Comments 0 References Permalink Tags: openvista_cis, openvista_cis_internals, openvista_cis