Skip to content

Quentier internals overview

Dmitry Ivanov edited this page Mar 12, 2018 · 4 revisions

This article is primarily for developers who would like to contribute to Quentier but aren't quite sure where to start. Here the high-level overview of the app design is explained.

First, let's briefly enumerate the kinds of technology used in Quentier project:

  • Qt/C++
  • CMake
  • SQLite
  • JavaScript
  • CSS (a bit)

If you are familiar with any of these technologies, you are most likely able to help developing Quentier! However, note that there are some specific ways in which these technologies are used in the project. For example, currently the list of supported Qt versions ranges from Qt 4.8.6 to the latest and greatest Qt 5.x. However, Qt 4.8.6 is only really supported on Linux while on Windows and on Mac it's much more reasonable to use newer Qt 5.x.

Another point is that even though C++ is known as a hard to grasp language for system programmers, in Quentier project only a relatively simple subset of it is used - the use of exceptions is very limited, most part of the code is written in C++98 style with only a handful of features from C++11 standard employed. That makes is possible to build Quentier using as old compilers as gcc-4.5 or Visual C++ 2010. In future the support for such old compilers is going to be dropped at some point though in order to employ more useful features from more recent C++ standards.

The core functionality of Quentier is encapsulated in libquentier library which is distributed under the terms of GNU LGPL v.3. It has no tight bindings to Quentier application (otherwise it won't make much sense to make it a separate library at all) so it can be used by other applications as well. Libquentier provides the following essential functionality:

  1. Local storage of notes, notebooks, tags, saved searches, resources. The data elements of each kind can be added, updated, removed, listed, looked up etc.
  2. Synchronization of data from local storage with Evernote servers. Both full and partial synchronization algorithms are implemented.
  3. Note editor UI component which enables one to view and edit notes.
  4. Conversion between ENML and HTML used for presenting notes within the note editor.
  5. A bunch of utility functions and classes.

Libquentier's functionality is in turn based on QEverCloud library which essentially is the Qt-friendly replacement of the official Evernote C++ SDK: QEverCloud handles the transmission of data between Evernote servers and the outside world and also offers nice Qt-friendly API. A large part of QEverCloud's code was generated automatically from Evernote thrift IDL files. IDL stands for "interface definition language". Thrift was originally invented within Facebook and later on was contributed to Apache Foundation. Thrift IDL is a thing like pseudo-code only it focuses on the definitions of structs instead of commands. Thrift IDL code itself is not compiled into executable code but it can be parsed and converted to code in some real programming language. Normally the generation of code in real programming language is performed by thrift compiler but it doesn't have a Qt-friendly C++ backend so QEverCloud's sources are generated using a custom tool - QEverCloudGenerator. The Qt-friendliness of the generated code means that all the most convenient Qt's data types are used instead of their plain C++ counterparts so no conversion between different data types needs to be done. Most notably QString is used instead of std::string.

The local storage within libquentier uses SQLite as its backend i.e. all user's data is stored within a SQLite database. However, it is just an implementation detail, the fact of SQLite usage is completely hidden from libquentier's public API. So in theory is is possible to change the database backend in future to whatever one is considered better - even to storage of plain text files. Internally SQLite is used via Qt's QtSql module.

The synchronization is implemented according to Evernote's document. That document explains a lot of details but some non-obvious subtleties are not mentioned, unfortunately, so I plan to write more about them in further posts. The synchronization of user's own accounts, public notebooks and notebooks shared by other users is fully supported.

Note editor is a very special thing within libquentier: it is unfortunately quite a complex collection of C++ classes, JavaScript code and a bit of CSS. The complexity of the note editor code comes primarily from the unexpected move of Qt devs who decided to deprecate QtWebKit in favour of QtWebEngine. I decided to support both backends after the semi-complete implementation of QtWebKit based note editor. As a result, note editor's source code has become full of ifdefs splitting the logical branches between the two alternative backends. Another source of complexity is the support for advanced "smart" undo-redo stack as well as various convenient actions like resizing images, resizing table columns etc.

ENML converter serves the needs of the note editor: it performs the conversion between ENML which is Evernote's format for note content storage and HTML. Currently this converter has some hardcoded parts to best serve the needs of the note editor. It would be nice to add some configurability to it in future.

Quentier app's source code is mostly about handling various aspects of user interface and orchestration of work performed by the code within libquentier. Here are the primary parts of Quentier app's code:

  1. AccountManager: this class controls the discovery of available and last used accounts, switching between accounts and other account management details.
  2. MainWindow: this class is the central class within the entire app and it represents, as its name suggests, the main window of Quentier app. It also works as the mediator between several other classes.
  3. NoteEditorTabsAndWindowsCoordinator: this class maintains the list of open note editor widgets, both tabbed ones and editors in separate windows. For performance and memory consumption concerns tabbed note editor widgets are automatically closed as new tabbed note editors are opened i.e. the LRU cache of tabbed note editors is maintained. The contents of automatically closed note editor tabs are automatically saved, of course, so no loss of data occurs.
  4. SystemTrayIconManager: this class, as its name suggests, maintains the Quentier's system tray icon and handles various actions which might be requested from the tray icon's context menu.
  5. EnexExporter and EnexImporter are two helper classes which orchestrate the libqentier's code performing export and import of notes to and from data in ENEX format.
  6. Models: classes from src/models folder within Quentier's source tree. These are models in Qt's model-view framework sense: each is a subclass of QAbstractItemModel. There are several such classes:
    • FavoritesModel - contains data about favorited data items within the current account
    • NotebookModel - contains data about notebooks within the current account
    • TagModel - contains data about tags within the current account
    • SavedSearchModel - contains data about saved searches within the current account
    • NoteModel - contains data about notes within the current account
    • NoteFilterModel - subclass of QSortFilterProxyModel, implements filtering over the list of notes (sorting is implemented within the NoteModel itself)
    • LogViewerModel - subclass of QAbstractTableModel, contains data parsed from Quentier's log file; serves for convenient visual presentation of the log file's contents
  7. Views: classes from src/views folder within Quentier's source tree. These are views in Qt's model-view framework sense: each is a subclass of QTreeView or QListView. There are several such classes:
    • ItemView - intermediate helper class within the inheritance chain between QTreeView and particular Quentier's view classes
    • NotebookItemView - implements various actions available via context menu on notebooks from this view as well as special behaviour on notebook selection - the list of notes is filtered by the currently selected notebook unless it is a linked notebook (which is the current technical limitation due to the possibility of name clashes between notebooks from user's own account and linked notebooks or between different linked notebooks)
    • TagItemView - implements various actions available via context menu on tags from this view
    • SavedSearchItemView - implements various actions available via context menu on saved searches from this view
    • DeletedNoteItemView - implements various actions available via context menu on deleted notes from this view
    • NoteListView - implements various actions available via context menu on non-deleted notes from this view
  8. Delegates: classes from src/delegates folder within Quentier's source tree. These are delegates in Qt's model-view framework sense: each is a subclass of QStyledItemDelegate and implements customized rendering and/or editing of items corresponding to a particular view. I won't list them here since basically each mentioned view has its own delegate + there are some other delegates which are best understood by looking at how and where they are used within Quentier's source code.

To put things into perspective, Quentier primarily consists of three large parts:

  1. Local storage
  2. Synchronization
  3. UI

These three parts interact with each other, primarily asynchronously, via signals and slots. Local storage and synchronization operate within their own threads of execution, the UI is processed primarily in the main (GUI) thread so the operations inside it need to be fast.


This article is also available as a blog post.