Code Walkthrough

This section will cover the different components of the codebase with a brief description of their purpose and implementation.

Skin Parser (/lib/skin_parser)

The Theme Editor uses the same skin parser as the Rockbox core, compiled by way of a separate Makefile in the parser directory. The skin parser is plain old C, with #ifdef'd extern "C" declarations so

skin_parser.h contains the definitions of the basic data structures used throughout the skin parser, and skin_parser.c contains the recursive-descent parser that actually parses the skin documents. Calling the function skin_parse will parse a skin file and return the root of a parse tree as a pointer to struct skin_element.

skin_scan.c and skin_scan.h define and implement some simple scanning functions for use by the skin parser. In general, these shouldn't be used directly in anything but the parser.

tag_table.h defines the data structure used to populate the tag table, and the various token constants for the tags. tag_table.c defines the tag table, which specifies all the legal tags and the parameters they accept/require.

skin_debug.h and skin_debug.c include functions for printing debug information and obtaining the error state after parsing a skin document.

Theme Editor (utils/themeeditor/)

The Theme Editor itself is written in C++ using the Qt framework. As is typical of GUI applications, main.cpp does very little work other than setting some application-wide variables and initializing the main window, and the rest of the work is done by the GUI classes and the skin parser library.

main.cpp initializes a QApplication instance for the application, sets some QCoreApplication variables (this way QSettings can be used to get application settings more easily), and displays the main window.

GUI Classes (utils/themeeditor/gui/)

The GUI classes are, obviously enough, responsible for presenting the user interface. Most of them extend QWidget and get used as either dialogs or components of windows, although one (RBConsole) is actually used in the skin preview window. All class files (.h, .cpp, and .ui for the Qt Designer files) are simply the class name in lower case with the appropriate extension. For instance, RBConsole is made up of RBConsole.h, RBConsole.cpp, and RBConsole.ui.

CodeEditor is a subclass of QPlainTextEdit used to display code in the theme editor. It adds line numbering, syntax highlighting, and auto-completion (with the help of SyntaxCompleter) to the base class. This class was originally authored by Nokia for use in one of their Qt examples, and provided under the LGPL v2.1 (which is compatible with GPLv2 or any later version). I have extensively modified their work, and chose to relicense my work on this class under the original LGPL v2.1, rather than GPLv2+ like the rest of the project.

ConfigDocument is a class responsible for displaying a configuration document. It inherits the abstract class TabContent which allows it to be shown in the main window's tab display.

DeviceState allows the user to change the settings of the simulated device so they can see the effect of the settings on the skin preview. Most of this class' interface is created at runtime based on the contents of the file /utils/themeeditor/resources/deviceconfig. That file includes an explanation of its syntax. The deviceconfig file is compiled into the binary, so users messing with it isn't an issue, unless they're playing around with the source tree anyways.

EditorWindow presents the main window. It's responsible for controlling all the separate components of the editor and handling menus, docks, state save/load on shutdown/startup, and etc.

FontDownloader is a dialog box that downloads and extracts the font pack, using the QuaZip library (in /utils/themeeditor/quazip directory). It gets launched from the preferences dialog.

NewProjectDialog is the dialog box that allows the user to create a new file. It reads the targetdb (/utils/themeeditor/resources/targetdb) and presents a list of targets for the user to choose from for the new project.

PreferencesDialog is the application preferences dialog box. It's responsible for all the application-wide settings.

ProjectExporter is a dialog that zips up all the project's files using the QuaZip library and shows warnings or errors for any missing resources along the way.

RBConsole is a simple error console that gets inserted into the skin preview (using QGraphicsScene::addWidget) to alert the user to any missing resources in a skin file.

SkinDocument displays a skin document, as its name suggests. It manages a CodeEditor instance and the parse tree for the document, along with dealing with changes to the document, application settings, and device settings.

SkinTimer is a simple dialog that allows the user to automatically step through simulated time so they can watch their theme in motion.

SkinViewer is the class that shows the skin preview, along with the zoom buttons and the code generate/undo buttons.

SyntaxCompleter is the class responsible for implementing autocompletion. It gets the list of valid tags from /utils/themeeditor/resources/tagdb.

TabContent is an abstract class for GUI elements that can be loaded in one of the main window's tabs.

TargetDownloader is a dialog box that downloads the latest targetdb file from the SVN repo.

Model Classes (utils/themeeditor/models/)

These classes are all used to model data. Most of them implement parts of Qt's MVC architecture.

ParseTreeModel is the model that wraps a parse tree from the skin parser and allows it to be displayed and edited with a tree view. It also starts the rendering and code generation processes.

ParseTreeNode is a single node of a ParseTreeModel, and non-root nodes always hold either a struct skin_element or a struct skin_tag_parameter. ParseTreeNode is responsible for most of the code generation and rendering processes.

ProjectModel is a simple model list for project files that allows them to be displayed in and opened from a list view.

TargetData is a class that parses the targetdb file and returns information about available targets.

Graphics Classes (utils/themeeditor/graphics/)

The graphics classes are used to assemble the skin preview. When a skin preview is rendered, the scene should only have one or two root items: an RBScreen instance, of which all skin elements are children, and an RBConsole instance which will only show up if needed to show rendering errors due to missing resources.

RBAlbumArt shows an album art area. It will display a red checkerboard pattern in place of actual album art, and is capable of scaling/positioning its rendered rectangle as the Rockbox rendering engine would do it. It implements RBMovable, so the user can move and resize it graphically.

RBFont is a class that reads a Rockbox font file and can then be used to generate raster text as RBText instances. Font files are cached during the lifetime of the application using the RBFontCache class.

RBFontCache is used to access the global cache of font files. After a font file has been loaded once, it will subsequently be loaded from the cache instead of disk.

RBImage is used to display images in the skin preview. It can be moved and resized by the user and changes will be made to the parse tree, although resizing won't affect the code, as image size is always determined by the bitmap file.

RBMovable is an abstract class implemented by scene items that can be moved and resized. It overrides some QGraphicsItem event handlers to allow resizing functionalities, and sets flags to allow user movement.

RBProgressBar displays a progress bar like the Rockbox rendering engine. It implements RBMovable, so it can be moved and resized by drag and drop.

RBRenderInfo is a simple class used to hold information that gets passed around a lot in the rendering process for convenience.

RBScreen is the root element of a skin preview. It sets the boundaries for rendering and handles image loading, album art position, font loading, and similar top-level tasks.

RBText holds a raster image of rendered text and allows it to be scrolled within its rendering boundaries.

RBTextCache implements the global text cache, which operates in a manner similar to the font cache.

RBTouchArea shows a touch area hovering above the screen. It accepts mouse click events and will carry out a subset of the available touch area actions on the simulated device (see the function RBTouchArea::mousePressEvent to see or change the supported actions).

RBViewport represents a viewport. It implements RBMovable, so it can be moved and resized graphically, as long as it is not the default viewport.

Other Classes (utils/themeeditor/...)

The remaining classes are libraries by other authors.

QtFindReplaceDialog is a find/replace dialog written by Lorenzo Bettini that I've used for find/replace in the code editor class. I have modified it to make it work on both QTextEdit and QPlainTextEdit instances.

QuaZip is a Qt ZIP library. This has been included unmodified.

ZLib is a compression library, but only headers are included (ZLib is included with Qt, so the shared libraries will necessarily be present on any platform the Theme Editor compiles on).

Adding and Modifying Tags

In brief, to add or modify a tag for the Theme Editor, you'll need to go through the following steps.

Get the tag set up correctly in the tag table (/lib/skin_parser/tag_table.c) so that the parser will recognize it. If your changes make any radical change to the parsing syntax, you'll need to modify skin_parser.c as well to make it parse correctly.

If the tag returns a value, you will want to add it to /utils/themeeditor/resources/deviceoptions, using the syntax described in the file header. Unless its value depends on the value of another tag, in which case you will want to leave it out of deviceoptions (which changes the options shown to the user) and add a special exception for it in ParseTreeNode::evalTag, found in /utils/themeeditor/models/parsetreenode.cpp.

If the tag involves some variety of rendering, add a case for it in ParseTreeNode::execTag, found in /utils/themeeditor/models/parsetreenode.cpp. The tags are arranged in nested switch statements, one outer switch statement for the first letter in the tag name, one inner statement for the second letter in the tag name. These will be replaced soon by a single switch statement using the unique tag token constants defined in the tag table. The viewport argument will give you a pointer to the viewport you're working in, and the info parameter gives you an RBRenderInfo reference that should contain all the information you need to render your tag. If it doesn't, you may need to add a field to the RBRenderInfo class, in which case you'll need to search for the locations where RBRenderInfo is instantiated (or let the compiler find them for you and error out) and add the new field to the constructor calls.

If you add a case to the execTag function, make sure to finish it by returning true, which will stop the renderer from trying to evaluate the tag and display it as text.