Archive

Archive for February, 2017

Smart Pascal: A real life desktop

February 16, 2017 6 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.

20% discount on HexLicense!

February 10, 2017 Leave a comment

For a short time Quartex Components offer you the FMX, VCL and VJL package with a whopping $40 discount! That is a significant saving for a great product!

By acting now you gain¬†full access to the¬†classical component packages ‚Äď as well as the¬†next-generation licensing engine and platform:

Ironwood now supports Smart Pascal! The Delphi edition is just around the corner. Start using Hexlicense in Delphi today!

Ironwood now supports Smart Pascal! The Delphi update is just around the corner. Start using Hexlicense in Delphi today!

  • 12 month subscription
    • 4 updates guaranteed
  • Full source code
    • VCL version
    • FMX version
      • Windows
      • OS X
      • iOS
      • Android
    • JVL version (Smart)
      • All mobile platforms
      • HTML5 applications
      • Node.js client and server applications
  • Solid documentation
  • Easy to use and drop into existing projects
  • Ships with examples
  • Support via E-mail

Ironwood

ironwood2The next generation HexLicense formula and license generator is nicknamed “Ironwood”. This has been in the making for some time and is through the beta phase. It uses¬†the absolute latest RTL for Smart ‚Äď which will be in circulation within a 1 to 2¬†weeks (hopefully sooner!). So you are getting the absolute latest to play with ‚Äď which is can be used by both visual and node.js projects.

By acting now you not only save money but you also get a great deal on our classical Delphi components. Most importantly however, is that the discount buys you access to the next generation components for Delphi as well. These will retail at a higher price when they hit the market.

Smart Pascal

With mobile application development taking place more and more through HTML5 and phonegap ‚Äď not to mention that node.js is quickly becoming a market standard server-side, compilers that targets the JavaScript virtual machine is becoming increasingly important. Especially for traditional languages like Delphi and C++.

With access to Ironwood for Delphi and Smart Pascal, your existing VCL and FMX products can continue to function as they do right now ‚Äď while you move your licensing mechanisms to the cost-effective, scalable and portable node.js platform.

Why pay huge amounts of money to rent a full virtual instance to host a native¬†Delphi service¬†‚Äď when you can do the exact same under node.js for but a fraction of the price? Not to mention the many benefits node brings to the table.

Discount covers all platforms!

discountThe offer gives you the entire system, including VCL, FMX and JVL editions. You also secure access to Ironwood for Delphi.

Again, this package will retail at a higher price and forms the basis of our future cloud based licensing services.

Hexlicense for Delphi can be dropped directly into existing projects, comes with a license generator application and is considered very easy to use.

By acting now you secure early access!

Buy Now Button with Credit Cards

Note: This is a time limited offer. Only the link above this text is valid for this discount.

To read more about HexLicense, head over to the website. You can also download the documentation which is substancial and covers everything.

Smart Pascal: Changes

February 6, 2017 3 comments

The changes to Smart Mobile Studio over the past 12 months have been tremendous. At first glance they might not seem huge, but if you do a raw compare on the upcoming RTL and the now more than a year old RTL – I think you will find that there are very few places in the code where I havent done improvements.

In this post I will try to collect some of the changes. A final change log will be released together with the update, but at least this “preview” will tell you something about the hours, days, weeks and months I have put into the RTL.

The Smart Lab, this is where most of my ideas turn into code :)

The Smart Lab, this is where most of my ideas turn into code ūüôā

There will also be changes to the IDE; We have 3 members that both have added new features in the past, and members that are adding changes right now. The immediate being an update from Chromium Embedded CEF2 to CEF4, which is a huge change in speed and preview quality. And there are other more important changes being worked on, the most pressing being the designer and “live” rendering of controls.

Ok, let’s just dive into it!

RTL Changes

  • The units that make up the RTL are now organized according to a namespace scheme
    • Visual units are prefixed with SmartCL
    • Universal units are prefixed with System
    • Low level API units for node.js are prefixed with NodeJS
    • High level Node classes are prefixed with SmartNJ
  • Better fragmentation: Code that was previously a part of one very large unit has been divided into smaller files to avoid large binaries. For example:
    • ¬†TRect, TPoint etc. are now in System.Types.Graphics
    • System.Time is extended with SmartCL.Time which gives graphical timing functions like TW3Dispatch.RequestAnimationFrame()
  • The unit System.Objects has been added, isolating¬†classes that work both in browsers, node and headless runtime environments:
    • TW3ErrorObjectOptions
    • TW3ErrorObject
    • TW3OwnedErrorObject
    • TW3HandleBasedObject
  • TW3Label has been completely re-written and is now faster, has synchronized state management, resize on ready-state and much more
  • The unit¬†System.NameValuePairs was added. This implements a traditional name/value list (or dictionary) that is used by various classes and standards throughout the RTL (http headers being an example)
  • A show stopping bug in SmartCL.Effects has been fixed. Effects now works as expected (simply add SmartCL.Effects to your uses clause)
  • TFileStream has been added to SmartNJ.Streams. This is unique for node.js since only node (and some hybrid runtime engines) support direct file access.
  • TBinaryData is extended in SmartNJ.Streams.pas to emit and consume node.js buffers which are different from traditional JS buffers. ToNodeBuffer() and FromNodeBuffer() allows you to consume special node buffer types.
  • A full software tweening engine has been written from scratch in pure Smart Pascal and added to the SmartCL namespace. The following units¬†have been added:
    • SmartCL.Tween
      • TW3TweenElement
      • TW3TweenEngine
      • function TweenEngine: TW3TweenEngine
    • SmartCL.Tween.Effect
      • TW3CustomEffect
      • TW3CustomTweenEffect
      • TW3ControlTweenEffect
      • TW3MoveXEffect
      • TW3MoveYEffect
      • TW3MoveToEffect
      • TW3ColorMorphEffect
      • TW3CustomOpacityEffect
      • TW3FadeInEffect
      • TW3FadeOutEffect
      • TW3OpacityEffect
    • SmartCL.Tween.Ease
      • TW3TweenEase
      • TW3TweenEaseLinear
      • TW3TweenEaseQuadIn
      • TW3TweenEaseQuadOut
      • TW3TweenEaseQuadInOut
      • TW3TweenEaseCubeIn
      • TW3TweenEaseCubeOut
      • TW3TweenEaseCubeInOut
      • TW3TweenEaseQuartIn
      • TW3TweenEaseQuartOut
      • TW3TweenEaseQuartInOut
      • TW3TweenEaseQuintIn
      • TW3TweenEaseQuintOut
      • TW3TweenEaseQuintInOut
      • TW3TweenEaseSineIn
      • TW3TweenEaseSineOut
      • TW3TweenEaseSineInOut
      • TW3TweenEaseExpoIn
      • TW3TweenEaseExpoOut
      • TW3TweenEaseExpoInOut
      • TW3TweenEaseCollection
      • function TweenEaseCollection: TW3TweenEaseCollection
  • Support for require.js¬†(simplified resource management and more) is now a part of the RTL. The unit SmartCL.Require gives you:
    • TRequireError
    • TW3RequireJSConfig
    • TW3RequireJS
    • function Require: TW3RequireJS;
    • procedure Require(Files: TStrArray);
    • procedure Require(Files: TStrArray; const Success: TProcedureRef);
    • procedure Require(Files: TStrArray; const Success: TProcedureRef; const Failure: TW3RequireErrHandler);
  • SmartCL (browser) based communication has been consolidated into more appropriate named units:
    • SmartCL.Net.http
    • SmartCL.Net.http.headers
    • SmartCL.Net.websocket
    • SmartCL.Net.jsonp
    • SmartCL.Net.socketIO
    • SmartCL.Net.rest
  • A callback bug in the REST api has been fixed (called wrong handler on exit)
  • Suppport for mutation events has been added to the RTL. This can be found in the unit SmartCL.Observer. Mutation observer allows you to listen for changes to any HTML element, its properties or attributes. This is a very important unit with regards to data-aware controls:
    • TMutationObserverOptions
    • TMutationObserver
  • A fallback shim for mutation observing has been added to the¬†$RTL\Shims folder. This ensures that mutation events can be observed even on older browsers.
  • A MSIE legacy shim that patches all missing IE functionality from the current back to IE5 has been added. This allows Smart applications to execute without problems on older browsers (which there are surpricingly many of)
  • The unit SmartCL.Styles has been added. This contains classes and methods that create a stylesheet at runtime. The stylesheet is deleted when the object instance is disposed. This allows for easier styling from within your code rather than having to pre-define it in the global css file.
  • Polygon helper classes have been isolated in SmartCL.Polygons. These expose functionality to TPointArray and TPointFArray:
    • TPolygonHelper
    • TPolygonFHelper
  • Support for socket.io has been added for the browser. The unit SmartCL.Net.SocketIO contains the following client class and methods:
    • TW3SocketIOClient
      • procedure Connect(RemoteHost: string)
      • procedure Disconnect
      • procedure Emit(EventName: string; Data: variant)
      • procedure On(EventName: string; Handler: TW3SocketIOHandler)
  • Support for socket.io has been added to the new SmartNJ namespace as well, allowing you to write fast, good performance SocketIO servers.
  • Support for WebSocketIO, an amalgamation of classic WebSocket and SocketIO has been added. This is the default socketio server type for our node.js system
  • Support for user-attributes has been added and isolated in SmartCL.Attributes. This allows you to easily add, read, write and remove custom attributes to any tag. This is a very important feature that is used both by the effects framework – and also by the database framework.
  • SmartCl.Buffers unit has been updated. Now uses the latest methods of the RTL and now works on all browsers, not just webkit and Firefox.
  • $RTL\SmartCL\Controllers\SmartCL.Scroll.Momentum has been removed
  • $RTL\SmartCL\Controllers\SmartCL.Scroll.plain has been removed
  • All visual controls have been updated, rewritten and thoroughly tested
  • The RTL now has support for ACE, the #1 JavaScript code editor. This is more or less a JavaScript implementation of Delphi’s Synedit, but with functionality closer to Sublime. Ace comprises¬†over 100 files. In this first release we have focused on getting a respectable basis in place.
    • The wrapper units is¬†SmartCL.AceEditor, which gives you the following classes:
      • TW3AceMode
      • TW3AceTheme
      • TW3AceEditor
        • Text: string
        • SelectedText: string
        • LineCount: integer
        • WordWrap: boolean {get; set;}
        • ShowPrintMargin: boolean {get; set;}
        • EditorMode:¬†TW3AceMode {get; set;}
        • Theme: TW3AceTheme {get; set;}
    • Modes and syntax we added support for:
      • AceEdit.Mode.Abap
      • AceEdit.Mode.Abc
      • AceEdit.Mode.ActionScript
      • AceEdit.Mode.Ada
      • AceEdit.Mode.Apache
      • AceEdit.Mode.AppleScript
      • AceEdit.Mode.Ascii
      • AceEdit.Mode.AutoHotKey
      • AceEdit.Mode.Base
      • AceEdit.Mode.BatchFile
      • AceEdit.Mode.C9Search
      • AceEdit.Mode.Cirru
      • AceEdit.Mode.Clojure
      • AceEdit.Mode.Cobol
      • AceEdit.Mode.Coffee
      • AceEdit.Mode.ColdFusion
      • AceEdit.Mode.Cpp
      • AceEdit.Mode.CSharp
      • AceEdit.Mode.CSS
      • AceEdit.Mode.Curly
      • AceEdit.Mode.D
      • AceEdit.Mode.Dart
      • AceEdit.Mode.Diff
      • AceEdit.Mode.django
      • AceEdit.Mode.Pascal
      • AceEdit.Mode.VbScript
      • AceEdit.Mode.X86Asm
    • Themes we¬†support:
      • AceEdit.Theme.Ambiance
      • AceEdit.Theme.Base
      • AceEdit.Theme.Chaos
      • AceEdit.Theme.Chrome
      • AceEdit.Theme.CloudMidnight
      • AceEdit.Theme.Clouds
      • AceEdit.Theme.Cobalt
      • AceEdit.Theme.CrimsonEdit
      • AceEdit.Theme.Dawn
      • AceEdit.Theme.Dreamweaver
      • AceEdit.Theme.eclipse
      • AceEdit.Theme.Github
      • AceEdit.Theme.Monokai
      • AceEdit.Theme.Twilight
  • The RTL has a completely re-written ready-state engine. Since JavaScript is ASync by nature, the handle or reference to the TAG a class manages may not be ready during the constructor. The ready-state engine waits in the background for the handle to become valid and the control available in the DOM (document object model). It then sets the appropriate component-state flags and issues a resize to ensure that the control looks as it should.
  • Support for Control-State (which is common in Delphi and Lazarus) has been added to visual controls. The following state flags can be read or set:
    • csCreating
    • csLoading
    • csReady
    • csSized
    • csMoved
    • csDestroying
  • TW3CustomControl now supports creation-flags. This is also a feature common to Delphi’s VCL and Lazarus’s LCL. It allows you to set some flags that disable fundamental behavior when you don’t need it. For instance, a label which will always have a fixed size or be in a fixed place does not need Resize() management. Turning off resize-checking makes you application execute faster. The following creation flags can be defined:
    • cfIgnoreReadyState
    • cfSupportAdjustment
    • cfReportChildAddition
    • cfReportChildRemoval
    • cfReportMovement
    • cfReportResize
    • cfAllowSelection
    • cfKeyCapture
  • To enable or disable component states, use the following methods of TW3CustomControl:
    • procedure AddToComponentState(const Flags: TComponentState)
    • procedure RemoveFromComponentState(Const Flags: TComponentState)
  • Support for keyboard input and charcode capture has been added to the RTL. Simply add the flag cfKeyCapture to the CreationFlags() function of your custom-control and the control’s OnKeyPress event will fire when the control has focus and a key is pressed. By default this is turned off.
  • Support for text-selection and disabling text-selection has been added. Simply add or remove¬†cfAllowSelection from your controls CreationFlags() function. By default text selection is turned off (except for obvious controls like text-edit).
  • TW3CustomControl now has a zIndex property. This represents the z-order of a control (which control is in front of others).
  • The function TW3CustomControl.GetZOrderList(sort: boolean) can be called to get a sorted or un-sorted list of child elements. This provides both the handle for each control and its zindex value.
  • The method TW3CustomControl.Showing() has been updated, it now takes height for IFrame and CSS4 GPU positioning (read: it will understand that its offscreen even if you use CSS3 to move it).
  • TW3CustomControl now has a Cursor property. This allows you to read and set the mouse cursor for a control. The following cursors are supported:
    • crAuto
    • crDefault
    • crInherited
    • crURL
    • crCrossHair
    • crHelp
    • crMove
    • crPointer
    • crProgress
    • crText
    • crWait
    • crNResize
    • crSResize
    • crEResize
    • crWResize
    • crNEResize
    • crNWResize
    • crNSResize
    • crSEResize
    • crSWResize
    • crEWResize
  • To simplify mouse cursor handling, the class TW3MouseCursor, which is a static class, has been added to SmartCL.System.pas. It expose the following methods:
    • function ¬†CursorByName(const CursorName: string): TCursor
    • function ¬†NameByCursor(const Cursor: TCursor): String
    • function ¬†GetCursorFromElement(const Handle: TControlHandle): TCursor
    • procedure SetCursorForElement (const Handle: TControlHandle; const Cursor: TCursor)
  • TW3CustomControl have two new methods to deal with cursor changes. These are virtual and can be overriden:
    • ¬†function GetMouseCursor: TCursor;
    • procedure SetMouseCursor(const NewCursor: TCursor);
  • Basic device capabillity examination support has been added. The class¬†TW3DOMDeviceCapabilities has been added to SmartCL.System. This exposes the following methods and properties:
    • DevicePixelRatio: float
    • DisplayPixelsPerInch:¬†TPixelsPerInch
    • GetMouseSupport: boolean
    • GetTouchSupport: boolean
    • GetGamePadSupport: boolean
    • GetKeyboardSupported: boolean
    • GetDevicePixelRatio: float
    • GetDisplayPixelsPerInch: TPixelsPerInch
  • In order to make the VJL more architectually compatible with Delphi’s VCL and Freepascal’s LCL – TW3Component as name has been pushed back. TW3TagObj used to be the root class for visual components, followed by TW3Component, TW3MovableControl and finally TW3CustomControl. However, since we want to use TW3Component as a common non-visual control, this name was pushed back. So TW3TagObj now inherits from TW3Component, and TW3TagContainer has taken the place TW3Component once had. Here is the new inheritance chain
    • TW3CustomComponent
      • TW3Component
        • TW3TagObj
          • TW3TagContainer
            • TW3MovableControl
              • TW3GraphicControl
              • TW3CustomControl
  • TW3Component is now the basis for non-visual components, which opens up for a whole new set of controls that can be dragged onto a form or datamodule. Sadly Datamodules did not make it into this update, but it is the next logical step – hopefully we will have it in place with the IDE update that will appear after this.
    • Controllers will eventually be re-incarnated as TW3Component’s
    • Rouge classes will be implemented as TW3Components
    • Database classes will become non-visual TW3Components
    • Communication classes will become non-visual TW3Components
  • TW3Timer is moved to the unit System.Time.pas
  • TW3Timer now inherits from¬†TW3Component
  • The following procedures¬†have been deprecated and moved to System.Time.pas. Please use the corresponding methods in TW3Dispatch (Note: The w3_* methods will be deleted in the next update!):
    • w3_Callback = TW3Dispatch.Execute()
    • w3_SetInterval =¬†TW3Dispatch.SetInterval()
    • w3_ClearInterval = TW3Dispatch.ClearInterval()
    • w3_SetTimeout = TW3Dispatch.SetTimeOut()
    • w3_ClearTimeout = TW3Dispatch.ClearTimeOut()
  • TW3Dispatch, which is a class dealing with timing and scheduling, now support the following new methods:
    • class function JsNow: JDate;
    • class function Ticks: integer;
    • ¬†class function TicksOf(const Present: TDateTime): integer;
    • class function TicksBetween(const Past, Future: TDateTime): integer;
    • class procedure RepeatExecute(const Entrypoint: TProcedureRef; const RepeatCount: integer; const IntervalInMs: integer);
  • SQLite has been recompiled from C to JS and is now version 3.8.7.4
  • The SQLite units have been moved from SmartCL.SQLite to System.SQLite
  • The following SQLite classes have been given an overhaul:
    • TSQLiteDatabase
    • TSQLiteDBObject
    • TSQLiteRowValues
    • TSQLiteResult
    • TSQLParamData
    • TSQLitePair
    • TSQLiteParams
    • TSQLiteStatement
  • The SQLiteInitialize() global method is no longer requires and has been removed. When you include System.SQLite.pas in your project, the whole database engine is automatically linked and loaded into memory on execute. Use the SQLiteReady() global function to check if the database engine has loaded before use.
  • TReader, TWriter which was exclusively a re-implementation based on¬†LCL and Delphi has been fragmented. TReader and TWriter are now base classes that takes an interface as parameter in their constructor (IBinaryTransport). Both TStream and TBinaryData, which are the easiest ways of dealing with binary files and memory – implements the¬†IBinaryTransport interface.
  • TStreamReader and TStreamWriter has been added to the RTL, these are implemented in¬†System.Stream.Reader.pas and¬†System.Stream.Writer.pas. When working with streams, make sure you use TStreamReader and TStreamWriter. Using TReader and TWriter directly may cause problems if you are not intimately familiar with the RTL.
  • The unit System.Structure has been added to the RTL. This is to simplify working with structured records, abstracting you from the underlying format. The following classes are exposed in System.Structure:
    • EW3Structure [exception]
    • TW3Structure
      • procedure WriteString(Name: string; Value: string; const Encode: boolean);
      • procedure WriteInt(const Name: string; value: integer);
      • procedure WriteBool(const Name: string; value: boolean);
      • procedure WriteFloat(const Name: string; value: float);
      • procedure WriteDateTime(const Name: string; value: TDateTime);
      • function ReadString(const Name: string): string;
      • function ReadInt(const Name: string): integer;
      • function ReadBool(const Name: string): boolean;
      • function ReadFloat(const Name: string): float;
      • function ReadDateTime(const Name: string): TDateTime;
      • function Read(const Name: string): variant;
      • procedure Write(const Name: string; const Value: variant);
      • procedure Clear;
      • procedure SaveToStream(const Stream: TStream);
      • procedure LoadFromStream(const Stream: TStream);
      • procedure LoadFromFile(Url: string;¬†const callback: TW3StructureLoadedCallback);
  • The unit¬†System.Structure.JSON implements a TW3Structure class that stores data as JSON. The data is stored in memory as a single record, but naturally you can add further records as child properties – and then export the complete data as binary format to a stream, or as a single string through the ToString() method.
  • The unit System.Structure.XML implements a TW3Structure class which emits XML. Since the XML interface in browsers is both unstable and wildly different between vendors, we use our own BTree engine to manage the data. The class emits valid XML and will also deal with sub-records just as TW3JSonStructure.
  • The unit System.JSON has been added, which contains the following classes and methods. TJSON is a highly effective wrapper over the built-in JSON API, making it easier to work with JSON in general. The constructor is highly overloaded so you can wrap existing JS elements directly. This class is used by TW3JSONStructure to simplify its work:
    • TJSONObjectOptions
    • EJSONObject [exception]
    • TJSONObject
    • TJSON
  • The Delphi parser library TextCraft has been added to the RTL. When I write added that is not really correct. TextCraft was first written in Smart and then post converted to Delphi. But the RTL now has the latest update of this library, making advanced, recursive text-parsing possible:
    • System.Text.Parser.pas
    • System.Text.Parser.Words.pas
  • TW3Borders has been re-coded and optimized.
    • TW3Border.EdgeString() now use a lookup table for maximum efficiency
    • TW3Border’s edges now use a lookup table to quickly map types to strings
    • All superfluous reference testing has been removed, resulting in much faster and efficient code
    • Styles are no longer read via the older w3_read/write mechanisms but rather directly from the controlhandle
  • Two new classes has been added to deal with border-radius.
    • All controls can set the radius for all edges using the older TW3MovableControl.BorderRadius property, but this has now been expanded on. The following classes has been added. TW3BorderRadius can be accessed via the new TW3CustomControl.EdgeRadius property, which creates an instance on demand (first access):
      • TW3BorderEdgeRadius
      • TW3BorderEdgeTopRadius
      • TW3BorderEdgeBottomRadius
      • TW3BorderRadius
  • TStringBuilder has been added to System.Type. It deviates some from the Lazarus and Delphi variations because it adds functions that actually makes sense ! ūüôā
  • All custom controls have a cool background class that allows you to set color, add images and export pixmaps. A new class called¬†TW3ControlBackgroundSize has been added to this (TW3CustomControl.Background.Size) making things even easier. Especially detecting if a background is a graphic and its true size (!)
    • function ¬†BackgroundIsImage: boolean;
    • property Mode: TW3ControlBackgroundSizeMode
    • property Width: integer;
    • property Height: integer;
  • TW3Constraints (inherits from TW3OwnedObject) has been added to the RTL. This is a class that deals with size constraints for controls. Please note that this is experimental (!) When active it forces size restrictions much like the VCL and LCL — but it can cause havoc in a design that auto-scales (!). It implements the following:
    • property Enabled: boolean
    • property MinWidth: integer
    • property MinHeight: integer
    • property MaxWidth: integer
    • property MaxHeight: integer
    • procedure ¬† ApplyToOwner
    • function GetMaxWidth: integer;
    • function GetMaxHeight: integer;
    • procedure SetMaxWidth(const NewMaxWidth: integer);
    • procedure SetMaxHeight(const NewMaxHeight: integer);
    • function GetMinWidth: integer;
    • function GetMinHeight: integer;
    • procedure SetMinWidth(aValue: integer);
    • procedure SetMinHeight(aValue: integer);
    • procedure ¬† SetEnabled(const NewValue: boolean); virtual;
  • Event classes has finally been added! Need another OnClick event? just Create an event object and attach it to your control. The following event classes are now in SmartCL.Events :
    • TW3DOMEvent
    • TW3StandardDOMEvent
    • TW3MouseEnterEvent
    • TW3MouseLeaveEvent
    • TW3MouseDownEvent
    • TW3MouseMoveEvent
    • TW3MouseUpEvent
    • TW3ElementRemovedEvent
    • TW3ElementAddedEvent
    • TW3ElementContextMenuEvent
    • TW3ElementClickEvent
    • TW3ElementDblClickEvent
    • TW3ElementMouseOverEvent
    • TW3ElementKeyDownEvent
    • TW3ElementKeyPressEvent
    • TW3ElementKeyUpEvent
    • TW3ElementChangeEvent
    • TW3ElementMouseWheelEvent
    • TW3DOMEventAPI
      • class procedure RegisterEvent(Handle: TControlHandle; EventName: string; EventHandler:TW3JSEventHandler; Mode: TW3DOMEventMode); static;
      • class procedure UnRegisterEvent(Handle: TControlHandle; EventName: string; EventHandler:TW3JSEventHandler; Mode: TW3DOMEventMode); static;
  • Since events are not DOM or browser bound, they also occur in node.js and other JavaScript virtual machines (and you can also register your own events just like you do in Delphi or C#). As such I added System.Events.pas which contains the basic functionality for OOP events. The following classes and methods are added:
    • TW3SystemEventObject
      • procedure Attach(NameOfEvent: string);
      • procedure Detach;
      • property ¬†Attached: boolean
      • property ¬†EventName: string
  • TW3CustomBrowserAPI.Styles references “window.styles” which is no longer supported by browsers. This has been updated to “window.document.head.style”
  • BrowserAPI() now expose key browser objects as actual class objects. The following objects are exposed as handles only (as they have been for some time):
    • Document
    • Body
    • Window
    • Styles
    • Console
    • Navigator
    • Self
    • Event
  • Besides the above handles, we now expose direct access to the mapped class objects directly. The W3C units have been updated to reflect modern browsers (so the objects have the latest methods exposed):
    • DocumentObject: JDocument
    • BodyObject: JHTMLElement
    • WindowObject: JWindow
    • StylesObject: JCSSStyleDeclaration
    • NavigatorObject: JNavigator
    • EventObject: JEvent
  • Added missing W3C.localStorage unit file which expose the localstorage API. This can now be accessed via BrowserAPI().WindowObject.localStorage. It supports the following functions:
    • property ¬†key[const keyId: string]: variant
    • property ¬†length: integer
    • procedure setItem(const key: string; const Value: variant)
    • procedure removeItem(const key: string)
    • procedure clear
    • function ¬†valueOf: variant [* prototype]
    • function ¬†hasOwnProperty(const key: string): boolean¬†[* prototype]
  • TW3Image now has a fitstyle property which wraps the “object-fit” css style. This gives you finer control over how the image is presented inside the control (or the background of a control). The following presentation styles are supported:
    • ¬†fsNone
      Image will ignore the height and width of the parent and retain its original size.
    • fsFill
      This is the default value which stretches the image to fit the content box, regardless of its aspect-ratio.
    • fsContain
      Increases or decreases the size of the image to fill the box whilst preserving its aspect-ratio.
    • fsCover
      The image will fill the height and width of its box, once again maintaining its aspect ratio but often cropping the image in the process.
    • fsScaleDown
      The control will compare the difference between fsNone and fsContain in order to find the smallest concrete object size.
  • TW3Image now supports binary IO besides the common HTML src property. The following methods have been added to TW3Image:
    • function ¬†ToBuffer: TBinaryData;
    • function ¬†ToStream: TStream;
    • function ¬†ToDataUrl: string;
    • function ¬†ToImageData: TW3ImageData;
    • procedure LoadFromURL(aURL: String);
    • procedure LoadFromImageData(Const Data:TW3ImageData);
    • procedure LoadFromBinaryData(const Memory:TBinaryData);
    • procedure LoadFromStream(const Stream:TStream);
    • procedure Allocate(aWidth,aHeight:Integer);
  • TW3Image now expose 3 new properties that are very helpful. The PixelWidth and PixelHeight gives you the actual size of an image, regardless of scale or viewport proportions. This is handy when using the Allocate() method to create a pixel-buffer that matches an existing image:
    • property ¬†PixelWidth: integer
    • property ¬†PixelHeight: integer
    • property ¬†Complete: boolean
  • The following procedures and functions have been removed. Faster to assign and deal with events directly on the handle – no need for the extra call:
    • procedure w3_bind
    • procedure w3_bind2
    • procedure w3_unbind
    • procedure w3_unbind2
  • TStreamHelper has been added to SmartCL.System. It adds data objectification. Generating an internal URL to access the data via a link. These functions wraps the createObjectURL API (https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL) The following methods are in the helper:
    • function ¬†GetObjectURL: string;
    • procedure RevokeObjectURL(const ObjectUrl: string);
  • TAllocationHelper has been added to SmartCL.System. It does exactly the same as TStreamHelper but for the memory class TAllocation (system.memory.allocation.pas).
  • TBinaryData which is a very flexible and fast memory buffer class (system.memory.buffer.pas), inherits from TAllocation – as such TAllocationHelper also affects this class.
  • The class¬†TW3URLObject, which is a static class, has been added to SmartCL.System.pas. It wraps the same API as TStreamHelper and TAllocationHelper, but operates independent of medium. This class also adds forced download methods – where you can start a “save as” of binary data (or any data) with a single call. The class interface contains the following:
    • class function ¬†GetObjectURL(const Text, Encoding, ContentType, Charset: string): string;
    • class function ¬†GetObjectURL(const Text: string): string;
    • class function ¬†GetObjectURL(const Stream: TStream): string;
    • class function ¬†GetObjectURL(const Data: TAllocation): string;
    • class procedure RevokeObjectURL(const ObjectUrl: string);
    • class procedure Download(const ObjectURL: string; Filename: string);
    • class procedure Download(const ObjectURL: string; Filename: string;
      const OnStarted: TProcedureRefId);
  • TW3CustomControl now have automatic ZIndex assignment. On creation a ZIndex value is automatically assigned to each control (auto-inc number). The following methods have been added for that value:
    • function ¬†GetZIndex: integer;
    • procedure SetZIndex(const NewZIndex: integer);
  • To better deal with z-order and the creation sequence of child elements, the following methods have been added to TW3CustomControl. This makes it easier to control how child elements stack and overlap:
    • function ¬†GetZOrderList(const Sort: boolean): TW3StackingOrderList;
    • function ¬†GetChildrenSortedByYPos: TW3TagContainerArray;
    • function ¬†GetChildrenSortedByXpos: TW3TagContainerArray;
  • TW3CustomControl’s Invalidate() method has previously done nothing. It has been present for TW3GraphicControl (which inherits from TW3CustomControl) as means of redrawing or refreshing graphics.
    Invalidate for non-graphic controls now calls resize() to update the layout. While coders should use BeginUpdate / EndUpdate, there are times when a call to Invalidate() is all you need.
  • Text-shadow, which is a very effective CSS effect for text, has been isolated in its own class (TW3TextShadow) and is now exposed as TW3CustomControl’s TextShadow property. The class is created on-demand and will not consume memory until used.
  • TW3TagStyle, a class that parses and allows you to add, check and remove CSS style-rules to your control has been updated. The previous parsing routine has been replaced by a much faster string.split() call – removing the need for the for/next loop used to extract styles previously. This class is exposed as TW3CustomControl’s “TagStyle” property. Like Text-Shadow it is created on demand.
    Note: This replaces the older “CssClasses” property which has now been deprecated.
  • The TW3AnimationFrame class has been moved from SmartCL.Components.pas to SmartCL.Animation.pas which seems more fitting.
  • TControlHandleHelper is a strict helper-class for TControlHandle, which is the handle type used for HTML elements in the DOM. Many of the rouge utility functions that were scattered around the RTL have now been isolated here. It now gives you the following functionality:
    • function Defined: boolean;
    • function UnDefined: boolean;
    • function Valid: boolean;
    • function Equals(const Source: TControlHandle): boolean;
    • function Ready: Boolean;
    • function GetChildById(TagId: string): TControlHandle;
    • function GetChildCount: integer;
    • function GetChildByIndex(const Index: integer): TControlHandle;
    • function GetChildOf(TagType: string; var Items: TControlHandleArray): boolean;
    • function GetChildren: TControlHandleArray;
    • function HandleParent: TControlHandle;
    • function HandleRoot: TControlHandle;
    • procedure ReadyExecute(OnReady: TProcedureRef);
    • procedure ReadyExecuteEx(const Tag: TObject; OnReady: TProcedureRefEx);
    • procedure ReadyExecuteAnimFrame(OnReady: TProcedureRef);
  • function w3_GetCursorCSSName,¬†w3_GetCursorFromCSSName has been removed from SmartCL.System.pas – use the methods in the static class TW3MouseCursor instead
  • w3_DOMReady,¬†w3_CSSPrefix and¬†w3_CSSPrefixDef has been removed from SmartCL.System.pas – use the methods in BrowserAPI instead
  • The RTL now has its own stylesheet classes. The unit SmartCL.CSS.StyleSheet.pas and SmartCL.CSS.Classes represents the CSS management units for the system.

    TSuperstyle (see below) generates animation styles on-the-fly and returns the name for it. You can add this name to a control with Control.TagStyle.add().

    TCSS is a helper class for building complex styles via code. TSuperstyle uses this to create the animation effects. Note that these two classes are meant as examples (!) SmartCL.CSS.StyleSheet contains the following classes:

    • TW3StyleSheet
      • property ¬† ¬†Handle: TControlHandle
      • property ¬† ¬†StyleSheet: JCSSStyleSheet
      • property ¬† ¬†StyleRules: JCSSRuleList
      • property ¬† ¬†Count: integer
      • property ¬† ¬†Items[const Index: integer]: string
      • class function ¬†CreateStyleId: string;
      • class function ¬†CreateStylesheetId: string;
      • class function ¬†CreateStyleElement: TControlHandle;
      • class procedure DisposeStyleElement(const Handle: TControlHandle);
      • class function DefaultStylesheet: TW3StyleSheet;
      • function GetSheetObject: THandle;
      • function GetRuleObject: THandle;
      • function GetCount: integer;
      • function GetItem(const Index: integer): string;
    • TSuperStyle
      • class function EdgeRound(Size: integer): string; overload;
      • class function EdgeRound(TopLeftP, TopRightP, BottomLeftP, BottomRightP: integer):String; overload;
      • class function EdgeTopaz: string;
      • class function EdgeAngaro: string;
      • class function AnimGlow(GlowFrom, GlowTo: TColor): string;
      • class procedure AnimStart(Handle: TControlHandle; animName: String);
    • TCSS
      • function KeyFrames: TCSS;
      • function From: TCSS;
      • function &To: TCSS;
      • function Enter: TCSS;
      • function Leave: TCSS;
      • function PercentOf(Value: integer): TCSS;
      • function IntOf(Value: integer): TCSS;
      • function ColorOf(Value: TColor): TCSS;
      • function &inc(Value: string): TCSS;
      • function CRLF: TCSS;
      • function OpenParam: TCSS;
      • function CloseParam: TCSS;
      • function Background: TCSS;
      • function BoxShadow(Left, Top, Right, Bottom: integer): TCSS;overload;
      • function BoxShadow(Left, Top, Right, Bottom: integer; Color:TColor):TCSS;overload;
      • function LinearGradientV(aFrom, aTo: TColor): TCSS;
      • function LinearGradientH(aFrom, aTo: TColor): TCSS;
      • function LinearGradientTL(aFrom, aTo: TColor): TCSS;
      • function LinearGradientTR(aFrom, aTo: TColor): TCSS;
      • function BeginComplexGradient(Angle: integer): TCSS;
      • function EndComplexGradient: TCSS;
      • function ColorPercent(PercentOf: integer; Color: TColor): TCSS;
      • function LinearGradientAngle(aFrom, aTo: TColor; const Angle: float): TCSS;overload;
      • function LinearGradientAngle(Colors: Array of TColor; const Angle: float): TCSS; overload;
      • function AnimationName(Name: string): TCSS;
      • function AnimationDuration(Secs, MSecs: integer): TCSS;
      • function AnimationInfinite: TCSS;
      • class function Make: TCSS;
      • function Assign(Value: string): TCSS;

Controllers

A new concept has been added to the RTL, namely something called controllers. These are classes that attach to a control and either alter its behavior or exposes functionality that is not available by default.

SmartCL.Controller.EdgeSense

This unit exposes the class TW3EdgeSenseController, which can be attached to any visual control. Once attached the controller will fire events whenever the mouse-pointer or touch is close to the border of the control. This is typically used by controls that is expected to be resized, like a grid header column.

SmartCL.Controller.Swipe

This unit provides the following classes:

  • TW3SwipeControllerOptions
  • TW3SwipeRange
  • TW3SwipeController

The TW3SwipeController attaches itself to any visual control and will trigger an event if a swipe gesture is performed on the target control. Since gesures such as swipe is one of the most common gestures, it made sense to isolate this in particular.

This controller is presently used by one of the new components, TW3Win10Header, which is a html5 implementation of the Windows 10 mobile header. This is a good-looking control that allows you to swipe through categories, much like a page-control for Windows applications, but better suited for mobile designs.

SmartCL.Controller.TagAttributes

This controller implements access to custom data attributes for any visual control. It is essentially the functionality of TW3ElementAttributes (unit: SmartCL.Attributes= but here implemented as a controller.

The class used by the RTL for this behavior today is TW3ElementAttributes, but this will be deprecated in the future in favour of the controller.

SmartCL.Scroll.IScroll

iScroll is now the default scrolling mechanism for visual Smart Mobile Studio applications. The new TW3ScrollWindow and TW3CustomScrollList visual classes all derive their fantastic accurate scrolling and movement capabilities from iScroll – which by many is regarded as the defacto JavaScript scrolling library.

To make iScroll easier to use when writing custom controls that don’t inherit from TW3ScrollWindow or TW3CustomScrollList – you can use the controller instead. Simply create an instance, attach it to the parent container (which contains the elements you want to scroll) and you pretty much have instant iOS / Android scrolling.

Note: $Libraries\IScroll\iScroll.js has been updated to iScroll 5.2.0, which works fine on older Android devices (there were some problems with iScroll before). I have also updated our class implementation to include ALL options, events and functionality.

New Visual Controls

Indeed. While more or more controls is going to be added once the IDE update is in place, we at least have a few new controls in place that can liven up your applications.

First of all, iScroll is now (as explained) the default scroll mechanism in Smart Mobile Studio. When you combine that with the already impressive list of effects (smartcl.effects.pas) writing controls that gives visual feedback when you touch them, that scroll smoothly with momentum, bounce and snap to – is now almost ridiculously simple.

TW3DBGrid

First up is the long-awaited DB grid. This is a fast paced grid that can show thousands of elements without any significant speed penalty. This is because it creates the elements as they come into view – and it uses a very effective cache system to keep track of which elements (rows / cols) have been created, and which needs some attention.

grid

It also allows you to resize and move columns as you would expect from a native grid, so hopefully this first incarnation is just what you expected.

TW3CategoryHeader

Next we have the fancy Windows 10 mobile category header control. This is a touch driven, swipe responsive header that shows both a category and a small ingress text.

categories

This is an excellent control to create with Application.Display as a parent. That way it remains on top regardless of form – and the user can quickly navigate through swipes.

TW3SimpleLabel

We have also added a more lightweight label control. This is best suited for static text that don’t need horizontal alignment. It is a lot more resource friendly than TW3Label which is a composite custom control.

Ace editor control

Ace, the #1 Javascript code editor, akin to Delphi’s SynEdit or the popular Sublime editor, has likewise been added. This should make a lot of things easier, especially if you are creating front-end code for your cloud services (which you can now write in pristine node.js).

ace

TW3LayoutControl

TW3LayoutControl is another nice addition. This is essentially a scrollbox with classical rulers top and left. Perfect for image display programs, paint programs, layout designers and similar tasks. You can override and adapt this heavily and make it the basis for a calendar grid if you like.

We have also completely updated, re-written and quality checked every single control in the RTL. They are now more robust, they update and synchronize with their ready-state more or less like native controls do – and I have spent quite some time making sure they behave as they should.

Oh and yes, there are more controls (this post is getting long).

NodeJS high-level classes

Without a doubt the feature that has received most attention in this update are the Node.JS high level classes and units.

The previous NodeJS namespace and project type gave you more or less the bare-bones of node.js. This could be quite confusing for developers coming straight from Delphi or Lazarus. Especially if you don’t really want to dive to deep into the world of JavaScript – but enjoy some abstraction and ease of use.

The new SmartNJ namespace, where the files also are prefixed with the sane, implements common classes for both clients and servers. Writing an HTTP server is for instance very simple – and the code is in many cases as close to the other server types as possible.

The idea is that if you know how to write one type of server, you should also be able to write other types as well; because they share the same ancestor and commonalities.

As of writing the following server types are supported:

  • UDP
  • HTTP
  • Websocket (WebSocketIO)

The class architecture looks more or less like this:

  • TNJCustomServer
    • TNJUDPService
      • TNJUDPServer
    • TNJHTTPServer
    • TNJWebSocketServer
  • TNJCustomClient
    • TNJUDPService
      • TNJUDPClient
    • TNJHttpClient
    • TNJWebSocketClient

Clients and servers have traditional auxillary objects, such as request and response objects:

  • TNJServerChildClass
    • TNJCustomServerRequest
      • TNJHttpRequest
    • TNJCustomServerResponse
      • TNJHttpResponse

The following units make up the SmartNJ namespace today:

  • SmartNJ.Filesystem
  • SmartNJ.Network.Bindings
  • SmartNJ.Network
  • SmartNJ.Server
  • SmartNJ.Server.Http
  • SmartNJ.Server.Udp
  • SmartNJ.Server.WebSocket
  • SmartNJ.Streams
  • SmartNJ.System
  • SmartNJ.Udp
  • SmartNJ.WebSocket

Unified filesystem

In the previous versions of the RTL we presented several ways to store information. We had thin wrappers over cookies, local storage and filestorage. You could also use one of the 3 database engines to store data (sandboxed).

These unit still ship with the RTL, so should you want to pick one in particular that is of course not a problem. But in order to streamline and make IO as uniform as possible, I have introduced the notion of a standard “filesystem”.

The filesystem base-class can be found (as expected) in the system.filesystem.pas file. This contains the abstract class that makes up the most common filesystem operations.

NodeJS gives you a filesystem class that has direct access to the real files and folders, so be careful when you use that. As expected this filesystem implementation can be found under SmartNJ.Filesystem.pas.

And last but not least you will find a filesystem unit for visual browser applications, under SmartCL.Filesystem.pas.

The idea here is that you can write the exact same code and it will work regardless of which platform you run on.The same code you write for node.js will then save or load data in the browser as well.

This system will be expanded to include Cordova Phonegap, NodeWebkit (a hybrid post-compiler to make desktop applications) and embedded platforms.

Note: NodeJS is special in that it gives you both an asynchronous filesystem and blocking filesystem. If you want to use the blocking version, simply access it via the blocking interface (see unit file). But we strongly suggest that you avoid this since that will have a huge impact on performance. You must never use the blocking IO interface on servers, expect perhaps during startup Рit will completely undermine the node.js runtime scheme and yield terrible performance.

Database support

This has been a pressing issue for quite some time, and while we are close, we still need to add more infrastructure before we can wrap the engines we want. Although classes will be available soon – being able to visually hook up data in common RAD style is what we want.

For that to happen I need the following features in the IDE:

  • Datamodules
  • Designer drop-zone for non-visual components
  • Design support for property mapping

Without these features database support under node.js will be flawed at best. But fret not, it is being worked on and we are presently looking at the following engines:

Client side we will provide the same DB API, but here it will wrap the existing engines:

  • WebSQL [deprecated by W3C]
  • SQLite [default]
  • TW3Dataset
  • IndexDB

The reason this is more complicated than it may look is first of all because IndexDB and MongoDB are object based databases, not SQL based. So the architecture really needs property mapping and the IDE to back it up. Naturally it can be done in pure code (which we have started) but the IDE with the built-in database designer is what will make this more sane to work with.

Action support

Yes, finally it is here. TCustomAction and TAction has been added to the RTL, and appropriate controls now expose an Action property that you can assign to.

Again we need the next update before this becomes more easy to work with. You need to be able to drop a TW3ActionManager on your form or datamodule, and then assign that via the property inspector to the control.

The IDE will automatically generate the “glue” code on compile for you, and it will execute as a part of your {$I ‘form: impl’} section and set things up.

Right now you have to create and assign actions manually — but at least it’s finally in place. Which is really good because that allows me to write default-actions for various tasks just like you have in Delphi, C++ builder and Lazarus.

Final words

This has been a preview, not the full load. There are many other changes to the codebase, some of the changes quite radical. Hopefully you find this interesting and will enjoy using it as much as we have enjoyed creating it.

Things that havent been covered yet are:

  • Pixi.js
  • Plumb.js
  • Stacktrace support
  • Codec framework
  • eSpeak
  • Vector graphics display control
  • Support for Scetchpad 3d models
Loading HD 3d models into your smart applications is now possible (and easy). Stills doesnt do this model justice ..

Loading HD 3d models into your smart applications is now possible (and easy). Stills doesnt do this model justice ..

And yes, I have also added support for asset management. This wont be fully visible before the IDE update, but being able to pre-load pictures, sounds and resources before your application starts is now simplicity itself. As a bonus we also added support for Require() which is more or less the de-facto JavaScript way of loading assets (and more) these days. You even get a sweet callback when its done or when something goes wrong.

Stay tuned! I’m working as fast as I can

/Jon

Smart Pascal: Cool tricks for better code

February 2, 2017 1 comment

What is the most costly operation for a HTML5 application? While there are many to pick from the most time-consuming tasks are without a doubt sizing elements and creating elements.

Both of these operations are time-consuming (in cpu terms) because they have a direct impact on the entire layout. In other words, when you create a visible element (which happens when a Smart visual control is created), the entire DOM is affected. The same naturally happens when you adjust the size of an element, because the browser will go through all it’s nodes and re-build its calculated stylesheet (which is a hidden stylesheet that the browser amalgams together).

Since this is the case it makes sense to try to avoid sizing controls as much as possible. You can’t completely avoid it of course – but the less change to width and height the better.

Use the percentages

The Smart Pascal RTL follows the traditional, Delphi and LCL esquire component model. This means that the position and width or height of a control is measured in pixels. But HTML, as you no doubt are aware of, can also work in percentages. This is a lot faster since the browser completely takes care of sizing.

A neat trick you can use is to alter the size of a child control using percentages Рand this way avoid any manual calls to Resize(). It all depends on the situation of course, but if (for example) you have a child control that should always be the total width of its parent Рyou can in fact directly set the width to 100%.

Since the width and height properties are managed by the RTL you want to avoid altering those. They expect values to be in pixels, so changing these values can result in unexpected side-effects. But you can modify the minimum and maximum size styles without affecting the RTL.

w3_setStyle(FContent.Handle, 'min-width', '100%');
w3_setStyle(FContent.Handle, 'min-height', '100%');

The above code takes a child control (“FContent” in this example) and forces it to cover 100% of the parent’s surface. This will work as long as the actual width property does not exceed 100%. If your container is 200 pixels wide, the above code will work fine unless you manually change width to be 201 or more.

I actually use this quite often, especially when I create any form of lists, listboxes or menu systems. Normally the child items is expected to cover the whole width of the parent (a row in a grid for example), with variable height. In that case I can just adjust the height and leave the width to the browser.

Pre calculate content

Like i mentioned above the most time-consuming tasks are size-changes and creation of elements. Of the two, creation of controls is by far the most time consuming for the document object model.

While the RTL gives you controls that are more or less compatible with Delphi or LCL, they have the downside of being quite heavy. There is a lot of code involved which gives the components great depth – but that depth comes at a cost.

What the browser does really fast however, is to create elements in bulk. Just stop and think about it for a while. If creating elements is so time-consuming – then why does pages appear almost instantly? Well, if you examine the webkit HTML renderer you will discover that parsing and creating elements en-mass is highly optimized. This is sadly not the case for the createElement() function that Smart uses to create components at runtime.

What this means is that its faster to create 1000 child elements as raw HTML and inject the text into the DOM – than it is to create 1000 controls. And not just fast: extremely fast!

But then we face a problem, namely that our RTL does something very useful for us: it keeps track of handles for each element and exposes the functionality as object pascal. If we just dump in a ton of HTML then how are we going to locate, use and manipulate our child elements?

A thin wrapper

This is where I tend to use a thin wrapper. This is a class designed to introduce as little code as possible – and only expose the underlying functionality of the document object model. The DOM is actually quite rich in functions even though they can be intimidating at first.

Here is a thin wrapper I use quite a lot.

unit SmartCL.ThinWrapper;

interface

uses
  System.Types, System.Colors,
  SmartCL.System, SmartCL.Graphics, SmartCL.Components,
  SmartCL.Fonts, SmartCL.Borders;

type

  TElementArray = class external
  protected
    function GetItems(const Index : integer) : TControlHandle; external array;
  public
    property length: integer;
    property items[const Index : integer] : TControlHandle read GetItems; default;
  end;

  TWrappedElement = class
  private
    FHandle:  TControlHandle;
  protected
    function  ValueToInt(const Value: variant): integer;
    function  IntToPixels(const Value: integer): string;
    function  GetColor: TColor;
    procedure SetColor(const NewColor: TColor);
  public

    class function GetElementById(const Parent: TControlHandle;
              const ChildId: string): TControlHandle; overload;

    class function GetElementsByType(const Parent: TControlHandle;
              TagName: string;
              var Items: array of TControlHandle): boolean; overload;

  public
    property  Handle: TControlHandle read FHandle;

    property  Id: string
              read  ( FHandle.id )
              write ( Fhandle.id := Value );

    property  Title: string
              read  ( FHandle.title )
              write ( FHandle.title := Value );

    // Offset
    property  OffsetLeft: integer
              read  (ValueToInt(FHandle.offsetLeft))
              write (FHandle.offsetLeft := IntToPixels(Value));

    property  OffsetTop: integer
              read  (ValueToInt(FHandle.offsetTop))
              write (FHandle.offsetTop := IntToPixels(Value));

    property  OffsetWidth: integer
              read  (ValueToInt(FHandle.offsetWidth))
              write (FHandle.offsetWidth := IntToPixels(Value));

    property  OffsetHeight: integer
              read  (ValueToInt(FHandle.offsetHeight))
              write (FHandle.offsetHeight := IntToPixels(Value));

    // Scroll
    property  ScrollLeft: integer
              read  (ValueToInt(FHandle.scrollLeft))
              write (FHandle.scrollLeft := IntToPixels(Value));

    property  ScrollTop: integer
              read  (ValueToInt(FHandle.scrollTop))
              write (FHandle.scrollTop := IntToPixels(Value));

    property  ScrollWidth: integer
              read  (ValueToInt(FHandle.scrollWidth))
              write (FHandle.scrollWidth := IntToPixels(Value));

    property  ScrollHeight: integer
              read  (ValueToInt(FHandle.scrollHeight))
              write (FHandle.scrollHeight := IntToPixels(Value));

    // Client
    property  clientLeft: integer
              read  (ValueToInt(FHandle.clientLeft))
              write (FHandle.clientLeft := IntToPixels(Value));

    property  clientTop: integer
              read  (ValueToInt(FHandle.clientTop))
              write (FHandle.clientTop := IntToPixels(Value));

    property  clientWidth: integer
              read  (ValueToInt(FHandle.clientWidth))
              write (FHandle.clientWidth := IntToPixels(Value));

    property  clientHeight: integer
              read  (ValueToInt(FHandle.clientHeight))
              write (FHandle.clientHeight := IntToPixels(Value));

    // Node
    property  NodeName: string read (FHandle.nodeName);
    property  NodeType: string read (FHandle.nodeType);
    property  NodeValue: variant
              read  (FHandle.nodeValue)
              write (FHandle.nodeValue := Value);

    property  Children: TElementArray read ( TElementArray(FHandle.children) );

    property  InnerHTML: string
              read  ( Handle.innerHTML )
              write ( Handle.innerHTML := Value);

    property  InnerText: string
              read  ( Handle.innerText )
              write ( Handle.innerText := Value);

    property  Color: TColor read GetColor write SetColor;

    function  Wrap(const Handle: TControlHandle): TWrappedElement;

    function  GetElementById(const Id: string): TControlHandle; overload;

    function  GetElementsByType(const TagName: string;
              var Items: array of TControlHandle): boolean; overload;

    procedure Click;

    constructor Create(TagId: string); overload; virtual;
    constructor Create(Parent: TControlHandle; TagId: string); overload; virtual;
    constructor Create(TagHandle: TControlHandle); overload; virtual;
  end;

implementation

//#############################################################################
// TWrappedElement
//#############################################################################

constructor TWrappedElement.Create(TagId: String);
begin
  inherited Create;
  FHandle := BrowserAPI.Body.GetChildById(TagId);
end;

constructor TWrappedElement.Create(TagHandle: TControlHandle);
begin
  inherited Create;
  FHandle := TagHandle;
end;

constructor TWrappedElement.Create(Parent: TControlHandle; TagId: string);
begin
  inherited Create;
  FHandle := Parent.GetChildById(TagId);
end;

procedure TWrappedElement.Click;
begin
  FHandle.click();
end;

function TWrappedElement.Wrap(const Handle: TControlHandle): TWrappedElement;
begin
  result := TWrappedElement.Create( Handle );
end;

function TWrappedElement.IntToPixels(const Value: integer): string;
begin
  result := Value.ToString() + 'px';
end;

function TWrappedElement.ValueToInt(const Value: variant): integer;
begin
  asm
    if (@Value)
    {
      if (typeof(@Value) === "number") {
        @result = @Value
      } else {
        if (typeof(@Value) === "string")
        {
          @Value = parseInt(@Value);
          if (!isNaN(@Value))
            @result = @Value;
        }
      }
    } else {
      @result = 0;
    }
  end;
end;

function TWrappedElement.GetColor: TColor;
begin
  if (FHandle) then
  result := StrToColor( w3_getStyleAsStr(FHandle, 'backgroundColor') ) else
  result := clNone;
end;

procedure TWrappedElement.SetColor(const NewColor: TColor);
begin
  if (FHandle) then
  begin
    if NewColor <> clNone then
    FHandle.style.backgroundColor := ColorToWebStr(NewColor) else
    FHandle.style.backgroundColor := 'transparent';
  end;
end;

class function TWrappedElement.GetElementsByType(const Parent: TControlHandle;
         TagName: string;
         var Items: array of TControlHandle): boolean;
begin
  if (parent) then
  begin
    asm
      @items = (@Parent).getElementsByTagName(@TagName);
    end;
  end else
  Items.Clear();
  result := Items.Count > 0;
end;

function TWrappedElement.GetElementsByType(const TagName: string;
         var Items: array of TControlHandle): boolean;
begin
  if (FHandle) then
  begin
    asm
      @items = (@FHandle).getElementsByTagName(@TagName);
    end;
  end else
  Items.Clear();
  result := Items.Count > 0;
end;

class function TWrappedElement.GetElementById(const Parent: TControlHandle;
         const ChildId: string): TControlHandle;
begin
  if (Parent) then
  begin
    result := Parent.getElementById(ChildId);
    if not (result) then
      result := Parent.getElementById( ChildId.ToLower().Trim() );
  end else
  result := unassigned;
end;

function TWrappedElement.GetElementById(const Id: string): TControlHandle;
begin
  if Id.Length > 0 then
  begin
    result := FHandle.getElementById(Id);
    if not (result) then
      result := FHandle.getElementById( Id.ToLower().Trim() );
  end else
  result := unassigned;
end;

end.

If you are pondering how on earth is that going to help, here is how it works.

All Smart controls are simply JavaScript code designed to manage a real, underlying HTML element. The default element type TW3CustomControl creates is DIV. Controls like TW3TextBox overrides the function that creates this element and replace that with an input element instead. And other controls do the same.

So just because something is not visible to a fully blown TW3CustomControl, doesn’t mean it’s not there. And by using the wrapper we can easily hook rouge or non classified html elements without creating them.

Let’s for example say you inject a bit of HTML into a panel, like this:

procedure TForm1.MakeHTML;
var
  x: integer;
  LHtml: string;
begin
  // make X number of div items
  for x:=1 to 100 do
  begin
    LHtml += Format('
<div id="obj%d">Item #%d</div>
', [x,x]);
  end;

  // inject into our panel
  W3Panel1.InnerHTML := LHtml;
end;

To access one of these DIV elements (which we now have 100 of), we can use our thin wrapper to make it programatically easier:

procedure TForm1.MakeHTML;
var
  x: integer;
  LHtml: string;
  LItem: TWrappedElement;
begin
  // make X number of div items
  for x:=1 to 100 do
  begin
    LHtml += Format('
<div id="obj%d">Item #%d</div>
', [x,x]);
  end;

  // inject into our panel
  W3Panel1.InnerHTML := LHtml;

  // Create wrapper for item #12
  // We pass the handle to the parent (which is the form)
  // and the name. The class will find the element
  LItem := TWrappedElement.Create(W3Panel1.Handle, 'obj12');
end;

Voila! LItem can now be used just like any other Smart control. But remember that this is a thin wrapper, meaning that you are actually digging into the document object model directly.

I should mention a few words about the browser’s calculated stylesheet. One of the things you are going to notice is that width and height is not always going to be correct. This is not due to our code, but because the browser always regards your values as proposals, not absolutes.

So even if you set a control to say, 400px in width – depending on the situation the browser may find it more suitable to set 389px instead. And if you make the mistake of reading it back from the stylesheet – it will report 400px. But this is because the stylesheet is regarded as a proposal.

What you need to do is to write to the stylesheet, but read from the calculated styles. This is why the Smart RTL calls the w3_getStyleAsInt() function when reading these values.

Just so you know in case you think the wrapper is reporting wrong values. It’s not. The browser just works differently because of CSS. Cascading means that styles will merge together, so a button can have 50 gradients assigned to it – and they will all be amalgamated into one and represented in the calculated stylesheet as a single whole.

Other tricks

I could go on for days but I think these two will be handy for now. I would urge you to examine and learn how to use the GetElementByType() and GetElementById() so you get familiar with navigating the DOM like that. GetElementByType() is really cool – it allows you to extract a list of items based on type.

So to get all the DIV elements you can simply do:

var LDivs := LItem.GetElementByType(W3Panel1.Handle, 'div');

I should also mention that a thin-wrapper is only as good as you make it. The code above was never made to do the same as TW3CustomControl can. There is no cosy class wrapping of fonts, constraints, borders or reading of style properties. To enjoy these high-level RTL functions you will have to stick to the RTL.

But, for cases where you want to pre-generate relatively simple elements, like rows in a listbox or some form of menu items – a thin wrapper can mean the difference between useless and brilliant.

Oh, you might be interested in a “Styles” wrapper. I have only fleshed out a handfull of properties, but it does make low-level access a lot easier. If you finish it PM me. The documentation can be found here: http://www.w3schools.com/jsref/dom_obj_style.asp

  TWrappedStyle = class external
  public
    // stretch|center|flex-start|flex-end|space-between|space-around|initial|inherit
    property  alignContent: string;

    // stretch|center|flex-start|flex-end|baseline|initial|inherit
    property  alignItems: string;

    // auto|stretch|center|flex-start|flex-end|baseline|initial|inherit
    property  alignSelf: string;

    // http://www.w3schools.com/jsref/prop_style_animation.asp
    property  animation: string;

    // time|initial|inherit
    property  animationDelay: string;

    // normal|reverse|alternate|alternate-reverse|initial|inherit
    property  animationDirection: string;

    // time|initial|inherit
    property  animationDuration: string;

    // none|forwards|backwards|both|initial|inherit
    property  animationFillMode: string;

    // number|infinite|initial|inherit
    property  animationIterationCount: string;

    // none|keyframename|initial|inherit
    property  animationName: string;

    // linear|ease|ease-in|ease-out|cubic-bezier(n, n, n, n)|initial|inherit
    property  animationTimingFunction: string;

    // running|paused|initial|inherit
    property  animationPlayState: string;

    // color image repeat attachment position size origin clip|initial|inherit
    property background: string;

    // scroll|fixed|local|initial|inherit
    property backgroundAttachment: string;

    // color|transparent|initial|inherit
    property backgroundColor: string;

    // url('URL')|none|initial|inherit
    property backgroundImage: string;

    // http://www.w3schools.com/jsref/prop_style_backgroundposition.asp
    property backgroundPosition: string;

    // repeat|repeat-x|repeat-y|no-repeat|initial|inherit
    property backgroundRepeat: string;

    // border-box|padding-box|content-box|initial|inherit
    property backgroundClip: string;

    // padding-box|border-box|content-box|initial|inherit
    property backgroundOrigin: string;
  end;

Well, more and more tricks will surface, so stay tuned guys!

Smart Pascal: BTree storage anyone?

February 1, 2017 Leave a comment

btreeDictionaries are cool but they are only as good as the mechanisms supporting them. So I figured I could see if we could get more bang for the buck with a dedicated BTree class for Smart Pascal.

If you are wondering what on earth a BTree routine is, head over to Wikipedia and gander up on the technical side here: https://en.wikipedia.org/wiki/B-tree. In short it allows you to store some data connected to a string. Actually, its connected to a value identifier Рbut we can do a checksum of a string and use that as the key. So dictionary. Sort of.

What is special about the Smart Pascal version? Well, for one it doesn’t use pointers. And secondly you can save it to a stream. Typically classes like this don’t ship with a SaveToStream() method because it’s mostly intended to be used at runtime. But JavaScript have a few perks that force us to think differently.

Hope you enjoy it!


unit BTree;

interface

uses
  System.Types,
  System.Types.Convert,
  System.JSON,
  System.NameValuePairs,
  System.Streams,
  System.Stream.Writer,
  System.Stream.Reader;

type

  TBTreeNode = class(JObject)
  public
    Ident: integer;
    Data: variant;
    Left: TBTreeNode;
    Right: TBTreeNode;
  end;

  TBTreeFileItem = record
    fiIdent:  integer;
    fiData: variant;
  end;

  TBTreeProcess = procedure (const Node: TBTreeNode; var Cancel: boolean);

  TBTree = class(TObject)
  private
    FRoot:  TBTreeNode;
    FCurrent: TBTreeNode;
  public
    property  Root: TBTreeNode read FRoot;
    property  Empty: boolean read ( FRoot = nil );

    function  Add(const Ident: integer; const Data: variant): TBTreeNode;overload;virtual;
    function  Add(const Ident: string; const Data: variant): TBTreeNode;overload;virtual;

    function  &Contains(const Ident: integer): boolean;overload;virtual;
    function  &Contains(const Ident: string): boolean;overload;virtual;

    function  Remove(const Ident: integer): boolean;overload;virtual;
    function  Remove(const Ident: string): boolean;overload;virtual;

    function  Read(const Ident: integer): variant;overload;virtual;
    function  Read(const Ident: string): variant;overload;virtual;

    procedure Write(const Ident: string; const NewData: variant);overload;virtual;
    procedure Write(const Ident: integer; const NewData: variant);overload;virtual;

    procedure Clear;overload;virtual;
    procedure Clear(const Process: TBTreeProcess);overload;virtual;

    function  ToArray: TVarArray;
    function  ToString: string;
    function  Size: integer;

    function  ToJSON: string;
    procedure FromJSON(const Data: string);

    procedure SaveToStream(const Stream: TStream);
    procedure LoadFromStream(const Stream: TStream);

    procedure ForEach(const Process: TBTreeProcess);

    constructor Create;
  end;


implementation


(* These are the IO signatures used for storage *)
const
CNT_BTREE_STREAM_HEADER = $BABE0001;
CNT_BTREE_ITEM_HEADER = $0001BABE;

//#############################################################################
// TBTree
//#############################################################################

constructor TBTree.Create;
begin
  inherited Create;
  FRoot := nil;
  FCurrent := nil;
end;

procedure TBTree.Clear;
begin
  FCurrent := nil;
  FRoot := nil;
end;

procedure TBTree.Clear(const Process: TBTreeProcess);
begin
  ForEach(Process);
  Clear;
end;

function TBTree.ToJSON: string;
begin
  if FRoot<>nil then
    result := Json.stringify(FRoot);
end;

procedure TBTree.FromJSON(const Data: string);
begin
  if not empty then
    Clear;
  FRoot := TBTreeNode( JSON.Parse(data) );
end;

procedure TBTree.SaveToStream(const Stream: TStream);
var
  LWriter: TStreamWriter;
  LData: Array of TBTreeFileItem;
  LRaw: TByteArray;
  LItem: TBTreeFileItem;
begin

  (* First, cache up all the data in an array. We need to do this
     in order to write the node-count on top of the file *)
  ForEach( procedure (const Node: TBTreeNode; var Cancel: boolean)
  begin
    LItem.fiIdent := Node.Ident;
    LItem.fiData := Node.Data;
    LData.add(LItem);
  end);

  LWriter := TStreamWriter.Create(Stream);
  try
    (* Write the magic identifier for the file *)
    LWriter.WriteInteger(CNT_BTREE_STREAM_HEADER);

    (* Write the number of items in the file *)
    LWriter.writeinteger(LData.Count);

    (* Now write each item *)
    for LItem in LData do
    begin
      (* Convert variant to byte-array *)
      LRaw := TDataType.VariantToBytes(LItem.fiData);

      (* Write the identifier for the record *)
      LWriter.WriteInteger(CNT_BTREE_ITEM_HEADER);

      (* Write the item-id *)
      LWriter.WriteInteger(LItem.fiIdent);

      (* Write the # of bytes in the data section *)
      LWriter.WriteInteger(LRaw.Count);

      (* Write the data section *)
      LWriter.Write(LRaw);
    end;
  finally
    LWriter.free;
  end;
end;

procedure TBTree.LoadFromStream(const Stream: TStream);
var
  LReader: TStreamReader;
  LHead: integer;
  LId: integer;
  LBytes: integer;
  LRaw: TByteArray;
  LValue: variant;
  LCount: integer;
begin
  (* Flush content if not empty *)
  if not Empty then
    Clear;

  (* Setup the reader *)
  LReader := TStreamReader.Create(Stream);
  try

    (* Validate the header *)
    LHead := LReader.ReadInteger;
    if LHead = CNT_BTREE_STREAM_HEADER then
    begin
      (* Get the count *)
      LCount := LReader.ReadInteger;

      while LCount>0 do
      begin
        LHead := LReader.ReadInteger;
        if LHead = CNT_BTREE_ITEM_HEADER then
        begin
          (* Read the identifier *)
          LId := LReader.ReadInteger;

          (* read the # of bytes for the variant *)
          LBytes := LReader.ReadInteger;

          (* Flush any lingering data *)
          LRaw.Clear;

          (* Read the raw data that makes up the variant *)
          if LBytes>0 then
          begin
            LRaw := LReader.read(LBytes);

            (* Convert from bytes to intrinsic *)
            LValue := TDatatype.BytesToVariant(LRaw);

            (* Add to tree *)
            self.Add(Lid, LValue);
          end;

        end else
        raise EW3Exception.CreateFmt('Invalid item header, expected %d not %d',
        [CNT_BTREE_ITEM_HEADER,LHead]);

        dec(LCount);
      end;

    end else
    raise EW3Exception.CreateFmt('Invalid stream header, expected %d not %d',
    [CNT_BTREE_STREAM_HEADER,LHead]);

  finally
    LReader.free;
  end;
end;

function TBTree.Size: integer;
var
  LCount: integer;
begin
  ForEach( procedure (const Node: TBTreeNode; var Cancel: boolean)
    begin
      inc(LCount);
    end);
  result := LCount;
end;

function TBTree.ToArray: TVarArray;
var
  Data: TVarArray;
begin
  ForEach( procedure (const Node: TBTreeNode; var Cancel: boolean)
    begin
      Data.add(Node.Ident);
    end);
  result := data;
end;

function TBTree.ToString: string;
begin
  for var x in ToArray do
  begin
    result += TVariant.AsString(x) + #13;
  end;
end;

function TBTree.Add(const Ident: string; const Data: variant): TBTreeNode;
begin
  result := Add( TString.CalcCRC(Ident), Data);
end;

function TBTree.Add(const Ident: integer; const Data: variant): TBTreeNode;
var
  LNode:  TBTreeNode;
begin
  LNode := new TBTreeNode;
  LNode.Ident := Ident;
  LNode.Data := data;

  if (FRoot = nil) then
  FRoot := LNode;

  FCurrent := FRoot;

  while (true) do
  begin
    if (Ident < FCurrent.Ident) then
    begin
      if (FCurrent.left = nil) then
      begin
        FCurrent.left := LNode;
        break;
      end else
      FCurrent := FCurrent.left;
    end else
    if (Ident > FCurrent.Ident) then
    begin
      if (FCurrent.right = nil) then
      begin
        FCurrent.right := LNode;
        break;
      end else
      FCurrent := FCurrent.right;
    end else
    break;
  end;
  result := LNode;
end;

function TBTree.Read(const Ident: string): variant;
begin
  result := Read( TString.CalcCRC(Ident) );
end;

function TBTree.Read(const Ident: integer): variant;
begin
  Result := unassigned;
  FCurrent := FRoot;
  while (FCurrent <> nil) do
  begin
    if (Ident < FCurrent.Ident) then
    FCurrent := Fcurrent.left else
    if (Ident > Fcurrent.Ident) then
    FCurrent := FCurrent.Right else
    begin
      result := FCUrrent.Data;
      break;
    end
  end;
end;

procedure TBTree.Write(const Ident: string; const NewData: variant);
begin
  Write(TString.CalcCRC(Ident), NewData);
end;

procedure TBTree.Write(const Ident: integer; const NewData: variant);
begin
  FCurrent := FRoot;
  while (FCurrent <> nil) do
  begin
    if (Ident < FCurrent.Ident) then
    FCurrent := Fcurrent.left else
    if (Ident > Fcurrent.Ident) then
    FCurrent := FCurrent.Right else
    begin
      FCurrent.Data := NewData;
      break;
    end
  end;
end;

function  TBTree.&Contains(const Ident: string): boolean;
begin
  result := &Contains(TString.CalcCRC(Ident));
end;

function TBTree.&Contains(const Ident: integer): boolean;
begin
  Result := false;
  if FRoot <> nil then
  begin
    FCurrent := FRoot;

    while ( (not Result) and (FCurrent <> nil) ) do
    begin
      if (Ident < FCurrent.Ident) then
      FCurrent := Fcurrent.left else

      if (Ident > Fcurrent.Ident) then
        FCurrent := FCurrent.Right else
      begin
        Result := true;
      end
    end;
  end;
end;

function TBTree.Remove(const Ident: string): boolean;
begin
  result := Remove(TString.CalcCRC(Ident));
end;

function TBTree.Remove(const Ident: integer): boolean;
var
  LFound: boolean;
  LParent: TBTreeNode;
  LChildCount: integer;
  LReplacement,
  LReplacementParent: TBTreeNode;
begin
  LFound := false;
  LParent := nil;
  FCurrent := FRoot;

  while (not LFound) and (FCurrent<>nil) do
  begin
    if (Ident < FCurrent.Ident) then
    begin
      LParent := FCurrent;
      FCurrent:= FCurrent.left;
    end else
    if (Ident > FCurrent.Ident) then
    begin
      LParent := FCurrent;
      FCurrent := FCurrent.right;
    end else
    begin
      LFound := true;
    end;

    if (LFound) then
    begin
      LChildCount:=0;
      if (FCurrent.left<>nil) then inc(LChildCount);
      if (FCurrent.right<>nil) then inc(LChildCount);
      //LChildCount := (if FCurrent.left <> nil then 1 else 0) + (if FCurrent.right <> nil then 1 else 0);
      if (FCurrent = FRoot) then
      begin
        case (LChildCOunt) of
        0: FRoot := nil;
        1: FRoot := if FCurrent.right = nil then FCurrent.left else FCurrent.Right;
        2: begin

            LReplacement := FRoot.left;
            while (LReplacement.right <> nil) do
            begin
              LReplacementParent := LReplacement;
              LReplacement := LReplacement.right;
            end;

            if (LReplacementParent <> nil) then
            begin
              LReplacementParent.right := LReplacement.Left;
              LReplacement.right := FRoot.Right;
              LReplacement.left := FRoot.left;
            end else
            LReplacement.right := FRoot.right;
          end;
        end;

        FRoot := LReplacement;
      end else
      begin
        case LChildCount of
        0:  if (FCurrent.Ident < LParent.Ident) then
            Lparent.left  := nil else
            LParent.right := nil;
        1:  if (FCurrent.Ident < LParent.Ident) then
            begin
              if (FCurrent.Left = NIL) then
              LParent.left := FCurrent.Right else
              LParent.Left := FCurrent.Left;
            end else
            begin
              if (FCurrent.Left = NIL) then
              LParent.right := FCurrent.Right else
              LParent.right := FCurrent.Left;
            end;
        2:  begin
              LReplacement := FCurrent.left;
              LReplacementParent := FCurrent;

              while (LReplacement.right <> nil) do
              begin
                LReplacementParent := LReplacement;
                LReplacement := LReplacement.right;
              end;
              LReplacementParent.right := LReplacement.left;

              LReplacement.right := FCurrent.right;
              LReplacement.left := FCurrent.left;

              if (FCurrent.Ident < LParent.Ident) then
              LParent.left := LReplacement else
              LParent.right := LReplacement;
            end;
          end;
        end;
      end;
  end;

  result := LFound;
end;

procedure TBTree.ForEach(const Process: TBTreeProcess);

  function ProcessNode(const Node: TBTreeNode): boolean;
  begin
    (* Default to false. If true is defined here, the operation
       has been canceled by the user *)
    result := false;

    if (Node <> nil) then
    begin

      (* Process left path first *)
      if (node.left <> nil) then
      begin
        result := ProcessNode(Node.left);
        if result then
        exit;
      end;

      (* process midpoint *)
      Process(Node, result);
      if result then
      exit;

      (* current right path *)
      if (Node.right <> nil) then
      begin
        result:=ProcessNode(Node.right);
        if result then
        exit;
      end;
    end;
  end;

begin
  ProcessNode(FRoot);
end;

end.