Code Maintenance Guide for Non-Qt Developers
March 12, 2026 · View on GitHub
This guide explains how to understand and maintain mycroft-gui-qt6 code without deep Qt expertise.
Quick Mental Model
Think of Qt applications like this:
┌─────────────────────┐
│ Application │
│ (main event loop) │
└──────────┬──────────┘
│
┌──────▼──────┐
│ Objects │ (C++ classes)
│ & Data │
└──────┬──────┘
│
┌──────▼──────┐
│ QML │ (UI markup, like HTML)
│ Templates │
└─────────────┘
Key Concept: Qt constantly monitors objects for changes and updates the UI automatically.
Common File Types
1. Header Files (.h)
Declare what classes do. Example structure:
#pragma once // Prevent double-inclusion
#include <QObject> // Base class from Qt
#include <QString> // Qt's string type (like Python's str)
class MyClass : public QObject { // Inherit from Qt base class
Q_OBJECT // Qt magic macro - makes introspection work
public:
MyClass(); // Constructor
~MyClass(); // Destructor
void doSomething(); // Public method (call from anywhere)
private:
QString m_data; // Member variable (starts with m_)
Q_SIGNALS: // Qt's observer pattern
void dataChanged(); // Emit when something happens
};
Key Pattern: Methods starting with m_ are private members (don't touch from outside)
2. Implementation Files (.cpp)
Contains actual code. Example:
#include "myclass.h"
MyClass::MyClass() {
// Constructor - runs once at creation
m_data = "initial";
}
void MyClass::doSomething() {
m_data = "changed";
Q_EMIT dataChanged(); // Tell observers something changed
}
3. QML Files (.qml)
UI markup (like HTML). Example:
import QtQuick
import QtQuick.Controls
Rectangle { // Base shape (like a div)
width: 200
height: 100
color: "lightblue"
Text { // Display text (like <p>)
text: "Hello" // Static text
anchors.centerIn: parent // Center in parent
}
Button { // Clickable button
text: "Click me"
onClicked: {
// C++ signal handler
MyObject.doSomething()
}
}
}
Key Idea: QML is declarative (you describe what it should look like, not how to draw it)
Signal/Slot System (Qt's Observer Pattern)
This is how different parts notify each other:
// In header
class MyObject : public QObject {
Q_OBJECT
Q_SIGNALS: // These can be emitted to notify listeners
void valueChanged(int newValue);
public Q_SLOTS: // These respond to signals
void handleValueChange(int val);
};
// In implementation
void MyObject::handleValueChange(int val) {
qDebug() << "Value changed to" << val;
}
Connecting signals to slots:
MyObject obj1, obj2;
// When obj1 emits valueChanged, obj2's handleValueChange runs
connect(&obj1, &MyObject::valueChanged,
&obj2, &MyObject::handleValueChange);
In QML:
Button {
onClicked: {
// This is a signal handler
MyObject.sendMessageToServer()
}
}
Property System (Bindings)
Qt properties automatically update the UI:
// Header
class MyClass : public QObject {
Q_OBJECT
Q_PROPERTY(QString name READ getName WRITE setName NOTIFY nameChanged)
public:
QString getName() { return m_name; }
void setName(const QString& n) {
m_name = n;
Q_EMIT nameChanged(); // Notify UI
}
Q_SIGNALS:
void nameChanged();
private:
QString m_name;
};
In QML:
Text {
text: MyObject.name // Automatically updates when C++ changes it
}
Common Qt Patterns in This Project
1. Singleton Pattern (MycroftController)
class MycroftController : public QObject {
public:
static MycroftController* instance() {
static MycroftController* s_instance = nullptr;
if (!s_instance) s_instance = new MycroftController();
return s_instance;
}
};
Why: Only one controller per app (represents "the server connection")
Usage:
MycroftController::instance()->sendMessage("hello");
2. Parent-Child Ownership
class Parent : public QObject {
public:
Parent() {
// Child automatically deleted when parent is deleted
new Child(this); // 'this' is the parent
}
};
Why: Prevents memory leaks - Qt cleans up automatically
3. Q_ENUM and Message Types
// Header
class MyClass : public QObject {
Q_OBJECT
public:
enum MessageType {
PageShow,
PageDelete,
DataUpdate
};
Q_ENUM(MessageType)
};
// Usage - Compile time checked!
MyClass::MessageType type = MyClass::PageShow;
Why: Type-safe instead of string literals ("page.show")
Threading Notes
Important: Qt is single-threaded by design
// All of this runs on "main thread"
void MyClass::sendMessage() {
// ✓ OK - runs on main thread
MyObject.doSomething();
// ✗ DON'T - would crash, runs on different thread!
// thread.start(lambda { MyObject.doSomething(); });
}
Exception: Network operations (WebSocket) are handled internally by Qt
Memory Management Checklist
When writing code:
// ✓ GOOD - parent handles deletion
new Child(parentPointer); // parentPointer must be valid QObject
// ✓ GOOD - explicit parent set later
Child* c = new Child();
c->setParent(parent);
// ✗ BAD - no parent, will leak!
new Child();
// ✓ GOOD - stack variable, auto-deleted
MyClass obj;
// ✓ GOOD - smart pointer (modern C++)
std::unique_ptr<MyClass> obj(new MyClass());
Debugging Tips
1. Print Debug Messages
#include <QDebug>
qDebug() << "Value is:" << m_value;
// Output: Value is: 42
qWarning() << "Something wrong"; // Yellow warning
qCritical() << "Error!"; // Red error
2. Check Object Connection Status
bool connected = connect(obj1, &MyClass::signal1,
obj2, &MyClass::slot1);
if (!connected) {
qWarning() << "Connection failed!";
}
3. QML Debugging
import QtQuick
Rectangle {
Component.onCompleted: {
console.log("Rectangle created"); // Log when component loads
console.warn("This is a warning");
}
}
Common Mistakes
Mistake 1: Modifying objects from wrong context
// ✗ DON'T - this crashes if called from different thread
void setValueFromNetwork(int val) {
m_value = val; // ← Might crash!
}
// ✓ DO - use Qt's signal/slot mechanism
void NetworkHandler::onDataReceived(int val) {
Q_EMIT valueReceived(val); // Signal on any thread is safe
}
// Connect to: MyObject::setValue(int) which updates on main thread
Mistake 2: Calling C++ methods directly from QML
// ✗ DON'T
Button {
onClicked: MyObject.internalPrivateMethod() // Might break encapsulation
}
// ✓ DO - use public slots
Button {
onClicked: MyObject.userDidClickButton() // Clear intent
}
Mistake 3: Memory in lambdas
// ✗ DANGEROUS - if obj is deleted, this crashes!
connect(button, &Button::clicked, [this]() {
someObject->doSomething(); // 'someObject' might be invalid
});
// ✓ SAFE - use context object
connect(button, &Button::clicked, someObject, [this]() {
someObject->doSomething(); // Qt disconnects if someObject deleted
});
Reading Existing Code
Step 1: Find the Entry Point
- Look in
application/main.cpp - Understand what gets created first
Step 2: Trace Key Classes
MycroftController- handles server connectionAbstractSkillView- displays individual skill UIsSessionDataModel- holds skill data
Step 3: Look for Q_PROPERTY
Q_PROPERTY(bool speaking READ isSpeaking NOTIFY isSpeakingChanged)
// ^type ^name ^getter ^signal when changes
This tells you:
- Variable name:
speaking - How to read it:
isSpeaking() - When it changes:
isSpeakingChanged()signal
Step 4: Follow Signal/Slot Connections
Find connect() calls - these show how objects talk to each other:
connect(sender, &SenderClass::signal,
receiver, &ReceiverClass::slot);
// ↑ This happens ↓ Triggers this
Testing Strategy
// Test files in autotests/ directory
#include <QtTest>
class MyTest : public QObject {
Q_OBJECT
private Q_SLOTS:
void test_something() {
MyClass obj;
obj.doSomething();
QCOMPARE(obj.getValue(), expectedValue); // Assert
}
};
QTEST_MAIN(MyTest) // Run this test
Run tests:
./servertest # Test server communication
./modeltest # Test data models
./message_routing_test # Test message types
Documentation Standard
Every class should have:
/**
* @class MyClass
* @brief What does this class do in one sentence?
*
* Longer description of purpose and responsibilities.
*
* Example:
* @code
* MyClass obj;
* obj.doSomething();
* @endcode
*
* @note Thread safety: This class is not thread-safe.
* @see RelatedClass
*/
class MyClass : public QObject {
// ...
};
Getting Help
- Qt Documentation: https://doc.qt.io/qt-6/ (official reference)
- Q_PROPERTY System: Search for "Qt properties" in docs
- Signal/Slots: Search "Qt signals and slots"
- QML Guide: https://doc.qt.io/qt-6/qtqml-index.html
Before Making Changes
- ✓ Understand which class you're modifying
- ✓ Look at existing examples in the file
- ✓ Check if similar code exists elsewhere
- ✓ Run tests before and after
- ✓ Write what changed in commit message