-
Notifications
You must be signed in to change notification settings - Fork 0
Main Window (MainFrame)
Relevant source files
This document details the MainFrame class, which serves as the primary application window and main orchestrator for the MouseMacros application. The MainFrame is responsible for:
- Constructing and managing the main UI layout with control buttons and status log area
- Initializing and registering global input hooks via JNativeHook
- Coordinating interactions between the macro system, configuration, localization, and theming subsystems
- Handling user-triggered actions and hotkey management
- Applying system-wide theme settings
For information about the macro recording and playback functionality triggered by this window, see page 4 (Macro Recording and Playback System). For configuration management, see page 5.1 (ConfigManager). For theming implementation details, see page 7.5 (Theming System).
Sources: src/io/github/samera2022/mouse_macros/ui/frame/MainFrame.java L1-L193
The MainFrame class follows a singleton-like pattern with a public static instance:
public static final MainFrame MAIN_FRAME = new MainFrame();
| Member | Type | Purpose |
|---|---|---|
logArea |
static JTextArea |
Displays status messages throughout the application lifecycle |
startBtn |
JButton |
Initiates macro recording |
stopBtn |
JButton |
Stops macro recording |
playBtn |
JButton |
Executes recorded macro |
abortBtn |
JButton |
Aborts currently running macro playback |
saveBtn |
JButton |
Saves recorded macro to .mmc file |
loadBtn |
JButton |
Loads macro from .mmc file |
settingsBtn |
JButton |
Opens settings dialog |
macroSettingsBtn |
JButton |
Opens macro-specific settings dialog |
GML |
static GlobalMouseListener |
Singleton listener instance for global input events |
keyRecord |
static int |
Native key code for recording hotkey (default: F2) |
keyStop |
static int |
Native key code for stop hotkey (default: F3) |
keyPlay |
static int |
Native key code for playback hotkey (default: F4) |
keyAbort |
static int |
Native key code for abort hotkey (default: F5) |
The static logArea and MAIN_FRAME allow other subsystems (particularly LogManager) to access the UI without complex dependency injection.
Sources: src/io/github/samera2022/mouse_macros/ui/frame/MainFrame.java L28-L39
The MainFrame constructor executes a specific initialization order critical for application functionality:
sequenceDiagram
participant Constructor
participant ConfigManager
participant SystemUtil
participant Localizer
participant UI Components
participant GlobalScreen
participant ComponentUtil
note over Constructor: "Step 1: System Settings Sync"
Constructor->>ConfigManager: "config.followSystemSettings?"
loop ["followSystemSettings == true"]
Constructor->>ConfigManager: "getAvailableLangs()"
ConfigManager-->>Constructor: "availableLangs[]"
Constructor->>SystemUtil: "getSystemLang(availableLangs)"
SystemUtil-->>Constructor: "system language"
Constructor->>SystemUtil: "isSystemDarkMode()"
SystemUtil-->>Constructor: "dark mode boolean"
Constructor->>ConfigManager: "Update config.lang, enableDarkMode"
end
note over Constructor: "Step 2: Localization"
Constructor->>Localizer: "load(config.lang)"
Constructor->>Localizer: "setRuntimeSwitch(true)"
note over Constructor: "Step 3: Hotkey Configuration"
Constructor->>ConfigManager: "Read config.keyMap"
Constructor->>Constructor: "Set keyRecord, keyStop, keyPlay, keyAbort"
note over Constructor: "Step 4: UI Construction"
Constructor->>UI Components: "Create JTextArea logArea"
Constructor->>UI Components: "Create JScrollPane with CustomScrollBarUI"
Constructor->>UI Components: "Create 8 JButtons"
Constructor->>UI Components: "Create GridLayout panels (2 rows)"
Constructor->>UI Components: "Add action listeners to buttons"
note over Constructor: "Step 5: Window Sizing"
Constructor->>ComponentUtil: "adjustFrameWidth(this, buttons...)"
note over Constructor: "Step 6: Global Hook Registration"
Constructor->>GlobalScreen: "registerNativeHook()"
Constructor->>GlobalScreen: "addNativeKeyListener(GML)"
Constructor->>GlobalScreen: "addNativeMouseListener(GML)"
Constructor->>GlobalScreen: "addNativeMouseWheelListener(GML)"
Constructor->>GlobalScreen: "addNativeMouseMotionListener(GML)"
note over Constructor: "Step 7: Theme Application"
Constructor->>ComponentUtil: "setMode(contentPane, mode)"
note over Constructor: "Step 8: Final Layout"
Constructor->>Constructor: "pack()"
Constructor->>Constructor: "setSize()"
src/io/github/samera2022/mouse_macros/ui/frame/MainFrame.java L41-L45
checks if config.followSystemSettings is enabled. If so, the system language and dark mode preference are automatically detected and synchronized:
- Language detection uses
SystemUtil.getSystemLang(availableLangs)to match the OS language against available translations - Dark mode detection uses
SystemUtil.isSystemDarkMode()which queries Windows Registry on Windows systems
src/io/github/samera2022/mouse_macros/ui/frame/MainFrame.java L47-L50
initializes the localization system:
Localizer.load(config.lang); // Load language file
Localizer.setRuntimeSwitch(enableLangSwitch); // Enable runtime language switching
src/io/github/samera2022/mouse_macros/ui/frame/MainFrame.java L51-L60
loads custom hotkey mappings from config.keyMap. The default values (F2-F5) are overridden if the configuration contains:
-
"start_macro"→keyRecord -
"stop_record"→keyStop -
"play_macro"→keyPlay -
"abort_macro_operation"→keyAbort
src/io/github/samera2022/mouse_macros/ui/frame/MainFrame.java L68-L112
constructs the UI hierarchy with a BorderLayout:
-
CENTER:
JScrollPanecontaininglogAreawithCustomScrollBarUI -
SOUTH: Container with
JSeparatorand button panel
src/io/github/samera2022/mouse_macros/ui/frame/MainFrame.java L132-L140
registers the global input hook system:
GlobalScreen.registerNativeHook();
GlobalScreen.addNativeKeyListener(GML);
GlobalScreen.addNativeMouseListener(GML);
GlobalScreen.addNativeMouseWheelListener(GML);
GlobalScreen.addNativeMouseMotionListener(GML);
This critical step enables OS-level input capture for macro recording and hotkey detection. JNativeHook logging is disabled src/io/github/samera2022/mouse_macros/ui/frame/MainFrame.java L128-L130
to prevent console spam.
src/io/github/samera2022/mouse_macros/ui/frame/MainFrame.java L142
applies the configured theme (dark or light mode) to the entire component tree via ComponentUtil.setMode().
Sources: src/io/github/samera2022/mouse_macros/ui/frame/MainFrame.java L39-L149
The main window uses a structured layout hierarchy:
flowchart TD
MF["MainFrame<br>(JFrame)"]
BL["BorderLayout"]
Center["CENTER<br>Region"]
South["SOUTH<br>Region"]
SP["JScrollPane"]
LA["logArea<br>(JTextArea)"]
SC["southContainer<br>(JPanel)"]
Sep["JSeparator"]
BP["panel<br>(GridLayout 2x1)"]
R1["row1<br>(FlowLayout)"]
R2["row2<br>(FlowLayout)"]
StartBtn["startBtn"]
StopBtn["stopBtn"]
PlayBtn["playBtn"]
AbortBtn["abortBtn"]
SaveBtn["saveBtn"]
LoadBtn["loadBtn"]
SettingsBtn["settingsBtn"]
MacroSettingsBtn["macroSettingsBtn"]
MF --> BL
BL --> Center
BL --> South
Center --> SP
SP --> LA
South --> SC
SC --> Sep
SC --> BP
BP --> R1
BP --> R2
R1 --> StartBtn
R1 --> StopBtn
R1 --> PlayBtn
R2 --> AbortBtn
R2 --> SaveBtn
R2 --> LoadBtn
R2 --> SettingsBtn
R2 --> MacroSettingsBtn
| Component | Layout Manager | Purpose |
|---|---|---|
MainFrame |
BorderLayout |
Root container |
panel |
GridLayout(2, 1, 5, 5) |
Two-row button container with 5px gaps |
row1 |
FlowLayout(CENTER, 10, 0) |
First button row with 10px horizontal gap |
row2 |
FlowLayout(CENTER, 10, 0) |
Second button row with 10px horizontal gap |
The button panel includes 5px border padding src/io/github/samera2022/mouse_macros/ui/frame/MainFrame.java L83
:
panel.setBorder(BorderFactory.createEmptyBorder(5, 0, 5, 0)); // Top and bottom padding
src/io/github/samera2022/mouse_macros/ui/frame/MainFrame.java L115
calls ComponentUtil.adjustFrameWidth() to automatically size the window based on button widths:
ComponentUtil.adjustFrameWidth(this, startBtn, stopBtn, playBtn, saveBtn, loadBtn, settingsBtn);
Sources: src/io/github/samera2022/mouse_macros/ui/frame/MainFrame.java L68-L115
Each button connects to specific MacroManager operations with guard conditions:
flowchart TD
StartBtn["startBtn"]
StopBtn["stopBtn"]
PlayBtn["playBtn"]
AbortBtn["abortBtn"]
SaveBtn["saveBtn"]
LoadBtn["loadBtn"]
SettingsBtn["settingsBtn"]
MacroSettingsBtn["macroSettingsBtn"]
StartRecording["MacroManager<br>.startRecording()"]
StopRecording["MacroManager<br>.stopRecording()"]
Play["MacroManager<br>.play()"]
Abort["MacroManager<br>.abort()"]
SaveToFile["MacroManager<br>.saveToFile(this)"]
LoadFromFile["MacroManager<br>.loadFromFile(this)"]
OpenSettings["new SettingsDialog()<br>.setVisible(true)"]
OpenMacroSettings["new MacroSettingsDialog()<br>.setVisible(true)"]
StartBtn --> StartRecording
StopBtn --> StopRecording
PlayBtn --> Play
AbortBtn --> Abort
SaveBtn --> SaveToFile
LoadBtn --> LoadFromFile
SettingsBtn --> OpenSettings
MacroSettingsBtn --> OpenMacroSettings
src/io/github/samera2022/mouse_macros/ui/frame/MainFrame.java L118-L125
defines the action listeners:
| Button | Guard Condition | Action | Notes |
|---|---|---|---|
startBtn |
!MacroManager.isRecording() && !HotkeyDialog.inHotKeyDialog |
MacroManager.startRecording() |
Prevents starting if already recording or configuring hotkeys |
stopBtn |
MacroManager.isRecording() && !HotkeyDialog.inHotKeyDialog |
MacroManager.stopRecording() |
Logs error if not recording |
playBtn |
!MacroManager.isRecording() && !HotkeyDialog.inHotKeyDialog |
MacroManager.play() |
Prevents playback during recording |
abortBtn |
MacroManager.isPlaying() && !HotkeyDialog.inHotKeyDialog |
MacroManager.abort() |
Logs error if not playing |
saveBtn |
!MacroManager.isRecording() |
MacroManager.saveToFile(this) |
Opens file chooser dialog |
loadBtn |
!MacroManager.isRecording() |
MacroManager.loadFromFile(this) |
Opens file chooser dialog |
settingsBtn |
None | new SettingsDialog().setVisible(true) |
Uses SwingUtilities.invokeLater()
|
macroSettingsBtn |
None | new MacroSettingsDialog().setVisible(true) |
Uses SwingUtilities.invokeLater()
|
The HotkeyDialog.inHotKeyDialog flag prevents hotkey actions during hotkey configuration, avoiding conflicts when the user presses F2-F5 to set those keys as custom hotkeys.
Sources: src/io/github/samera2022/mouse_macros/ui/frame/MainFrame.java L118-L125
The MainFrame manages four customizable hotkeys through static fields:
flowchart TD
SF["Static Hotkey Fields"]
CM["config.keyMap"]
GML["GlobalMouseListener"]
MM["MacroManager"]
KR["keyRecord<br>=VC_F2"]
KS["keyStop<br>=VC_F3"]
KP["keyPlay<br>=VC_F4"]
KA["keyAbort<br>=VC_F5"]
Rec["Record"]
Stp["Stop"]
Ply["Play"]
Abrt["Abort"]
SF --> KR
SF --> KS
SF --> KP
SF --> KA
CM --> KR
CM --> KS
CM --> KP
CM --> KA
KR --> GML
KS --> GML
KP --> GML
KA --> GML
GML --> MM
MM --> Rec
MM --> Stp
MM --> Ply
MM --> Abrt
src/io/github/samera2022/mouse_macros/ui/frame/MainFrame.java L51-L60
loads hotkey mappings from config.keyMap:
if (config.keyMap != null) {
if (config.keyMap.containsKey("start_macro")) {
try { keyRecord = Integer.parseInt(config.keyMap.get("start_macro")); } catch (Exception ignored) {} }
// ... similar for keyStop, keyPlay, keyAbort
}
src/io/github/samera2022/mouse_macros/ui/frame/MainFrame.java L132-L140
registers the GlobalMouseListener instance with JNativeHook:
GlobalScreen.registerNativeHook();
GlobalScreen.addNativeKeyListener(GML);
GlobalScreen.addNativeMouseListener(GML);
GlobalScreen.addNativeMouseWheelListener(GML);
GlobalScreen.addNativeMouseMotionListener(GML);
The GlobalMouseListener receives native events and compares them against MainFrame.keyRecord, MainFrame.keyStop, etc., to trigger macro operations. See page 4.2 (Global Input Capture) for details.
Hook registration failures are logged src/io/github/samera2022/mouse_macros/ui/frame/MainFrame.java L132-L136
:
try {
GlobalScreen.registerNativeHook();
} catch (Exception e) {
log(Localizer.get("hook_registration_failed") + e.getMessage());
}
Sources: src/io/github/samera2022/mouse_macros/ui/frame/MainFrame.java L32-L35, src/io/github/samera2022/mouse_macros/ui/frame/MainFrame.java L62-L71, src/io/github/samera2022/mouse_macros/ui/frame/MainFrame.java L142-L150
The MainFrame extensively uses Localizer.get() to retrieve translated strings for all UI text:
src/io/github/samera2022/mouse_macros/ui/frame/MainFrame.java L87-L98
creates buttons with localized text:
startBtn = new JButton(getStartBtnText());
stopBtn = new JButton(getStopBtnText());
playBtn = new JButton(getPlayBtnText());
abortBtn = new JButton(getAbortBtnText());
saveBtn = new JButton(Localizer.get("save_macro"));
loadBtn = new JButton(Localizer.get("load_macro"));
settingsBtn = new JButton(Localizer.get("settings"));
macroSettingsBtn = new JButton(Localizer.get("macro_settings"));
src/io/github/samera2022/mouse_macros/ui/frame/MainFrame.java L171-L174
defines helper methods that combine localized text with hotkey display:
private String getStartBtnText() {
return Localizer.get("start_record") + " (" + OtherUtil.getNativeKeyDisplayText(keyRecord) + ")";
}This produces button text like "Start Recording (F2)" with the current hotkey dynamically inserted.
src/io/github/samera2022/mouse_macros/ui/frame/MainFrame.java L152-L162
implements refreshMainFrameTexts() to update all UI text after language changes:
public void refreshMainFrameTexts() {
setTitle(Localizer.get("title"));
refreshSpecialTexts(); // Updates buttons with hotkey text
saveBtn.setText(Localizer.get("save_macro"));
loadBtn.setText(Localizer.get("load_macro"));
settingsBtn.setText(Localizer.get("settings"));
macroSettingsBtn.setText(Localizer.get("macro_settings"));
ComponentUtil.adjustFrameWidth(this, startBtn, stopBtn, playBtn, saveBtn, loadBtn, settingsBtn, abortBtn, macroSettingsBtn);
}
The final call to adjustFrameWidth() ensures the window resizes to accommodate the new text widths in different languages.
Sources: src/io/github/samera2022/mouse_macros/ui/frame/MainFrame.java L87-L98, src/io/github/samera2022/mouse_macros/ui/frame/MainFrame.java L152-L174
The MainFrame applies themes through ComponentUtil.setMode() which recursively styles all child components:
src/io/github/samera2022/mouse_macros/ui/frame/MainFrame.java L72-L73
applies CustomScrollBarUI to the log area's scrollbars:
scrollPane.getVerticalScrollBar().setUI(new CustomScrollBarUI(config.enableDarkMode? OtherConsts.DARK_MODE:OtherConsts.LIGHT_MODE));
scrollPane.getHorizontalScrollBar().setUI(new CustomScrollBarUI(config.enableDarkMode? OtherConsts.DARK_MODE:OtherConsts.LIGHT_MODE));
src/io/github/samera2022/mouse_macros/ui/frame/MainFrame.java L142
applies the theme to the entire content pane:
ComponentUtil.setMode(getContentPane(), config.enableDarkMode?OtherConsts.DARK_MODE:OtherConsts.LIGHT_MODE);
flowchart TD
MFCtor["MainFrame<br>Constructor"]
GCP["getContentPane()"]
CU["ComponentUtil<br>.setMode()"]
RS["Recursive<br>Styling"]
Btns["JButtons"]
TA["JTextArea<br>logArea"]
Pnls["JPanels"]
SBs["JScrollBars"]
Sep["JSeparator"]
MFCtor --> GCP
GCP --> CU
CU --> RS
RS --> Btns
RS --> TA
RS --> Pnls
RS --> SBs
RS --> Sep
The ComponentUtil.setMode() method handles:
- Background and foreground colors for all component types
- Special handling for
JScrollPane,JPanel,JButton,JTextField,JTextArea,JComboBox, etc. - Application of
CustomScrollBarUIto scrollbars - Caret color for text components
When the user changes the theme in SettingsDialog, ComponentUtil.setMode() is called again on the main frame, immediately updating all colors without requiring an application restart.
Sources: src/io/github/samera2022/mouse_macros/ui/frame/MainFrame.java L72-L73, src/io/github/samera2022/mouse_macros/ui/frame/MainFrame.java L142
The static logArea field enables system-wide logging through LogManager:
flowchart TD
MM["MacroManager"]
GML["GlobalMouseListener"]
CM["ConfigManager"]
OC["Other<br>Components"]
LM["LogManager<br>.log(msg)"]
SU["SwingUtilities<br>.invokeLater()"]
LA["MainFrame<br>.logArea"]
TA["logArea.append<br>(msg+newline)"]
MM --> LM
GML --> LM
CM --> LM
OC --> LM
LM --> SU
SU --> LA
LA --> TA
The LogManager class provides a static logging method:
public static void log(String msg) {
SwingUtilities.invokeLater(() -> MainFrame.logArea.append(msg + "\n"));
}
The use of SwingUtilities.invokeLater() ensures thread safety when non-EDT threads (like macro playback threads) write log messages.
Examples of logged messages:
-
"Recording started"when macro recording begins -
"Macro saved successfully"after file save -
"Hook registration failed: ..."on JNativeHook errors -
"Macro not recording"when user clicks Stop without recording
All messages are localized via Localizer.get() before logging.
Sources: src/io/github/samera2022/mouse_macros/ui/frame/MainFrame.java L26
The MainFrame applies display scaling adjustments to ensure correct sizing across different DPI settings:
src/io/github/samera2022/mouse_macros/ui/frame/MainFrame.java L144
applies window size caching:
ComponentUtil.applyWindowSizeCache(this, "title", 430, 330);
This method retrieves cached window dimensions from cache.json if available, or uses the default dimensions of 430×330 pixels.
src/io/github/samera2022/mouse_macros/ui/frame/MainFrame.java L176-L179
provides a static method for external width adjustments:
public static void adjustFrameWidth(){
MAIN_FRAME.pack();
MAIN_FRAME.setSize(MAIN_FRAME.getWidth(), (int) (660/SystemUtil.getScale()[1]));
}
This is called after language changes to resize the window based on new text widths. The height is dynamically calculated using SystemUtil.getScale()[1] to account for display scaling.
Sources: src/io/github/samera2022/mouse_macros/ui/frame/MainFrame.java L144, src/io/github/samera2022/mouse_macros/ui/frame/MainFrame.java L176-L179
The MainFrame serves as the integration point for multiple subsystems:
flowchart TD
MF["MainFrame"]
MM["MacroManager"]
GML["GlobalMouseListener"]
CM["ConfigManager"]
Loc["Localizer"]
CU["ComponentUtil"]
SU["SystemUtil"]
LM["LogManager"]
SD["SettingsDialog"]
MSD["MacroSettingsDialog"]
HD["HotkeyDialog"]
JNH["GlobalScreen"]
MF --> MM
MF --> GML
MF --> CM
MF --> Loc
MF --> CU
MF --> SU
MF --> LM
MF --> SD
MF --> MSD
MF --> JNH
JNH --> GML
GML --> MF
SD --> HD
HD --> MF
Key imports src/io/github/samera2022/mouse_macros/ui/frame/MainFrame.java L3-L18
:
| Import | Purpose |
|---|---|
com.github.kwhat.jnativehook.* |
JNativeHook library for global input hooks |
io.github.samera2022.mouse_macros.Localizer |
Translation system |
io.github.samera2022.mouse_macros.constant.ColorConsts |
Color scheme definitions |
io.github.samera2022.mouse_macros.constant.OtherConsts |
Mode constants (DARK_MODE, LIGHT_MODE) |
io.github.samera2022.mouse_macros.listener.GlobalMouseListener |
Global input listener |
io.github.samera2022.mouse_macros.manager.MacroManager |
Macro operations |
io.github.samera2022.mouse_macros.manager.ConfigManager |
Configuration access |
io.github.samera2022.mouse_macros.util.ComponentUtil |
UI theming utilities |
io.github.samera2022.mouse_macros.util.SystemUtil |
System property detection |
io.github.samera2022.mouse_macros.manager.LogManager |
Static logging via log()
|
Sources: src/io/github/samera2022/mouse_macros/ui/frame/MainFrame.java L3-L18
The MAIN_FRAME and logArea static fields enable global access without dependency injection:
public static final MainFrame MAIN_FRAME = new MainFrame();
public static JTextArea logArea;
This allows LogManager.log() to append to the log area from any thread.
Action listeners include state checks before executing operations:
startBtn.addActionListener(e -> {
if ((!MacroManager.isRecording()) && (!HotkeyDialog.inHotKeyDialog))
MacroManager.startRecording();
});
This prevents invalid state transitions and race conditions.
Settings dialogs are created on-demand rather than during construction:
settingsBtn.addActionListener(e ->
SwingUtilities.invokeLater(() -> new SettingsDialog().setVisible(true))
);
This improves startup performance and ensures fresh dialog state.
The theme application uses recursive traversal of the component tree via ComponentUtil.setMode(), ensuring all nested components receive consistent styling.
Sources: src/io/github/samera2022/mouse_macros/ui/frame/MainFrame.java L28-L39, src/io/github/samera2022/mouse_macros/ui/frame/MainFrame.java L128-L135