Theme, colour, and presentation architecture¶
This page documents how OmegaT resolves colours and other presentation attributes for
segment markers (active source, untranslated, fuzzy match highlights, and so on), and how
Theme, Styles, and Preferences layer on top of each other. It exists to give contributors
a map of the existing mechanism before proposing changes to it (for example, extending it
to cover formatting, audio, or accessibility labels; see
ADR 2026003).
There are three layers, resolved in order: Theme default, Styles registry, Preferences
override.
1. Theme: UIDefaults keys under the OmegaT.* namespace¶
A Theme is a LookAndFeel implementation, installed via
UIDesignManager.setTheme(String lafClassName). In addition to the usual Swing LaF keys, an
OmegaT theme also seeds a set of application-specific colours as UIDefaults entries under
the OmegaT. prefix, for example OmegaT.activeSource, OmegaT.untranslated,
OmegaT.terminology.
Concretely, DefaultFlatDarkTheme.getDefaults() builds on FlatDarkLaf, then calls a
private setDarkDefaults(UIDefaults) method that does, among other things:
defaults.put("OmegaT.activeSource", new Color(0x287128));
defaults.put("OmegaT.untranslated", new Color(0x4d4daa));
defaults.put("OmegaT.terminology", new Color(0x8f5500));
// ...
DefaultFlatLightTheme (light) does the equivalent for the light variant, in the theme
Gradle module. DefaultFlatTheme (in src/main/java) is a shared base used by both — it
supplies the newUI.* icon keys and the adjustRGB helper, not the marker colours
themselves. Each installed theme is free to define its own values for every OmegaT.* key;
nothing requires a theme to supply all of them.
Fallback: bundled colour scheme properties¶
If, after a theme is installed, UIManager.getColor("OmegaT.source") is still null (the
theme did not define the OmegaT-specific keys at all — relevant for third-party theme
plugins), UIDesignManager.initialize() falls back to loading
src/main/resources/org/omegat/ColorScheme_dark.properties or ColorScheme_light.properties
via ResourcesUtil.getBundleColorProperties(style) and UIDesignManager.loadDefaultColors.
This is a safety net for incomplete themes, not the primary path for the bundled themes.
Theme plugin constraint¶
Theme plugins register their LaF class with the shared UIManager, which requires global
visibility. This is why theme plugins share one ClassLoader rather than getting per-plugin
isolation; see ADR 2025012 for the full
rationale.
2. Registry: org.omegat.util.gui.Styles (EditorColor enum)¶
Application code never reads UIManager.getColor("OmegaT.xxx") directly for marker colours.
It goes through Styles.EditorColor, an enum where each constant corresponds to one marker
and resolves its default from the active theme at construction time:
COLOR_ACTIVE_SOURCE(OStrings.getString("COLOR_ACTIVE_SOURCE"),
UIManager.getColor("OmegaT.activeSource")),
COLOR_UNTRANSLATED(OStrings.getString("COLOR_UNTRANSLATED"),
UIManager.getColor("OmegaT.untranslated")),
A few constants (for example COLOR_PROJECT_FILES_PROGRESS_LOW) use a three-argument
constructor that supplies a hard-coded hex fallback if the UIManager key is absent, instead
of depending on the theme having set it. A handful of constants (for example
COLOR_ACTIVE_SOURCE_FG, COLOR_GLOSSARY_SOURCE) have no theme-backed default at all and
rely entirely on a Preferences value or remain null until one is set.
EditorColor is a registry, not just a lookup: at construction it also calls
setColorFromPreference(), which is where layer 3 comes in.
3. Preferences override¶
Each EditorColor constant stores the user override under a Preferences key equal to its own
enum name (Preferences.getPreferenceDefault(name(), null), for example the preference key
for COLOR_ACTIVE_SOURCE is literally "COLOR_ACTIVE_SOURCE"). If a preference value is
present and is not the sentinel "__DEFAULT__", it overrides the theme-derived colour.
setColor(Color) is the write path used by the Colours preference panel
(org.omegat.gui.preferences.view.CustomColorSelectionController /
AppearanceController); passing the theme’s own default writes back the "__DEFAULT__"
sentinel rather than a literal colour, so switching themes later is not masked by a
stale override.
Resolution order, summarized¶
Theme (UIDefaults "OmegaT.*")
-> fallback: ColorScheme_{dark,light}.properties (only if theme omitted a key)
-> Styles.EditorColor default (read once, at enum construction, from UIManager)
-> Preferences override (checked at construction, and whenever setColor() is called)
Because EditorColor reads its default once per JVM at enum construction, changing the
active theme at runtime does not retroactively change EditorColor defaults already
resolved; theme switches are applied on restart (see switch_colour_theme.js script
documentation for the user-facing workaround of a scripted theme swap).
Formatting attributes already exist, independent of colour¶
Styles.createAttributeSet(...) already accepts bold, italic, strikethrough, and underline
flags in addition to foreground/background colour, and returns a Swing AttributeSet used to
render segment text:
public static AttributeSet createAttributeSet(Color fg, Color bg,
Boolean bold, Boolean italic, Boolean strikethrough, Boolean underline)
This means the formatting channel is not new plumbing to invent; it is already wired at the
AttributeSet level. What does not exist yet is a per-marker-category configuration for
these flags (comparable to what EditorColor provides for colour) or a Theme/Preferences
resolution chain for them. Any proposal to add formatting as a configurable channel should
extend EditorColor (or a sibling registry following the same three-layer pattern) to also
carry these flags, rather than adding a separate formatting mechanism.
What is not covered by this mechanism¶
Audio/sound cues and accessibility labels/descriptions have no equivalent registry today; there is no
OmegaT.*UIDefaults convention orStylesenum for either.The chain above governs colour and, at the
AttributeSetlevel, text formatting. It says nothing about how a value is synchronised or persisted beyond the singlePreferences.setPreference(name(), ...)call — that is a separate, already-solved concern (see ADR 2026002 for the Preferences store’s own testability architecture, which this page does not depend on or duplicate).