What would it require to implement multiple desktop support in Pharo ?

Tinkering before coding:

One has to know that Pharo is still based on a UI system called Morphic. Morphic itself is based on some nice ideas — but it also had some design flaws and years ago when Pharo forked as its own open source project from the Squeak Smalltalk system the morphic code was in a horrible state.

Fortunately the Pharo community was able to clean the dark sides as much as possible to make it less painful and also a replacement for Morphic called “Block” and “Brick” is already in the pipe.

Before any line of code could be written the author played around in the Pharo system. The Pharo playground is an excellent tool for that and one can use it to inspect another global variable called “World” by running:

World inspect

One can send a message like #inspect or use the menu to inspect the result of an object returned by a code snippet

This global variable “World” points to an object representing the current world/screen that is displayed in front of the user. The inspector tells us that the world is an instance of WorldMorph. In an object oriented system like Smalltalk we can ask an object for its class and inspect that as well:

World class inspect

If we want to have a code browser showing us the methods this class supports we can do by simply sending the message #browse to an object.

World browse

Side note: usually one opens the tools like browsers using menu or shortcuts. But using the #browse method is also very handy if you do not yet know about the class of an object you work on.

Reading a little bit the methods on the class and instance side of WorldMorph there were two methods that got my special attention: one was an instance method called #install to install an existing world object and one was a class side method called #installNewWorld. So it is already possible to have new worlds and install them. Yay — lets move on!

In Morphic any UI related object is a morph and can have submorphs. Lets inspect the submorphs of our current world:

World submorphs inspect

Nice: I got a list of several morphs including the Pharo windows that are currently open. There is even a TaskBarMorph instance as a submorph in our world representing the task bar at the bottom. Using the inspector it was easy to dive deeper into the world structure and using the class browser understand more about their implementation.

Let’s get back to our idea of multiple desktops/multiple worlds. Another check revealed that in a fresh started Pharo image there is only one instance of WorldMorph:

WorldMorph allInstances size

Initially we have only a single world — the number of instances of WorldMorph is exactly one

That is the instance of WorldMorph the global variable “World” is pointing to. Next experiment was:

WorldMorph installNewWorld.

WorldMorph allInstances size

revealing that it was possible to create a second instance or WorldMorph. By using:

WorldMorph allInstances first install

and

WorldMorph allInstances last install

I was able to switch between the two worlds already. Nice!

Mmmmh … let’s summarize what we found out:

Initially there is only one “world(morph) object” in the system representing the desktop and holding the visible windows it is an instance of class WorldMorph There is the possibility to create new worlds and switch to

them using #install

Unfortunately when switching the worlds the windows did not fully redraw leaving ugly rectangles on the screen. We need to redraw and restore the display. Wait, there already is a global variable “Display” that we used when switching fullscreen. And Nautilus the class browser quickly revealed that there is a restore method:

The DisplayScreen class selected in the Nautilus system browser with the #restore method

That means we can restore the display after “world switching” easily:

WorldMorph allInstances last install.

Display restore.

Still cumbersome — but it works. Having this knowledge we can now move on in finding a design for a goodie to create and manage multiple worlds/desktops in Pharo.

Desktops and a desktop manager

Initial experiments finished —time to write some code. The author started to create a new code package called “DesktopManager-Core” in the system browser and a category/tag “Base” for the basic classes we need:

A new package that we use to store the source code

The first class implemented in our design is a new class called “Desktop” for representing the different desktops:

Let’s foresee an attribute (instance variable) “world” to hold the world the desktop represents:

In programming languages like C++, C# or Java a class usually would be defined in a source code. A class definition file (Desktop.cpp/ Desktop.cs/ Desktop.java) in these languages would be a dumb text definition file fed into a compiler to verify and translate.

In an interactive and lively system like Pharo a class could be created like any other object by sending instance creation methods. The reason is simple: in a pure OO environment anything is an object, so even a class is an object. Remember: there are only objects and messages.

So in Smalltalk a new (sub)class is typically created by sending a message to the superclass. We want to inherit from the base class Object so we can quickly fill out the template provided in the Nautilus system browser:

Nautilus browser with the new class created by filling the template and accepting the code

and create a new Desktop class. Do not forget to add a class comment! Using the Refactoring menu of the Nautilus browser we can quickly generate accessor methods (getter/setter) for our world attribute. Additionally we define some convinience methods to be able to write code like this:

Desktop world: World

I will not go into all the details of the implementation. The full source code can be found on SmalltalkHub:

http://www.smalltalkhub.com/#!/~TorstenBergmann/DesktopManager

To manage the multiple desktops we need some kind of desktop manager holding an ordered collection of the multiple desktop instances. As there is always at least one desktop for the initial world in this relationship collection the cardinality of this relationship is not 0…n (“zero to n desktops”) but 1…n (“one to n desktops”). Let’s sketch this with an UML class diagram:

A desktop manager manages one or more desktops

followed by creating the class in the Pharo environment:

The class DesktopManager

When providing multiple desktops we want the user to be able to navigate by moving to the next or to the previous desktop. For this we need to know the index of the current active desktop in our collection of desktops. We honor this with an attribute to keep this info:

An additional attribute to know about the index to the active desktop

To initialize new instances of our new manager class we implement an instance side #initialize method:

initialize

super initialize.

desktops := OrderedCollection with: (Desktop world: World).

currentDesktopIndex := 1

So initially we have a collection of one desktop referencing the current World. As this one is also the active desktop our index needs to be 1.

Another design decision is that there should only be one desktop manager in the system. So we implement a class side #soleInstance method returning the singleton.

Now one could write

DesktopManager soleInstance

to access it. Additionally we add another convinience unary method called #manager — this time in class Desktop to be able to write:

Desktop manager

This is by far more easy to remember than the previous expression.

Now code was written for handling:

Creation of new desktops (see #createNewDesktop method) Adding a new desktop to the collection (see #addNewDesktop method) Switching to a desktop (see #switchToDesktop: method) Navigation (see #switchToNextDesktop and #switchToPreviousDesktop methods)

This new code allowed to navigate already using Smalltalk expressions:

Desktop manager addNewDesktop; switchToNextDesktop

or check the desktop collection:

Desktop manager desktops size

Even working with the desktops is possible:

Desktop manager desktops first isActive

Please check the full source code for the details.