-
Notifications
You must be signed in to change notification settings - Fork 838
2. Developer Guide
Authors: Jonathan Cohen
Fritzing is built entirely using Qt, a powerful and stable C++ GUI coding framework (www.qt-project.org). We looked at quite a number of other frameworks/languages, and overall Qt seemed like the most stable, full-featured, and cross-platform-enabled development environment available. We had built a previous version of Fritzing using the Eclipse GMF/GEF framework, and though that allowed us to quickly create our first versions of Fritzing (for Mac and PC only), we found that we hit a wall, and had an incredibly difficult time moving forward from that point. So far, we have hit no such walls using Qt, and with a few exceptions we have had very little trouble building more-or-less simultaneously for Linux, Mac, and PC platforms.
We chose C++ over Java or Python bindings of Qt for three reasons. First, we figured that C++ might buy us a little extra performance. Second, C++ seemed more stable: Qt Jambi--the Java binding--though part of Trolltech's product line--had only been out for about a year, and the Python binding was offered by a third party (Subsequent events have proved us right--Trolltech/Nokia has dropped support for Qt Jambi.) Third, the Qt framework adds enough support (for example, by automatically deleting child objects when a parent is deleted, or with the signals-and-slots message-passing mechanism) that it feels like working in a higher-level language.
Another reason we chose Qt is that it offered us a set of classes for dealing with a large number of dynamic interactive 2D graphical objects. This is the QGraphicsView
framework and we make extensive use of it. We also extensively make use of other software technologies available through Qt, including: SVG rendering, SQLLite, Stylesheets, and Webkit.
Qt is also IDE independent, though it comes with its own IDE QtCreator, which some of use have been quite happily working with. But we have also built and run Fritzing on Mac using Xcode, on PC and Linux platforms using Eclipse, and on PC using Visual Studio (and earlier-on with some well-intentioned but only-partially-complete open-source Qt-specific IDEs). To get the full benefits of the debugging environments in XCode and Visual Studio, you have to build Qt from source.
Fritzing is currently built using the open source release of Qt, version 5.2.1.
The program starts in main.cpp
. This simply creates an instance of FApplication
and executes it. FApplication
runs a typical event loop lasting until the user quits Fritzing. FApplication
creates a number of MainWindows
--each MainWindow
represents a single file which is a Fritzing sketch. MainWindows
have multiple parts: a sketch area (SketchWidget
and its subclasses); and DockWidgets
which contain tools, such as the parts bin, parts inspector, view navigator, etc.
Each MainWindow
actually contains three sketch areas, though the user can see only one at a time (at least for the moment). The sketch areas contain the three different views of the sketch: breadboard view (BreadboardSketchWidget
), schematic view (SchematicSketchWidget
), and pcb view (PCBSketchWidget
). Keeping these views in sync is an evolving problem, and will be discussed below.
SketchWidgets
are subclasses of QGraphicsView
. Parts and wires that appear in SketchWidgets
are subclasses of QGraphicsItem
, and in fact, almost all parts inherit from QGraphicsSvgItem
, since we display parts as SVGs. A QGraphicsItem
can only exist in one view at a time, so we use different instances of QGraphicsItem
in different views to represent the same part: each instance displays a different view-dependent SVG. But that's not all. In certain views there is a need to visualize a single part in different layers. For example, in PCB view, a single part may be visible in each of copper0, copper1 and silkscreen layers. Furthermore, all silkscreen layers appear above (in the z-order sense) all copper0 layers. But QGraphicsItems
can only exist in a single z-plane. So to solve this problem, we came up with the idea of a LayerKin
class: a way for a single part in a given view to be represented by multiple QGraphicsItems
, each in a different z-plane. So to summarize, a single part will be represented by different QGraphicsItems
across different views, and may even be represented by a set of QGraphicsItems
within a single view.
The Qt Framework supports unlimited undo using QUndoStack
and QUndoCommand
objects. Thus, almost anything a user does in Fritzing is wrapped in a command object. Furthermore, command objects can be grouped hierarchically, so a single user-action in Fritzing may be represented by an entire cluster of command objects. The bad news with command objects is that the deferred nature of executing them makes them tricky to implement. In other words, you can't just execute an action directly, you have to figure out how to wrap it up so it can be executed (and retracted) later. In addition, we use this mechanism to synchronize actions across views (for example, a delete in one view triggers equivalent deletions in the other views).
For certain actions which are expensive to calculate, or are difficult to determine in advance (before data structures are actually updated), we have introduced a notion of ex post facto command objects. With these command objects, the action is executed directly, and the command object (cluster) is constructed as the action is being executed. Any command object inheriting from BaseCommand
has the potential to be an ex post facto command object. For example, it's very difficult to calculate a ratsnest change in pcb view based on a wire being deleted in breadboard view, until you've actually deleted the wire and updated the data structures. At that point, you can go through the current set of connections and determine how to fix up the ratsnest. We can then append those ratsnest changes to the command object, so that on subsequent undo and redo of that command we get the same results.
Currently, each MainWindow
has its own undo stack, which means that actions across views (within a single MainWindow) are all stored on the same stack. But note that some actions affect all views and some only a single view. For example, a delete action will delete an object across all three views, whereas a move action only applies to a single view. Actually, some objects only exist in a single view, so some delete actions only affect a single view after all.
Fritzing kinda-sorta uses the Model-View pattern. That is, you will find a number of Models in the system: a sketch model, a palette model (for parts bins), a reference model (for all the parts). In theory, Models are meant to be pure data structures which operate in synchrony with views (i.e. graphical renderings) of that model. But it is also the case that QGraphicsView
itself is a model. So in practice, particularly where sketches are concerned, most actions operate directly on QGraphicsViews
, with the SketchModel
being synchronized more-or-less as an afterthought. Certain actions, like saving and loading, start with--and are organized by--the SketchModel
, but at some point, Model objects call on their respective view objects to deal with view-specific data (e.g. part locations are stored in the view, not in the model).
Mostly, what the models are, is collections of parts. The class that holds a part-model is called, wait-for-it, a ModelPart
. Parts can be connected to wires or other parts at connectors, which are modelled using the Connector
class. When connectors within a part are already connected (like in a breadboard), we use a Bus
class to model that.
Each time a part is added to a sketch, a new ModelPart
is added to the SketchModel
, and some number of Connectors
and (optionally) Buses
will be added to the ModelPart
. If you add two of the same part to a sketch, it will create two ModelPart
instances and also two instances of each Connector
and Bus
on that part. However, since most of the data per ModelPart
, Connector
, and Bus
, is the same across parts, we have created classes called ModelPartShared
, ConnectorShared
, and BusShared
which hold those common pieces of data. In other words, if you drop two Red LEDs onto the sketch, that will create two ModelParts, but only one ModelPartShared, which is pointed to by each ModelPart.
There are a number of file types which Fritzing uses, and most of them correlate with some object or entity within Fritzing. All of them are XML-based (though a bundle is actually a zipped-set of xml files). Under the hood, most of these file types use very similar protocols, so we are able to reuse a lot of code for reading and writing them.
file type | extension |
---|---|
sketch | .fz |
part | .fzp |
module | .fzm |
bin | .fzb |
bundle | .fzz |
- A sketch has an .fz suffix, and it represents the set of parts in a sketch, which other parts each part is connected to, and for each view that the part appears in, where that part is located.
- A part is represented by an .fzp file. A part file contains metadata about the part, paths to the SVGs for each view (plus the icon) of the part, and the set of connectors the part has.
- A module is a collection of parts that acts like a single part. Module files have an .fzm suffix, and they have XML elements from both parts and sketches. (this is not yet implemented)
- A bin is a collection of unconnected parts. Bins appear in the Parts Bin Dock, and you drag parts from the bin to drop onto a sketch. Bins have an .fzb suffix.
- A bundle is a zipped collection of files: a sketch, and whatever auxiliary files are needed to make up that sketch (for example, a custom part file. Core parts come with the application, so they aren't stored in the bundle). Bundles have an .fzz suffix. There are also bundled parts (a single .fzp with its associated SVGs, .fzpz) and bundled bins (a single .fzb with all its associated fzp/svgs, .fzbz) for eased sharing of parts and bins.
This is a brief overview, for more details look into the part file format description.
A part can have a set of connectors, each of which must have a different name, even if they have the same function. These connector names are also used in the SVG files that make up a part's appearance--the names are used as the id attribute of a given SVG element. Thus, the actual location of the connector relative to the rest of the part graphic is stored in the SVG file. Connectors can be separate SVG elements or you can simply attach an id attribute to an element that already exists in the SVG. Another name correlation between the fzp file and the SVG file is something called the terminal point. This is the point within the connector to which a wire will attach. The default is the center of the connector, but if you want something other than the default, you can define or reuse another SVG element as a terminal point using the same id attribute scheme.
The folder structure for storing parts is a little bit complicated. We wound up with the current structure for two reasons: first to separate parts that are shipped with Fritzing (and therefore are officially "blessed") from those created by any user; second, so that we could reuse SVG files both among and within parts. The folder structure is as follows:
core
contrib
user
svg
- core
- breadboard
- icon
- pcb
- schematic
- contrib
- breadboard
- icon
- pcb
- schematic
- user
- breadboard
- icon
- pcb
- schematic
fzp files go into the top-level core
, contrib
, and user
folders. The svg files those FZPs refer to are contained in the view subfolders (breadboard
, icon
, pcb
, schematic
). For any given part, it is not necessary to have an SVG in each of the four view subfolders.
Currently, at (first) start up time, Fritzing churns recursively through the parts folder looking for fzp files. As each part file is found, it is loaded, and the data is added to an SQLLite database. This database makes up a reference model that we use throughout Fritzing. We also use this database to back part-swapping. It's not yet clear how we're going to deal with parts also being stored on the web.
There are a few Fritzing/Qt debugging facilities available in addition to whatever might be available from your IDE.
- in release mode, the project defines a Qt macro
QT_NO_DEBUG
, so you'll see a lot of code bracketed with#ifndef QT_NO_DEBUG
. - Qt itself offers a couple of macros, notably
qDebug()
, for outputting messages to your IDE. - there's a
DebugDialog
class in Fritzing, with one main function:debug()
.DebugDialog::debug()
usesqDebug()
to output messages, adds them to aDebugDialog
window which you can open in Fritzing, and also stores them in a file (debug.txt
--created somewhere near the same folder as your executable). - another file output in debug mode is
undostack.txt
--this is a listing of all the commands added to the undo stack. - under Visual Studio, in debug mode, Fritzing generates a file called
fritzing_leak_log.txt
which is very helpful for catching memory leaks. It lists the line number and source file for each object that is created and not released. The technique used may not catch absolutely every memory leak, but I think it finds the majority of them.
Qt has very nice built-in support for text internationalization. This is discussed in the Qt documentation and our translator instructions, so just a few quick additional points:
- Use the
tr()
macro around text strings that you want translated - Don't use the
tr()
macro around text strings you don't want translated (debug messages, for example) - Translation lookup occurs at run-time. Therefore, text that is initialized statically (outside a function call with a
tr()
macro around it) does not get translated at run time, since it is initialized before the translation code is loaded.
Qt supports resources (i.e. non-code assets, such as icons) by compiling them into the binary. In Fritzing the asset files to be compiled are listed in phoenixresources.qrc
. To load a resource, most of Qt's file operations will accept a file path that begins with :/
, for example, :/resources/images/aboutbox_FHP.png
tbd
tbd
COPYRIGHT FRITZING.ORG ALL RIGHTS RESERVED