Archive

Archive for May, 2016

Selecting text, Smart Mobile Studio

May 31, 2016 Leave a comment

A really good question came my way regarding text selection and why ordinary browser behavior is disabled in Smart Mobile Studio applications. It was Jarto Tarpio that posted it on the SMS Facebook page, and I was a little surpriced at first to say the least.

Now the VJL was designed to look and feel more or less identical to any ordinary, native UI framework. This means that ordinary HTML text selection (marking text with your mouse) is something you dont want to have.

But what Jarto pointed out was that there is something fishy about how it’s implemented in Smart at the moment, because even when he removed the CSS rules that disables this, selecting text is still not possible.

That one line ..

Two RTL updates ago I remember dealing with this exact topic. I was happy to find that CSS had a couple of universal rules you could use, which meant that i could remove the code I was using to avoid selection ever taking place.

Sadly, I must have forgotten all about it (or it got accidentally filtered out during a manual unit merge) because it was till there (!) In other words not only did the CSS make sure you couldnt enable text selection, the startup code for all TW3TagObj derived classes kidnaps the OnSelectStart event — effectively killing initializing a selection at all.

Patching the RTL yourself

The change you need is basically a “one liner”, and it wont affect your programs at all. If you open up SmartCL.Components (right click the unit in your units clause), hit ALT + F which brings up the search dialog and then enter “TW3CustomControl.HookEvents” that’s where the problem lies:

procedure TW3CustomControl.HookEvents;
begin
  inherited;
  Handle['onclick'] := @CBClick;
  //w3_bind2(Handle, 'onselectstart', CBNoBehavior); //Remove this line !!
  w3_bind2(Handle, 'onfocus', CBFocused);
  w3_bind2(Handle, 'onblur', CBLostFocus);
end;

So simply delete the first “w3_bind2” call, the one that assigns OnSelectStart to the “no operation” event handler CBNoBehavior — now go to your main form unit, type something and then click Save.

Note: The editor doesnt monitor RTL files for edit-changes since these units are not meant to be edited. So you have to alter something in your project and force the IDE to allow your change to be saved.

Making selection an option

With that nasty (and no longer needed) line of code out of the way – I have added two new methods to the VJL that gives you 100% control over content selection. This will be in the next update but for those that cant wait, the following methods have been added to TW3MovableControl:

function GetContentSelectionMode: TW3ContentSelectionMode;virtual;
procedure SetContentSelectionMode(const NewMode: TW3ContentSelectionMode);virtual;

Here is the code if you want to get these features straight away:


type
  TW3ContentSelectionMode = (
    tsmNone,
    tsmAuto,
    tsmText,
    tsmAll,
    tsmElement
    );

function TW3MovableControl.GetContentSelectionMode: TW3ContentSelectionMode;
begin
  var CurrentMode := Handle.style[BrowserAPI.PrefixDef("user-select")];
  if (CurrentMode) then
  begin
    case TVariant.AsString(CurrentMode).ToLower() of
    'auto': result := tsmAuto;
    'text': result := tsmText;
    'all': result := tsmAll;
    'element': result := tsmElement;
    else
      result := tsmNone;
    end;
  end else
  begin
    Handle.style[w3_CSSPrefixDef("user-select")] := 'none';
    result := tsmNone;
  end;
end;

procedure TW3MovableControl.SetContentSelectionMode
  (const NewMode: TW3ContentSelectionMode);
begin
  case NewMode of
  tsmAuto:  Handle.style[w3_CSSPrefixDef("user-select")] := 'auto';
  tsmAll:   Handle.style[w3_CSSPrefixDef("user-select")] := 'all';
  tsmText:  Handle.style[w3_CSSPrefixDef("user-select")] := 'text';
  tsmNone:  Handle.style[w3_CSSPrefixDef("user-select")] := 'none';
  tsmElement: Handle.style[w3_CSSPrefixDef("user-select")] := 'element';
  end;
end;

Writing your custom controls with selection turned on

Ok, first let’s write a class that allows text-selection. We are going to use a DIB, which is the default element of TW3CustomControl, so this will be a short example:

type

TSelectTestControl = class(TW3CustomControl)
protected
  procedure ObjectReady;override;
public
  property Text:string read (GetInnerText) write (SetInnerText(Value));
end;

procedure TSelectTestControl.ObjectReady;
begin
  inherited;
  SetContentSelectionMode(tsmText);
end;

Since user-select is set to “none” in all our CSS style themes, enabling editing means telling the DOM (document object model) that this control does allow text selection. In the above example I do that in the ObjectReady method, just to make sure the DIV TSelectTestControl represent is in the clear and has been created successfully and injected into the DOM.

So lets go back to our main form and create an instance of our new control. You should also drop a TW3DIVHtmlElement on the form first, so we have something to compare with.

Ok, here is the form unit completed:

unit Form1;

interface

uses
  System.Colors, SmartCL.Controls.Elements, SmartCL.System,
  SmartCL.Graphics, SmartCL.Components, SmartCL.Forms,
  SmartCL.Fonts, SmartCL.Borders, SmartCL.Application;

type

  (* our spanking new control. Not a TW3DIVHtmlElement in sight! *)
  TSelectTestControl = class(TW3CustomControl)
  protected
    procedure ObjectReady;override;
  public
    property Text:string read (GetInnerText) write (SetInnerText(Value));
  end;

  TForm1 = class(TW3Form)
  private
    {$I 'Form1:intf'}
  protected
    procedure InitializeForm; override;
    procedure InitializeObject; override;
    procedure Resize; override;
  end;

implementation

{ TForm1 }

uses system.types, system.time;

procedure TSelectTestControl.ObjectReady;
begin
  inherited;
  SetContentSelectionMode(tsmText);
end;

procedure TForm1.InitializeForm;
begin
  inherited;
  // this is a good place to initialize components
  var LTemp := TSelectTestControl.Create(self);
  LTemp.background.fromColor(clRed);
  LTemp.Text:="this is some text";
  LTemp.setBounds(10,10,200,200);
  W3DivHTMLElement1.innerhtml := "<i>This is some text";
end;

procedure TForm1.InitializeObject;
begin
  inherited;
  {$I 'Form1:impl'}
end;

procedure TForm1.Resize;
begin
  inherited;
end;

initialization
  Forms.RegisterForm({$I %FILE%}, TForm1);
end.

And the results are exactly what we wanted:

select text

The red box is our control, as you can see you can now mark the text

Final words

I know this has been a topic many people have asked about in the past. But this time we have made sure this is possible (and possible per individual control).

Well what are you waiting for! Go patch that RTL right now and get cracking 🙂

 

Momentum Scrolling

May 31, 2016 5 comments

Momentum scrolling is something we havent had as an option in the VJL directly. We excluded it initially because there were excellent JavaScript libraries especially for this (like iScroll), but in retrospect I guess it wouldnt hurt to have it in the VJL written in object pascal.

Here is a little something I slapped together the other day. Im going to make both TListbox and the ordinary content containers have an option for this.

scroller

Oooo.. sexy sexy scroller thingy!

 

Note: This supports both mouse and touch, and if you are confused about the event objects then head over to Github and snag a copy of that there. Just remove the references to units you dont have and include eventobjs.pas in your uses clause!

The call to SetInitialTransformationStyles() should be replaced with (this makes the browser mark the element for GPU, which is very fast):

    FContent.Handle.style[BrowserAPI.Prefix('transformStyle')] := 'preserve-3d';
    FContent.Handle.style[BrowserAPI.Prefix('Perspective')] := 800;
    FContent.Handle.style[BrowserAPI.Prefix('transformOrigin')] := '50% 50%';
    FContent.Handle.style[BrowserAPI.Prefix('Transform')] := 'translateZ(0px)';

Oh and it fades out the indicator after a scroll session, quite nice if I say so myself 🙂

Enjoy!

unit Form1;

interface

uses
  System.types, System.Colors,
  System.Events, System.Time, System.Widget, System.Objects,

  W3C.Date, W3C.DOM,

  SmartCL.Effects,

  SmartCL.Events, SmartCL.MouseCapture, SmartCL.System, SmartCL.Graphics,
  SmartCL.Components, SmartCL.Forms,  SmartCL.Fonts, SmartCL.Borders,
  SmartCL.Application, SmartCL.Controls.Listbox, SmartCL.Controls.Panel,
  SmartCL.Controls.CheckBox, SmartCL.Controls.Button;

type

  TScrollContent = class(TW3CustomControl)
  end;

  TW3ScrollIndicator = class(TW3CustomControl)
  end;

  TW3VScrollControl = class(TW3CustomControl)
  private
    FYOffset: integer;
    FContent: TScrollContent;
    FVRange:  TW3Range;
    FHRange:  TW3Range;
    FPressed: boolean;
    FStartY:  integer;

    FTarget: integer;
    FAmplitude: double;
    FTimestamp: integer;
    FVelocity: double;
    FFrame: double;
    FTicker: TW3DispatchHandle;
    FFader: TW3DispatchHandle;
    FTimeConstant: double;

    FMouseDownEvent: TW3DOMEvent;
    FMouseUpEvent: TW3DOMEvent;
    FMouseMoveEvent: TW3DOMEvent;
    FTouchDownEvent: TW3DOMEvent;
    FTouchMoveEvent: TW3DOMEvent;
    FTouchEndsEvent: TW3DOMEvent;

    FIndicator: TW3ScrollIndicator;
    function  GetYPosition(const E: variant): integer;
    procedure MoveBegins(sender: TObject; EventObj: JEvent);
    procedure MoveEnds(sender: TObject; EventObj: JEvent);
    procedure MoveUpdate(sender: TObject; EventObj: JEvent);
    procedure HandleContentSizeChanged(sender: TObject);
  protected
    procedure Track;virtual;
    procedure AutoScroll;virtual;

    procedure ScrollBegins;virtual;
    procedure ScrollEnds;virtual;

    procedure Resize;override;
    procedure InitializeObject; override;
    procedure FinalizeObject; override;
    procedure ObjectReady;override;
    procedure ScrollY(const NewTop: integer);
  public
    Property  Content:TScrollContent read FContent;
  end;

  TForm1 = class(TW3Form)
    procedure W3Button1Click(Sender: TObject);
  private
    {$I "Form1:intf"}
    FBox: TW3VScrollControl;
  protected
    procedure InitializeForm; override;
    procedure InitializeObject; override;
    procedure Resize; override;
  end;

implementation

//###################################################################
// TW3VScrollControl
//###################################################################

procedure TW3VScrollControl.InitializeObject;
begin
  inherited;
  FPressed:=false;
  FYOffset := 0;
  FStartY := 0;

  FTimeConstant := 325;

  Background.fromColor(clWhite);
  FContent := TScrollContent.Create(self);
  FIndicator:=TW3ScrollIndicator.Create(self);
  FIndicator.width:=8;
  FIndicator.height:=32;
  FIndicator.StyleClass:='TW3ScrollContentIndicator';
  FIndicator.Transparent := true;

  FMouseDownEvent := TW3DOMEvent.Create(self);
  FMouseDownEvent.Attach("mousedown");
  FMouseDownEvent.OnEvent := @MoveBegins;

  FMouseMoveEvent := TW3DOMEvent.Create(self);
  FMouseMoveEvent.Attach("mousemove");
  FMouseMoveEvent.OnEvent := @MoveUpdate;

  FMouseUpEvent := TW3DOMEvent.Create(self);
  FMouseUpEvent.Attach("mouseup");
  FMouseUpEvent.OnEvent := @MoveEnds;

  FTouchDownEvent := TW3DOMEvent.Create(self);
  FTouchDownEvent.Attach("touchstart");
  FTouchDownEvent.OnEvent:= @MoveBegins;

  FTouchMoveEvent := TW3DOMEvent.Create(self);
  FTouchMoveEvent.Attach("touchmove");
  FTouchMoveEvent.OnEvent := @MoveUpdate;

  FTouchEndsEvent := TW3DOMEvent.Create(self);
  FTouchEndsEvent.Attach("touchend");
  FTouchEndsEvent.OnEvent := @MoveEnds;

  FContent.Handle.ReadyExecute(
  procedure ()
  begin
    (* Mark content for GPU acceleration *)
    FContent.SetInitialTransformationStyles;
  end);
end;

procedure TW3VScrollControl.ObjectReady;
begin
  inherited;
  FContent.OnReSize := HandleContentSizeChanged;
  FIndicator.left:=ClientWidth-FIndicator.width;
  FIndicator.bringToFront;
  FIndicator.Visible:=false;
  resize;
end;

procedure TW3VScrollControl.FinalizeObject;
begin
  FContent.free;
  inherited;
end;

procedure TW3VScrollControl.HandleContentSizeChanged(sender: TObject);
begin
  if not (csDestroying in ComponentState) then
  begin
    FVRange := TW3Range.Create(0, FContent.Height - ClientHeight);
    FHRange := TW3Range.Create(0, FContent.Width - ClientWidth);
  end;
end;

procedure TW3VScrollControl.Resize;
var
  LClient:  TRect;
begin
  inherited;
  if (csReady in ComponentState) then
  begin
    LClient := ClientRect;
    FVRange := TW3Range.Create(0, FContent.Height - LClient.Height);
    FHRange := TW3Range.Create(0, FContent.Width - LClient.Width);
    FContent.SetBounds(0,FContent.top,LClient.Width,FContent.height);
    FIndicator.MoveTo(ClientWidth-FIndicator.Width,FIndicator.top);
  end;
end;

procedure TW3VScrollControl.ScrollY(const NewTop: integer);
var
  LGPU: string;
  LIndicatorTarget: integer;

  function GetRelativePos:double;
  begin
    result := (ClientHeight - FIndicator.Height) / (FContent.Height - ClientHeight);
  end;

begin
  if not (csDestroying in ComponentState) then
  begin
    if (csReady in ComponentState) then
    begin
      (* Use GPU scrolling to position the content *)
      FYOffset := FVRange.ClipTo(NewTop);
      LGPU := "translate3d(0px,";
      LGPU += FloatToStr(-FYOffset) + "px, 0px)";
      FContent.Handle.style[BrowserAPI.Prefix("Transform")] := LGPU;

      (* Use GPU scrolling to position the indicator *)
      LIndicatorTarget := FYOffset * GetRelativePos;
      FIndicator.left := clientwidth - FIndicator.width;
      LGPU :="translateY(" + TInteger.ToPxStr(LIndicatorTarget) + ")";
      FIndicator.Handle.style[BrowserAPI.Prefix("Transform")]:= LGPU;
    end;
  end;
end;

procedure TW3VScrollControl.Track;
var
  LNow: integer;
  Elapsed: integer;
  Delta: double;
  V: double;
begin
  LNow := TW3Dispatch.JsNow.now();
  Elapsed := LNow - FTimestamp;
  FTimestamp := TW3Dispatch.JsNow.now();
  Delta := FYOffset - FFrame;
  FFrame := FYOffset;
  v := 1000 * Delta / (1 + Elapsed);
  FVelocity := 0.8 * v + 0.2 * FVelocity;
end;

procedure TW3VScrollControl.ScrollBegins;
begin
  TW3Dispatch.ClearInterval(FFader);
  if not (csDestroying in ComponentState) then
  begin
    FIndicator.Visible := true;
    FIndicator.AlphaBlend := true;
    FIndicator.Opacity := 255;
  end;
end;

procedure TW3VScrollControl.ScrollEnds;
begin
  TW3Dispatch.ClearInterval(FFader);
  if not (csDestroying in ComponentState) then
  begin
    FFader:=TW3Dispatch.SetInterval(procedure ()
      begin
        FIndicator.AlphaBlend := true;
        FIndicator.Opacity := FIndicator.Opacity - 10;
        if FIndicator.Opacity=0 then
        begin
          TW3Dispatch.ClearInterval(FFader);
        end;
      end,
      50);
  end;
end;

procedure TW3VScrollControl.AutoScroll;
var
  Elapsed: integer;
  Delta: double;
begin
  if FAmplitude<>0 then
  begin
    Elapsed := TW3Dispatch.JsNow.now() - FTimestamp;
    Delta := -FAmplitude * Exp(-Elapsed / FTimeConstant);
  end;

  (* Scrolled passed end-of-document ? *)
  if (FYOffset >= (FContent.Height - ClientHeight)) then
  begin
    TW3Dispatch.ClearInterval(FTicker);
    FTicker := unassigned;
    ScrollY(FContent.Height-ClientHeight);
    ScrollEnds;
    exit;
  end;

  (* Scrolling breaches beginning of document? *)
  if (FYOffset < 0) then   begin     TW3Dispatch.ClearInterval(FTicker);     FTicker := unassigned;     ScrollY(0);     ScrollEnds;     exit;   end;   if (delta > 5) or (delta < -5) then   begin     ScrollY(FTarget + Delta);     W3_RequestAnimationFrame(AutoScroll);   end else   begin     ScrollY(FTarget);     ScrollEnds;   end; end; function TW3VScrollControl.GetYPosition(const e: variant): integer; begin   if ( (e.targetTouches) and (e.targetTouches.length >0)) then
  result := e.targetTouches[0].clientY else
  result := e.clientY;
end;

procedure TW3VScrollControl.MoveBegins(sender: TObject; EventObj: JEvent);
begin
  FPressed := true;
  FStartY := GetYPosition(EventObj);
  FVelocity := 0;
  FAmplitude := 0;
  FFrame := FYOffset;
  FTimestamp := TW3Dispatch.JsNow.now();
  TW3Dispatch.ClearInterval(FTicker);
  FTicker := TW3Dispatch.SetInterval(Track,100);
  EventObj.preventDefault();
  EventObj.stopPropagation();
end;

procedure TW3VScrollControl.MoveUpdate(sender: TObject; EventObj: JEvent);
var
  y, delta: integer;
begin
  if FPressed then
  begin
    y := GetYPosition(eventObj);
    delta := (FStartY - Y);
    if (Delta>2) or (Delta < -2) then     begin       FStartY := Y;       ScrollY(FYOffset + Delta);     end;   end;   EventObj.preventDefault();   EventObj.stopPropagation(); end; procedure TW3VScrollControl.MoveEnds(sender: TObject; EventObj: JEvent); begin   FPressed := false;   TW3Dispatch.ClearInterval(FTicker);   if (FVelocity > 10) or (FVelocity < -10) then
  begin
    FAmplitude := 0.8 * FVelocity;
    FTarget := round(FYOffset + FAmplitude);
    FTimeStamp := TW3Dispatch.JsNow.Now();

    ScrollBegins;
    w3_requestAnimationFrame(autoscroll);
  end;
  EventObj.preventDefault();
  EventObj.stopPropagation();
end;

{ TForm1 }

procedure TForm1.W3Button1Click(Sender: TObject);
begin
  self.FBox.Content.height:=1000;
end;

procedure TForm1.InitializeForm;
begin
  inherited;

  // this is a good place to initialize components
  FBox := TW3VScrollControl.Create(self);
  FBox.SetBounds(10,10,300,300);

  //

  var LText :="
<table cellpadding=|0px| style=|border-collapse: collapse| width=|100%|>";
  for var x:=1 to 400 do
  begin
    if ((x div 2) * 2) = x then
    LText += "
<tr padding=|0px| style=|border: 0px solid black; background:#ECECEC|>" else
    LText += "
<tr style=|border: 0px solid black; background:#FFFFFF|>";
    LText += "
<td padding=|0px| height=|32px| style=|border-bottom: 1px solid #ddd|>" + x.toString + "</td>
";
    LText += "
<td style=|border-bottom: 1px solid #ddd|>List item #" + x.toString + "</td>
";
    LText += "</tr>
";
  end;
  LText +="</table>
";
  LText := StrReplace(LText,'|','''');

  FBox.Content.innerHTML := LText;
  FBox.Content.width:=1000;
  FBox.Content.height := FBox.Content.ScrollInfo.ScrollHeight;

end;

procedure TForm1.InitializeObject;
begin
  inherited;
  {$I "Form1:impl"}
end;

procedure TForm1.Resize;
begin
  inherited;
  if (csReady in ComponentState) then
  begin
    //FBox.setBounds(10,10,clientwidth div 2, clientHeight div 2);
  end;
end;

initialization
begin
  Forms.RegisterForm({$I %FILE%}, TForm1);
end;

end.

Giving away Raptor Invoice, with source

May 30, 2016 Leave a comment

Like most developers I have a ton of projects. Some are not used, others are sold and yet more are in the making.

In the spirit of teaching Delphi I am now giving away one of my best products of all time, Torro Invoice (also sold as Raptor Invoice in Norway) for free. With source. It is a 100% fully working, fast and well organized invoicing system!

Raptor

Raptor is perfect for small to medium sized companies that ship less than 20.000 invoices a year. This was first coded 14 years ago, but it has been updated as laws and tax regulations have changed

Just not enough time

Raptor, as it was initially named, was created around 14 years ago. It is not poorly written side project – but a commercial class invoicing system. I sold around 2000 copies of this and there are quite a few using it around the contry.

I have also updated the program over the years, especially with new tax laws (Norwegian), but like most invoicing systems – the math is universal. So it doesnt matter if you are living in India or France, calculating taxes, overdue fees and inkasso percentages are pretty much identical world-wide.

Raptor02

Creating new databases is easy, and you can copy over customers etc. to the new DB from an existing database – including the running id-numbers

I am giving it away because I quite frankly dont have the time to tinker with it anymore. So instead of this great program that I have used myself (and continue to use) should just collect dust – or suffer poor customer support… well, It’s now in the hands of the Object Pascal community!

Norwegian to English

Since it’s a Norwegian product all text is in Norwegian. But it’s really simple stuff and anyone should be able to port it over to English in 2-3 work days with little more than google translate.

To get you started, here are a few “key” words:

  • Faktura = Invoice
  • Fakturer = Finalize invoice, once finalized you are not allowed to edit the invoice. You can copy it or create a credit-note. This is standard accounting laws everywhere I think.
  • Skriv ut faktura = Print invoice (skriv = write, ut = out).
    Note: Remember to check-off the print option when you finalize an invoice, or it will print X copies for you (defaults to 3 copies. One for the customer, one for accounting and one for archive).
  • Mva = Vat (Ex mva = before vat, Ink mva = with vat)
  • Leverandør = Supplier
  • Produkt = Product (mhm).
  • Faktura register = invoice database
    In Raptor you can create as many registers (databases) as you want. When you create a new register you can also choose to copy over customers, suppliers, products and the invoice-id. This happens automatically when the year is over (you are informed in january if you try to use the register from last december).
  • Åpne = Open
  • Lukk = Close
  • Kreditnota = Credit note (also called “credit memorandum”):
    A “credit note must be created to adjust for a wrong invoice already in the ledger. It is also used on product returns or if a customer has payed to much”. To simplify: It creates an invoice in minus to make up for an invoice in plus. When you create a credit note, it is directly connected to the initial invoice.
    In most contries you print 3 duplicates of both invoice and memorandums, one for the customer, one for accounting, and one for the IRS (or similar institution).
  • Kunde gruppe = Customer group. Organize your customers and give good customers a fixed “percent-off”
  • Purre gebyr = reminder fee, if you keep having to re-send a bill to a customer that doesnt pay, you are allowed to add a fee for each payment (regulated, so the percentage you should use here depends on where you live).
    After 2 reminders of payment due, you are allowed send an inkasso warning.
  • Inkasso varsel = Inkasso warning
    After having sent 2 or 3 payment due notices, you can start sending inkasso warnings. This has higher percentages connected to it (again, check with your accountant what the norm is where you live).In Norway companies usually operate with 2 strikes, and if the bill remains unpaied, you typically send the whole case to an inkasso company (payment collector) that buys the debt from you.
  • Inn pris = In price (what you pay for the product)
  • Ut pris = Selling price
  • Avanse = Sales factor, how much you sell the product for (typically x2 or x3 the price you pay your supplier). This is variable and can be overriden.
  • Journal = Ledger
  • Antall = Quanta (number of items)
    Post nummer = Zip code
  • Telefon = Phone number
  • Rediger = edit

Auto zipcode to location name function

One thing you probably want to change straight away is that Raptor has a DB table with all the zipcodes and location names in Norway. So when you fill out an invoice and populate the zip-code, it will do a lookup in the zipcode table and fill out the shire (location) name.

The database engine

Fast, binary, pure object pascal

Fast, binary, pure object pascal

Back when I wrote this there existed a nice little DB engine called Volga. Super simple yet powerful. It cost like $15 or something like that. It has SQL support but for this project I quite frankly didnt need it. Either way, the Volga DB engine was released as public-domain a few years later. If you look in the “Volga Stuff” folder you will find both the delphi source and DB editor for that old DB engine.

So you probably want to install that, or at least copy over the table structure to another database. Although you really dont have to. Volga is written in vanilla object pascal, requires almost no memory and Raptor doesnt use SQL (!) Its all primary keys, Delphi’s filter functions and inter-linked tables.

This should make porting it very easy. Also, all access to the data in the source-code goes through objects. You create a datastore, open it with a folder location, and all the calculation etc. takes place in that datastore object (which is a datamodule).

Dependencies

There are two commercial dependencies that I naturally cant deliver (and wont). I urge you to buy Raized components for pure Windows desktop applications. Its an awesome component set.

  • Developer Express DB grid version 1.2 (best they ever made, before all the super duper thousands of options grids)
  • Raize Components (one of the best component packages of all time)

Since the DB grid relies heavily on that extra comments field, you may want to just use VirtualListView and code a flat one. Im not sure the latest DevEx grid can be made to look like this old and lightweight ancestor.

Use the source luke

Well, hope you like this product. It has served me well for over a decade, works great, is fast and very easy to use. The code should be tidy enough for everyone to read and play with.

Copyright etc.

I release it “as is” with what I consider to be flexible: The Apache 2.0 license. I dont expect you to pay me a dime, nor are you obligated to stick to the framework. You are however obligated to not miss-represent the origin of the software.

LibJL and SysEssentials codebase

LibJL was my own personal “tools” library ages ago, and before that I also had Sysessentials. There are a few gold nuggets in LibJL, like the DIB graphics code, the image editor, the thumbnail viewer and so on.

Again, take what you wish and polish it as needed.

The GIF codec is pretty sweet!.You also have color reduction code in there and palette management. Quite rare these days but important non-the-less.

Well, thats pretty much it — enjoy!

Visit Github Here: https://github.com/quartexNOR/RaptorInvoice

Inifiles with markup support

May 26, 2016 2 comments

INIInifiles are boring, stiff and bordering on jurrasic park technology-wise; Yet most Delphi developers end up with TInifile for both preferences and language medium. Inifiles may be simple, but they are effective and to the point.

But can we improve on the formula? Let’s jump into the nearest Tardis and fly back around 13 years, thats when I wrote this class (and I still use it to this day).

Markup?

The code below let’s you define key/value pairs as normal, but also re-use them as parts of other key/value pairs. This makes it so much easier to write language-files, especially long strings that are 90% identical.

How do i use it?

First, lets look at a small example. Its so simple that it requires only seconds to get it, yet i find it useful in it’s simplicity:

[database]
server1=192.0.2.62
server2=192.0.2.63
server3=192.0.2.64
server4=192.0.2.65
port=143
file="payroll.dat"

Notice how the IP addresse are 90% identical? Well, with my little class you can now do this:

[database]
root=192.0.2.
server1={database:root}.62
server2={database:root}.63
server3={database:root}.64
server4={database:root}.65
port=143
file="payroll.dat"

Now you dont save much space, but you gain the ability to define and re-use values within the same ini-file. And language files can grow large quickly, so the more you can re-use segments and phrases, the better the language file will be.

[DISK]
internal_error=An internal error occured,
file_error=A file error occured,
file_write_error = {DISK:file_error} write operation failed (%s)
file_read_error = {DISK:file_error} read operation failed (%s)
file_lock_error = {DISK:file_error} unable to obtain file-lock (%s)

I also added support for CRLF, for this we use the “|” (pipe) symbol. Its also thread safe, which is a bonus.

Enjoy!

  unit jllanguage;

  interface

  uses
  sysutils, classes, inifiles, syncobjs;

  Const
  CNT_JL_LANGUAGEFILE_MACRO_BEGIN = '{';
  CNT_JL_LANGUAGEFILE_MACRO_END   = '}';
  CNT_JL_LANGUAGEFILE_LINEFEED    = '|';
  CNT_JL_LANGUAGEFILE_NAMESTOP    = ':';

  type

  TJLLanguageFile = Class(TComponent)
  Private
    FLock:      TCriticalSection;
    FLanguage:  TInifile;
    FFilename:  TFileName;
  Protected
    Function    GetActive:Boolean;
    Procedure   SetActive(Value:Boolean);
    Procedure   SetFileName(Value:TFileName);
    Function    IniFormat(IniFile:TCustomIniFile;
                Value:String):String;
  Public
    Function    ReadStr(Section,Ident,DefValue:String):String;
    Function    ReadInt(Section,Ident:String;DefValue:Integer):Integer;
    Function    ReadCurr(Section,Ident:String;DefValue:Currency):Currency;
    Function    FormatLangStr(Value:String):String;
    Function    Open(Filename:String):Boolean;
    Function    Close:Boolean;
  Published
    Property    Filename:TFilename read FFilename write SetFileName;
    Property    Enabled:Boolean read GetActive write SetActive;
    Constructor Create(AOwner:TComponent);override;
    Destructor  Destroy;Override;
  End;

  implementation

  //###########################################################################
  // TJLLanguageFile
  //###########################################################################

  Constructor TJLLanguageFile.Create(AOwner:TComponent);
  Begin
    inherited Create(Aowner);
    FLock:=TCriticalSection.Create;
    FLanguage:=NIL;
  end;

  Destructor TJLLanguageFile.Destroy;
  Begin
    FLock.Enter;
    try
      if FLanguage&lt;&gt;NIl then
      FreeAndNIL(FLanguage);
    finally
      FLock.Leave;
    end;

    FreeAndNIL(FLock);
    inherited;
  end;

  Function TJLLanguageFile.GetActive:Boolean;
  Begin
    FLock.Enter;
    try
      result:=assigned(FLanguage);
    finally
      FLock.Leave;
    end;
  end;

  Procedure TJLLanguageFile.SetActive(Value:Boolean);
  Begin
    If Value&lt;&gt;GetActive then
    Begin
      Close;
      if Value then
      Open(FFilename);
    end;
  end;

  Procedure TJLLanguageFile.SetFileName(Value:TFilename);
  Begin
    if (csDesigning in ComponentState) then
    FFilename:=Value else
    Begin
      If not GetActive then
      FFileName:=Value else
      Raise Exception.Create
      ('Filename can not be changed in active component');
    end;
  end;

  Function TJLLanguageFile.IniFormat(IniFile:TCustomIniFile;
           Value:String):String;
  var
    x,y:    Integer;
    FLen:   Integer;
    FEnd:   Integer;
    FName:  String;
    FSec:   String;
  Begin
    Value:=trim(Value);
    Flen:=Length(Value);
    SetLength(result,0);
    If FLen&gt;0 then
    Begin
      x:=0;
      while x&lt;FLen do       Begin         inc(x);                  if Value[x]=CNT_JL_LANGUAGEFILE_MACRO_BEGIN then         Begin           (* locate end of macro *)           FEnd:=0;           for y:=x+1 to FLen do           Begin             if value[y]=CNT_JL_LANGUAGEFILE_MACRO_END then             Begin               FEnd:=y;               Break;             end;           end;           if FEnd&gt;0 then
          Begin

            FName:=Copy(Value,x+1,(y-x)-1);
            x:=y;

            y:=pos(CNT_JL_LANGUAGEFILE_NAMESTOP,FName);
            if y&gt;0 then
            Begin
              FSec:=Copy(FName,1,y-1);
              Delete(FName,1,y);
              result:=result + IniFile.ReadString(FSec,FName,'');
            end else
            result:=result + CNT_JL_LANGUAGEFILE_MACRO_BEGIN
            + FName + CNT_JL_LANGUAGEFILE_MACRO_END;

          end else
          result:=result + Value[x];
        end else
        result:=result + Value[x];
      end;
    end;
  end;

  Function TJLLanguageFile.Open(Filename:String):Boolean;
  Begin
    result:=FileExists(Filename);
    If result then
    Begin
      FLock.Enter;
      try
        result:=False;

        (* release previous language data *)
        if assigned(Flanguage) then
        FreeAndNil(Flanguage);

        try
          Flanguage:=TIniFile.Create(Filename);
          result:=True;
        except
          on exception do
          exit;
        end;

      finally
        FLock.Leave;
      end;
    end;
  end;

  Function TJLLanguageFile.Close:Boolean;
  Begin
    FLock.Enter;
    try
      result:=assigned(Flanguage);
      If result then
      FreeAndNIL(Flanguage);
    finally
      FLock.Leave;
    end;
  end;

  Function TJLLanguageFile.FormatLangStr(Value:String):String;
  Begin
    FLock.Enter;
    try
      If Flanguage&lt;&gt;NIL then
      Begin
        if pos(CNT_JL_LANGUAGEFILE_MACRO_BEGIN,result)&gt;0 then
        result:=IniFormat(Flanguage,result) else
        result:=Value;
      end else
      result:=Value;
    finally
      FLock.Leave;
    end;
  end;

  Function TJLLanguageFile.ReadStr(Section,Ident,DefValue:String):String;
  Begin
    FLock.Enter;
    try
      If Flanguage&lt;&gt;NIL then
      Begin
        result:=Flanguage.ReadString(Section,Ident,DefValue);

        (* add support for extended formating *)
        if pos(CNT_JL_LANGUAGEFILE_MACRO_BEGIN,result)&gt;0 then
        result:=IniFormat(Flanguage,result);

        if pos(CNT_JL_LANGUAGEFILE_LINEFEED,result)&gt;0 then
        result:=StringReplace(result,CNT_JL_LANGUAGEFILE_LINEFEED,
        #13,[rfReplaceAll]);

      end else
      result:=DefValue;
    finally
      FLock.Leave;
    end;
  end;

  Function TJLLanguageFile.ReadInt(Section,Ident:String;
           DefValue:Integer):Integer;
  Begin
    FLock.Enter;
    try
      If Flanguage&lt;&gt;NIL then
      result:=Flanguage.ReadInteger(Section,Ident,DefValue) else
      result:=DefValue;
    finally
      FLock.Leave;
    end;
  end;

  Function TJLLanguageFile.ReadCurr(Section,Ident:String;
           DefValue:Currency):Currency;
  Begin
    FLock.Enter;
    try
      If Flanguage&lt;&gt;NIL then
      result:=FLanguage.ReadFloat(section,ident,defvalue) else
      result:=DefValue;
    finally
      FLock.Leave;
    end;
  end;

  end.

Smart Data, working on it!

May 24, 2016 4 comments

Right! Its been two hell of a couple of weeks. I’m multi-tasking as best I can between:

  • Re-writing large parts of the VJL
  • Writing a book
  • Talking to key figures in the community
  • Presenting ideas and meeting with the Smart Pascal Consortium
  • Yoga and inner peace
  • Kids, GF and a snapping 3-level spinal injury
  • Not in that order

Whats on the table?

In short, a quantum leap. We have the technology but it’s all buried in unit files and the lack of documentation is just “enough”. So I have gotten to the stage where I will just deal with it come hell or high water.

I have had a little debate with David Berneda, owner and all-round super-coder over at Steema software about databases. He knows data since his components run on several platforms, under many different languages — so he was indeed the guy to talk to and get some inspiration.

The problem with data is threefold:

  • The pascal side of things
  • How data is dealt with in the JS community in general
  • Traditional access to data via nodeJS and native drivers

In effect Smart Mobile Studio needs to have a system that covers all these aspects. The pascal code (regardless of what is wrapped or underneath) should be, well, pascal. It should be directly readable, infinitely intuitive for Lazarus and Delphi programmers – but at the same time flexible and expose all the dynamic features JavaScript has (that Delphi doesnt).

A kid made $4 million on a flashlight app, a blank white screen written in Javascript. When Delphi finally got to Appstore, it was all over.

And no, I’m not out to get Delphi; A bit pissed at EMB for screwing over the community on iOS and Android (not to mention the pricing which is just ridicules), but still a loyal Spartan (!).

What I am trying to do, and in fact the whole SMS team, is to get your native Delphi products into places where Embarcadero cannot reach. To compliment Delphi and rejuvinate your skill-set which is being phased out due to the mistakes of EMB (and they did inherit a fair bit of mistakes from Borland so its not all their fault).

And JSVM is not on their to-do list, they are strictly native. Same with Remobjects. So SMS is the only one surfing on the JavaScript virtual machine.

Speaking of data

Then you have inter-operative functionality. Namely that you want your SMS applications talking to off-the-shelves JS solutions out there. If the data in SMS is to “Delphi” and we have to do conversion just to get Linq.js to chew it, then we have lost something.

This is what Linq (from C#) looks like under JavaScript (we can do better!). You can use Linq with SMS today, its actually childs-play to wrap it in a unit.

Linq.JS -- 1:1 identical with C# and the dot net framework -- and you can use this today in smart if you like

Linq.JS — 1:1 identical with C# and the dot net framework

Just look at that mess. Seriously, we can do better and make it worthy object pascal.

I want SMS programmers to just go “damn! this rocks! Now I can get my Delphi data running on my website, my phone, my embedded devices and whatever IOT comes next” without having to wait 2-3 years for Embarcadero to write yet-another-compiler.

Do you know why im so pissed at Embarcadero? Because they cheated us not once, but twice! First with the iPhone and then Android. By the time we got to code for these platforms the market was saturated. Every possible application was done and every concivable window of opportunity you may have had dead.

A kid made $4 million on a flashlight app, a blank white screen written in Javascript. When Delphi finally got to Appstore, it was all over.

And they did this not once, but twice! 

We just can’t have an infrastructure or programming language that takes 2 blody years to pivot. It’s just not acceptable. Freepascal was able to support iOS in what, 3 weeks? And even with stealing their codebase it took Embarcadero 2 years? And people ask “why JavaScript”. It should be considered a global intellectual emergency that a company can charge these prices.

Dude, Javascript has what — 20.000 commiters? Millions of active users and the amount of coders contributing to the runtime ranks in the tens of thousands (if not more). The V8 LLVM powered JSVM has seen more commits and improvements than Microsoft Windows. It is being payed for by Google, Apple and a whole range of Linux companies. JS is able to pivot on a dime and target a piece of hardware in less than an hour. Especially with the LLVM sub-strata. I also noticed a fork by Apple using Clang, so native languages and the desktop has an expiration date. Just saying..

Do you think we picked JSVM just for fun?

You have missed the rise of the full stack JavaScript developer, and its happening right now, while the desktop is being marginalized at rapid pace.

A whole new world

Did you know that JavaScript ranks #8 on the list of used languages in the world? Did you know that 50% (fifty percent!) of all the programs being written right now on this planet, is written in JavaScript? Just let that sink in for a moment.

There is a whole reality of IOT and business that Delphi developers are missing. Jobs thin on the ground? No. Not really. But if you limit yourself to only delivering pascal projects then sure. Add JavaScript to the list and see what happens.

This is a podcast I find quite interesting: https://javascriptair.com/

Any-ho, then, just when things are looking cosy and within grasp data-side, nodeJS and IO goes and adds support for native database plugins. You know, stuff that pretty much looks like what we already have in Lazarus and Delphi (although slightly perverted being javascript after all).

“Fine, I only have one bullet so your gonna have to share!”
-Source: Deadpool, the movie

For a taste of how RAW NodeJS DB connectivity looks like, have a taste of this. Not really elegant is it? So im going to make that puppy into easy to use, sexy, slick and (drumroll) object pascal style components.

Now, I'm about to do to you what Limp Bizkit did to music in the late 90s.

Now, I’m about to do to you what Limp Bizkit did to music in the late 90s.

I’m thinking MSSQL, MySQL, MariaDB and Oracle should be more than enough to get you started (server-side). We already have Remobjects SDK support and Datasnap, Websocket.. that should about cover it (when the docs are done).

mORMotsupport, finally!

Got in touch with Arnaud Bouchez over at Synopse, and we will finally add built-in support for mORMot. No time-table on that yet, but ill keep you posted as we move along. Arnaud has some great code and I am really looking forward to getting the IDE talking with their framework!

Also super tempted to have a stab at compiling his Spidermonkey fork of the Mozilla Javascript virtual machine, on ARM to see if we can get that linked and running under freepascal on the PI3. That would be awesome.

Non visual components

Yes, it’s finally in the RTL. Again, no time-table on the IDE changes but it’s now an actual part of the inheritance chain. TW3Widget is now the ancestor of TW3TagObj, so we keep the SmartCL namespace (document object model) away from the non-visual ancestor for now. But it’s there and working!

TAction and TActionManager

Yup. Done, tested and very sexy! Again no time-table on when we get this into the IDE, but from the VJL point of view you can now create actions and bind that to controls that supports it (in the traditional 1 action – many subscribers). That is something i have missed!

Tweening and software animation

We already have GPU animations and control under wraps, thats been there for quite some time now (smartcl.effects.pas), but tweening gives you better control and for desktop applications it can be a better option. And it’s there. Hopefully we can get a visual aid for it, although an editor for tweening is overkill.. really. But if it makes it easier to use, so be it.

A tweening prototype you can play with straight away can be found here (its more evolved in the RTL naturally).

Is there more?

Yes. But thats hush-hush stuff so I cant talk about it.

 

 

Partial classes in Smart Pascal, how?

May 19, 2016 Leave a comment
Fast, binary, pure object pascal

Partial classes

Ralf Wesseling mentioned something very important to me today, namely that since partial classes is not in Delphi – getting to know this under Smart Pascal, which sadly is seriously lacking in the documentation department, is a bit of a sour apple.

So to rectify this I hope this little document will help.

What is partial classes?

A partial class is, as the name hints to, a class that is only implemented in part. Depending on the language (and even dialect), partial classes can have many aspects. But in essence it comes down to:

  • You can define parts of a class spread out over several units
  • The class is not sealed (or considered complete) until the churned through everything

Ok — so let’s define a class and see what it looks like:


unit firstunit;

interface

uses
  SmartCL.System, SmartCL.Graphics, SmartCL.Components, SmartCL.Forms,
  SmartCL.Fonts, SmartCL.Borders, SmartCL.Application;

type

  // Our own new partial class !
  TMyButton = partial class(TW3Button)
  public
    procedure Testmethod;
  end;

implementation

procedure TMyButton.Testmethod;
begin
end;

end.
 

Nothing to fancy there, in fact, what is the difference?

The difference is that you can go into another unit, one that links to the unit we have our above code in, and then continue to implement it there:

unit secondunit;

interface

uses
  firstunit,
  SmartCL.System, SmartCL.Graphics, SmartCL.Components, SmartCL.Forms,
  SmartCL.Fonts, SmartCL.Borders, SmartCL.Application;

type

  // Our own new partial class !
  TMyButton = partial class(TW3Button)
  public
    procedure Testmethod2;
  end;

implementation

procedure TMyButton.Testmethod2;
begin
end;

end.

Normally this would not be allowed. Had we been using non-partial classes, the class in second unit would have to inherit from TMyButton in order to extend it.

What the compiler does when it encounter a partial class, is that it waits until all the units have been parsed and then it collects all the pieces and puts together “the final virtual method table” for that class.

Cool side effects

Start a new visual smart mobile studio project. Just a plain visual project.

Now go to the uses clause and add: “SmartCL.effects” to the list of units.

Now go down to InitializeForm, and write

self.

The code proposal window should pop-up: Notice how suddenly ALL TW3MovableControl based components have suddenly gained 20+ “fx” special effects methods 🙂

  TW3MovableControl = partial class(TW3Component)
    function  fxFadeOut(const Duration:Float):TW3MovableControl;overload;
    Procedure fxFadeOut(const Duration:Float;
              const OnFinished:TProcedureRef);overload;

    function  fxFadeIn(const Duration:Float):TW3MovableControl;overload;
    Procedure fxFadeIn(const Duration:Float;
              const OnFinished:TProcedureRef);overload;

    function  fxWarpOut(const Duration:Float):TW3MovableControl;overload;
    Procedure fxWarpOut(const Duration:Float;
              const OnFinished:TProcedureRef);overload;

    function  fxWarpIn(const Duration:Float):TW3MovableControl;overload;
    procedure fxWarpIn(const Duration:Float;
              const OnFinished:TProcedureRef);overload;

    function  fxZoomIn(const Duration:Float):TW3MovableControl;overload;
    Procedure fxZoomIn(const Duration:Float;
              const OnFinished:TProcedureRef);overload;

    function  fxZoomOut(const Duration:Float):TW3MovableControl;overload;
    Procedure fxZoomOut(const Duration:Float;
              const OnFinished:TProcedureRef);overload;

    function  fxScaleTo(const aToX,aToY,aToWidth,aToHeight:Integer;
              const Duration:Float):TW3MovableControl;overload;
    Procedure fxScaleTo(const aToX,aToY,aToWidth,aToHeight:Integer;
              const Duration:Float;
              const OnFinished:TProcedureRef);overload;

    function  fxMoveTo(const dx,dy:Integer;
              const Duration:Float):TW3MovableControl;overload;
    Procedure fxMoveTo(const dx,dy:Integer;
              const Duration:Float;
              const OnFinished:TProcedureRef);overload;

    function  fxMoveBy(const dx,dy:Integer;
              const Duration:Float):TW3MovableControl;overload;
    Procedure fxMoveBy(const dx,dy:Integer;
              const Duration:Float;
              const OnFinished:TProcedureRef);overload;

    function  fxMoveUp(const Duration:Float):TW3MovableControl;overload;
    Procedure fxMoveUp(const Duration:Float;
              const OnFinished:TProcedureRef);overload;

    function  fxMoveDown(const Duration:Float):TW3MovableControl;overload;
    procedure fxMoveDown(const Duration:Float;
              const OnFinished:TProcedureRef);overload;

    function  fxSizeTo(const aWidth,aHeight:Integer;
              const Duration:Float):TW3MovableControl;overload;
    Procedure fxSizeTo(const aWidth,aHeight:Integer;
              const Duration:Float;
              const OnFinished:TProcedureRef);overload;

    function fxScaleDown(aFactor:Integer;
              const Duration:Float):TW3MovableControl;overload;
    procedure fxScaleDown(aFactor:Integer;const Duration:Float;
              const OnFinished:TProcedureRef);overload;

    function  fxScaleUp(aFactor:Integer;
              const Duration:Float):TW3MovableControl;overload;
    Procedure fxScaleUp(aFactor:Integer;const Duration:Float;
              const OnFinished:TProcedureRef);overload;

    function  fxBusy:Boolean;
    Procedure fxSetBusy(const aValue:Boolean);
  End;

This is the magic of partial classes. Without the SmartCL.effects.pas unit, they were just boring old controls. But since TW3MovableControl is marked as partial, the effects unit can extend the class with all that new cool stuff.

Well, that was a crash course in partial classes — hope it clears things up. There is more to it than this ofcourse, but thats an immediate feature you can start using straight away 🙂

 

Porting ACE to Smart Pascal

May 14, 2016 2 comments

People have been asking me for this since the dawn of time, and while I figured someone would make a package for this – I guess a small demonstration is in order.

What is Ace?

Ace is basically a code editor written in Javascript, more or less the same as SynEdit is for Delphi. Except it looks better and runs on the JavaScript Virtual Machine and Document Object Model.
Faster and better than SynEdit, with highlighting and syntax checking!

Faster and better than SynEdit, with highlighting and syntax checking!

Ok, here is how to get ACE into your Smart Mobile Studio codebase:
var LEditor: TW3AceEditor;
LEditor := TW3AceEditor.Create(self);
LEditor.SetBounds(32,33,400,250);
LEditor.Text := "This is so cool!";

Note that the property EditorHandle is the direct reference to the Ace editor instance. So now you can go to the ACE docs and call the functions you need via that handle.

Right, here is the whole shabam — you may want to isolate the different syntax highlighters and themes in their own units, because it takes a few seconds to load all the files. A splash screen should make that look good.

Oh, and remember to remove this from your stylesheet or the cursor will calculate wrong!

font-family: “Helvetica Neue”, Helvetica, sans-serif;

unit SmartCL.AceEditor;

interface

uses 
  system.types,
  system.types.convert,
  SmartCL.Components,
  SmartCL.System;

type

  TW3AceEditor = class(TW3CustomControl)
  private
    FEditor:  THandle;
  protected
    procedure InitializeObject;override;
    procedure FinalizeObject;override;
    procedure Resize;override;
  public
    property  EditorHandle: THandle read FEditor;
    property  Text: string read ( FEditor.getValue() ) write ( FEditor.setValue(Value) );
  end;


implementation


procedure TW3AceEditor.InitializeObject;
var
  LEditor: THandle;
  LId:  string;
begin
  inherited;

  Handle.ReadyExecute( procedure ()
    begin
      LId := TagId;
      asm
        @LEditor = ace.edit(@LId);
        (@LEditor).setTheme("ace/theme/monokai");
        (@LEditor).getSession().setMode("ace/mode/pascal");
      end;
      FEditor := LEditor;
      TW3Dispatch.DelayedDispatch( procedure ()
        begin
          FEditor.resize();
        end,100);
    end);
end;

procedure TW3AceEditor.Resize;
begin
  inherited;
  if (FEditor) then
    FEditor.resize();
end;

procedure TW3AceEditor.FinalizeObject;
begin
  if (FEditor) then
  begin
    // Blank out the editor, this kills the instance
    FEditor := TVariant.CreateObject;
    FEditor := unassigned;
  end;
  inherited;
end;


{$R "ace.js"}
{$R "ext-beautify.js"}
{$R "ext-chromevox.js"}
{$R "ext-elastic_tabstops_lite.js"}
{$R "ext-emmet.js"}
{$R "ext-error_marker.js"}
{$R "ext-keybinding_menu.js"}
{$R "ext-language_tools.js"}
{$R "ext-linking.js"}
{$R "ext-modelist.js"}
{$R "ext-old_ie.js"}
{$R "ext-searchbox.js"}
{$R "ext-settings_menu.js"}
{$R "ext-spellcheck.js"}
{$R "ext-split.js"}
{$R "ext-static_highlight.js"}
{$R "ext-statusbar.js"}
{$R "ext-textarea.js"}
{$R "ext-themelist.js"}
{$R "ext-whitespace.js"}
{$R "keybinding-emacs.js"}
{$R "keybinding-vim.js"}
{$R "mode-abap.js"}
{$R "mode-abc.js"}
{$R "mode-actionscript.js"}
{$R "mode-ada.js"}
{$R "mode-apache_conf.js"}
{$R "mode-applescript.js"}
{$R "mode-asciidoc.js"}
{$R "mode-assembly_x86.js"}
{$R "mode-autohotkey.js"}
{$R "mode-batchfile.js"}
{$R "mode-c_cpp.js"}
{$R "mode-c9search.js"}
{$R "mode-cirru.js"}
{$R "mode-clojure.js"}
{$R "mode-cobol.js"}
{$R "mode-coffee.js"}
{$R "mode-coldfusion.js"}
{$R "mode-csharp.js"}
{$R "mode-css.js"}
{$R "mode-curly.js"}
{$R "mode-d.js"}
{$R "mode-dart.js"}
{$R "mode-diff.js"}
{$R "mode-django.js"}
{$R "mode-dockerfile.js"}
{$R "mode-dot.js"}
{$R "mode-eiffel.js"}
{$R "mode-ejs.js"}
{$R "mode-elixir.js"}
{$R "mode-elm.js"}
{$R "mode-erlang.js"}
{$R "mode-forth.js"}
{$R "mode-ftl.js"}
{$R "mode-gcode.js"}
{$R "mode-gherkin.js"}
{$R "mode-gitignore.js"}
{$R "mode-glsl.js"}
{$R "mode-gobstones.js"}
{$R "mode-golang.js"}
{$R "mode-groovy.js"}
{$R "mode-haml.js"}
{$R "mode-handlebars.js"}
{$R "mode-haskell.js"}
{$R "mode-haxe.js"}
{$R "mode-html.js"}
{$R "mode-html_elixir.js"}
{$R "mode-html_ruby.js"}
{$R "mode-ini.js"}
{$R "mode-io.js"}
{$R "mode-jack.js"}
{$R "mode-jade.js"}
{$R "mode-java.js"}
{$R "mode-javascript.js"}
{$R "mode-json.js"}
{$R "mode-jsoniq.js"}
{$R "mode-jsp.js"}
{$R "mode-jsx.js"}
{$R "mode-julia.js"}
{$R "mode-latex.js"}
{$R "mode-lean.js"}
{$R "mode-less.js"}
{$R "mode-liquid.js"}
{$R "mode-lisp.js"}
{$R "mode-live_script.js"}
{$R "mode-livescript.js"}
{$R "mode-logiql.js"}
{$R "mode-lsl.js"}
{$R "mode-lua.js"}
{$R "mode-luapage.js"}
{$R "mode-lucene.js"}
{$R "mode-makefile.js"}
{$R "mode-markdown.js"}
{$R "mode-mask.js"}
{$R "mode-matlab.js"}
{$R "mode-maze.js"}
{$R "mode-mel.js"}
{$R "mode-mips_assembler.js"}
{$R "mode-mipsassembler.js"}
{$R "mode-mushcode.js"}
{$R "mode-mysql.js"}
{$R "mode-nix.js"}
{$R "mode-nsis.js"}
{$R "mode-objectivec.js"}
{$R "mode-ocaml.js"}
{$R "mode-pascal.js"}
{$R "mode-perl.js"}
{$R "mode-pgsql.js"}
{$R "mode-php.js"}
{$R "mode-plain_text.js"}
{$R "mode-powershell.js"}
{$R "mode-praat.js"}
{$R "mode-prolog.js"}
{$R "mode-properties.js"}
{$R "mode-protobuf.js"}
{$R "mode-python.js"}
{$R "mode-r.js"}
{$R "mode-razor.js"}
{$R "mode-rdoc.js"}
{$R "mode-rhtml.js"}
{$R "mode-rst.js"}
{$R "mode-ruby.js"}
{$R "mode-rust.js"}
{$R "mode-sass.js"}
{$R "mode-scad.js"}
{$R "mode-scala.js"}
{$R "mode-scheme.js"}
{$R "mode-scss.js"}
{$R "mode-sh.js"}
{$R "mode-sjs.js"}
{$R "mode-smarty.js"}
{$R "mode-snippets.js"}
{$R "mode-soy_template.js"}
{$R "mode-space.js"}
{$R "mode-sql.js"}
{$R "mode-sqlserver.js"}
{$R "mode-stylus.js"}
{$R "mode-svg.js"}
{$R "mode-swift.js"}
{$R "mode-swig.js"}
{$R "mode-tcl.js"}
{$R "mode-tex.js"}
{$R "mode-text.js"}
{$R "mode-textile.js"}
{$R "mode-toml.js"}
{$R "mode-twig.js"}
{$R "mode-typescript.js"}
{$R "mode-vala.js"}
{$R "mode-vbscript.js"}
{$R "mode-velocity.js"}
{$R "mode-verilog.js"}
{$R "mode-vhdl.js"}
{$R "mode-wollok.js"}
{$R "mode-xml.js"}
{$R "mode-xquery.js"}
{$R "mode-yaml.js"}
{$R "theme-ambiance.js"}
{$R "theme-chaos.js"}
{$R "theme-chrome.js"}
{$R "theme-clouds.js"}
{$R "theme-clouds_midnight.js"}
{$R "theme-cobalt.js"}
{$R "theme-crimson_editor.js"}
{$R "theme-dawn.js"}
{$R "theme-dreamweaver.js"}
{$R "theme-eclipse.js"}
{$R "theme-github.js"}
{$R "theme-idle_fingers.js"}
{$R "theme-iplastic.js"}
{$R "theme-katzenmilch.js"}
{$R "theme-kr_theme.js"}
{$R "theme-kuroir.js"}
{$R "theme-merbivore.js"}
{$R "theme-merbivore_soft.js"}
{$R "theme-mono_industrial.js"}
{$R "theme-monokai.js"}
{$R "theme-pastel_on_dark.js"}
{$R "theme-solarized_dark.js"}
{$R "theme-solarized_light.js"}
{$R "theme-sqlserver.js"}
{$R "theme-terminal.js"}
{$R "theme-textmate.js"}
{$R "theme-tomorrow.js"}
{$R "theme-tomorrow_night.js"}
{$R "theme-tomorrow_night_blue.js"}
{$R "theme-tomorrow_night_bright.js"}
{$R "theme-tomorrow_night_eighties.js"}
{$R "theme-twilight.js"}
{$R "theme-vibrant_ink.js"}
{$R "theme-xcode.js"}
{$R "worker-coffee.js"}
{$R "worker-css.js"}
{$R "worker-html.js"}
{$R "worker-javascript.js"}
{$R "worker-json.js"}
{$R "worker-lua.js"}
{$R "worker-php.js"}
{$R "worker-xml.js"}
{$R "worker-xquery.js"}

end.

Smart Mobile Studio Unleashed, book

May 1, 2016 3 comments
cover

Embedded? Cloud? Oh i’ll give you something to play with alright

Spent the better 3 weeks writing a book, a book that should have been written ages ago but I never had the time to do so.

What will it cover? Well, let’s just say this is the book you want for all things esoteric and secret about Smart Mobile Studio. It is not a book for beginners in object pascal, so a background in Delphi or Freepascal is a pre-requisite.

I am presently at 64 A4 pages packed full of stuff, and that’s not even half of the curriculum defined. We go through the inheritance chain for visual controls, from TW3TagObj all the way up to the culmination in TW3CustomControl.

We cover esoteric topics like talking to the GPU for blistering fast graphics, how to effectively use the class helpers attached to each control handle, memory management, font handling, enumerating child elements properly (read: why JavaScript runs faster the more you fragment) — and of-course, all my secrets in writing fast displays, like TW3DataGrid (not yet published) that can cut through thousands of rows without breaking a sweat.

But fear not, I also over fundamental concepts like how the VJL is organized, the namespaces, the platforms we support — and more importantly, WHY partial classes plays such an important part of it all.

But the juicy parts that’s gonna make you get all girly inside are in the NodeJS chapters, how to write clustering and make your NodeJS services scale beyond anything delivered by Delphi or Freepascal to date.

I am co-authoring the book with my good friend Peter Dunne of Jazenga Software who is taking my second-language-english and making it more natural to english speaking natives.

What can only be called “the black book of smart pascal sorcery” will be available through LuLu publishing later this year. It will co-incide with a major shift in the VJL.

You’ll be running rings around expensive native cloud services quite soon.

Here are a few teasers just to wet your appetite. These are fresh off the mint so to speak, so not even spellchecked yet. But should be enough to get your creative side going 🙂

snapshot_01snapshot_02snapshot_03snapshot_04

plexi

Writing a web front-end for your NAS or nodeJS embedded service is a piece of cake!