Saturday, January 28, 2012

A Sign of Maturity

No IDE is mature until it has a TSmiley component.
                                            
--Anonymous

This week I got a sudden urge to write TSmiley component for Smart Mobile Studio IDE. After all, what is a development environment without a smiley?

My TSmiley is not an enterprise-y solution like latest Nick’s revisit of the old theme ;), oh no, it is a lean and mean component without any special bells and whistles. You can select the smiley’s emotion and it will change the look. And that’s it.

I thought that a story about TSmiley for Smart would be instructive for you, dear reader, as this is almost the simplest component one can write. Read on to see how writing a component for Smart is very similar to writing a component for Delphi and how it is also utterly different.

Getting the Images

Before I wrote any code I had to find graphic files with smiling faces. You’d thought that would not be hard as the internet is full of yellow smiling faces. And cats. Mostly cats. Soft cats, funny cats … sorry, I’m digressing. I needed smiling faces. The problem with most of the smiling faces of the internet is the license or lack thereof. I wanted to find a free graphics which I could also include in a component that will be possibly used in a commercial application and that proved to be a problem.

It took some googling but finally I found FreeSmileys.org which is giving home to thousands of smiling images, all free. Through this site I proceeded to the Smiley-Faces.org where I downloaded few images. (Or, rather, where I didn’t download them – but I’ll come back to this in a second.)

Writing the Component

A component in Smart is derived from any of the existing components, defined in w3ctrl and w3components units. [I write component but actually I mean control as my component will have a visual representation on the screen.] The smiley component is based on the most basic control of all – TW3CustomControl. [If I would be writing a true component, I would derive from TW3Component.]

With that in mind I wrote the interface part of the SmartSmiley unit, which looks almost identical as it would have in Delphi.

uses w3system, w3components;

type
  TSmileyEmotion = (seSmiling, seGrinning, seLaughing, seSad, seCrying,
    seCool, seMad, seWinking, seSurprised);

  TSmiley = class(TW3CustomControl)
  private const
    CEmotionName: array [seSmiling..seSurprised] of string =
      ['smiling', 'grinning', 'laughing', 'sad', 'crying', 'cool',
       'mad', 'winking', 'surprised'];
  private
    FEmotion: TSmileyEmotion;
  protected
    function  GetEmotionData(emotion: TSmileyEmotion): string;
    procedure InitializeObject; override;
    procedure SetEmotion(const emotion: TSmileyEmotion);
    procedure StyleTagObject; override;
  public
    function EmotionName: string;
    property Emotion: TSmileyEmotion read FEmotion write SetEmotion;
  end;

There is an enumeration of smiley’s emotional states, some internal worker methods, property exposing the emotion and a function returning the string representation (a name) of the current emotion. This function grabs strings directly from the internal CEmotionName array, which is initialized directly in the code with – wait, what’s that! Square brackets in the constant initializer?

Yes, your eyes are working fine. DWScript uses this non-standard way to initialize an array. My deep thanks go to Eric who explained this bit of syntax [not covered in the official documentation] to me and also added an interesting reason for this deviation from the Delphi.

Constant arrays are delimited with square brackets [ ]:
   const CList: array [lOne..lThree] of string = ['1', '2', '3'];
   const CList: array [Low(TList)..High(TList)] of string =
     ['1', '2', '3'];
so you can also create and assign constant arrays inline, and assign them to a fixed-size array variable, or a dynamic array.
   var list: array [lOne..lThree] of string;
   ...
   list := ['1', '2', '3'];

So. Now you know.

By the way – you may have noticed that TSmiley is not re-publishing various mouse & touch events (like OnClick). This is because the base class – TW3CustomControl – already marks all those events as public.

Some Basic Stuff

Some parts of the component are trivial.
function TSmiley.EmotionName: string;
begin
  Result := CEmotionName[FEmotion];
end;

procedure TSmiley.InitializeObject; 
begin
  inherited;
  Emotion := seSmiling;
end;

procedure TSmiley.StyleTagObject;
begin
  inherited;
  StyleClass := 'TW3Image';
end;

EmotionName returns the string representation of the emotion, InitializeObject sets the initial emotion to smiling and StyleTagObject connect the component with the built-in CSS style for images.

Displaying the Image

The whole point of the TSmiley component is to display a smiling face on the screen. I could go the Delphi way and paint this image on the HTML canvas but in this example I decided to do something much simpler. Every component in Smart is represented with some HTML code and in this case I am just changing this code to insert the <img> tag.

procedure TSmiley.SetEmotion(const emotion: TSmileyEmotion);
begin
  FEmotion := emotion;
  InnerHTML := '<img valign="middle" align="middle" alt="" ' +
    'src="' + GetEmotionData(emotion) + '" />';
end;

Every time the emotion changes, InnerHtml property (which represents the HTML used to render the component) is changed to an <img> tag which refers to some image. All good and well, but where does this image came from?

Bitmaps in the Source

GetEmotionData does not provide an answer.

function TSmiley.GetEmotionData(emotion: TSmileyEmotion): string;
const
  EmotionData: array [seSmiling..seSurprised] of string =
    [CSmiling, CGrinning, CLaughing, CSad, CCrying, CCool, CMad,
     CWinking, CSurprised];
begin
  Result := EmotionData[emotion];
end;

It just maps emotions into some constants. Let’s take a look at one of them.

  CSmiling =
    'data:image/gif;base64,R0lGODlhFAAUAOZ7AP7lIv/lIv7kIfzhIPHPGvrdH/nbH+Tl4+' +
    'nCFu/y9N3d2O/MGWVQHaKYfP3iIZKFY+7x8vbXHeK3EnhnPOa8FPDOGgAAAKOZfnVjNvXWHZ' +
    '52CuW8FF9JFNjY0fLRG9HPxdTSyeS6FGJNGeW7FHdmOqGWeuvGF9WhC2xOB29WCptxB2pQCb' +
    'SNDtTTyufEGLCTEt6vEKJ8C5pvB2lNCM2qFK2PEq19B5+VeOa/Fpp+EKeKEWlVIHJfMd+yEd' +
    'i3F76QC9KyFuvFF+/y89XUzKWbgdCbCmZQHKeehLuKCXNhM9DOxNWiDGdTHuK3E29UCd6xEK' +
    'SDD2hLCKaCDenHGfPUHXppP+S8FtmsEJtwB86dC9inDezJGe/RHG1QCLaPDu7MGt2vEKiGD9' +
    'uuEKGXe4lkB2xTCsCSC9OwFdmnDdfWz49tCtjX0GlUINSfC82aC3tqQJJyDMKkFZOGZXhmO8' +
    '6ZCb2MCd6wENLQx/jbHkM0EP/mIvX5/QAAAAAAAAAAAAAAACH5BAEAAHsALAAAAAAUABQAAA' +
    'f/gHuCg4SFhoQJQ0dvIiJVRC0Jh4IHDWU6QF9bNFArDQeHHRg5XHgFBXgZFVZwGB2FCjtxeA' +
    'MCAAACA3geCCxsCoNCci8GDgF6yHoBDgYEFDEPEIJ3KVQDx3nJeQEDESZiXR+CYzV4AsjZ6H' +
    'oCeAsSGiWCEz4FAOrqAAUEIWYTggxT6iUbqCffviwM/rkwRzAZuwVN3CTcM+FMBAvHCHK7+K' +
    'TOHEElwhAwsG3gMpIU0Ki4IeiDExwe8OSxhWsASQQwiqBQIgjCAykIKmTIQ7Qogh4nsEQbpI' +
    'CJlw1BFhAgsMDEBjAnbBgBRmhNEjVXJIwYIcGOFjpkeKQ5VGmGhh9LGNogkRHl06Q9CUBcIM' +
    'GBA4kLICTdHVwoEAA7';

This is a trick I learned from Lennart, the author of the Smart Mobile Studio. Instead of putting a separate image file somewhere on the web server, they are included as in-place images. Each such resource is just a GIF file encoded with base64 encoding and prefixed with a header that specifies the type of the image and encoding used (data:image/gif;base64).

A tool to create such representations is supposed to find its way into the Smart IDE. For the time being you can roll up a base64 encoder of your own or use a web-based one. I was using this simple tool which has an additional bonus – it will grab images directly from any internet location. I just pasted links from the Smiley-Faces.org and converted dropped out a base64 encoding for them. [And that’s why I didn’t have to download images from the Smiley-Faces.org.]

Installing the Component

Well, not really. As there is no designer in the Smart yet (it will appear in the beta cycle, or so we are told), I just created the component in the code and connected it to a mouse/touch events that change its emotion.

procedure TFormMain.DescribeEmotion;
begin
  FEmotion.Text := 'TSmiley is ' + FSmiley.EmotionName;
end;

procedure TFormMain.InitializeObject;
begin
  inherited;
  FSmiley := TSmiley.Create(Self);
  FSmiley.OnClick := HandleMouseClick;
  FSmiley.OnTouchBegins := HandleTouchBegin;
  FEmotion := TW3Label.Create(Self);
  DescribeEmotion;
end;

procedure TFormMain.HandleMouseClick(Sender: TObject);
begin
  if FSmiley.Emotion = seSurprised then
    FSmiley.Emotion := seSmiling
  else
    FSmiley.Emotion := Succ(FSmiley.Emotion);
  DescribeEmotion;
end;

procedure TFormMain.HandleTouchBegin(Sender: TObject; info: TW3TouchData);
begin
  HandleMouseClick(Sender);
end;

procedure TFormMain.Resize; 
begin
  FSmiley.SetBounds(10, 10, 20, 20);
  FEmotion.SetBounds(40, 10, 200, 20);
end;

Once the designer is working in the Smart IDE I’ll be returning to the topic but for today this is it.

TSmiley on Your Screen

You can test the TSmiley on my web server [label text is strangely truncated in Opera but works in Chrome] and download the code for the component.

TSmiley

Credit Where Credit is Due

Hats off to Nick Hodges for creating such a timeless component!

3 comments:

  1. DWScript array initialization is now documented at http://code.google.com/p/dwscript/wiki/Language?ts=1327907253&updated=Language

    ReplyDelete
  2. FSmiley.emotion := seCool;
    Sadly, DescribeEmotion seems to not work in firefox...
    FSmiley.emotion := seSad;

    ReplyDelete
  3. It seems that the Firefox exhibits the same problem as Opera - labels are truncated.

    At the moment, Smart targets WebKit installations, which means iOS, Safari, Chrome and (partially) Android.

    ReplyDelete