Skip to content

Main Window (MainFrame)

Samera2022 edited this page Jan 30, 2026 · 1 revision

Main Window (MainFrame)

Relevant source files

Purpose and Scope

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


Class Structure and Singleton Pattern

The MainFrame class follows a singleton-like pattern with a public static instance:

public static final MainFrame MAIN_FRAME = new MainFrame();

Key Members

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


Initialization Sequence

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()"
Loading

Step 1: System Settings Synchronization

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

Step 2: Localization Loading

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

Step 3: Hotkey Configuration

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

Step 4: UI Component Construction

src/io/github/samera2022/mouse_macros/ui/frame/MainFrame.java L68-L112

constructs the UI hierarchy with a BorderLayout:

  • CENTER: JScrollPane containing logArea with CustomScrollBarUI
  • SOUTH: Container with JSeparator and button panel

Step 5: Global Hook Registration

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.

Step 6: Theme Application

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


UI Layout Architecture

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
Loading

Layout Details

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

Dynamic Width Adjustment

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


Event Handling and Action Listeners

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
Loading

Action Listener Implementations

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()

HotkeyDialog Integration

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


Global Hook and Hotkey Management

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
Loading

Hotkey Loading

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
}

GlobalScreen Registration

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.

Error Handling

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


Localization Integration

The MainFrame extensively uses Localizer.get() to retrieve translated strings for all UI text:

Initial Text Assignment

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"));

Dynamic Button Text with Hotkeys

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.

Runtime Language Switching

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


Theme Application

The MainFrame applies themes through ComponentUtil.setMode() which recursively styles all child components:

Initial Theme Setup

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);

Theme Application Flow

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
Loading

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 CustomScrollBarUI to scrollbars
  • Caret color for text components

Runtime Theme Switching

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


Logging System Integration

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
Loading

LogManager Implementation

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.

Usage Throughout Application

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


Window Sizing and Scaling

The MainFrame applies display scaling adjustments to ensure correct sizing across different DPI settings:

Final Size Calculation

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.

Static Width Adjustment

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


Dependencies and Subsystem Integration

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
Loading

Import Analysis

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


Key Design Patterns

Static Singleton Access

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.

Guard Conditions on Actions

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.

Lazy Dialog Initialization

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.

Recursive Component Theming

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

Clone this wiki locally