Home > Amiga, Delphi, JavaScript, nodeJS, Object Pascal, OP4JS, Smart Mobile Studio > Smart Pascal: A real life desktop

Smart Pascal: A real life desktop

February 16, 2017 Leave a comment Go to comments

Every now and then I get feedback like “can SMS use jQuery?” or “Why don’t you use Sencha’s widgets“. And it just continues with everyone having their own favorite framework that they want SMS to adopt.

First of all, this is to completely misunderstand the architecture of Smart. You can use any framework you like. But you also have to sit down and write some code to incorporate it into the VJL. And believe me it’s not that hard.

The workbench

One of the cooler demo’s that I have been working on for .. oh, 8 hours now, is more or less a full Amiga OS 4 desktop clone. Sounds useless right? Well not really. It demonstrates some fundamental concepts:

  • How to create windows just like eh, windows have
  • How to display file-items in a listview
  • How to create controls outside the form so they remain unaffected by form switching
  • How to host demos and pre-compiled applications inside forms

But why stop there? Why not setup a node.js server and have the desktop act as a front-end to your server?

Always fun to see how far we can push the system

Check out this video on youtube 🙂

NAS front end

If you go out and buy a NAS today, chances are it comes with an HTML interface. So once you plug it into your router, you can browse to it and control the device via the browser.

Ring a bell? That is exactly what I’m doing right here. And it took me less than one working day to get this up and running. Here is how it looks right now:

Running a full remake of a classic demo in a window. No problem. You can run as much code as memory can hold

Running a full remake of a classic demo in a window. No problem. You can run as much code as memory can hold

But I want widget set x, y, z!

Had I done you the disservice of using Sencha or jQueryUI or whatever widget framework, you would have been stuck with that forever. Instead, you get a VCL like framework that is build in a way that ensures – that it can absorb and integrate any UI.

But let’s get back to that desktop.

You probably think: Ok so you have a fake desktop in a browser, it can run some JS demos and look cool. But so what?

You don’t get it. Did you know that X, the display system on Linux is by client / server system by default? Did you know that the entire Linux desktop is just an X client that connects to the server (the server being in the same distro) in order to do it’s business? If you start looking at what you can do – as opposed to what you imagine is impossible, there is a lot of cool stuff you can do for your company right now.

Doing some GPU profiling and watching the callstack

Doing some GPU profiling and watching the callstack

The first thing you need is to set up a server. A node.js server of course, that the website can talk to. Heck you can even do OAuth2 calls to dropbox and whatever online service you like – and get that on your HTML5 desktop without node.

Node however allows you to get system-level access. Listing files, loading files, saving files and even loading programs. Programs here being compiles Smart applications that the desktop can inject and execute inside a “window”.

Whenever you need something executing on the server – well then you call the server via websocket.

If node.js doesn’t do it for you then write it in Delphi or C++, it doesnt really matter. What matters is that you have a universal access point in the browser.

A quick visit to npm and git and you can download fully functional text-processors and large-scale HTML5 applications that does exactly what OpenOffice does. And with that websocket connection to your back-end, you have a real-life solution on your hands.

Here is a quick and dirty storage device API I made. Ram-disk will just store data to a B-Tree based “fake” in-memory filesystem. The real deal will come via websocket on the server.

unit Wb.desktop.Devices;

interface

uses
  System.Types, System.Types.Convert,
  System.Streams, System.Reader, System.Writer,
  System.Stream.Reader, System.Stream.Writer,
  System.Time,
  SmartCL.System,
  SmartCL.Time;

type

  TWbStorageDevice        = class;
  TWbStorageDeviceRamDisk = class;
  TWbDeviceManager        = class;
  TWbCustomFileSystem     = class;
  TWbVirtualFileSystem    = class;
  TWbRemoteFileSystem     = class;
  TWbLocalFileSystem      = class;
  TWbStorageDeviceClass = class of TWbStorageDevice;

  TWbCustomFileSystem = class(TObject)
  end;

  TWbVirtualFileSystem = class(TWbCustomFileSystem)
  end;

  TWbRemoteFileSystem = class(TWbCustomFileSystem)
  end;

  TWbLocalFileSystem = class(TWbCustomFileSystem)
  end;

  /* Requirements for using a device */
  TWbStorageDeviceOptions = set of
    (
      doRequireLogin, // Require authentication before Mount()
      doReadOnly      // Device is read-only
    );

  /* Filesystem access rights */
  TWbStorageDeviceAccess  = set of
    (
      daNone,       // none
      daReadOnly,   // read files only
      daReadWrite,  // read and write [create]
      daExecute     // can execute
    );

  TWbAuthenticatedEvent = procedure (Sender: TWbStorageDevice; Access: TWbStorageDeviceAccess);
  TWbMountEvent = procedure (Sender: TWbStorageDevice);

  /* Abstract storage device */
  TWbStorageDevice = class(TObject)
  private
    FId:            string;
    FName:          string;
    FFileSystem:    TWbCustomFileSystem;
    FOptions:       TWbStorageDeviceOptions;
    FMounted:       boolean;
    FAuthenticated: boolean;
    FManager:       TWbDeviceManager;
  protected
    procedure   SetName(const NewName: string); virtual;
    procedure   SetIdentifier(const NewId: string); virtual;
    procedure   SetFileSystem(const NewFileSystem: TWbCustomFileSystem); virtual;
    function    GetFileSystem: TWbCustomFileSystem; virtual;
    procedure   SetOptions(const NewOptions: TWbStorageDeviceOptions); virtual;
    function    GetOptions: TWbStorageDeviceOptions; virtual;
    procedure   SetAuthenticated(const NewState: boolean); virtual;
  public
    property    Name: string read FName;
    property    Identifier: string read FId;
    property    FileSystem: TWbCustomFileSystem read GetFileSystem;
    property    Options: TWbStorageDeviceOptions read GetOptions;
    property    Mounted: boolean read FMounted;
    property    Authenticated: boolean read FAuthenticated;
    property    DeviceManager: TWbDeviceManager read FManager;

    procedure   Authenticate(UserName, Password: string; const Success: TWbAuthenticatedEvent); overload;
    procedure   Authenticate(UserName, Password, Domain: string; Success: TWbAuthenticatedEvent); overload;
    procedure   Authenticate(AuthKey: string; Success: TWbAuthenticatedEvent); overload;

    procedure   Mount(const Success: TWbMountEvent);
    procedure   UnMount;

    constructor Create(const Manager: TWbDeviceManager); virtual;
    destructor  Destroy; override;
  end;

  /* RAM DISK */
  TWbStorageDeviceRamDisk = class(TWbStorageDevice)
  protected
    function    GetFileSystem: TWbCustomFileSystem; override;
  public
    constructor Create(const Manager: TWbDeviceManager); override;
  end;

  /* Cache disk */
  TWbStorageDeviceCache = class(TWbStorageDevice)
  protected
    function    GetFileSystem: TWbCustomFileSystem; override;
  public
    constructor Create(const Manager: TWbDeviceManager); override;
  end;

  TWbDeviceManager = class(TObject)
  private
    FClasses: array of TWbStorageDeviceClass;
    FObjects: array of TWbStorageDevice;
  public
    procedure RegisterDevice(const DeviceClass: TWbStorageDeviceClass);

    property  Count: integer read ( FObjects.Count );
    property  Device[const Index: integer]: TWbStorageDevice read ( FObjects[Index] ); default;

    destructor Destroy; override;
  end;

implementation

//#############################################################################
// TWbDeviceManager
//#############################################################################

destructor TWbDeviceManager.Destroy;
begin
  while FObjects.Count >0 do
  begin
    FObjects[0].free;
    FObjects.Delete(0,1);
  end;
  FClasses.Clear();
  inherited;
end;

procedure TWbDeviceManager.RegisterDevice(const DeviceClass: TWbStorageDeviceClass);
begin
  if FClasses.IndexOf(DeviceClass) < 0 then
  begin
    FClasses.add(DeviceClass);
    FObjects.add( DeviceClass.Create(self) );
  end;
end;

//#############################################################################
// TWbStorageDeviceCache
//#############################################################################

constructor TWbStorageDeviceCache.Create(const Manager: TWbDeviceManager);
begin
  inherited Create(Manager);
  SetName('DH0');
  SetIdentifier('{2D58F4D9-D8FE-434C-AC32-8B27EEC0AEE2}');
  SetOptions([doReadOnly]);
end;

function TWbStorageDeviceCache.GetFileSystem: TWbCustomFileSystem;
begin
  result := inherited GetFileSystem();
  if result = nil then
  begin
    result := TWbVirtualFileSystem.Create;
    SetFileSystem(result);
  end;
end;

//#############################################################################
// TWbRamDisk
//#############################################################################

constructor TWbStorageDeviceRamDisk.Create(const Manager: TWbDeviceManager);
begin
  inherited Create(Manager);
  SetName('Ram-Disk');
  SetIdentifier('{2E6D58D0-A0C3-4D62-8AC4-0300619418A6}');
  SetOptions([]);
end;

function TWbStorageDeviceRamDisk.GetFileSystem: TWbCustomFileSystem;
begin
  result := inherited GetFileSystem();
  if result = nil then
  begin
    result := TWbVirtualFileSystem.Create;
    SetFileSystem(result);
  end;
end;

//#############################################################################
// TWbStorageDevice
//#############################################################################

constructor TWbStorageDevice.Create(const Manager: TWbDeviceManager);
begin
  inherited Create;
  FManager := Manager;
end;

destructor TWbStorageDevice.Destroy;
begin
  if FFileSystem <> nil then
    FFileSystem.free;
  inherited;
end;

procedure TWbStorageDevice.Mount(const Success: TWbMountEvent);
begin
  if FMounted then
    UnMount;

  FMounted := true;

  if assigned(Success) then
  begin
    TW3Dispatch.Execute( procedure ()
      begin
        Success(self);
      end, 100);
  end;
end;

procedure TWbStorageDevice.UnMount;
begin
  if FMounted then
  begin
    FMounted := false;
  end;
end;

procedure TWbStorageDevice.SetAuthenticated(const NewState: boolean);
begin
  FAuthenticated := NewState;
end;

procedure TWbStorageDevice.SetOptions(const NewOptions: TWbStorageDeviceOptions);
begin
  FOptions := NewOptions;
end;

function TWbStorageDevice.GetOptions: TWbStorageDeviceOptions;
begin
  result := FOptions;
end;

procedure TWbStorageDevice.SetName(const NewName: string);
begin
  FName := NewName;
end;

procedure TWbStorageDevice.SetIdentifier(const NewId: string);
begin
  FId := NewId;
end;

procedure TWbStorageDevice.SetFileSystem(const NewFileSystem: TWbCustomFileSystem);
begin
  FFileSystem := NewFileSystem;
end;

function TWbStorageDevice.GetFileSystem: TWbCustomFileSystem;
begin
  result := FFileSystem;
end;

procedure TWbStorageDevice.Authenticate(UserName, Password: string; const Success: TWbAuthenticatedEvent);
begin
end;

procedure TWbStorageDevice.Authenticate(UserName, Password, Domain: string; Success: TWbAuthenticatedEvent);
begin
end;

procedure TWbStorageDevice.Authenticate(AuthKey: string; Success: TWbAuthenticatedEvent);
begin
end;

end.

So. Writing the foundation of a NAS front-end in Smart, a virtual desktop with a windowing toolkit took me less than 8 hours. How long would it take you in vanilla JS?

So forgive me if I dont take jQuery serious.

Advertisements
  1. February 17, 2017 at 5:47 pm

    I think you’ve missunderstood point of external GUI AddOns. Yes, you can write any control you like in SMS and it will work, but how many days do you need to write it. For example when I started using SMS/JS it took me two months (not kidding) to write good and fast grid control working on all browsers and devices. I’m sure you would have wrote it in a week but you’ve been using JS for years so there is no comparison here. It’s inpractical for new user (or any user for that matter) to write controls from scratch especially with tight deadlines (so almost always).

    Yes you can add any GUI framework to SMS and use it, but that basically means throw away all SMS visual library framework and start from bare bone JS element, it also prevents from adding controls in form editor.

    Imagine how productive SMS developer would be if he can kind of install some GUI framework (Framework7/Ionic) and get 100+ components available at click of a mouse. Also how easy would be to layout controls with frown. So some kind of smart wrapper for external controls that would keep
    what external control has and does but also add TW3CustomControl/RTL capability into it.

    We do not need JQuery, most of it is already in RTL but we sure do need more controls. Since there are many free controlsets available why not use the them inside SMS?

    • March 7, 2017 at 7:09 am

      I have no problem with that at all, but again we come back to time vs. effort vs. ease of use. Wrapping a full framework is not hard, but it does take time to get it right. Also, the entire system would have to be re-written. Then you have the problem with properties – the list goes on. There is a reason there are very few systems like this – because it becomes very complex quickly when dealing with two huge codebases: the native delphi side, and the SMS side.

      And I like the grid! Nothing wrong with that. I have looked at many different types and it all boils down to time. Some external systems are very hard to wrap due to how the system works. Some frameworks wrap the elements in js objects as well – making it hard to map instance to element.
      But i have nothing against that – I simply dont have the funding

    • March 7, 2017 at 7:14 am

      In the update there is a class called “TW3HtmlElement”. This is a hand-written representation of a html element – including the styles dictionary and everything else.
      This class makes it very easy to work with raw elements, so this should make it much easier to wrap and pascal-ify external frameworks.
      I would do it myself, but there is a limit to how much one man can do.Right now making the RTL rock solid is my first priority – and after that the IDE. When those are finally done, then I will start looking at frameworks we can import and add to the component palette

  2. February 19, 2017 at 11:36 pm

    Hi jon,

    Take a look at this post:
    http://forums.smartmobilestudio.com/index.php?/topic/4297-smart-component-generator/

    This is the SMSComponent generator that uses JHTMLElement interfaces implemented in the w3C.HTML5 unit to create lightweight visual components. The code insight/completion available in the smart IDE, such parameter/methods hits is available everywhere. We can easily assign event handlers directly on the corresponding JElement node or indirectly via a custom published property.

    • March 7, 2017 at 7:04 am

      Yes i noticed! Very cool!
      In the update we have a TW3ThinWrapper which does more or less the same. But having a generator is handy indeed! Great work!

  3. February 27, 2017 at 6:17 am

    Very Cool..!!! Will you be releasing the source code to the window/desktop framework ? (and the Grid from prior posts). Or will it be part of a new Smart Mobile Studio release RTL / Examples ?

  1. No trackbacks yet.

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: