Chapter 5: View
Here we come across the big picture. The View layer. We'll focus on the main TB view:

In fact, let me drag out a 2D view:

Perfect. Let's dissect this.
Core layout
The root of all this is a MapFrame.
class MapFrame : public QMainWindow
{
//...
std::shared_ptr<MapDocument> m_document;
//...
SwitchableMapViewContainer* m_mapView = nullptr;
//...
QPointer<MapViewBase> m_currentMapView;
InfoPanel* m_infoPanel = nullptr;
Console* m_console = nullptr;
Inspector* m_inspector = nullptr;
//...
QPointer<QDialog> m_compilationDialog;
QPointer<ObjExportDialog> m_objExportDialog;
//...
private: // shortcuts
using ActionMap = std::map<const Action*, QAction*>;
ActionMap m_actionMap;
//...
public:
MapFrame(FrameManager& frameManager, std::shared_ptr<MapDocument> document);
~MapFrame() override;
//...
};
In essence:
MapFramehosts the entire UI,MapViewBaseinstances (MapView3D,MapView2D, hosted bySwitchableMapViewContainer) are renderable viewports that accept various user input events, andMapDocumentis the central component of all map editing operations.- All UI classes have a
createGuimethod - called from the constructor - and it creates all necessary widgets and layout, e.g. labels, images, splitters etc. - Some UI classes have a
connectObserversmethod - called from the constructor - and it connects the class' methods to various editing events, e.g. changes to entities.
MapDocument is not a UI component, but rather a host of map data and a set of operations on said data. Widgets and actions of all sorts ultimately interact with MapDocument, and so do other layers, indirectly so.
Here is a more visual illustration of all this:

Hopefully this will help you navigate the code a bit! From here onward you can pretty much intuit the location of everything else in TrenchBroom, e.g. the "View Options" button in MapViewBar, or the "Face" tab in Inspector.
Event loop
Most of the core event handling business is done in MapViewBase, which also supports rendering. MapFrame is what creates these "map views" as well as creating a MapDocument.
The flow of events goes like this:
- The user interacts with a smart editor, or uses an action via a keyboard shortcut:
- The respective UI component interacts with
MapDocumentto modify some nodes for instance. MapDocumentproceeds to visit the selected nodes.
- The respective UI component interacts with
- The user interacts with a 2D or 3D viewport:
- Events are passed to
MapViewBase, which, inheriting fromToolBoxConnector, passes it over to aToolChain. - The currently appropriate
ToolControllerinterprets the event, e.g.ExtrudeToolController. - The controller then interacts with its respective
Tool. - The tool interacts with
MapDocumentto modify some geometry for instance. - Finally,
MapDocumentproceeds to visit e.g. selected brush nodes.
- Events are passed to
Reacting to changes
TrenchBroom has this thing called NofifierConnection, and it's basically a way of observing changes in other UI components.
void MyWidget::connectObservers()
{
notifierConnection += notifier.connect(this, &MyWidget::myObservingMethod);
}
For example, the entity property editor observes changes in the map document!
void EntityPropertyEditor::connectObservers()
{
auto document = kdl::mem_lock(m_document);
m_notifierConnection += document->selectionDidChangeNotifier.connect(
this, &EntityPropertyEditor::selectionDidChange);
m_notifierConnection +=
document->nodesDidChangeNotifier.connect(this, &EntityPropertyEditor::nodesDidChange);
}
Every time the user selects another entity or modifies the selection (e.g. by pressing PgUp or PgDn to change their position), the entity editor is updated to reflect those changes. For bookkeeping purposes, each UI class that connects to notifiers, needs to have a NotifierConnection member.
The other side of this story is Notifier<T> which, if you're coming from C#, is essentially like event Action<T>, with added C++ fluff. In either case, it's a super powerful component of TB's code:
class MapDocument : public Model::MapFacade, public CachingLogger
{
//...
Notifier<MapDocument*> documentWillBeClearedNotifier;
Notifier<MapDocument*> documentWasClearedNotifier;
Notifier<MapDocument*> documentWasNewedNotifier;
Notifier<MapDocument*> documentWasLoadedNotifier;
Notifier<MapDocument*> documentWasSavedNotifier;
Notifier<> documentModificationStateDidChangeNotifier;
And here is how they're invoked:
void MapDocumentCommandFacade::performSelect(
const std::vector<Model::BrushFaceHandle>& faces)
{
selectionWillChangeNotifier();
// ...
Selection selection;
selection.addSelectedBrushFaces(selected);
selectionDidChangeNotifier(selection);
}