Skip to content

Systems Architect & Software Engineer

Many Years Building Modular, Long-Lived Industrial Software Systems

What I've Built

Custom Widget & Layout System with Event-Driven Architecture

I built a system where custom widgets connect to a shared interface for data and event communication. Layouts are created in a limited but functional wayβ€”widgets exchange data through this central interface rather than knowing about each other directly.

Qt-Based Property Sheet Configuration System

Using Qt Designer, I create complex layouts with splitters and panels. In the live application:

  • Widgets in the layout can configure global data IDs or events to connect to specific widgets
  • Configuration pages are added dynamically depending on the widget's inheritance hierarchy
  • Each custom widget brings its own property page(s)
  • When editing is enabled, Ctrl+Shift+RightMouseButton triggers the property sheet to launch

This means non-programmers can configure complex behavior through the UIβ€”no code changes needed.

Modular, Dynamically-Loadable Plugin Architecture

I designed the entire system using modular, dynamically loadable modules. The core principle:

Each module only deals with 2 or 3 interfaces.

This makes every module humanly manageableβ€”a developer can comprehend a single module quickly and test it independently. The architecture is easier and faster to work with because you don't need to understand the entire codebase to modify one piece.

Headless Document Generation Pipeline

Final output is always PDF. I chose to use LibreOffice in headless mode because:

  • The final deliverable is PDF anyway
  • LibreOffice is available on all target platforms
  • No custom PDF layout logic or image-embedding algorithms needed

This simplifies the deployment pipeline considerably.


Core Technical Skills

1. Qt Framework Expertise

  • Qt Designer for complex layout design (splitters, panels, nested hierarchies)
  • Qt property system and signals/slots architecture
  • Inheritance-driven dynamic UI generation
  • Real-time widget updates from data streams

2. Custom Widget Development

  • Designing reusable custom widgets
  • Implementing property pages for configuration
  • Widget inheritance hierarchies for extensibility
  • Data binding and event communication patterns

3. Modular Software Architecture

  • Interface-driven design (limiting modules to 2-3 clear interfaces)
  • Plugin-based, dynamically-loadable modules
  • Decoupling business logic from framework-specific code
  • System design that survives framework and technology transitions

4. Cross-Platform Software Design

  • Building systems that work across Windows and Linux
  • Designing abstraction layers that hide platform differences
  • Managing dependencies across multiple platforms

5. Document Generation & PDF Automation

  • LibreOffice headless PDF export
  • Template-based document generation
  • Cross-platform document processing (Windows and Linux)

6. System Integration & Configuration

  • Event-driven architecture and data flow management
  • Global configuration systems
  • Runtime widget configuration and data binding
  • Property-based UI generation

Design Philosophy & Approach

Modularity Through Interface Segregation

Rather than building monolithic applications, I break systems into small, independent modules. Each module exposes only 2-3 interfacesβ€”no more. This achieves several goals:

  • A developer can understand a module without reading the entire codebase
  • Testing is straightforwardβ€”mock a few interfaces, test the module
  • Adding or replacing a module has zero ripple effects on others
  • The system remains maintainable as it grows over years

Separation of Concerns: Data from Presentation

Core algorithms and data processing logic are completely isolated from the visual framework. This means:

  • The business logic never knows whether it's running in Qt, a web framework, or headless
  • Updating the GUI framework doesn't require touching the core system
  • Data can flow through multiple display systems simultaneously

Event-Driven, Loosely Coupled Communication

Widgets and modules communicate through events and a shared data interface, not by calling each other directly. This creates flexibility:

  • New widgets can be added without modifying existing ones
  • Widgets operate independentlyβ€”they don't need to know about each other
  • The system can be extended with new modules without recompiling the core

Pragmatic Technology Choices

I select tools based on fitness for purpose, not fashion. For example:

  • LibreOffice headless is the right choice for PDF generationβ€”it's proven, available everywhere, and eliminates custom code
  • Qt is chosen for its cross-platform capabilities and property system
  • Custom modules are kept minimal and focused

Practical Implementation Details

How the Property Sheet System Works

The user interface allows operators to configure how widgets behave:

  1. User creates a layout in Qt Designer: splitters, panels, custom widgets
  2. At runtime, operator can Ctrl+Shift+RightClick any widget
  3. The property sheet inspects that widget's class and inheritance hierarchy
  4. For each level of inheritance, configuration pages are loaded
  5. Each custom widget provides its own property page(s)
  6. Operator configures data IDs, event bindings, and widget-specific settings
  7. Changes apply immediately without restarting the application

Result: Supervisors can configure complex behavior through UI without programming.

How the Modular Architecture Scales

The system grows by adding new modules, not by making existing modules larger:

  • Acquisition Module: reads raw data from hardware
  • Analysis Module: processes data (FFT, defect detection, etc.)
  • Visualization Module: renders graphs and indicators
  • Reporting Module: generates PDFs
  • Each module communicates only through defined interfaces
  • New analysis methods are added as new modules, not by editing existing ones

A developer joining the team can learn one module in a few days, work independently, and understand the entire system in weeks rather than months.

How Data Flows Through Custom Widgets

Widgets are configured to connect to data sources and events:

  • Widget is created in Designer
  • Operator configures via property sheet: "Listen to channel sensor/temperature"
  • Central event bus pushes updates to that channel
  • Widget receives updates and renders them
  • No widget knows about any other widget
  • Easy to swap widgets, add duplicates, or rearrange layout

How Document Generation Works

When a report is needed:

  1. Application receives trigger (button click or scheduled task)
  2. Data is formatted and ready
  3. LibreOffice runs in headless mode
  4. Template is processed and data injected
  5. Output is PDF
  6. PDF is stored or emailed
  7. No user interaction required; no GUI needed

Advantage: Works identically on Windows and Linux servers.


Technical Architecture (For Engineering Teams)

Interface-Driven Module Design

Each module presents a minimal public interface. Example structure:

class IResultData {
public:
  virtual void updateValue(const Data& data) = 0;
  virtual Data getLatestValue() const = 0;
};

class IAnalysisModule {
public:
  virtual void processRawData(const ResultData& raw) = 0;
  virtual AnalysisResult getResult() const = 0;
};

class IVisualization : public QWidget {
public:
  virtual void setDataChannel(const std::string& channel) = 0;
  virtual void onDataUpdated(const Data& newData) = 0;
};

Every module, no matter how complex internally, talks to the rest of the system only through these contracts. A developer can modify a module's internal implementation without affecting anything else.

Property Sheet Inheritance Traversal

When property sheet launches, it walks the widget's inheritance chain:

Widget β†’ BaseDataWidget β†’ QWidget

For each level: - Query its properties via Qt's meta-object system - Load or create a configuration page for those properties - Add page to tab control - Bind UI controls to underlying properties via Qt's property system

Result: Zero hardcoded UI. New widget types automatically get configurable property pages.

Event Bus / Publish-Subscribe Pattern

Data flows through a central event system:

Acquisition Module β†’ publishes to "sensor/temperature"
                    ↓
            Event Bus (central dispatcher)
                    ↓
        (All subscribers listen to this channel)
                    ↓
Visualization Widget A (registered: "sensor/temperature")
Visualization Widget B (registered: "sensor/temperature")
Logging Module (registered: "sensor/temperature")
Analysis Module (registered: "sensor/temperature")

Each subscriber is decoupled. Adding a new subscriber doesn't modify any existing code.

Layout Persistence & Factory Pattern

Layouts are saved as lightweight JSON schemas (not serialized framework objects):

{
  "LayoutName": "Dashboard",
  "Widgets": [
    {
      "Type": "TemperatureGraph",
      "Id": "widget_1",
      "DataChannel": "sensor/temperature",
      "Position": {"X": 0, "Y": 0, "Width": 4, "Height": 3}
    },
    {
      "Type": "StatusIndicator",
      "Id": "alert_1",
      "DataChannel": "system/alarms",
      "Position": {"X": 4, "Y": 0, "Width": 2, "Height": 1}
    }
  ]
}

On startup, a Factory loop reads this schema:

for each widget in schema:
  lookup WidgetFactory.create("TemperatureGraph")
  instantiate the widget
  set Position and Size from schema
  subscribe to DataChannel
  add to container

Benefit: Same layout schema works across different frameworks (Qt, Avalonia, web-based).

Cross-Platform Deployment

  • Core modules compile on both Windows and Linux
  • Platform-specific code is isolated to thin abstraction layers
  • LibreOffice headless works identically on both platforms
  • No platform-specific logic in business code

Why This Architecture Matters

Long-term sustainability: The modular design means the system can evolve without becoming unmaintainable. Individual modules can be upgraded, replaced, or rewritten without cascading failures.

Team scaling: New developers can work on single modules without requiring expert-level knowledge of the entire system.

Technology independence: Core algorithms don't depend on any UI framework or runtime, making them portable and durable.

Operational flexibility: The system can be deployed as desktop application, server-side service, or embedded systemβ€”same code, different interfaces.