Quartex Pascal: Easter thoughts

I hope everyone is enjoying their easter! Norway is lovely at the moment with plenty of sun and the sound of birds and nature waking up again.

Between gardening chores and preparing for summer I decided to spend a little time with Claude to spice things up a bit on the NodeJS side – and write down some reflections on where Quartex is going.

Where are we right now?

Happy Easter everyone!

The roadmap we had setup to span the entire year has (architecturally) more or less, been completed in half that time (a few tickets left like drag & drop database). We have support for datamodules in the bag, we have support for non-visual components (a pre-requisite for data binding) – and we have added a full MCP Server for deep AI integration directly into the IDE.

I feel the IDE now covers the fundamental features you expect from an IDE, but obviously with room for refinement, improvement and expansion.

What I mean with refinement is to take a feature that is already there and simply make it better. A good example is the search and replace dialog. The dialog itself works fine, but a normal ‘quick search’ panel that appears beneath the current editor tab-page is what people are used to from Delphi and Visual Studio. The old dialog will still be there, but only appear when you want it to.

There are several such cases in the IDE, cases where we have the baseline functionality in place – but it could use some specialization and polish. I don’t feel there too many such cases, but enough that you notice them. So we have a few such ‘sub tasks’ that we will be doing.

Going forward

If we look at the IDE as a tree with a several branches, who in turn have child branches of varying complexity -the following off-shoots will see more love as we move forward:

  1. IDE behavior
    • Missing details, general polish and refinement
      • Search & Replace (covered above)
      • Welcome tab is too heavy. Get rid of the browser instance and instead have a clean, native, to the point welcome page.
      • Expose more options for toolbars and panels in popup menus, making it easier to tune the IDE as you want it. This can later be expanded so be loaded/saved. I dont feel layout config files are needed unless we allow drag & drop re-orientations of the IDE elements (e.g being able to drag the inspector and place it somewhere else). We can return to this for the Lazarus build later, as Lazarus have some good platform independent solutions for this.
      • Reduce themes to ‘dark’ and ‘light’, no need to ship a myriad of themes nobody actually uses. On Linux and MacOS we have to follow the system themes either way
        • Automatically switch editor colors based on theme. This is only really possible if we settle on a “light or dark” scheme. Right now there is no way to know what colors match any given theme, and having separate color-set-files for each theme is overkill.
      • Do a better job with the default colors for the code-editor. Right now Pascal and JS looks ok, but the other supported filetypes could use some adjustment.
      • implement missing language syntax to our unit interface parser. Right now the parser expects the ‘unit’ syntax rule to be followed. ‘program’ must be added, and it should handle units with no unit or program declarations without throwing an exception.
        • Add support for missing “property xyz: TType read external ‘sym’ write external ‘sym’ [default xyz];”
        • Add support for partial external class. While only useful when wrapping a library compiled with QTX, the parser needs to deal with it
        • Add better support for method declarations in classes (e.g “method xyz: boolean” is the same as “function xyz: boolean”).
    • Add support for robo-help extraction, so that ///<symmary> blocks can be extracted from the source-code and exported as a JSON documentation stub, markup or HTML.
      • Add IDE support for JSON documentation stubs. If a package ships with such stubs they should be cached by the IDE (extended help). This is especially useful for AI which will query about classes and methods, and the IDE will be able to answer quickly when there are 1:1 direct match on entity names.
    • Make internal logging more consistent, there are still spots where logging only happens when an error occurs, while other parts of the IDE is better at logging circumstantial info that can be useful when hunting for complex issues. We need both, and it must be consistent throughout the entire codebase.
    • Export log viewer as a separate application. This helps keep the codebase lean and the log viewer will not be affected by UI locks within the IDE process should they happen.
    • The component palette needs to support user-defined categories. Just our own system packages will quickly fill up the list once all the classes that should be TQTXComponents are upgraded, and it will quickly become difficult to maintain. Especially when third party packages start growing.
  2. AI and LLM integration
    • Look at ways to add closer integration with local LLM runtimes like LM-Studio, so free models can be used with the IDE in a similar fashion as Claude.
      • Search LAN for LM-Studio instances (if possible) for easier overall use
      • Implement our own console window for LLM use directly in the IDE
    • Add support for more local clients. Currently the IDE can emit claude.md into new projects which teaches claude what our MCP server offers and general language guidelines. Other online AI vendors have similar but different init structures
  3. Project support
    • Allow the IDE to have multiple projects open at the same time, with a simple way of moving between them
    • Add support for project groups (*.qprg file-type needed), which can be very powerful in client / server debugging scenarios. Personally I prefer to open two instances of the IDE.
    • Explore PhoneGap and React project types for building native binaries directly from the IDE
    • Add support for turning a node server into native apps. This functionality is already there, we just need to expose it in the IDE
    • Look at adding Arduino and Micro-controller project types, as well as DB projects with a ready to use setup – and other project types that would simplify getting started
  4. Form design
    • Captions for non-visual components
    • Redraw composition and region sorting
    • Collection pattern: Add support for visual management of TQTXCollection and TQTXCollectionItem’s
    • Designer behavior isolation: Right now the behavior of the form-designer is a part of the actual design control and largely triggered by mouse-down, mouse-move and mouse-up handlers. These should be isolated as a separate component. Once isolated this allows us to implement different behaviors separately in a clean way.
    • Vertical designer: Right now we have a normal form designer, where controls can be positioned anywhere, or aligned to a particular edge or client region. A top-down designer that operates with horizontal panels stacked one after the other -panels that always are 100% in width, is much more suited for top-down web pages. The same design philosophy is used by GTK on Linux (and QT Creator for that matter) to ensure proportional form layout regardless of scaling size.
    • Offscreen WYSIWYG HTML5 live rendering: We need to explore what the QT Framework (which we use for multi platform builds) can deliver. On Windows with a clean offscreen Chromium wrapper it’s relatively simple to do live rendering, but we would need to make it work on Windows, Linux and MacOS for both x64 and ARM. There are some smaller alternatives, like HTMLComponents and Pixie, but again — it needs to be tested first.
  5. RTL and code generation
    • Threading
      • DFM to code
      • Unit interface parsing
      • Thread the build process (partially done)
    • Ragnarok
      • Implement protocol client codegen. This will expose the client side messages as easy to use methods (e.g fClient.Login( .. ), fClient.DoSomething( .. ) and so on).
      • Implement protocol server codegen. The generated component(s) exposes server side responses as easy to use handlers. Protocols are always client-initiated, so the handshake and any replies are always in response to a client calling. The relationship between request and response messages is defined in the protocol designer.
      • Implement IQTXTransport interface for all supported network objects (http/s, websocket/s, udp, tcp/s etc). This allows an attached ragnarok client or server to ‘peek’ at incoming data, and take ownership of a received message. This way the same TQTXHttpClient or TQTXHttpServer component can be shared by several tasks without colliding. You can drop a TQTXHttpClient component and use it for posting data to a website, and at the same time have a protocol using it as a transport medium without the two tasks colliding.
  6. The entire RTL has plenty of classes that should be lifted up to TQTXComponent, but it will take time since the RTL is large and changes can have unexpected consequences if not properly planned
    • NodeJS specific
      • Move all server and client types up to TQTXComponent
      • Cover more modules, both standard and popular NPM modules
      • Generate wrappers for more database types (postgres, firebird etc)
      • Add support for automatically created datamodules for all node.js projects. This requires changes in the build-config and also TQTXApplication and how child objects register.
  7. Documentation
    • NodeJS needs to be properly documented, as well as all the default widgets. There is also a lot of ‘how to’ and general understanding that should be added. While a lot of self-evident to developers that already know object pascal, be it they come from Delphi or Lazarus, being able to find reliable info directly in the product is obviously important

The above points are not carved in stone, but rather what I am thinking about at the moment. Some might be pushed forward once we publish our next roadmap, others might be finished before it even becomes a ticket on the map.

AI and the future

Instead of relying solely on Claude, which can get expensive if you are using AI heavily on larger projects – we have actually taken the step of training our own QTX specific LLM model from scratch!

AI is perfect for boilerplate, boring tasks. Like creating new theme files!

This is not the same as just injecting RAG data into an existing vector database (that would be easy). Instead we rented a GPU rack that is building an LLM model specifically for Quartex Pascal. We based the model on the latest Qwen3-Coder series, so the baseline is already highly specialized for programming tasks and fluent in a myriad of languages. When the model comes back it will be quite large (somewhere in the 85 gigabyte range) and needs to be pruned. When the pruning is done we will have a model between 10 and 18 gigabytes (read: normal size).

The benefit of having our own LLM is that it can easily be hosted on our domain and used for automation. But it also means our customers can run our model locally, which further reduces cost compared to Claude or the other commercial solutions. There is also something about being in control of your own AI.

Ai changes everything

Like most developers in their 40s and 50s we are used to IDE’s where we, the human developer, is all that matters. While I still think that is valid – there is no escaping that AI changes how we view and use an IDE.

One of the core design goals for Quartex Pascal was to not add a lot of crazy complex functionality to the IDE which becomes impossible to maintain over time. Instead, I wanted to keep the IDE small, compact and instead keep refining the most commonly used IDE functionality (read: what you expect to find) until that becomes as good as humanly possible.

I think this design philosophy is it’s own reward right now, because had we gone crazy and added a ton of functionality, stuff that would now be replaced by an AI, we would have to maintain a ton of features that no longer makes sense. Of at least is starting to lose it’s importance as LLM evolves.

The benefit of AI is not so much that it takes over. An AI is good at doing the boring parts, the repetitive tasks, the boilerplate stuff – leaving you to focus on what you actually want to create. An AI has by definition no actual creative spark, there is no observer there. And while it’s become very clever at finding solutions to surprisingly complex tasks — you still need an environment where you can implement code or techniques that are new, or novel, or breaks with tradition in some way.

So instead of us adding everything from fancy recordable macros to advanced refactoring functionality which is rapidly becoming extinct — we will instead focus on making the fundamental features shine.

Telling Claude to roll an Android theme.css file takes a few minutes, but it’s ultimately a thing AI can easily automate

For example, spending weeks on a CSS theme designer would be cool ( would actually love that). But I think most people will just explain how the CSS styling works in QTX to the AI, and it will spit out a much better looking new CSS file in a couple of minutes.

The same goes for graphics. Eventually we will no doubt add AI graphics generation to the system, which means the AI will literally be able to spit out not just code, but also the graphics for a website, game or app you are making.

Using AI with Quartex

We are putting the finishing touches on our MCP server, which is built into the IDE itself, making for smooth integration with AI providers such as Antrophic. We are using Claude code ourselves at the moment, but we are also busy training our own LLM models from scratch.

MCP server?

If you are new to AI: an MCP server is a REST server that an AI model use to request information. The MCP implements functions for reading units, adding units, indexing the RTL and files, accessing documentation -or even creating, opening and compiling projects. The AI will also receive any error messages from the compiler so that it can see when something went wrong – and jump straight to fixing it.

Hosting your own AI

Using Claude Code is surprisingly affordable considering the amount of code it can churn out for you in record time. But the more complex the challenges are, the more intensive and token hungry the AI will be. Commercial systems like Claude can burn through $100 in record time if you are not careful, so there is definitively an argument for running a ‘lesser model’ on your own hardware.

Depending on what type of applications you work on, or what you need help with, there are thankfully some free alternatives. One of them is to run the AI locally on the same computer, or on another computer connected to your network.

LM Studio makes it absurdly simple to download, run and use local AI models

In my case I have a powerful laptop that I work on in my home office, but I also have a powerful stationary PC in our guestroom that is rarely used. The stationary PC has a much more powerful GPU than my laptop, and will run the AI models better than my laptop ever could.

What you need

The easiest way to host your own AI is, in my view, to use LM Studio. This is a free desktop client and server that runs on Mac, Linux and Windows. It allows for direct download of LLM models from huggingface (the website where the latest and greatest open source models can be found) via the GUI, and it takes care of drivers for your particular GPU and CPU configuration (e.g differences between AMD and Intel for example).

You really don’t need to be AI savvy to use it, it’s literally select a model, download, run. It is the Apple iPhone of LLM runtimes to put it like that.

You can search for LLM models to use directly in the LM Studio application

Another cool feature of LM Studio is that it supports MCP registration (which is important when it comes to Quartex Pascal). So once you have downloaded a model, you can register the QTX MCP in LM Studio – and voila! LM Studio can now talk directly to Quartex Pascal. Granted, it’s not going to be as smooth as installing Claude code, but if you just want to try something out and get your feet warm, LM Studio is the way to go.

Broader scenarios

I mentioned that I have a scenario where I actually have 2 machines, and that it would be nice if I could use my stationary machine to do the heavy lifting, while I enjoy coding on my laptop This is also possible with LM Studio, but with a few caveats:

  • You can access LM Studio as a web service. This requires a web UI and is somewhat limited. It will be a bit like talking to Grok or ChatGPT, not quite the same as running the AI via the commandline.
  • You can access LM Studio via the commandline, using the tool “lms” (downloadable as a separate install. You dont need this if you plan to just run locally, the LM Studio installer gives you this automatically). This is the magic bullet that makes local AI sane, and it’s more or less the same as Claude code. Well, except you get to decide what machine you host the model on, and you dont need a subscription.

So for my scenario the recipe becomes easy:

  1. Install LMS Studio on my stationary PC
  2. Download a suitable LLM model and run that
  3. Enable the server (see config window)
  4. Register Quartex Pascal MCP server, use machine name rather than IP so that it asks your router for the IP every time. That way it wont care if the router gives me a new IP between workdays.
  5. Install lms on my laptop
  6. Edit the appdata\local\lms\lms.config file, add a section for my stationary PC:
    [servers]
    lm_studio = tcp://my-stationary-pc-name:port
  7. open up a command-line window and type “lms -chat”. It should connect to the server straight away since you only have one server registered

Final thoughts

Getting to grips with AI is getting much easier, regardless if you use an external provider, or roll your own.

We hope this article was useful. Make sure to check back soon as the IDE with MCP support should be out later today!

Quartex IDE AI support

Those of you that keep up with our discord channel or Facebook group already know that we have been busy adding AI support to the IDE, but if you are just starting out with Quartex Pascal or are giving it a try – here is what will be available in the next IDE update.

Full MCP server

Claude AI has (as you probably know) opened up for working with the AI locally. Meaning that you install a small program called “Claude code”, and you can then use that with a project folder where Claude can then see and work with your project files.

In order for this process to be smooth, Claude needs as much information as possible. This is especially important for new programming languages, or dialects that have features and syntax differences – so that AI can make full use of the language when building code.

The only way to do this properly is to have an MCP server. MCP is short for Model Context Protocol, and it’s a protocol that allows the AI to ask for information and query knowledge it lacks.

The IDE now has a full MCP server implementation in the IDE itself

Instead of us just shipping a vanilla MCP server, adding a dependency you might not way -we decided to implement the whole thing ourselves. So Quartex Pascal now has a full blown MCP server bolted into the IDE.

This gives QTX developers some advantages:

  • The AI has direct access to the RTL. This means that it knows what classes are actually there, rather than “guessing” based on Delphi or Lazarus.
    • The AI is able to understand the RTL at a deeper level, not just blindly guess functionality based on method names and idioms. It builds a map of the RTL, picking up our dialect of object pascal in the process – and inspects how things work, not just superficial syntax mimicry.
  • The AI has direct access to our documentation. As with any language and framework, there are some subtle features that only make sense if you have actually read the documentation (or have followed the evolution of a language long enough).
  • The AI can create projects, open projects and interact with the IDE directly
  • Custom claude.md file which defines the parameters of the language and how things are done, resulting in better code

Needless to say, this opens up for some very powerful projects!

How do you use it?

So far we have only worked with Claude Code, but you can actually use whatever AI model you like, including models you host yourself at home.

Once everything is setup, you can ask Claude to create anything!

Just a brief overview of how to get started:

  1. Sign up to Claude. You need a pro account for this, the free version will not do.
  2. Download and install Claude-Code, this is a shell application that allows Claude (the remote AI) to talk to your local MCP server, and access the files in your project. It basically acts as a bridge between the cloud, your project data and the MCP server.
  3. Create a new project (a node project for example) in Quartex Pascal
  4. Copy our Claude.md file into said folder. This describes the Quartex Pascal language and basic guidelines for how things are done in Quartex Pascal (as opposed to Delphi, Lazarus or any of the other languages out there).
  5. Open up a shell window and cd to the project folder
  6. type “claude init” in the shell window. Claude will then create a folder where it keeps the context for your project (so it remembers what you are doing and things you talk about), index files and get everything ready.
  7. With that, the project is setup for AI and you can tell Claude what you need! Like, “implement a log server. I also need a drop in client class for my DOM based projects that talks to the log server. Use Websocket as the transport mechanism”. And voila, Claude will crank out a websocket based server for you ready to rock.

Obviously, the better description you give the better the results you get. It’s often a good idea to plan ahead so that your prompts are as descriptive as possible.

Availability

We are hoping to push this feature out to our customers ASAP, so either this weekend or at the beginning of next week.

And we are just getting started!

TQTXPlatform

The only external dependency to the Quartex runtime library so far, has been platform.js. This is a tiny library that provides in-depth info about the browser. Things like operating system, version, the browser type and other useful tidbits. Well, that dependency is no more since we now have our own, pascal only version in the RTL!

Same interface

Since we want as much as possible to be the same between NodeJS and DOM projects from an RTL point of view -we now have a simple base-class called TQTXPlatform in the unit qtx.platform.pas (system package).

We then have TQTXPlatformDOM in qtx.dom.platform.pas (DOM package) which inherits from TQTXPlatform. And likewise, TQTXPlatformNode in qtx.node.platform.pas within the NODE package.

Being able to query the runtime environment uniformly is always good

So depending on project type, you include either qtx.dom.platform or qtx.node.platform to your uses clause if you need info about the runtime environment.

Properties

The following properties are exposed uniformly:

  • Runtime: TQTXRuntime
  • Version: TQTXPlatformVersion
  • OSType: TQTXPlatformType
  • OSArchitecture: TQTXArchitecture
  • Product: string
  • Manufacturer: string
  • Description: string

The ‘OSType’ enum is perhaps the most useful besides ‘Runtime’, and provides the operating system you are running on (as far as it can be detected). The following systems are checked for:

  • pmUnknown (returned if no OS can be recognized at all)
  • pmAmiga
  • pmWindows
  • pmLinux
  • pmUnix
  • pmMac
  • pmiOS
  • pmChromeOS
  • pmAndroid
  • pmEmbedded
  • pmWindowsPhone
  • pmXBox
  • pmPlaystation
  • pmNintendoSwitch

While Mac is technically a Unix system, it made sense to separate it from systems like FreeBSD, OpenBSD or IBM AiX. We also included the 3 most prominent gaming platforms (XBox, PS and Nintendo) since these have browser capability.

How to use?

The baseclass (TQTXPlatform) has a class property that is initialized depending on what unit you have included. So for a DOM project you will naturally add “qtx.dom.platform” to your uses clause. You can then access the instance like this:

TQTXPlatform.Current.[property]

For example:

writeln( TQTXPlatform.Current.Description );

Changes to the RTL

There were a couple of places in our code that we had lose functions for doing similar things earlier (which called platform.js). These are now obsolete and everything in the RTL that needs to check things like browser type etc – now uses the platform system.

Just be aware of this if you recompile any code that uses the older functions.

The code will be in the next RTL update!

Quartex Pascal v1.0.1.4 is out

We are happy to report that a new update of Quartex Pascal is out and available for download. This is quite a monumental build that introduces more infrastructure for non-visual components – and several fixes to issues that showed up with the introduction of datamodules.

What’s new and cool?

Visually there is not much change, but under the hood there has been a plethora of changes. As with all technology there is a buildup phase that culminates in new functionality. We introduced datamodules and non-visual components in the previous release, and in this release we introduce more infrastructure in the RTL and IDE for using that.

Non visual components with delegate support

Our visual widgets all create and manage a DOM element throughout it’s lifecycle, and thus obtains most of it’s power and features from the DOM. With non-visual components however, there is not always any element to “manage”. This means that things we take for granted, such as delegates, needs to be added. This means creating an instance of JEventTarget manually (which our new TQTXEventComponent does) so that it smoothly interfaces with the rest of the RTL.

This is especially important when it comes to NodeJS where the entire substrata is completely different from the browser. Our RTL makes it much easier to approach that world, and will culminate in drag & drop server and database components (and whatever else people will need).

Changelog for the IDE & Backend

  • Adaptation of codebase to be more compatible with Freepascal, so far we have made internal changes to around 100 units and created our own component packages as “middleware” to ensure behavior is identical when building with Delphi or Lazarus.
  • Implemented utilities and configuration for automatic deployment and incremental updates on the server. Our build server now builds the QT framework from source (C/C++) including our own webview wrapper (which Lazarus for some reason dont have).
  • Added timestamp and delta columns to log entries for easier profiling on ARM
  • Added several Wayland fixes to the Linux aspect of our codebase
  • Major improvements to the compiler we use for code-suggestion, avoid recompiling if we have an AST in the cache, and no real changes has been made to the in-memory model
  • Fixed errors that appeared when compiling the latest code under Delphi, this affected the AST provider sub-structure and form designer units
  • Fixed a problem with the Hide() method on THintWindow, since this is a thin wrapper over WinAPI it expected a releasehandle() call
  • Reverted to calling inherited when doing segmented drawing in the form designer, this allows Delphi to do it’s thing and produces less risk of clutter when resizing
  • Implemented TQTXGroupBox as a proxy over TGroupbox, shielding us from subtle differences between Lazarus and Delphi. Same with TQTXPopupmenu, TMaskedEdit and TChecklistbox
  • First build for ARM64 Linux, quite emotional to see our baby fire up on a Raspberry PI, ODroid N2 and RockPi 5b!
  • Updated our server-side signing tool, it’s now much faster and reduces build time by quite a margin
  • Added support for class reference fields to the IDE, making it possible to bind non-visual components to visual components in the RTL, this gives us the same capability as Delphi and Lazarus when it comes to isolating behavior in separate, non-visual components.
  • Cleaned up the form designer tab in the IDE and added support for renaming and/or removing references to class-reference fields. So if you have a component named “mycomponent” that is linked to some widget, and you rename your component – the IDE ensures that the widget’s named reference is updated
  • Implemented regional clipping for faster graphics in the form designer, including segmented redraw and overlap culling
  • Optimization of the form designer, removed several instances where it produced redraw calls that were not needed. The result is a more snappy and natural feeling form designer that works well on slower devices
  • Implemented preliminary control renderer. This has not yet been activated, but will be an option further down the line. This teaches the IDE to draw the components in a more familiar way (e.g a button will look like a button, a listbox will look like a listbox etc).
  • Added $APP:Platform support to the IDE for obtaining platform defines in a QTX unit ( {$I “APP::PLATFORM”}
  • Added the platform defines and made them omnipresent (you dont need to explicitly use the above APP::PLATFORM moniker), this makes it possible for you to write platform dependent code. You can now use the following $IFDEF’s in any QTX unit.
    • {$IFDEF PLATFORM_DOM}
    • {$IFDEF PLATFORM_NODE}
    • {$IFDEF PLATFORM_ANY}
  • Major fix for the form designer’s deleteSelected() method, this expected the container to be of type TQTXForm and became cataclysmic when it received a TQTXDatamodule instead
  • Added internal routines to the IDE for easier distinction between form and datamodules
  • Added SnapShot() and Restore() to the IDE tab, allowing us to close all files and instantly restore them when needed
  • Fixed a problem when opening the license dialog where it would reload the packages when you close it, regardless of any changes. This broke any filesource references in open tabs since the old packages were disposed. We now Snapshot() any open units, close them if there has been a change, and re-create them when the packages have been reloaded
  • The IDE expected to find TQTXForm based classes in the current unit when generating delegate handlers. Expanded this to include datamodules.

RTL changelog

  • Updated TQTXEvent and TQTXDelegate to better work with both the DOM and NODE
  • Added TQTXMessageDelegate and TQTXDOMMessageDelegate to enable proper JS message handling
  • Added TQTXAppEvents component, this supports TQTXDOMMessageDelegate
  • Added TQTXOperatingSystem enum definition to sysutils
  • Added PercentOfValue() method to TInt32 helper class in sysutils
  • Added qtx.components.pas to the system namespace / package, with TQTXEventComponent and Delegate baseclasses, these are for components that dont manage a native handle / element. An instance of JEventTarget is automatically the handle.
  • Added [RegisterDelegate] that works with both native delegates and custom-events.
  • [RegisterWidget] attribute works on both components and widgets.

Changes to come

  • We have spent a week doing non-visual components, but we want to test them more in-depth before we add them to the RTL, but they are coming!
  • Forms register with TQTXApplication, but datamodules do not. This must be changed so datamodules can be referenced directly (just add a unit variable for now) with ease.
  • NodeJS can now finally have a proper application object as its root singleton -and expose the same level of control as its DOM counterpart.
  • Experimental support for standard delphi events is soon possible, so we can support both (!) In the inspector.
  • Add app-wide messages can also be done, opening up for TAction and TActionList components.
  • Collection and collection-item handling through attributes
  • MCP server and AI directly in the IDE!

Working with JS events

By Jon-Lennart Aasenden

JavaScript is a bit different from the strict and rigid reality of C/C++ and Object Pascal that many developers are used to, and this is especially visible when it comes to event handling.

In this article I will focus on a relatively advanced topic, meant for those of you that implement components and widgets yourself. Especially if you are writing components that is meant to be used by JavaScript developers outside your object pascal ecosystem – or sold as a commercial package for Quartex Pascal.

Custom events?

As you no doubt have noticed QTX supports both event paradigms:

  • Modern delegate objects (also called multi-cast events)
  • Classical procedural events

I have touched on delegates before so I wont rehash too much. But in short: delegates as events as object. Objects that you can attach any number of procedural handlers to, and they all fire in the same sequence you added them. So yes, you can have 100 separate OnClick handlers attached to a button if you like.

So in this article we will cover the full path from creating the event your component issues – to making delegate object that you register with the IDE. Once registered the delegate becomes available in the ‘add new delegate’ dialog for your components.

Widget vs Component?

Before we continue we need to talk about the differences between the DOM (browser) and NodeJs (server coding). All visual controls in QTX inherits from TQTXWidget. This is a root class that creates a specific DOM element, manages it during it’s lifecycle -and finally destroys the element in the destructor.

Visual and non-visual are radically different

More or less all visual DOM elements expose the same delegates (or supports the same events). Delegates such as Mouse, Pointer and Keyboard are all intrinsic. Meaning, that we did not manually implement the mechanisms behind it – we simply wrapped what was already there in the browser.

With non-visual components that inherit from TQTXComponent the story is very different. The DOM is all about visual elements that has a handle (a reference to the HTML element itself) that we manage. So when a TQTXWidget is created, it also creates the HTML element that it represent -and thus absorb all the features DOM elements have. Delegates included.

With TQTXComponent that doesn’t happen, because by default there is no underlying element to manage. There is no handle property that we can typecast and get easy access to event mechanisms that are already there. The positive side of this is that it grants us complete freedom.

The EventTarget class

Hidden in the qtx.delegates.pas unit there is a class definition called JEventTarget. This is (simply put) the class you need to deal with event subscription and dispatching the JavaScript way.

Thankfully, the class is small and fairly self explanatory:

The definition is a clean external class, exposing essential functions

If you are wondering why on earth they named this event-target when it’s more of an event-source, you are not alone! The rationale behind the name is that it must be seen from the event object’s point of view; where the event goes into this target and is dispatched to the subscribers.

Note: If you are snooping around the unit you will also notice another class, JEventEmitter, that looks more impressive. But that is a NodeJS exclusive class. If you want to write code that works for both the DOM and NodeJS then stick to JEventTarget.

Since our TQTXComponent has the scaffolding in place to manage a ‘handle’ but doesn’t magically create anything by default, the handle property is actually the perfect spot to store our JEventEmitter object!

Alright, enough theory – let’s setup the basics:

type
  // Forward declaration
  TQTXEventComponent = class;

  // Our constructor callback
  TQTXEventComponentConstructor = procedure (AComponent: TQTXEventComponent);

  // Our class definition
  TQTXEventComponent = class( TQTXComponent )
  protected
    function GetEventTarget: JEventTarget; virtual;
    property EventTarget: JEventTarget read GetEventTarget;
  public
    constructor Create(AOwner: TQTXComponent; CB: TQTXEventComponentConstructor); reintroduce; virtual;
    destructor Destroy; override;
  end;

constructor TQTXEventComponent.Create(AOwner: TQTXComponent; CB: TQTXEventComponentConstructor);
begin
  inherited Create(AOwner, procedure (AComponent: TQTXComponent)
  begin
    SetHandle( new JEventTarget );
    if assigned( CB ) then
      CB( self );
  end);
end;

destructor TQTXEventComponent.Destroy;
begin
  SetHandle( unassigned );
  inherited;
end;

function TQTXEventComponent.GetEventTarget: JEventTarget;
begin
  result := JEventTarget( GetHandle() );
end;

Since the public Handle property just exposes a plain-old untyped THandle, I have made a protected, typed property called EventHandle that casts the Handle for us. If you want to make that property public is up to you. I added it to make access easier from within the component.

Triggering events

Now that we have a base-line component for dealing with event subscription, removal and dispatching – how exactly do we trigger an event?

Before we can dispatch an event we first need to understand what JavaScript expects from us. And what it wants is that we isolate the data for the event in a separate object. So we end up with two parts here: first the event itself, and secondly the data structure we want to inform our subscribers about.

Let’s look at the actual event itself. JavaScript provides a special class for this called ‘CustomEvent’. This inherits from JEvent so it has all the bells and whistles we need. As you can guess from this class definition, the detail field is where it wants to store our data.

JCustomEvent allows us to create, well, custom javascript event objects

Since we will be needing a type for our detail field -let’s define a JS friendly class for that first. Classes that will be used directly with JavaScript should inherit from JObject rather than TObject. Let’s setup two fields for our event:

JMyData = class( JObject )
  mdValue:  int32;
  mdText: string;
end;

With that in place, let’s copy the JCustomEvent definition in full and change that variant datatype to our new JMyData type. There is no point inheriting out from JCustomEvent since we want to alter that detail datatype:

JMyCustomEvent = class external "CustomEvent" (JEvent)
public
  property    detail: JMyData;
  constructor Create(&type: string; EventData: variant); overload;
end;

With both the data and event itself defined, let’s make life easier for ourselves. Instead of having to create the event object all over the place – let’s isolate it in a method we can use:

procedure TQTXEventComponent.SignalCool(const Value: int32; const Text: string);
begin
  var lEvent := JMyCustomEvent.Create('cool',
    class
      mdValue := value;
      mdText := text;
    end);
  EventTarget.dispatchEvent( lEvent );
end;

If you are looking at that code scratching your head about the ‘class’ keyword in the middle of a constructor – that is understandable. This is a unique and awesome feature called an inline class. This feature makes JavaScript coding more sane, especially when wrapping external libraries.

Inline class

When you define a normal class in Quartex Pascal that inherit from TObject, you actually get a full VMT (virtual method table) powered object! We are not simply emitting paper thin JavaScript object. The compiler operates more or less exactly like Delphi or Lazarus does and emits a VMT structure for each instance. Without this it would be impossible to support virtual and abstract methods, inheritance, partial classes and interfaces.

But the VMT doesn’t play well with JavaScript, because JavaScript expects a clean structure. We have a couple of options at hand for this scenario:

  • Inherit from JObject instead of TObject
  • Use a record type
  • Create an empty JS object and manually add name/value pairs
  • Use an inline class

The reason I used the inline class approach is simply that it’s easier. You don’t need to pre-define a type, you don’t need to create an instance of said definition, and you can sculpt the object ad-hoc there and then. It also protects the field and method names so these are not scrambled if you compile with obfuscation (records are not protected and should only be used internally by your code, not as parameters for a JavaScript call).

So this code:

type
TMyData = class( JObject )
  mdValue: int32;
  mdText: string;
end;
var data := new TMyData;
data.mdValue := 12;
data.mdText := "hello there";

Can be more elegantly written as:

var data = class
  mdValue := 12;
  mdText := "hello there";
  end;

Since there is no need for us to keep the data once it’s dispatched, I simply defined it in-place as a parameter. You can use a variable for it if you like, but there really is no need and just wastes memory.

Making a delegate object

If all you want to do is add handlers at runtime via code and trigger events manually – you are all set. Simply expose the EventTarget property as public, and you can add as many handlers as you like.

But since the whole point of a rapid application development (RAD) tool is to quickly build up functionality through drag & drop – we really need to create a proper delegate class that can be registered with the IDE.

First we are going to need a procedural event type which represents the handler that the IDE will automatically create for you when you add a delegate:

type
TQTXCoolHandler = procedure (Sender: TQTXCoolDelegate; EventObj: JMyCustomEvent);

Next we need the actual delegate class, this is the class that is registered with the IDE later. I know this looks like a lot of work, but it’s pretty much copy & paste from the RTL and just change the datatypes to match:

TQTXCoolDelegate = class( TQTXDelegate )
protected
  procedure   DoExecute(Event: JEvent); override;
  function    GetEventName: string; virtual;
public
  procedure   Bind; reintroduce; overload; virtual;
  procedure   Bind(Handler: TQTXCoolHandler); reintroduce; overload; virtual;
published
  property    OnExecute: TQTXCoolHandler;
end;

function TQTXCoolDelegate.GetEventName: string;
begin
  result := 'cool';
end;

procedure TQTXCoolDelegate.DoExecute(Event: JEvent);
begin
  if assigned(OnExecute) then
    OnExecute(self, JMyCustomEvent(Event) );
end;

procedure TQTXCoolDelegate.Bind;
begin
  inherited Bind( GetEventName() );
end;

procedure TQTXCoolDelegate.Bind(Handler: TQTXCoolHandler);
begin
  var lTemp: TQTXDelegateHandler;
  asm
    @lTemp = @Handler;
  end;
  inherited Bind( GetEventName(), @lTemp);
end;

And the final piece is that we register our fancy new delegate with the IDE. This is done through attributes on the component(s) that support said delegate. So if you have an event called “cool” in other components, you can register the delegate for that class as well.

So let’s attach the delegate to the component we created earlier:

[BindDelegates([TQTXCoolDelegate])];
TQTXEventComponent = class( TQTXComponent )
protected
  function GetEventTarget: JEventTarget; virtual;
  property EventTarget: JEventTarget read GetEventTarget;
public
  procedure   SignalCool(const value: int32; const text: string);
  constructor Create(AOwner: TQTXComponent; CB: TQTXEventComponentConstructor); reintroduce; virtual;
  destructor Destroy; override;
end;

And that is it! You now have a non-visual component that supports established JavaScript event management, and a delegate class that registers with the IDE and that shows up in the “new delegate” dialog!

Ta-da! Our fancy new non-visual component! And our “cool” delegate shows up in the new delegate dialog

Tips and suggestions

If every event your component supports have different data then there will be a bit of copy and pasting for each event. I strongly suggest you plan your events before you begin writing delegate classes.

Also remember that you can use partial classes to put the delegates in their own unit, just to make large and complex components with many delegates more manageable.

I also suggest that you support the old procedural events at the same time. I usually define these first while I work, because it’s so easy to change them when needed. Once my code is fully operational and the events deliver what I need them to do – then I write delegates that match.

If the data you deliver in your delegates is the same for several events, then you can just use inheritance to cut down on the work. Simply override the GetEventName() and you can re-use the baseline.

Global defines and ARM targets

A day ago I wrote about the new platform defines that helps you implement platform specific code in the same unit. On second thought we have instead decided to just add these values to each unit when we process them. This way the values are always known to the compiler.

This has the benefit of being faster than resolving the includefile symbol. On modern computers you don’t really notice any difference, but if we can shave a few clock cycles off something and it doesn’t cause any issues – we will do that (looking at you Raspberry Pi!)

Testing Quartex on ARM

Yes you read that right! Behind the scenes we have been very busy adapting our entire codebase so it compiles with both Delphi and Lazarus, with the aim of delivering a fully native IDE for MacOS, Windows and Linux – under both x64 and ARM chipsets.

Quartex Pascal IDE running on an ODroid N2 single-board computer, here executing the web desktop project

Needless to say this is going to take some work. While Lazarus is a wonderful system with some amazing components and features, the LCL has a few quirks that needs to dealt with. There are also quite a few components that simply do not exist under Lazarus that we have to implement from scratch – or find a suitable replacement for.

We have decided to go with the TrollTech QT bindings, and Kjell has built everything from scratch from the C/C++ code so we could get the webview working too. This is not supported by default in the Lazarus bindings.

The reason I mentioned clock-cycles and speed earlier taps into ARM support. Namely that ARM single board computers are nowhere near as powerful as x64. Granted, the Raspberry PI 5b performs exceptionally well, as does more expensive boards like the Radxa Rock Pi 5b and Orange PI 5 or 6. But those kinds of boards are not cheap. A Radxa Rock Pi 5b with 32 gigabytes of ram will set you back around $400-450 with shipping and taxes (depends where you live, Norway is tax hell).

Finding the lowest spec’s to support

One of my all time favorite SBC’s (single board computer) is the ODroid N2. It is a spectacular piece of equipment that is a incredibly reliable and powerful. But the N2 is close to two generation behind us, and while it’s a perfect board for a number of scenarios – it’s not suited to host a full development studio like QTX.

The N2 was released back when the Raspberry PI 4b was king of the hill. It’s roughly 20% faster for single-core tasks and 50% faster for multi-core tasks (just ballpark numbers, don’t quote me on this). Compared to the current Raspberry PI 5b (which is what most people buy) it’s not even a contender. The PI 5b is around 3 times faster. And the Rock Pi 5b is around 5 times faster.

The ODroid N2 is a wonderful SBC and one of my favorites

My point here is that once you start running your code on these types of devices – you quickly notice bottlenecks and speed penalties that you never even knew was there. You are not going to notice much difference on a Rock PI 5b because that is closer to a Intel i5 mobile SoC in performance. But the moment you start targeting Raspberry PI 4b, ODroid N2 and that generation of devices – you feel every hit and speed bump.

But the good news is: we were expecting this.

We have avoided multi-threading in the IDE on purpose, because we have been able to cover all our needs on a single core. The IDE, the compiler, parsers, code generation — all of it runs in the main application thread.

At the same time we have made sure to add thread safe mechanisms in most places where we need them. Obviously we need to add a bit more here and there, but the broad strokes already exist. In short: the infrastructure for threading the compiler and code generation is partly in place by default.

Performance affinity

Linux operates with affinity rather than concrete thread priority (well, both actually). Affinity is closer to preference in this case, where whatever affinity you assign to a process or thread – informs the task scheduler which core to use when running your code.

This is very important because ARM SBC’s typically use something called ‘little-big architecture’. Meaning that a board will have some performance cores that run at higher clock-speeds, and X number of service cores that are weaker, often A55 or A57 (as opposed to A75). So you can have two performance cores for example, and 4 service cores. As the name implies you want to run as much of the OS services on those weaker cores, while reserving the performance cores for important stuff.

The tests we have done so far is without any particular affinity, meaning that the we didn’t specify any particular preference. As a result the kernel put the IDE under a service-core every single time.

So the agenda for ARM development going forward is to put the compiler, codegen and parser (we have several parsers) into their own threads – and ensure that it’s affinity is set to performance cores. The IDE in general might get by running on a vanilla service-core, I will have to test this more in depth.

For now, the lowest spec is a Raspberry PI 5b, but we do hope to optimize and make Raspberry PI 4b and ODroid N2 (and similar) a good experience too.

Platform specific defines

With datamodules and non-visual component support in place, requirements that makes writing components for both NodeJS and the DOM from the same unit begins to surface. While the RTL takes care for most situations – there are spots where being able to have a $IFDEF to test against saves you some time.

Magic includes

In QTX we have a few so-called ‘magic’ include files that the IDE and compiler generates on the fly. When you start out with QTX one of the first things you notice is that forms have a {$I “intf::form”} coupled with a {$I “impl::form”}, like you see below:

Solves a very real issue quite elegantly with zero overhead at runtime

Since you are compiling for either the browser (DOM) or server (NodeJS) the idea of loading a DFM resource that contains your design is something we want to avoid. So what we do is that we convert your form design into code, and inject that into the constructor of your class.

So in the above picture the {$I “intf::form1”} will be replaced by your named widgets that you have dropped on your form — while the {$I “impl::form1”} will be replaced by the code that creates these widgets (hence its positioned inside the constructor).

Application magic

The third magic include is {$I “app::form.init”} that you can find in the app.entrypoint.pas file of your project. As you probably suspect, this is replaced by the generated code that creates your forms or datamodules.

Simple but very effective

These “magic includes” solve challenges in a simple, straight forward and ad-hoc way, with as little overhead as possible.

App::config

Today we added a new token to the “APP::” magic namespace, namely “config”. As the name hints at this provides information about the current build configuration. Depending on the platform you target, the following definitions will be emitted:

  • {$DEFINE PLATFORM_DOM}
  • {$DEFINE PLATFORM_NODE}
  • {$DEFINE PLATFORM_ANY}

And depending on the type of project, the following:

  • {$DEFINE TARGET_DOM}
  • {$DEFINE TARGET_NODE}
  • {$DEFINE TARGET_LIBRARY}
  • {$DEFINE TARGET_CLUSTER}

A normal visual project will have {$DEFINE PLATFORM_DOM} and {$DEFINE TARGET_DOM}

A library project will have {$DEFINE PLATFORM_ANY} and {$DEFINE TARGET_LIBRARY}

So instead of having two separate units for the same component (where that applies of course) you can now do stuff like this:

Easier to implement platform dependent code

This new feature will be in the next update!

Using LLM’s with Quartex Pascal

Nico Wouterse is no stranger to anyone in the Quartex community and have been with us since the project’s inception. He has worked quite closely with LLM technologies the past few years, building his own design library and design principle framework.

He just published a booklet and ‘how toon using LLM’s with QTX, which is an exciting read. So head on over to his website and have peek:
https://lynkfs.com/docs/QTX-and-LLM-integration/

Future roadmap

We are presently at the cusps of finishing both our roadmap steps, and one of the points on the next roadmap – is a command-line compiler. Both for Linux, MacOS and Windows (covering both x86 and ARM).

When that becomes available it should make QTX a dream for people generating projects using AI, invoking the command-line compiler to build the final product.

We could even look at licensing options for no-code websites that wants to use QTX as their back-end, spinning it up in containers to do the heavy lifting for their own no-code product.

Anyways – head over to Nico’s! He also has other booklets that could be of interest!

Datamodules are now here!

In the previous post I talked about our support for non-visual components. This is now in place and I moved on to the next ticket on our list: datamodules! And we already have this working!

Datamodules changes everything

For those coming from Delphi, C++Builder or Lazarus – datamodules need no introduction. It allows us to quickly drag & drop non-visual components together which at runtime becomes a complex object structure.

Datamodules are now operational! I need to make the form designer more aware, as it creates a few properties that is strictly not needed for datamodules (left, top, width etc). So we should have everything ready in our next update!

While this is ‘old news’ for native languages, it is actually entirely new for the world of JavaScript. And it really comes into it’s own under Node and Deno (server side programming).

Node made easier

Node is what people use to create servers or local system-services that are platform agnostic and async in nature. The non-visual nature of the many modules and packages that Node servers are made from – makes it difficult to create any form of visual tooling for it.

This is where Datamodules come in! All those fancy classes we have created for NodeJS can now be elevated to TQTXComponent and registered with the component palette. That way you can drag & drop servers and non-visual classes just like you would in Delphi or C++Builder.

We can also introduce new project types where datamodules are created automatically by the application object (just like visual applications), which ties in nicely with the existing project build-config. This also applies for the library project type and should turbo charge creating complex libraries that can be consumed by JS developers.

Exciting times ahead!