Programmer's Introduction to the Fritzing Application Software
Fritzing is built entirely using Qt, a powerful and stable C++ GUI coding framework that was recently purchased by Nokia (www.trolltech.com). 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 Trolltech has just released 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 enviroments in XCode and Visual Studio, you have to build Qt from source (mac, pc).
Fritzing is currrently built using the open source release of Qt, version 4.7.0.
Very high-level overview
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 both copper0, 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 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, are 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 of every 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.
File types: sketches, modules, bins, bundles, parts
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.
- A sketch has an ".fz" sufffix, 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 files 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.
- 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.
A further note about part definition (fzp) files. 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:
Fzp files go into the top-level core, contrib, and user folders. The SVGs 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 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 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() uses qDebug() to output messages, adds them to a DebugDialog 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.
- 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 call0 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"