GUI

Important

If you need to quickly find out where a graphical item is defined in the source code, open the application and activate

Help ‣ Toggle Developer Hints

Then place your mouse over a widget. This will display in the window's status bar where the widget's source code is. This is probably the quickest way to get started.

Directory structure

All code related to the GUI resides in src/ui/. The directory structure (including the most important header files) is as follows:

src/ui/
├── objects/      # GObjects and subclasses
├── widgets/      # Reusable QWidgets
├── windows/      # Windows and dialogs
├── mainwindow.h  # MainWindow
├── sheetview.h   # SheetView
├── sheetscene.h  # SheetScene
├── operations.h  # Scene operations
├── commands.h    # Undo-able commands
└── vim.h         # Vim mode

The main window

The main window is implemented as a singleton in the class MainWindow. The active instance can be obtained using MainWindow::getInstance.

class MainWindow : public QMainWindow

Public Functions

QTabWidget *getTabWidget() const
int getTabId() const
SheetView *getTab(int index = -1)
Sheet *getSheet(int index = -1)

The schematic editor

Qt provides QGraphicsView and QGraphicsScene to visually represent QGraphicsItem-s. You should have at least a basic knowledge of these classes if you wish to develop the GUI. We have created custom subclasses of these classes. A sheet is represented by a SheetView and SheetScene, and an object is represented by a GObject.

Note

The base classes are also used on their own in some places.

SheetView

Defined in src/ui/sheetview.h

This is a widget that displays a schematic to the user. It cooperates with SheetScene to display and interact with graphical objects.

Rubber band selection

Schim supports two types of rubber band selection. If the user initially drags the rubber band to the right, objects are selected only if they are wholly contained in the band. On the other hand, if the user initially drags the band to the left, any objects that are touched by the band are selected. Here's a coarse overview of what happens in the program.

  • mousePressEvent receives a left click and memorizes the position

  • Subsequent calls of mouseMoveEvent try to determine whether dragging is to the left or to the right by calling processRubberBandDrag.

  • mouseReleaseEvent disables the state of dragging.

SheetScene

Defined in src/ui/sheetscene.h

Scene operations

Defined in src/ui/operations.h

Many user actions are composed of multiple steps. For example inserting a line requires the insertion of two points in succession. For this functionality, we have created the class SceneOperation.

To start an operation, construct it and call SheetScene::startOperation. The operation will receive and process any applicable events. Once the operation finishes, it will emit the SceneOperation::finished signal. The scene will pick up on this signal.

class SceneOperation : public QObject

Subclassed by OpInsertComponent, OpInsertLine, OpInsertRect, OpInsertText

Public Functions

explicit SceneOperation(SheetScene *scene)
virtual void mousePressEvent(QGraphicsSceneMouseEvent *event)
virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event)
virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
virtual void keyPressEvent(QKeyEvent *event)
virtual void cancel()

Signals

void finished()

The following operations are currently defined:

Snap feature

The snap-to-grid feature is enabled/disabled by calling SheetScene::setSnapEnabled.

Scene vs view

Sometimes it may seem arbitrary whether to implement a feature in the view or in the scene. Sometimes it is. But as a rule of thumb:

Implement a feature in the scene if:

  • it changes the structure of the sheet

  • objects need to be aware of the feature and/or react to it

Implement a feature in the view if:

  • it doesn't change the structure of the sheet (e.g. zooming, panning)

  • it requires access to the global coordinate system (an exception would be if the global position can be obtained from a mouse event)

  • it needs to display items that are transform-invariant

  • multiple views containing the same scene can use the feature at the same time

The general idea is to let the scene know as little as possible about the view. This is because multiple views can be used to display the same scene at the same time. If the scene (or an object by calling a scene function) needs to communicate with a view, use signals and slots. This way, only interested views can pick up on that.

GObject

Overview

Inheritance graph

../../_images/classGObject__inherit__graph.svg


Collaboration graph

../../_images/classGObject__coll__graph.svg

An object is visually represented as a GObject, which is a wrapper around an Object. Derived classes follow the same naming convention. Namely, the graphical class is named by prepending the corresponding model class name with the letter 'G' (for graphical). For example, a Line is wrapped by GLine.

To construct a GObject to wrap an Object obj call GObject::assign(obj).

The following classes are derived from GObject:

class GObject : public QGraphicsObject

Subclassed by GCompositeObject, GLine, GRect, GTerminal, GText

Public Functions

explicit GObject(Object *obj)
virtual ~GObject()
virtual Object *get()
bool isHovered() const
virtual void setCosmetic(bool cosmetic)
virtual void reloadFromModel()
virtual void applyToModel()
virtual void showHandles(bool show = true)
virtual void handleChanged(GObjectHandle *handle)
void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override
void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override
QRectF boundingRect() const override
void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget*) override
GCompositeObject *parentItem() const

Public Static Functions

static GObject *assign(Object *obj)

Dialogs

Vim mode

You should read TODO in the manual before proceeding.

Vim mode is implemented in src/ui/vim.h and src/ui/vim.cpp. It is enabled and disabled by calling Vim::enable.

When a QWidget or GObject receives a keyPressEvent, it has the option to process that event in Vim mode. It is supposed to call Vim::registerKeyPress, providing the incoming event and a pointer to a callback function as parameters. The callback function will be invoked if a proper key sequence is matched.

Todo

Add more details.

namespace Vim

Typedefs

typedef Count N

Functions

void enable(bool enable)
bool enabled()
Count n()
void addBinding(const QString &sequence, const QString &action)
void addBindings(const QMap<QString, QString> &map)
void registerKeyPress(QKeyEvent *event, std::function<bool(const Action &action)> callback, bool allowCount = true, )
void resetStroke()
QString getStatusText()
class Action

Public Functions

explicit Action(const QString &command, int count = 0)
QString getCommand() const
Count getCount() const
bool operator==(const QString &cmd) const
class Count

Public Functions

Count(int count)
operator int() const
int raw() const