building a qt 6.2 application — the power of encapsulation

I’ve reached a point where the central part of the USB Device Monitor, which contains the device table at top and the console at the bottom, has evolved into its own class. It started out as a class function of the primary class, MainWindow. But it quickly became evident I needed to better organize specific functionality into its own compilation unit. The best way to do that was with a unique class that encapsulated the data and structures unique to both the table and console. That’s the reason I like C++ and other object-oriented languages; encapsulation. Not inheritance, encapsulation.

Here is the unique header for CentralWidget:

#pragma once

#include <QSplitter>
#include <QWidget>
#include <QString>
#include <QStringList>
#include <QHeaderView>
#include <QTableWidget>
#include <QTableWidgetItem>
#include <QTextEdit>
#include <QPlainTextEdit>
#include <QSettings>
#include <QPalette>
#include <QSerialPort>
#include <QByteArray>
#include <QScrollBar>
#include <QFont>

class CentralWidget : public QSplitter {
    const QString CWIDGET_STATE{"cwidget/state"};

    explicit CentralWidget(QWidget *parent);
    void saveCWState(QSettings &settings);
    void restoreCWState(QSettings &settings);

    QTextEdit *console;
    QSerialPort *serialPort;
    QTableWidget *table;

    void readSerialData();
    void openUSBPort(QTableWidgetItem *);
    void closeUSBPort();

And the implementation, with some beginning comments:

#include "CentralWidget.hpp"
#include "devices.hpp"

CentralWidget::CentralWidget(QWidget *parent) :
    console(new QTextEdit),
    serialPort(new QSerialPort),
    table(new QTableWidget) {

        // Configure the console onto which we will communicate with a given
        // USB device.
        QPalette colors = palette();
        colors.setColor(QPalette::Base, Qt::black);
        colors.setColor(QPalette::Text, Qt::white);
        QFont font("Monospace", 12);

        // Connect the console with the serial port so that any data that is
        // recieved by the serial port will be displayed on the console.
            serialPort, &QSerialPort::readyRead, this,

        // Find all the USB devices connected to our system. Put their data in a
        // spreadsheet-like table.
        auto devices = getDevices();
        QStringList headers = {"Device", "Adapter Name", "Hex Identification"};
        int row{0};

        // Populate the table with eacj USB device, one row/devices.
        for (auto [device, values] : devices) {
            QString dev(device.c_str());
            QString v1(values[0].c_str());
            QString v2(values[1].c_str());
            table->setItem(row, 0, new QTableWidgetItem(dev));
            table->setItem(row, 1, new QTableWidgetItem(v1));
            table->setItem(row, 2, new QTableWidgetItem(v2));


        // Connect the table with the class function so that when a user
        // clicks on a port in the table, that port will be opened and
        // communicating with the console.
            table, &QTableWidget::itemClicked, this,

        // Finish construction by putting the table at the top and the
        // console beneath it.

void CentralWidget::readSerialData() {
    QByteArray data = serialPort->readAll();
    QScrollBar *scrollBar = console->verticalScrollBar();

void CentralWidget::openUSBPort(QTableWidgetItem *cell) {
    if (cell->column() == 0) {
        QString portName = "/dev/" + cell->text();
        console->insertPlainText(QString("\nPort open: %1\n").arg(portName));
        QScrollBar *scrollBar = console->verticalScrollBar();


void CentralWidget::closeUSBPort() {
    if (serialPort->isOpen()) {

void CentralWidget::saveCWState(QSettings &settings) {
    settings.setValue(CWIDGET_STATE, saveState());

void CentralWidget::restoreCWState(QSettings &settings) {
    if(settings.contains(CWIDGET_STATE)) {

The key features of the new class are:

  • The table is built based on the number of entries found by getDevices(), and the data now fits into the table accordingly. The code was reorganized so that the devices are found before the table is filled with the resultant data.
  • The table cell click event is now connected to openUSBPort(), so that clicking on a cell that contains a port will close any port that may be open, then open the new port clicked on.
  • The class handles saving and restoring its own internal state, instead of having MainWindow do it, via saveCWState() and restoreCWState().

Now when you start the application, it just sits there not displaying any information. When you click on a port, such as ttyACM0, then that port is opened and any data being sent from the device connected to that port is displayed on the console. The screen capture leading this post shows how this works now. The ttyACM0 was clicked, and then the port was opened.

The biggest problem right now is that if an text is displayed on the console that contains ANSI escape sequences, they are not interpreted and instead are just printed as raw data. I’m going to have to either find a Qt class that can interpret those sequences and display the text accordingly (usually colors), or else write my own. What I may wind up doing is stripping that ANSI data before writing to a log file.

Another chunk of future work is to put functional controls on the dock panel. I have some ideas what I want.

Once I get those tasks out of the way, I will probably release this as an open tool for others to use. As I’ve written before, more to come…


building a qt 6.2 application — beginning usb comms