Jump to content


Popular Content

Showing content with the highest reputation since 01/13/2019 in all areas

  1. 4 points

    scrolling big numbers

    Got challenged the other day by trying to scroll large numbers of rows in a listbox. Browsers are very good at scrolling, but when row numbers become large and the scroll context becomes complex, scroll behaviour deteriorates. A simple un-optimised scrolling div is, rule of thumb, able to scroll comfortably up to a couple of hundred rows. Depending on browser and complexity. In the native framework I tweaked that a bit by setting all rows outside the visible viewport to 'display:none'. That extends comfortable scrolling somewhat, say to numbers in the low thousands. A better way is to completely separate display from content. Meaning that the content needs to be held in some kind of memory structure, not in visual components, and that scrolling is redefined to re-using a small set of visual rows confined to the scroll-window. A structure to make that happen would be a <div> like component (TW3Panel) on a form with NativeScrolling set to true. Set up 2 child-panels on this component, where the first child-panel has dimensions equal to the maximum scrollable area, and a second childpanel dimensioned equal to this parent. The first child-panel is the scroller, which will always be completely empty. The second child-panel contains the visible rows, which will be refreshed on scroll-events. To keep this panel always in view, set the position attribute to '-webkit-sticky'. This demo shows that Chrome can readily handle 500,000 rows, even on mobile. FF a bit less. The good thing is that the number of rows doesn't really matter, scrolling behaviour stays the same regardless. Would be interesting to see if performance gets even better by pushing scrolling to the GPU, pretty sure Chrome does that by default. Infinite scrolling will be easy to implement using this structure too. Code procedure TForm1.W3Button1Click(Sender: TObject); begin //data var column1 := TVariant.CreateArray; for var i := 0 to 500000 do begin column1.push(inttostr(i)); end; var column2 := TVariant.CreateArray; for var i := 1 to 500001 do begin column2.push(inttostr(i)); end; //column2.sort(); //sorting works too //scrolling setup var rowHeight := 30; W3Panel.NativeScrolling := true; //Parent panel (in visual designer : width 408, height 266) var Panel1 : TW3Panel := TW3Panel.Create(W3Panel); //scroller (always empty!) Panel1.SetBounds (0,0,380,column1.length*rowHeight); //but with large height Panel1.BorderRadius := 0; //set up viewport var Panel2 : TW3Panel := TW3Panel.Create(W3Panel); //viewport, only the visual rows Panel2.SetBounds(0,0,380,262); //dimensions same as grid-parent Panel2.BorderRadius := 0; Panel2.handle.style.position := '-webkit-sticky'; Panel2.handle.style.position := '-moz-sticky'; Panel2.handle.style.position := '-ms-sticky'; Panel2.handle.style.position := '-o-sticky'; Panel2.handle.style.position := 'sticky'; Panel2.handle.style.top := '0px'; //set up columns Var CPanel1 : TW3Panel := TW3Panel.Create(Panel2); //column 1 CPanel1.SetBounds(-2,-2,200,264); CPanel1.BorderRadius := 0; Var CPanel2 : TW3Panel := TW3Panel.Create(Panel2); //column 2 CPanel2.SetBounds(200,-2,200,264); CPanel2.BorderRadius := 0; //initial fill viewport prior to first onscroll event for var j := 0 to 8 do begin //only first couple of rows var x : TW3Panel := TW3Panel.Create(CPanel1); //column 1 x.SetBounds(-2,j*rowHeight-2,200,rowHeight); x.BorderRadius := 0; x.innerHTML := column1[j]; x.onclick := procedure(sender:TObject) begin showmessage((sender as TW3Panel).innerHTML); end; // var y : TW3Panel := TW3Panel.Create(CPanel2); //column 2 y.SetBounds(-2,j*rowHeight-2,190,rowHeight); y.BorderRadius := 0; y.innerHTML := column2[j]; y.onclick := procedure(sender:TObject) begin showmessage((sender as TW3Panel).innerHTML); end; end; //fill viewport while scrolling var c1 := CPanel1.handle.children; var c2 := CPanel2.handle.children; W3Panel.onscroll := procedure(sender:tobject) begin var d : integer := trunc(W3Panel.handle.scrollTop/rowHeight); for var k := 0 to 8 do begin c1[k].innerHTML := column1[d+k]; c2[k].innerHTML := column2[d+k]; end; //fall back : ide chrome browser and iOS don't handle 'sticky' very well, //in that case set viewport position manually if Panel2.handle.style.position <> 'sticky' then if not w3_getIsSafari then Panel2.top := W3Panel.handle.scrollTop; end; end; infinity scroll : add to end of W3Panel.onscroll procedure //infinity scroll : if W3Panel.handle.scrollHeight - W3Panel.handle.scrollTop = W3Panel.handle.clientHeight then W3Panel.handle.scrollTop := 0;
  2. 4 points

    Update embedded Chromium

    Hi guys, I've been a busy for a while working on updating the Embedded Chrome we use in the IDE. The one we've used so far has been from Henri Gourvest. Unfortunately it's not been updated for 3 years, but there's a new fork by Salvador Diaz Fau, which is actively updated: https://github.com/salvadordf/CEF4Delphi
  3. 4 points

    async / await keywords

    look this following procedure: procedure main; begin Sleep(5000); console.log('A'); Sleep(3000); console.log('B'); end; when we run this code, it will immediately output 'A' and 'B' - JS the asyncronous nature. ...but I want our program to log 'A' after 5 seconds then log 'B' after 3 seconds... that why we, we need the async / await native keywords in SMS. In a "synchronous" manner. Await and Async are just syntactic sugar for working with promises. It would be cool if we can declare a method like: procedure main; async; The compiler can check whether the encompassing function has async() in then and emit expectd JS, for instance: (async function(){ })(); Yeah, we need to to wrap it inside an async function and add "await" before the method "Sleep" to work in synchrounous manner.ther await keyword native in SMS would be nice. await Sleep(5000); I found out an ugly workaround for the "Immediately Invoked Function Expression (IIFE) - async function" to work at SMS. { global functions } procedure async(fn: Variant = nil); external '(async function(){' ; procedure &end; external '})'; function await(promise: Variant): JPromise; external 'await '; function Sleep(ms: Integer): JPromise; begin Result := JPromise.Create( procedure(resolve,reject : TCallBack) begin window.setTimeout( procedure() begin console.log('Done waiting'); resolve(ms); end, ms); end); procedure main; begin async; await( Sleep(5000) ); console.log('A after 5s'); await (Sleep(3000)); console.log('B after 3s'); &end; end; end; the compiler will emit expected JS at least: function main() { (async function(){(null); await (Sleep$1(5000)); console.log("A after 5s"); await (Sleep$1(3000)); console.log("B after 3s"); })(); }; but it would super awesome if we could // define procedure as async function procedure main: async; procedure main; begin await Sleep(5000); console.log('A after 5s'); await Sleep(3000); console.log('B after 3s'); end;
  4. 4 points

    Development updates

    New update available: RTL: New controls: TW3SpinButton, TW3ArrowUpButton, TW3ArrowDownButton, TW3ArrowLeftButton and TW3ArrowRightButton. Add 1px margin to TW3ButtonBorder to prevent the border from being clipped. Bug fixes to Tween.Effects IDE: Bug fixes and changes to renaming of forms: Renaming in Project Manager will not change form class any more. Form class name can be renamed in Object Inspector. Improvements to Project Statistics: Only count Total time when Smart Mobile Studio IDE has focus. Only Count Design time when changes are made in Form Designer. Add clear button. Component Palette: Added new controls New icons for many existing controls
  5. 4 points

    Quantum computing with Smart

    Is it possible to do a bit of quantum computing using SMS ? The answer looks to be yes, using IBM's quantum computer resources which are partly made available for developers Basics first : IBM has released it's SDK to program these quantum computers using a visual composer, Python, a proprietary assembler QASM format and some run and execute utilities. There is however a second way of using its quantum resources, using the entry-points of is Q-experience REST server. Most if not all of the visual composer functions can be done by ajax-calls ! To demonstrate : the first thing is to acquire an api-token. (Create an account on https://quantumexperience.ng.bluemix.net/qx/experience. Log in and navigate to the Composer. Username > My Account, and then Advanced on the upper right. Then generate API Token. Api Tokens are personal and are not supposed to be shared.) The next thing is to generate a session token (access token). This can be done in code. This screenshot shows a couple of these REST server calls and its results : The first button (get access) generates a session token The second button queries which quantum computers are available, how many qubits can be used, if it is online and what type of system it is. Current results show 3 quantum systems - 2 real ones and 1 simulator The third button (current Temp) gives some processor info of the selected system. In this case the current temperature in Kelvin : the Melbourne processor is cooled to just above the absolute minimum. Next : the next button to implement will be to submit some code to one of the available processors for processing. Below is the project code for these REST calls unit Form1; interface uses System.Types, System.Types.Convert, System.Objects, System.Time, SmartCL.System, SmartCL.Time, SmartCL.Graphics, SmartCL.Components, SmartCL.FileUtils, SmartCL.Forms, SmartCL.Fonts, SmartCL.Theme, SmartCL.Borders, SmartCL.Application, SmartCL.Net.Http, ECMA.Json, SmartCL.Controls.Button, SmartCL.Controls.Label, HTMLTableElement, SmartCL.Controls.ScrollBox, SmartCL.Controls.Panel, SmartCL.Controls.ComboBox; type TForm1 = class(TW3Form) procedure W3Button3Click(Sender: TObject); procedure W3Button2Click(Sender: TObject); procedure W3Button1Click(Sender: TObject); private {$I 'Form1:intf'} protected procedure InitializeForm; override; procedure InitializeObject; override; procedure Resize; override; DBRows : integer := 0; FHttp : TW3HttpRequest; smscursor: variant; procedure GetAccessToken(Sender: TW3HttpRequest); procedure ListAvailableSystems(Sender: TW3HttpRequest); procedure GetTempKelvin(Sender: TW3HttpRequest); AccessToken : string; end; implementation { TForm1 } procedure TForm1.InitializeForm; begin inherited; // this is a good place to initialize components end; procedure TForm1.InitializeObject; begin inherited; {$I 'Form1:impl'} end; { Button1 } procedure TForm1.W3Button1Click(Sender: TObject); begin //Get Access Token var FHttp := TW3HttpRequest.Create; FHttp.OnDataReady := GetAccessToken; FHttp.open("POST",'https://quantumexperience.ng.bluemix.net/api/users/loginWithToken'); FHttp.RequestHeaders.Add("Content-type","application/json"); var api : variant := new JObject; api.apiToken := TString.encodeURIComponent('a59d26ebf........1d6d6d4a28c430b5336aba'); FHttp.send(json.stringify(api)); end; procedure TForm1.GetAccessToken(Sender: TW3HttpRequest); begin smscursor := JSON.parse(Sender.ResponseText); AccessToken := smscursor.id; W3Label1.Caption:= 'Access Token : ' + AccessToken; end; { Button2 } procedure TForm1.W3Button2Click(Sender: TObject); begin //List available Q systems var FHttp := TW3HttpRequest.Create; FHttp.OnDataReady := ListAvailableSystems; FHttp.open("GET",'https://quantumexperience.ng.bluemix.net/api/Backends?access_token=' + AccessToken); FHttp.RequestHeaders.Add("Content-type","application/json"); FHttp.send; end; procedure TForm1.ListAvailableSystems(Sender: TW3HttpRequest); begin var W3TableElement1 : TW3TableElement := TW3TableElement.Create(W3Panel1); //add 5 columns to the grid W3TableElement1.AddColumn('Name',200); //title, width W3TableElement1.AddColumn('Description',200); W3TableElement1.AddColumn('Qubits',50); W3TableElement1.AddColumn('System type',150); W3TableElement1.AddColumn('Status',50); smscursor := JSON.parse(Sender.ResponseText); W3ComboBox1.Clear; for var i := 0 to smscursor.length -1 do begin W3TableElement1.AddCell(i+1,1,smscursor[i].name); W3TableElement1.AddCell(i+1,2,smscursor[i].description); W3TableElement1.AddCell(i+1,3,smscursor[i].nQubits); if smscursor[i].simulator = true then W3TableElement1.AddCell(i+1,4,'quantum simulator') else W3TableElement1.AddCell(i+1,4,'quantum system'); W3TableElement1.AddCell(i+1,5,smscursor[i].status); W3ComboBox1.add(smscursor[i].name); end; W3Panel1.NativeScrolling := true; W3ComboBox1.OnChanged := procedure (Sender: TObject) begin writeln(W3ComboBox1.Items[W3ComboBox1.SelectedIndex]); end; end; { Button3 } procedure TForm1.W3Button3Click(Sender: TObject); begin //Get Temp var FHttp := TW3HttpRequest.Create; FHttp.OnDataReady := GetTempKelvin; //'https://quantumexperience.ng.bluemix.net/api/Backends/NAME/parameters?access_token=ACCESS-TOKEN'; FHttp.open("GET",'https://quantumexperience.ng.bluemix.net/api/Backends/' + W3ComboBox1.Items[W3ComboBox1.SelectedIndex] + '/parameters?access_token=' + AccessToken); FHttp.RequestHeaders.Add("Content-type","application/json"); FHttp.send; end; procedure TForm1.GetTempKelvin(Sender: TW3HttpRequest); begin If Sender.ResponseText = '{}' then begin W3Label3.Caption := 'Simulators are not cooled to (close to) absolute minimum'; end else begin smscursor := JSON.parse(Sender.ResponseText); if smscursor.fridgeParameters.Temperature.value = '' then W3Label3.Caption := 'Processor does not support Temp reading' else W3Label3.Caption:= 'Current Temp : ' + smscursor.fridgeParameters.Temperature.value + ' Kelvin'; end; end; procedure TForm1.Resize; begin inherited; end; initialization Forms.RegisterForm({$I %FILE%}, TForm1); end. In the end hopefully it will be possible to recreate the classic Battleships game using quantum computing, as described here
  6. 3 points

    visual processing

    A while ago I posted some ideas and a demo on the subject of 'nocode' or 'locode' development. The idea was to do a domain modelling exercise, and extract or generate an application from that with no or minimal coding required. The demo put a couple of the proposed models through its paces. After that post I put the subject back in the incubator for a bit. Sort of recently I realised that having an rtl with visual components and an ide with a form painter is a good thing to have, but only covers part of what is necessary if we need to cover process logic, or business rules, without having to resort to coding. Various computing packages (datamining, visual analytics etc) model business logic by providing specialised components, which can be strung together and parametrised. I tried this approach out (using KNIME) with the following (nonsensical) example process : "from the NorthWind database get all companies, and from the FishFacts database extract all species, collate only those entries from both datasources where the name starts with the letter 'L' and list them. Also produce a pie-chart of all fish species showing their length in cm." This (nonsense) process translates to the node structure above. Nodes typically can be selected from a list of available nodes and right-clicking gives parametrisation forms, f.i. the MYSQL connector asks for a host, database name, credentials etc. Stringing these components together is a simple process in itself, and the output produces something like Not too bad. I've coded a couple of these type of components in Smart, which works really well. For the technical architecture I've based these on the principles of FBP (flow based programming) where every node is a webworker, and the connectors between these nodes/webworkers are defined by channel-objects. Data transfer between these nodes/channels is done by messaging. See post here. It would be nice to extend the rtl with these types of components, including a process painter
  7. 3 points

    Chat Scroller - Need Some Tips

    I built this component to use in my Paintball Net game UI: unit UPBTInfoScroll; interface uses System.Types, System.Lists, System.Time, System.Colors, SmartCL.System, SmartCL.Theme, SmartCL.Components, SmartCL.Controls.Label, SmartCL.Scroll, SmartCL.Controls.ScrollBar, SmartCL.Css.Classes; type TPBTScrollItem = class(TW3CustomControl) protected FItemText: string; procedure InitializeItem; virtual; procedure SetItemText(aValue: string); public function CreationFlags: TW3CreationFlags; override; procedure InitializeObject; override; procedure UpdateDisplay; virtual; property ItemText: string read FItemText write SetItemText; end; TPBTScrollItemClass = class of TPBTScrollItem; TPBTScrollInfoItem = class(TPBTScrollItem) protected public procedure UpdateDisplay; override; property InfoText: string read ItemText write ItemText; end; TPBTScrollErrorItem = class(TPBTScrollInfoItem) end; TPBTChatType = (ctAnnounce, ctChat, ctSay, ctShout, ctWhisper, ctTell, ctPlan, ctTeamPlan, ctWizChat); TPBTScrollChatItem = class(TPBTScrollItem) private FChatType: TPBTChatType; FChatHeader: string; FChatText: string; FChatColor: TColor; public procedure UpdateDisplay; override; property ChatTYpe: TPBTChatType read FChatType; property ChatHeader: string read FChatHeader; property ChatText: string read FChatText; property ChatColor: TColor read FChatColor; end; TPBTScrollItems = class(TW3ScrollContent) private function GetItem(Index: Integer): TPBTScrollItem; function GetCount: integer; public function Add: TPBTScrollItem; overload; function Add(aScrollItemClass: TPBTScrollItemClass): TPBTScrollItem; overload; procedure Clear; virtual; procedure FinalizeObject; override; procedure Delete(aIndex: integer); published property Items[index: Integer]: TPBTScrollItem read GetItem; default; property Count: integer read GetCount; end; TPBTInfoScrollAddItemCallback = function: TPBTScrollItem; TPBTInfoScroll = class(TW3ScrollControl) private function GetItems: TPBTScrollItems; function GetScrollIsAtBottom: boolean; protected procedure InitializeObject; override; procedure Resize; override; function GetScrollContentClass: TW3ScrollContentClass; override; public function CreationFlags: TW3CreationFlags; override; procedure Clear; procedure ScrollToBottom; procedure RealignItems; procedure AddItem(aCallBack: TPBTInfoScrollAddItemCallback); procedure AddText(const aText: string); procedure AddChat(aChatType: TPBTChatType; const aChatHeader, aChatText: string; aChatColor: TColor); procedure AddInfo(const aInfoText: string); procedure AddError(const aErrorText: string); function MaxInfoWidth: integer; function MinInfoWidth: integer; published property Items: TPBTScrollItems read GetItems; property ScrollIsAtBottom: boolean read GetScrollIsAtBottom; end; implementation // TPBTScrollItem procedure TPBTScrollItem.InitializeItem; begin end; procedure TPBTScrollItem.SetItemText(aValue: string); begin if aValue <> FItemText then begin FItemText := aValue; UpdateDisplay; end; end; function TPBTScrollItem.CreationFlags: TW3CreationFlags; begin inherited; Include(Result, cfAllowSelection); end; procedure TPBTScrollItem.InitializeObject; begin inherited; SetContentSelectionMode(tsmText); end; procedure TPBTScrollItem.UpdateDisplay; begin InnerHTML := Format('<span style="user-select:text;-moz-user-select:text;-webkit-user-select:text;-ms-user-select:text;-khtml-user-select:text">%s</span>', [TString.EncodeTags(FItemText)]); end; // TPBTScrollInfoItem procedure TPBTScrollInfoItem.UpdateDisplay; begin inherited; end; // TPBTScrollErrorItem // TPBTScrollChatItem procedure TPBTScrollChatItem.UpdateDisplay; begin inherited; InnerHTML := Format('<span style="color:%s;user-select:text;-moz-user-select:text;-webkit-user-select:text;-ms-user-select:text;-khtml-user-select:text"><b>%s</b>: %s</span>', [ColorToWebStr(FChatColor), TString.EncodeTags(FChatHeader), TString.EncodeTags(FChatText)]); end; // TPBTScrollItems function TPBTScrollItems.GetItem(Index: Integer): TPBTScrollItem; begin Result := TPBTScrollItem(GetChildren[Index]); end; function TPBTScrollItems.GetCount: integer; begin Result := GetChildCount; end; function TPBTScrollItems.Add: TPBTScrollItem; begin Result := Add(TPBTScrollItem); end; function TPBTScrollItems.Add(aScrollItemClass: TPBTScrollItemClass): TPBTScrollItem; begin Result := aScrollItemClass.Create(Self); Result.InitializeItem; end; procedure TPBTScrollItems.Clear; begin // can't get rid of from [0]. Not sure why. while Count > 0 do GetChildren[Count - 1].Free; Height := 0; end; procedure TPBTScrollItems.FinalizeObject; begin Clear; inherited; end; procedure TPBTScrollItems.Delete(aIndex: integer); begin if aIndex < GetChildCount then GetChildren[aIndex].Free; end; // TPBTInfoScroll function TPBTInfoScroll.GetItems: TPBTScrollItems; begin Result := TPBTScrollItems(Content); end; function TPBTInfoScroll.GetScrollIsAtBottom: boolean; begin Result := (ScrollController.ContentTop <= -(Content.Height - (Height + 10))) end; procedure TPBTInfoScroll.InitializeObject; begin inherited; SetBarSize(CNT_SCROLLBAR_SIZE); ScrollBars := sbScrollBar; end; procedure TPBTInfoScroll.Resize; begin inherited; end; function TPBTInfoScroll.GetScrollContentClass: TW3ScrollContentClass; begin Result := TPBTScrollItems; end; function TPBTInfoScroll.CreationFlags: TW3CreationFlags; begin inherited; // Allow key-capture and selection include(result, cfKeyCapture); include(result, cfAllowSelection); end; procedure TPBTInfoScroll.Clear; begin Items.Clear; SetSize(ClientWidth, 0); ScrollController.Refresh; end; procedure TPBTInfoScroll.ScrollToBottom; begin ScrollController.ScrollTo(0, -(Content.Height - Height)); end; procedure TPBTInfoScroll.RealignItems; var ii: integer; begin ScrollController.Refresh; if Items.Count > 0 then begin Items.BeginUpdate; try Items[0].Top := 0; for ii := 1 to Items.Count - 1 do begin Items[ii].Top := Items[ii - 1].Top + Items[ii - 1].Height; end; finally Items.EndUpdate; end; end; end; procedure TPBTInfoScroll.AddItem(aCallBack: TPBTInfoScrollAddItemCallback); const margin = 5; MAX_ITEMS = 500; var lastItem, aItem: TPBTScrollItem; needRealign, wasAtBottom: boolean; begin Items.BeginUpdate; Content.BeginUpdate; try if Items.Count > 0 then lastItem := Items[Items.Count - 1]; needRealign := Items.Count > MAX_ITEMS; if needRealign then while Items.Count > (MAX_ITEMS div 3) * 2 do Items.Delete(0); if Content.Width <> (ClientWidth - GetBarSize) then Content.Width := (ClientWidth - GetBarSize); wasAtBottom := (ScrollController.ContentTop <= -(Content.Height - (Height + 10))); aItem := aCallBack; aItem.UpdateDisplay; if lastItem <> nil then aItem.Top := lastItem.Top + lastItem.Height else aItem.Top := 0; Content.Height := aItem.Top + aItem.Height + margin; if Content.Height <= Height then begin Content.Height := Height; wasAtBottom := True; end; finally Content.EndUpdate; Items.EndUpdate; end; if needRealign then RealignItems; ScrollController.Refresh; if wasAtBottom then ScrollToBottom; if needRealign then Invalidate; end; procedure TPBTInfoScroll.AddText(const aText: string); begin AddItem( lambda Result := Items.Add; Result.ItemText := aText; end ); end; procedure TPBTInfoScroll.AddChat(aChatType: TPBTChatType; const aChatHeader, aChatText: string; aChatColor: TColor); begin AddItem( lambda Result := Items.Add(TPBTScrollChatItem); TPBTScrollChatItem(Result).FChatType := aChatType; TPBTScrollChatItem(Result).FChatHeader := aChatHeader; TPBTScrollChatItem(Result).FChatText := aChatText; TPBTScrollChatItem(Result).FChatColor := aChatColor; end ); end; procedure TPBTInfoScroll.AddInfo(const aInfoText: string); begin AddItem( lambda Result := Items.Add(TPBTScrollInfoItem); TPBTScrollInfoItem(Result).InfoText := aInfoText; end ); end; procedure TPBTInfoScroll.AddError(const aErrorText: string); begin AddItem( lambda Result := Items.Add(TPBTScrollErrorItem); TPBTScrollErrorItem(Result).InfoText := aErrorText; end ); end; function TPBTInfoScroll.MaxInfoWidth: integer; var aMax: string; begin while aMax.Length < 70 do aMax += 'XXXXXXXXXX'; Result := MeasureText(aMax).tmWidth + GetBarSize; end; function TPBTInfoScroll.MinInfoWidth: integer; begin Result := MaxInfoWidth div 2; end; end. My goal was to have it act like a scrolling chat. New items are added to the bottom, pushing up the items already there. By default scrolling "sticks" to the bottom of the view area, unless the player has manually scrolled it back to review something or whatever. Overall, it works. What I'm looking for are missed opportunities and maybe some solutions to a couple issues: The biggest issue is the build up of items in the scroll. Right now, I have it check for a maximum size, then prune it down to half that size. I would *prefer* if it could be indefinitely large. The solutions I've seen for that have relied on items being the same height. That constraint doesn't work for me. Resizing isn't exactly snappy/smooth. Just seems like there's probably a way to make that better. Also, as a sorta sub-goal, I wanted to share what I had done. SMS needs a lot more sharing going on. 😃 So...hit me. Tell me how I'm doing it wrong and/or could do it better. Thanks! -David
  8. 3 points

    Development updates

    New update is available: Bug fixes and improvements to the IDE: BringToFront and SendToBack will now affect component creation order Opening files from Project Search Path is fixed
  9. 3 points
    to match/map the JSON fieldname, you have to declare the record definition is type TFishRecord = record Category: String; external 'Category'; Common_Name: String; external 'Common_Name'; Length_Cm: String; external 'Length_Cm'; Length_In: String; external 'Length_In'; Notes: String; external 'Notes'; Species_Name: String; external 'Species_Name'; Species_No: String; external 'Species_No'; end; to emit this JS Object, for instance: { Category : "Snapper", Common_Name : "Red Emperor", Length_Cm : "60", Length_In : "23.6220472440945", Notes : "Called seaperch in Australia.", Species_Name : "Lutjanus sebae", Species_No : "90030" }
  10. 3 points

    SMS 3 + mORMot = Error

    I download the mORMot_and_Open_Source_friends_234301_06b24ae728 today and patched some needed SynCrossPlatform units and mORMotWrappers (generate mORMot cross-platform clients code from the server). and some changes at the project 27 and 29, Now, it compiles and works in both SMS (lastest WinXP compatible) and SMS (development version)! download link: mORMot with SMS 2x and 3x
  11. 3 points

    Endpoints & Resources ?

    Well, you should always take what Jon says or writes with a grain of salt.
  12. 3 points


    Working on a component which involves printing. I've used external libraries like jsPDF, which generate printable content and they generally work fine. They also have some drawbacks though. So this component is going to rely on the browsers native 'print' command to print a complete page/form, or part thereof. 1) The print command is tied to a window object (window.print), so the possible implementations are limited to manipulating either the current window an iFrame element, which encapsulates a window object a new pop-up window (or new tab) Just to try these out, button 1-3 in the code below correspond to these possible implementations. var Button1 : TW3Button := TW3Button.Create(self); Button1.SetBounds(20,470,120,30); Button1.Caption := 'this window'; Button1.OnClick := procedure(sender:TObject) begin var originalContents := browserapi.document.body.innerHTML; browserapi.document.body.innerHTML := SrcDoc; browserapi.window.print(); browserapi.document.body.innerHTML = originalContents; end; var Button2 : TW3Button := TW3Button.Create(self); Button2.SetBounds(150,470,120,30); Button2.Caption := 'iframe window'; var IFrame1 := TW3IFrameHtmlElement.Create(self); IFrame1.SetBounds(20,20,400,400); Button2.OnClick := procedure(sender:TObject) begin IFrame1.handle.srcdoc := SrcDoc; IFrame1.handle.contentWindow.print(); end; var Button3 : TW3Button := TW3Button.Create(self); Button3.SetBounds(280,470,120,30); Button3.Caption := 'other window'; Button3.OnClick := procedure(sender:TObject) begin asm var mywindow = window.open('', 'PRINT', 'height=400,width=600'); mywindow.document.write(@SrcDoc); mywindow.setTimeout(function(){ mywindow.print(); mywindow.close(); }, 1000); //mywindow.print(); //mywindow.close(); end; end; //SrcDoc content : see below All of these approaches work. However if printable content contains images or other sizable resources, the actual printing must be delayed until all these resources have been downloaded - so either preload or set an appropriate Timeout, as in Button3 2) Screen and print layout differ in a couple of aspects : screens are continuous, paper is not and some elements like tables and images should not be cut in half over page breaks readability of fonts is different on screens and print output hints like blue links on webpages don't translate well to printing many web page elements like menus should not appear on print and more. A good overview is given in this article A separate print stylesheet can handle some/most/all of these concerns. A generic print stylesheet based on @media print is included below. Probably not the solution for each and every situation, but it handles most of the aspects above var SrcDoc : string := #' <!DOCTYPE html> <HTML> <HEAD> <style> @media print { @page { margin: 2cm } body { font: 13pt Georgia, "Times New Roman", Times, serif; line-height: 1.3; background: #fff !important; color: #000; } h1 { font-size: 24pt; } h2, h3, h4 { font-size: 14pt; margin-top: 25px; } a { page-break-inside:avoid } blockquote { page-break-inside: avoid; } h1, h2, h3, h4, h5, h6 { page-break-after:avoid; page-break-inside:avoid } img { page-break-inside:avoid; page-break-after:avoid; } table, pre { page-break-inside:avoid } ul, ol, dl { page-break-before:avoid } a:link, a:visited, a { background: transparent; color: #520; font-weight: bold; text-decoration: underline; text-align: left; } a { page-break-inside:avoid } a[href^=http]:after { content:" <" attr(href) "> "; } $a:after > img { content: ""; } article a[href^="#"]:after { content: ""; } a:not(:local-link):after { content:" <" attr(href) "> "; } nav, .sidebar, .heading { display: none; } p, address, li, dt, dd, blockquote { font-size: 100% } code, pre { font-family: "Courier New", Courier, mono} ul, ol { list-style: square; margin-left: 18pt; margin-bottom: 20pt; } li { line-height: 1.6em; } } </style> </HEAD> <BODY BGCOLOR="FFFFFF"> <CENTER><IMG SRC="res/logo.png"> </CENTER> <HR> <H1>Header H1</H1> <H2>Header H2</H2> <P>Paragraph <P><B>Bold paragraph</B> <BR><BR><B><I>This is a sentence with some length, longer than the iframe width, in bold italic.</I></B> <HR> </BODY> </HTML>'; This generates a print preview in all major browsers, with options to set paper size, select printer, orientation etc. see https://www.lynkfs.com/Experiments/ReportWriter/www/reportwriter.html Next thing to tackle is scaling.
  13. 3 points

    NodeJS file utils and native app

    Here's my example: Files := TW3NodeStorageDevice.Create(nil); Files.Mount(nil, procedure (Sender: TW3StorageDevice; Success: boolean) begin if Success then Writeln(PBNRS_FILE_SYSTEM_MOUNTED) else Writeln(Format(PBNRS_ERROR_MOUNTING_FILE_SYSTEM, [Files.LastError])); end ); And reading a file: Files.Load(PBNConfig.HelpFile, procedure (Sender: TW3StorageDevice; Filename: string; Data: TStream; Success: boolean) begin if Success then begin ... end else ... end
  14. 2 points
    Back in the mid-90's, my brother created what he called an "action MUD". He called it "Paintball Net". PBN used ANSI text terminal commands to create a combined roll-and-scroll and animated text experience. ! (exclamation points) were trees, _ (underscores) were grass, ^ (up carets) were mountains, and more. Your "avatar" was a "Y" (uppercase y) and enemies were * (asterisks). You used MUD-like commands to move around the world and target your paintball gun and buy and sell and more. I wish I had a screenshot of what the first version looked like, but you'll just have to imagine. 😃 He asked me to create the GUI for the game. That was 1996, and I had just purchased Delphi 2 and wrapped up my first "learn Delphi" project. This sounded like a good next step, and "PBTerm" was born. I *do* have screenshots of that. Oh, yeah. Love those 16-color Windows graphics. 😃 During the years it was online, the game evolved and upgraded to look more like this: We were never going to win any awards, but we had a very devoted player community. 😃 Paintball Net was online from the summer of 1996 through the summer of 2000, when we took it offline to focus on other projects. The game was never huge, but we had thousands of players come through over that period. And since then, every year at least a few of the players have reached out and asked/begged/pleaded/demanded if we were going to put it back online. There really wasn't much chance of the original version going back online. The original server, written in ANSI C for Linux had proven rather fragile, and was a big part of why we took the game offline. It was taking hours every day just to keep it up and running, in addition to time spent managing the community of players. On top of that, a hard drive incident in 2008 had cost me the source code of a number of core third-party/modified components for PBTerm. This past summer, though, I realized I might be able to make the game live again using Smart Mobile Studio. I would do a straight port of the ANSI C server to Smart Pascal using Node.JS and WebSocket, and I would rebuild the PBTerm client as a browser-based client. I'm not going to say it was *easy*, but it has been a lot of fun. ANSI C converts to Pascal without a lot of painful gyrations. And Node.JS seems a LOT more flexible, stable, and powerful than the TCP sockets approach we were using before. Also, game development is a lot easier when you already own all the graphic and audio resources. 😃 This is the server "in action"... Recreating the client has been more complicated. 20+ years of Delphi habits had to be adapted to the new reality of SMS and a browser-based UI. But that's coming together too. It might be obvious, but I'm not targeting this game at mobile. Paintball Net needed a mouse and keyboard in 1996, it's gonna need a mouse and keyboard in 2018. Also, I've made as few modifications to the gameplay as possible. I really wanted to bring back the original as much as I could. Today I got the handful of sound effects integrated, which was easier than I expected. I'm planning to start testing soon. I just need to line up a server to use and find a few volunteers. I'm excited. This would never have happened without Smart Mobile Studio. So I figured I would share. Merry Xmas! -David
  15. 2 points

    node ground zero

    I had the idea firmly rooted in my mind that Node is for server-side deployment only. This happens to be not the case, it is apparently feasible to use Node packages in the browser ! Talking about packages, the largest store of Node packages is NPM, which stores some 500,000 packages or more. (350,000 as per jan 2017) Not all of those are suitable for use in the browser, but a lot of them are. The ones which don't work client-side are the ones that require internet access, graphics or files, so this is not a work-around to be able to access databases on the client without going through a server, or enabling to write files client-side. Probably a good thing. One of the ways to use an existing node package (or a self-written one) client-side is Browserify. This product basically takes a packages and iterates through all its dependencies to other packages and collates them in a single bundle. Which can then be used client-side. Browserify also has written client-side versions of some of Node's core modules, which are automatically included in a bundle when necessary. HTTP and HTTPS for instance. That still doesn't make it possible to set up a http server within the browser, but other http-functions will work : the first program in this thread (see top of this thread) was a vanilla js node program which accesses www.random.org (a site which generates random integers) and prints out the value to the console. in plain vanilla javascript : var https = require('https'); //The url is: 'https://www.random.org/integers/?num=1&min=1&max=10&col=1&base=10&format=plain&rnd=new' var options = { host: 'www.random.org', path: '/integers/?num=1&min=1&max=10&col=1&base=10&format=plain&rnd=new' }; callback = function(response) { var str = ''; //another chunk of data has been received, so append it to `str` response.on('data', function (chunk) { str += chunk; }); //the whole response has been received, so we just print it out here response.on('end', function () { console.log(str); }); } https.request(options, callback).end(); Browserifying this package and accessing it from the browser works fine, and can be an alternative to issuing xmlhttprequests Another example using Node's event mechanism // require the core node events module var EventEmitter = require('events').EventEmitter //create a new event emitter var emitter = new EventEmitter // set up a listener for the event emitter.on('test', function (message) { console.log(message) }) // emit an event emitter.emit('test', 'this is an event test') works fine as well, an alternative to add event listeners etc It wouldn't be too difficult to Smarten this up either. I'm having a look at the other 500,000 packages. Daunting but interesting.
  16. 2 points
    Hi Tim, I assume you are trying to log in with your email address. Try using your screen name instead, ie "Tim Koscielski" without the quotes. It's a known issue
  17. 2 points

    Enable User Selection of Text

    I don't know. Maybe because text in Delphi apps is not selectable as default? Anyway, I'd propose changing this so, that we don't prevent ContentSelectionMode even if cfAllowSelections is missing. And if cfAllowSelection is in creation flags, we could set ContentSelectionMode automatically to tmsAuto. What do you guys think?
  18. 2 points


    update : on Android (Chrome) only the iFrame solution works as expected : Button1 (manipulating the main window object) prints the whole page rather than just the demo html Button3 (popup window) results in a rendering error see https://www.lynkfs.com/Experiments/ReportWriter/www/reportwriter.html Going with the IFrame solution
  19. 2 points

    Renaming form in ObjectInspector

    > Through Project manager: Through Property Inspector I think SMS in this should behave as Delphi. Project manager operates on units, so it will display unit name (for example MainForm.pas) and renaming there, renames unit. Object/Property inspector operates on components, so changing form name just does that, changes form name and class, unit is unaffected. For forms units I do like to have a different unit name and a different form name, for example I prefix all controls with it's type abbreviation, something like: Unit: SettingsForm.pas FormControl: frmSettings Panel: pnlMain ListBox: lbItems Panel: pnlRight ListBox: lbCategories Panel: pnlBottom Button: btnOK Button: btnCancel
  20. 2 points

    NodeJS file utils and native app

    yes to a node server with a back-end mysql db, no to a node server accessing the native file system I have these links on my system : node socket server with mysql db https://forums.smartmobilestudio.com/topic/4652-node-ground-zero/?tab=comments#comment-23125 file enumeration https://jonlennartaasenden.wordpress.com/?s=node.js I've done some conversions using nativefier, works ok I'm racking my brains what the name is of this other native library I've used once, but it eludes me at present
  21. 2 points

    Send an email ?

    a "smarted" up example of sending gmails using the gmail client library (replaces html file of option 2a previous post) a form with 3 buttons (button1 : log in / button2: log out / button3 : send gmail) .... var document external 'document': variant; var console external 'console': variant; var gapi external 'gapi': variant; implementation { TForm1 } procedure TForm1.InitializeForm; begin inherited; // this is a good place to initialize components var Script := document.createElement('script'); Script.src := 'https://apis.google.com/js/api.js'; Script.setAttribute('async',''); Script.setAttribute('defer',''); document.head.appendChild(Script); Script.onload := procedure begin // Load the API client and auth2 library gapi.load('client:auth2', procedure begin gapi.client.init(class apiKey = '********2qOeiRlg2humU4YifLyNqt2TrWR2kGk'; discoveryDocs = ["https://www.googleapis.com/discovery/v1/apis/gmail/v1/rest"]; clientId = '********7354-7kp9br0phbsdfkcsilu660u7uq9p6tqs.apps.googleusercontent.com'; scope = 'https://www.googleapis.com/auth/gmail.send'; end); end); end; end; procedure TForm1.W3Button1Click(Sender: TObject); //log in begin gapi.auth2.getAuthInstance().signIn(); end; procedure TForm1.W3Button2Click(Sender: TObject); //log out begin gapi.auth2.getAuthInstance().signOut(); end; procedure TForm1.W3Button3Click(Sender: TObject); // send gmail begin // asm const message = "From: from@gmail.com\r\n" + "To: to@hotmail.com\r\n" + "Subject: Subject of your email\r\n\r\n" + "with body of text here, separated by a blank line"; // The body needs to be base64url encoded. const encodedMessage = btoa(message); //replace all + with -, replace all / with _, and remove the trailing = to make it URL-safe //const reallyEncodedMessage = encodedMessage.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '') //problem : Smart compiler can't handle \ characters gapi.client.gmail.users.messages.send({ userId: 'me', resource: { raw: encodedMessage //should be : reallyEncodedMessage } }).then(function () { console.log("done")}); end; // end;
  22. 1 point

    Mixing Javascript and SMS

    Example 2 : see below (first post in that thread) for some function call examples Example 1 : depends on what attribute. In your example you want to manipulate the visibility property of a button. These types of properties usually are mapped on a property or attribute of the underlying DOM-element. In this case you could use the style element visibility : W3Button1.handle.style.visibility := 'hidden' / 'visible'. 'handle' is the doorway between object pascal on the left hand side and the dom on the right hand side. No need for an asm block here. you can go the other way too : var x : variant := W3Button1.handle; asm console.log((@x).style.visibility); end; but you can't directly manipulate the properties of an object pascal object, as in asm @W3Button1.Visible = true Of course you can do some conditional js coding and execute object pascal code (like W3Button1.Visible := true;) depending on the return outcomes. Out of habit I suppose I try to minimise the use of asm blocks (although there is nothing wrong with using them). The only times I have to use them if a js object doesn't have a create constructor but instead can only be instantiated by the 'new' keyword var formData : variant; asm @formData = new FormData(); end; //from here all methods and attributes of the js object are accessible outside an asm block formData.append("userfile", EditBox1.handle.files[0]); or wrapping a js function. (you can set @result in an asm block)
  23. 1 point

    Endpoints & Resources ?

    I'm not up to scratch on node myself, but what I understand is that when the node http-server receives a (post or get) request from a client, the body of the request is read by the server in chunks. Every chunk-read triggers an ondata event, and usually it is there where these chunks need to be concatenated until the onend event happens (end of read of request body). There is an example here
  24. 1 point

    RoadMap 2019

    Because i have faith in this product and the bright future it shows I purchased it yesterday But i would like to see a Road map as to where that future is though Dean
  25. 1 point
    whew, that's a mouth full "getting the row # of a button in a cell of a TW3ButtonColumn in Tw3Grid ?" So, I have a TW3Grid, and a TW3ButtonColumn. When the button in the column is clicked, I need to know the row # that button resides in. see attached screenshot