diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 593ad4763..2f1746f26 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -52,7 +52,8 @@ Notepad3 is a Win32 desktop text editor built on the **Scintilla** editing compo ### Core modules (in `src\`) -- **Notepad3.c/h** — Application entry point (`wWinMain`), window procedure, global state structs (`GLOBALS_T`, `SETTINGS_T`, `FLAGS_T`, `PATHS_T`) +- **Notepad3.c/h** — Application entry point (`wWinMain`), window procedure, global state structs (`GLOBALS_T`, `SETTINGS_T`, `FLAGS_T`, `PATHS_T`), `MsgCommand()` dispatcher with sub-handlers +- **Notepad3Util.c/h** — Utility functions extracted from Notepad3.c: bitmap/toolbar image loading (`NP3Util_LoadBitmapFile`, `NP3Util_CreateScaledImageListFromBitmap`), word-wrap configuration (`NP3Util_SetWrapIndentMode`, `NP3Util_SetWrapVisualFlags`), auto-scroll (`NP3Util_AutoScrollStart/Stop`) - **Edit.c/h** — Text manipulation: find/replace (PCRE2 regex), encoding conversion, clipboard, indentation, sorting, bookmarks, folding, auto-complete - **Styles.c/h** — Scintilla styling, lexer selection, theme management - **Dialogs.c/h** — All dialog boxes and UI interactions @@ -243,6 +244,7 @@ Notepad3 follows a **portable-app** design for its configuration file (`Notepad3 - **Admin redirect**: An administrator can place `Notepad3.ini=` in `[Notepad3]` section of the app-directory INI to redirect to a per-user path. Up to 2 levels of redirect are supported. Redirect targets **are** auto-created (the admin intended them to exist). - **Key paths**: `Paths.IniFile` = active writable INI (empty if none exists), `Paths.IniFileDefault` = fallback path for "Save Settings Now" recovery. - **Configuration code**: All INI init logic lives in `src\Config\Config.cpp` — `FindIniFile()` → `TestIniFile()` → `CreateIniFile()` → `LoadSettings()`. +- **Empty INI = no INI**: An empty (0-byte) INI file must produce identical saved output to having no INI file at all. `SettingsVersion` defaults to `CFG_VER_CURRENT` when missing — a missing key does NOT imply a legacy config. Empty lexer sections are removed after style saving (`IniSectionGetKeyCount()`). - **MiniPath** follows the same portable INI and admin-redirect pattern (`minipath\src\Config.cpp`). Redirect targets are auto-created via `CreateIniFileEx()`. - **New parameters**: When adding new `Settings2` (or other INI) parameters, always document them as commented entries in `Build\Notepad3.ini` diff --git a/CLAUDE.md b/CLAUDE.md index a294691e4..8b5fb3ed9 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -52,7 +52,8 @@ GitHub Actions (`.github/workflows/build.yml`) builds all four platforms (Win32, | File | Purpose | |------|---------| -| **Notepad3.c/h** | Entry point (`wWinMain`), window procedure (`MainWndProc`), global state structs (`Globals`, `Settings`, `Settings2`, `Flags`, `Paths`) | +| **Notepad3.c/h** | Entry point (`wWinMain`), window procedure (`MainWndProc`), global state structs (`Globals`, `Settings`, `Settings2`, `Flags`, `Paths`), `MsgCommand()` dispatcher | +| **Notepad3Util.c/h** | Utility functions extracted from Notepad3.c: bitmap/toolbar image loading, word-wrap configuration, auto-scroll (middle-click continuous scroll) | | **Edit.c/h** | Text manipulation: find/replace (PCRE2 regex), encoding conversion, clipboard, indentation, sorting, bookmarks, folding, auto-complete | | **Styles.c/h** | Scintilla styling, lexer selection, theme management, margin configuration | | **Dialogs.c/h** | All dialog boxes, DPI-aware UI interactions, window placement | @@ -73,6 +74,28 @@ MainWndProc (Notepad3.c) +-- Status Bar (16 configurable fields) ``` +### Menu / Command Architecture + +Menu items are defined in resource files (`language/np3_*/menu_*.rc`). Command handling in `Notepad3.c` is structured as: + +- **`MsgInitMenu()`** — `WM_INITMENU` handler; enables/disables/checks menu items based on current state +- **`MsgCommand()`** — `WM_COMMAND` thin dispatcher; handles timer/notification cases inline, then delegates to static sub-handlers: + +| Handler | Scope | +|---------|-------| +| `_HandleFileCommands` | File open/save/print/favorites (`IDM_FILE_*`) | +| `_HandleEncodingCommands` | Encoding & line endings (`IDM_ENCODING_*`, `IDM_LINEENDINGS_*`) | +| `_HandleEditBasicCommands` | Undo/redo/cut/copy/paste/indent (`IDM_EDIT_UNDO`..`CMD_VK_INSERT`) | +| `_HandleEditLineManipulation` | Line modify/sort/join/case (`IDM_EDIT_ENCLOSESELECTION`..`IDM_EDIT_INSERT_GUID`) | +| `_HandleEditTextTransform` | Comments/URL encode/escape/hex (`IDM_EDIT_LINECOMMENT`..`IDM_EDIT_HEX2CHAR`) | +| `_HandleEditFind` | Find/replace/bookmarks/goto (`IDM_EDIT_FINDMATCHINGBRACE`..`IDM_EDIT_GOTOLINE`) | +| `_HandleViewAndSettingsCommands` | View/settings/rendering (`IDM_VIEW_*`, `IDM_SET_*`) | +| `_HandleHelpCommands` | Help/about (`IDM_HELP_*`) | +| `_HandleCmdCommands` | Keyboard shortcuts/navigation/window positioning (`CMD_*`) | +| `_HandleToolbarCommands` | Toolbar button dispatch via `s_ToolbarDispatch[]` table (`IDT_*`) | + +Each handler returns `true` if it handled the command, `false` to try the next. All handlers are `static` in `Notepad3.c`. + ### Vendored Dependencies | Directory | Library | Purpose | @@ -139,6 +162,9 @@ Resource-based MUI system with 27+ locales. Each locale has a `np3_LANG_COUNTRY\ - **Admin redirect**: `Notepad3.ini=` in `[Notepad3]` section redirects to per-user path (up to 2 levels) - Key paths: `Paths.IniFile` (active writable INI), `Paths.IniFileDefault` (fallback for recovery) - INI init flow: `FindIniFile()` -> `TestIniFile()` -> `CreateIniFile()` -> `LoadSettings()` +- **SettingsVersion default**: `SettingsVersion` defaults to `CFG_VER_CURRENT` when missing from INI — an INI file without `SettingsVersion` is NOT treated as a legacy (pre-versioning) file. This ensures empty INI files and newly created INI files both get current defaults. +- **`bIniFileFromScratch`**: Set when INI file size is 0 bytes. Cleared after `SaveAllSettings()` completes. While true, `MuiLanguage.c` suppresses writing `PreferredLanguageLocaleName` to avoid polluting fresh INI files. +- **Style section saving**: `Style_ToIniSection()` removes empty lexer sections after processing via `IniSectionGetKeyCount()`. `Style_CanonicalSectionToIniCache()` establishes canonical section order but empty sections are cleaned up — only sections with non-default style values appear in the saved INI. - **MiniPath** follows the same portable INI and admin-redirect pattern (`minipath\src\Config.cpp`). Redirect targets are auto-created via `CreateIniFileEx()`. - **New parameters**: When adding new `Settings2` (or other INI) parameters, always document them as commented entries in `Build\Notepad3.ini` diff --git a/plans/deasater_recovery_prompt.txt b/plans/deasater_recovery_prompt.txt new file mode 100644 index 000000000..f468e37e2 --- /dev/null +++ b/plans/deasater_recovery_prompt.txt @@ -0,0 +1,21 @@ +please make a plan for implementing a kind of "disaster file recovery": +- on application crash/sigkill/sigint/sys-shutdown there should be a directory (user's %APPDATA%/Notepad3/recovery/) where recovery bundle files are kept +- one file to keep the original document (respect encryption) +- an accompaning ini file which keeps the original full filepath, original encoding, and other imortant attributes +- this recovery file should be updated (if save needed / dirty mode) in background every 2000ms (time should be configurable in settings) +- the recovery file bundle should be rmoved, if the application exits normally +- there should be a recovery bundle also for dirty non empty drafts (Untitled / No file path given) +- the mechanism should be multi instances aware (maybe process number in bundle name ?) +- on every application start check the "%APPDATA%/Notepad3/recovery/" if there are unhandled disaster recovery files and + start a new app instance to load the recovery, unhandled means, there is no instance taking care of this recovery file, must be tracked somehow: + + instance has file loaded with correct path - normal recovery mode (process-id of recovery file matches) + + file has no instance which is taking care of, new instance has to be started for this recovery file + + recovery file's process id does not fit to a "care of instance", but new instance has already been startet to take care of + + if a new instance takes care of the recovery file, the process-id has to be changed accordingly + + maybe, the commandline interface has to be extended to give an file path for handling and saving in original place + + maybe the modified date of both files must be checked to do not override newer files +- + + +- if you have suggestions derived from best practices for disaster recovery, please suggest +- if there are questions/uncertainty please ask diff --git a/plans/notepad3_refactoring.md b/plans/notepad3_refactoring.md new file mode 100644 index 000000000..71e39163f --- /dev/null +++ b/plans/notepad3_refactoring.md @@ -0,0 +1,230 @@ +# Notepad3.c Refactoring — Extract Utilities & Split MsgCommand() + +## Context + +`src/Notepad3.c` was the largest module in the project at **12,985 lines**. It contained ~55 static functions and ~60 static variables spanning unrelated concerns (auto-scroll, file observation, bitmap loading, text input helpers, TinyExpr evaluation, etc.) plus a monolithic `MsgCommand()` dispatcher (2,994 lines, 360+ case statements). This refactoring improves navigability and separation of concerns without changing any behavior. + +### Motivation + +- **Navigability**: Finding code in a 13K-line file is slow; logically grouped helpers belong in their own modules +- **Maintainability**: The `MsgCommand()` monolith made it hard to reason about individual command groups +- **Encapsulation**: Static variables for unrelated subsystems (auto-scroll, file observation, TinyExpr) were all in global file scope, obscuring their true scope +- **Consistency**: Other modules (`Edit.c`, `Styles.c`, `Dialogs.c`) already follow clean `.c/.h` pair boundaries + +### What NOT to extract + +These items are intentionally kept in `Notepad3.c`: +- **`_InitGlobals()` / `_CleanUpResources()`** — core app lifecycle, touches everything +- **Message queue helpers** (`_MQ_AppendCmd`, `MQ_ExecuteNext`) — tightly integrated with timer system and UI updates +- **UI update helpers** (`_UpdateStatusbarDelayed`, `_UpdateToolbarDelayed`, `_UpdateTitlebarDelayed`) — depend on message queue + complex global state +- **`_EditSubclassProc()`** — Scintilla subclass glue, belongs near `MainWndProc` +- **`ParseCmdLnOption()`** — command-line parsing, belongs with app startup +- **`MsgInitMenu()`** — reads 20+ state variables to enable/disable menu items; inherently whole-application state + +--- + +## Completed Work + +### Part 2: MsgCommand() Split (DONE) + +`MsgCommand()` refactored from a **2,994-line** switch into a **73-line thin dispatcher** that delegates to **10 static handler functions**, all remaining in `Notepad3.c`: + +| Handler | Cases | Purpose | +|---------|-------|---------| +| `_HandleFileCommands` | ~27 | `IDM_FILE_*` — open/save/print/favorites/grepWin | +| `_HandleEncodingCommands` | ~10 | `IDM_ENCODING_*`, `IDM_LINEENDINGS_*` | +| `_HandleEditBasicCommands` | ~30 | `IDM_EDIT_UNDO`..`CMD_VK_INSERT` — undo/redo/cut/copy/paste/indent | +| `_HandleEditLineManipulation` | ~42 | `IDM_EDIT_ENCLOSESELECTION`..`IDM_EDIT_INSERT_GUID` — line modify/sort/join/case | +| `_HandleEditTextTransform` | ~45 | `IDM_EDIT_LINECOMMENT`..`IDM_EDIT_HEX2CHAR` — comments/encode/escape/hex | +| `_HandleEditFind` | ~21 | `IDM_EDIT_FINDMATCHINGBRACE`..`IDM_EDIT_GOTOLINE` — find/replace/bookmarks | +| `_HandleViewAndSettingsCommands` | ~99 | `IDM_VIEW_*`, `IDM_SET_*` — view/settings/rendering | +| `_HandleHelpCommands` | ~5 | `IDM_HELP_*`, `IDM_SETPASS` | +| `_HandleCmdCommands` | ~90 | `CMD_*` — keyboard shortcuts/navigation/window positioning | +| `_HandleToolbarCommands` | ~30 | `IDT_*` — toolbar dispatch via `s_ToolbarDispatch[]` lookup table | + +**Dispatcher pattern:** +```c +LRESULT MsgCommand(HWND hwnd, UINT umsg, WPARAM wParam, LPARAM lParam) +{ + // Language/theme menu range checks (inline) + // Timer/notification cases (inline, return immediately) + switch(iLoWParam) { case SCEN_CHANGE: ... return FALSE; ... } + + // Handler dispatch chain + if (_HandleFileCommands(hwnd, umsg, wParam, lParam)) { return FALSE; } + if (_HandleEncodingCommands(...)) { return FALSE; } + ... + return DefWindowProc(hwnd, umsg, wParam, lParam); +} +``` + +Each handler returns `true` if handled, `false` to try the next. The 40 repetitive IDT_* toolbar cases were replaced with a `s_ToolbarDispatch[]` lookup table (29 standard entries + 2 special cases: `IDT_EDIT_COPY` falls back to COPYALL, `IDT_EDIT_CLEAR` falls back to `SciCall_ClearAll`). + +### Part 1, Phases 1-3: Notepad3Util.c/.h (DONE) + +New files created: `src/Notepad3Util.c` (349 lines), `src/Notepad3Util.h` (50 lines). + +**Phase 1 — Bitmap/Image Loading** (LOW risk, ~100 LOC): +- `NP3Util_LoadBitmapFile()` — loads toolbar bitmap, validates dimensions +- `NP3Util_CreateScaledImageListFromBitmap()` — creates DPI-scaled image list +- `NP3Util_XXX_CreateScaledImageListFromBitmap()` — legacy variant using fixed `NUMTOOLBITMAPS` +- `NUMTOOLBITMAPS` macro moved from Notepad3.c to Notepad3Util.h + +**Phase 2 — Word-Wrap Configuration** (LOW risk, ~100 LOC): +- `NP3Util_SetWrapStartIndent()` — sets wrap start indent based on `Settings.WordWrapIndent` +- `NP3Util_SetWrapIndentMode()` — sets wrap indent mode (same/indent/deep/fixed) +- `NP3Util_SetWrapVisualFlags(HWND)` — sets wrap visual flag symbols + +**Phase 3 — Auto-Scroll** (LOW-MED risk, ~200 LOC): +- 6 static variables moved: `s_bAutoScrollMode`, `s_bAutoScrollHeld`, `s_dwAutoScrollStartTick`, `s_ptAutoScrollOrigin`, `s_ptAutoScrollMouse`, `s_dAutoScrollAccumY` +- 4 `AUTOSCROLL_*` constants moved to header +- `NP3Util_AutoScrollStart/Stop()`, `NP3Util_AutoScrollTimerProc()` — core scroll logic +- `NP3Util_IsAutoScrollMode()`, `NP3Util_IsAutoScrollHeld()`, `NP3Util_GetAutoScrollStartTick()`, `NP3Util_SetAutoScrollHeld()`, `NP3Util_AutoScrollUpdateMouse()` — state accessors for `_EditSubclassProc` + +**Net result:** `Notepad3.c` reduced from 12,985 to 12,713 lines. + +### Files modified +- `src/Notepad3.c` — extracted code removed, call sites updated, `#include "Notepad3Util.h"` added +- `src/Notepad3Util.c` — new implementation file +- `src/Notepad3Util.h` — new header file +- `src/Notepad3.vcxproj` — `` and `` entries added +- `src/Notepad3.vcxproj.filters` — filter entries added (Source Files / Header Files) +- `CLAUDE.md` — Core Modules table updated, Menu/Command Architecture section added +- `.github/copilot-instructions.md` — Core modules list updated + +--- + +## Remaining Work — Phases 4-6 + +### Phase 4 — TinyExpr Evaluation (~130 LOC, MEDIUM risk) + +**Static variables to move** (Notepad3.c line 186-187): +- `s_dExpression` (double) — last evaluated expression result +- `s_iExprError` (te_int_t) — last expression error code + +**Functions to move:** +| Current | New | Line | LOC | +|---------|-----|------|-----| +| `_EvalTinyExpr(bool qmark)` | `NP3Util_EvalTinyExpr(bool)` | 2655 | ~150 | +| `_InterpMultiSelectionTinyExpr(te_int_t*)` | `NP3Util_InterpMultiSelectionTinyExpr(te_int_t*)` | 10086 | ~50 | + +**New accessor functions needed:** +- `NP3Util_GetLastExpression()` — returns `s_dExpression` (read by `_UpdateStatusbarDelayed`) +- `NP3Util_GetLastExprError()` — returns `s_iExprError` (read by `_UpdateStatusbarDelayed`) + +**Call sites to update (3):** +- Line 6839: `_EvalTinyExpr(false)` — in `_HandleCmdCommands`, case `CMD_ENTER_RETURN` +- Line 8921: `_EvalTinyExpr(true)` — in `_MsgNotifyFromEdit`, on `?` char insert +- Line 10399: `s_dExpression = _InterpMultiSelectionTinyExpr(&s_iExprError)` — in `_UpdateStatusbarDelayed` + +**Dependencies:** `Settings.EvalTinyExprOnSelection`, `Encoding_SciCP`, `SciCall_*` (all via headers), `te_interp()` (needs `#include "tinyexpr/tinyexpr.h"` in Notepad3Util.c), `AllocMem`/`FreeMem` (via `Helpers.h`). + +**Risk:** The statusbar code currently reads `s_dExpression`/`s_iExprError` directly — must switch to getters. The `te_interp()` call works on raw char buffers from Scintilla — encoding-sensitive but mechanically straightforward. + +--- + +### Phase 5 — Text Input Helpers (~300 LOC, MEDIUM risk) + +**Static variable to move** (line 189): +- `s_SelectionBuffer` (char*) — dynamically allocated buffer for auto-close bracket/quote tracking + +**Functions to move:** +| Current | New | Line | LOC | +|---------|-----|------|-----| +| `_HandleAutoIndent(int)` | `NP3Util_HandleAutoIndent(int)` | 8291 | ~45 | +| `_HandleAutoCloseTags()` | `NP3Util_HandleAutoCloseTags()` | 8338 | ~58 | +| `_SaveSelectionToBuffer()` | `NP3Util_SaveSelectionToBuffer()` | 8398 | ~16 | +| `_EncloseSelectionBuffer(char,char)` | `NP3Util_EncloseSelectionBuffer(char,char)` | 8416 | ~17 | +| `_HandleInsertCheck(SCNotification*)` | `NP3Util_HandleInsertCheck(...)` | 8435 | ~89 | +| `_HandleDeleteCheck(SCNotification*)` | `NP3Util_HandleDeleteCheck(...)` | 8526 | ~60 | + +Skip `_IsIMEOpenInNoNativeMode()` (line 8588) — dead code (`#if 0`). + +**Lifecycle functions needed:** +- `NP3Util_TextInputInit()` — allocates `s_SelectionBuffer`; called from `MsgCreate` +- `NP3Util_TextInputCleanup()` — frees `s_SelectionBuffer`; called from `_CleanUpResources` + +**Call sites to update (5, all in `_MsgNotifyFromEdit`):** +- Line 8635: `_HandleInsertCheck(scn)` +- Line 8642: `_SaveSelectionToBuffer()` +- Line 8658: `_HandleDeleteCheck(scn)` +- Line 8912: `_HandleAutoIndent(ich)` +- Line 8917: `_HandleAutoCloseTags()` + +**Dependencies:** `Settings.AutoIndent`, `Settings.AutoCloseQuotes`, `Settings.AutoCloseBrackets`, `Settings.AutoCloseTags` (globals), `SciCall_*`/`Sci_*` (headers), `EditReplaceSelection()` (via `Edit.h`), `AllocMem`/`FreeMem`/`ReAllocMem`/`SizeOfMem` (via `Helpers.h`). + +**Risk:** These functions run on the Scintilla notification hot path (`SCN_MODIFIED`, `SCN_CHARADDED`), but are called once per keystroke (not per character in bulk), so function-call overhead is negligible. Main risk is ensuring `s_SelectionBuffer` lifecycle remains correct. + +--- + +### Phase 6 — File Observation (~450 LOC, HIGH risk — do last) + +**Static variable to move** (line 483): +- `s_FileChgObsvrData` (`FCOBSRVDATA_T`) — contains event handles (`hEventFileChanged`, `hEventFileDeleted`), file metadata (`fdCurFile`), generation counter (`iObservationGeneration`, uses `InterlockedCompareExchange`/`InterlockedIncrement` seqlock pattern), background worker handle. **48 non-comment references** across Notepad3.c. + +**Functions to move:** +| Current | New | Line | LOC | +|---------|-----|------|-----| +| `IsFileReadOnly()` | `NP3Util_IsFileReadOnly()` | 471 | ~15 | +| `IsFileChangedFlagSet()` | `NP3Util_IsFileChangedFlagSet()` | 487 | ~4 | +| `IsFileDeletedFlagSet()` | `NP3Util_IsFileDeletedFlagSet()` | 492 | ~4 | +| `RaiseFlagIfCurrentFileChanged()` | `NP3Util_RaiseFlagIfCurrentFileChanged()` | 497 | ~50 | +| `ResetFileObservationData(bool)` | `NP3Util_ResetFileObservationData(bool)` | 548 | ~20 | +| `IsFileVarLogFile()` | `NP3Util_IsFileVarLogFile()` | 10861 | ~10 | +| `_ResetFileWatchingMode()` | `NP3Util_ResetFileWatchingMode()` | 10871 | ~10 | +| `NotifyIfFileHasChanged()` | `NP3Util_NotifyIfFileHasChanged()` | 12364 | ~20 | +| `WatchTimerProc(...)` | `NP3Util_WatchTimerProc(...)` | 12385 | ~15 | +| `LogRotateTimerProc(...)` | `NP3Util_LogRotateTimerProc(...)` | 12402 | ~25 | +| `AtomicSaveTimerProc(...)` | `NP3Util_AtomicSaveTimerProc(...)` | 12426 | ~50 | + +**Lifecycle functions needed:** +- `NP3Util_FileObservationInit()` — creates event handles; replaces code in `InitInstance()` (~lines 1843-1852) +- `NP3Util_FileObservationCleanup()` — destroys worker + event handles; replaces code in `_CleanUpResources()` (~lines 824-833) +- `NP3Util_GetFileObservationData()` — returns `PFCOBSRVDATA_T` pointer for `InstallFileWatching()` to access the struct + +**Circular dependency:** +Timer callbacks call back into Notepad3.c: +- `AtomicSaveTimerProc` → `InstallFileWatching(false)`, `FileSave(FSF_SaveAlways)` +- `LogRotateTimerProc` → `PostWMCommand(Globals.hwndMain, IDM_VIEW_CHASING_DOCTAIL)`, `InstallFileWatching(true)` +- `_ResetFileWatchingMode` → `CheckCmd(GetMenu(...))` + +**Resolution:** `Notepad3Util.c` already `#include`s `Notepad3.h` which declares these functions. The linker resolves cross-module calls — same pattern as `Edit.c` calling `FileLoad()`. + +**Major call sites to update (~48 references):** +- `InitInstance()` — event creation → `NP3Util_FileObservationInit()` +- `_CleanUpResources()` — cleanup → `NP3Util_FileObservationCleanup()` +- `InstallFileWatching()` — direct struct field access → `NP3Util_GetFileObservationData()->` +- `MsgFileChangeNotify()` — reads flags, resets observation data +- `_UpdateTitlebarDelayed()` — calls `IsFileChangedFlagSet()`/`IsFileDeletedFlagSet()` +- `MsgInitMenu()` — calls `IsFileReadOnly()` +- `_HandleViewAndSettingsCommands`, `_HandleCmdCommands` — various flag checks + +**Threading concern:** The generation counter uses `InterlockedCompareExchange`/`InterlockedIncrement` for a seqlock pattern (background worker vs. UI thread). Moving the struct doesn't change thread safety, but `NP3Util_GetFileObservationData()` returns a raw pointer — callers must not cache it across calls that could reallocate. + +**Risk: HIGH** — 48 reference sites (most mechanical renames), but `InstallFileWatching()` directly manipulates struct fields (worker start/cancel, event wait). Timer proc function pointers in `SetTimer()` calls must be updated. Threading correctness is critical. + +--- + +## Verification Strategy + +After each phase: + +1. **Build:** `Build\Build_x64.cmd Debug` (minimum) — no compile or link errors +2. **Diff audit:** `git diff` — confirm purely mechanical moves, no logic changes +3. **Smoke test per group:** + - Phase 4 (TinyExpr): Select `1+2` → press `?` → verify result inserted; check statusbar expression display with column selection + - Phase 5 (Text Input): Type `"` → verify auto-close quote; type `{` → verify auto-close bracket; press Enter after `if (...) {` → verify auto-indent; type `
` → verify auto-close `
`; press Backspace on `""` → verify pair deletion + - Phase 6 (File Observation): Edit file in another editor → Notepad3 must prompt for reload; enable log tail mode (Ctrl+Shift+L) → verify auto-refresh; test atomic save (Settings2.AtomicFileSave=1); test file deletion detection; open/close files rapidly → verify no timer leaks +4. **Full build** after all phases: `Build\BuildAll.cmd Release` (all 4 platforms) + +--- + +## Estimated Final Result + +| Metric | Before | After (all phases) | +|--------|--------|---------------------| +| `Notepad3.c` lines | 12,985 | ~11,700 | +| `Notepad3Util.c` lines | 0 | ~1,250 | +| `MsgCommand()` lines | 2,994 | ~73 (dispatcher) | +| Toolbar switch cases | 40 repetitive | dispatch table | +| Static helpers in Notepad3.c | ~55 | ~35 | diff --git a/src/Notepad3.c b/src/Notepad3.c index 01916a898..c12e736a4 100644 --- a/src/Notepad3.c +++ b/src/Notepad3.c @@ -48,6 +48,7 @@ #include "Notepad3.h" #include "Config/Config.h" #include "DarkMode/DarkMode.h" +#include "Notepad3Util.h" #include "StyleLexers/EditLexer.h" #if (defined(_DEBUG) || defined(DEBUG)) && !defined(NDEBUG) @@ -179,18 +180,7 @@ static int s_cxEditFrame = 0; static int s_cyEditFrame = 0; static bool s_bUndoRedoScroll = false; -// Middle-click auto-scroll state -static bool s_bAutoScrollMode = false; -static bool s_bAutoScrollHeld = false; // true while MMB is physically held down -static ULONGLONG s_dwAutoScrollStartTick = 0; // GetTickCount64() at MMB down -static POINT s_ptAutoScrollOrigin = { 0, 0 }; -static POINT s_ptAutoScrollMouse = { 0, 0 }; -static double s_dAutoScrollAccumY = 0.0; - -#define AUTOSCROLL_TIMER_MS 30 // ~33 fps -#define AUTOSCROLL_DEADZONE 15 // pixels from origin before scrolling starts -#define AUTOSCROLL_DIVISOR 60.0 // higher = slower (lines per pixel per tick scaling) -#define AUTOSCROLL_CLICK_THRESHOLD_MS 200 // ms: below = click (toggle), above = hold (release-to-stop) +// Auto-scroll state moved to Notepad3Util.c // for tiny expression calculation static double s_dExpression = 0.0; @@ -208,7 +198,7 @@ const char* const _assert_msg = "Broken UndoRedo-Transaction!"; // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // (!) ENSURE IDT_FILE_NEW -> IDT_VIEW_NEW_WINDOW corresponds to order of Toolbar.bmp -#define NUMTOOLBITMAPS (31) +// NUMTOOLBITMAPS defined in Notepad3Util.h // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ static TBBUTTON s_tbbMainWnd[] = { @@ -2376,96 +2366,7 @@ LRESULT CALLBACK MainWndProc(HWND hwnd, UINT umsg, WPARAM wParam, LPARAM lParam) } -//============================================================================= -// -// _SetWrapStartIndent() -// -static void _SetWrapStartIndent() -{ - int i = 0; - switch (Settings.WordWrapIndent) { - case 1: - i = 1; - break; - case 2: - i = 2; - break; - case 3: - i = (Globals.fvCurFile.iIndentWidth) ? 1 * Globals.fvCurFile.iIndentWidth : 1 * Globals.fvCurFile.iTabWidth; - break; - case 4: - i = (Globals.fvCurFile.iIndentWidth) ? 2 * Globals.fvCurFile.iIndentWidth : 2 * Globals.fvCurFile.iTabWidth; - break; - default: - break; - } - SciCall_SetWrapStartIndent(i); -} - - -//============================================================================= -// -// _SetWrapIndentMode() -// -static void _SetWrapIndentMode() -{ - BeginWaitCursorUID(Flags.bHugeFileLoadState, IDS_MUI_SB_WRAP_LINES); - - Sci_SetWrapModeEx(GET_WRAP_MODE()); - - if (Settings.WordWrapIndent == 5) { - SciCall_SetWrapIndentMode(SC_WRAPINDENT_SAME); - } else if (Settings.WordWrapIndent == 6) { - SciCall_SetWrapIndentMode(SC_WRAPINDENT_INDENT); - } else if (Settings.WordWrapIndent == 7) { - SciCall_SetWrapIndentMode(SC_WRAPINDENT_DEEPINDENT); - } else { - _SetWrapStartIndent(); - SciCall_SetWrapIndentMode(SC_WRAPINDENT_FIXED); - } - - EndWaitCursor(); -} - - -//============================================================================= -// -// _SetWrapVisualFlags() -// -static void _SetWrapVisualFlags(HWND hwndEditCtrl) -{ - UNREFERENCED_PARAMETER(hwndEditCtrl); - - if (Settings.ShowWordWrapSymbols) { - int wrapVisualFlags = 0; - int wrapVisualFlagsLocation = 0; - if (Settings.WordWrapSymbols == 0) { - Settings.WordWrapSymbols = 22; - } - switch (Settings.WordWrapSymbols % 10) { - case 1: - wrapVisualFlags |= SC_WRAPVISUALFLAG_END; - wrapVisualFlagsLocation |= SC_WRAPVISUALFLAGLOC_END_BY_TEXT; - break; - case 2: - wrapVisualFlags |= SC_WRAPVISUALFLAG_END; - break; - } - switch (((Settings.WordWrapSymbols % 100) - (Settings.WordWrapSymbols % 10)) / 10) { - case 1: - wrapVisualFlags |= SC_WRAPVISUALFLAG_START; - wrapVisualFlagsLocation |= SC_WRAPVISUALFLAGLOC_START_BY_TEXT; - break; - case 2: - wrapVisualFlags |= SC_WRAPVISUALFLAG_START; - break; - } - SciCall_SetWrapVisualFlags(wrapVisualFlags); - SciCall_SetWrapVisualFlagsLocation(wrapVisualFlagsLocation); - } else { - SciCall_SetWrapVisualFlags(0); - } -} +// _SetWrapStartIndent, _SetWrapIndentMode, _SetWrapVisualFlags moved to Notepad3Util.c //============================================================================= @@ -2473,57 +2374,7 @@ static void _SetWrapVisualFlags(HWND hwndEditCtrl) // Auto-Scroll (middle-click continuous scroll, Firefox-style) // -static void _AutoScrollStop(HWND hwndEdit) -{ - if (s_bAutoScrollMode) { - KillTimer(hwndEdit, ID_AUTOSCROLLTIMER); - ReleaseCapture(); - SciCall_SetCursor(SC_CURSORNORMAL); - s_bAutoScrollMode = false; - s_bAutoScrollHeld = false; - s_dAutoScrollAccumY = 0.0; - } -} - -static void CALLBACK _AutoScrollTimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime) -{ - UNREFERENCED_PARAMETER(uMsg); - UNREFERENCED_PARAMETER(idEvent); - UNREFERENCED_PARAMETER(dwTime); - - if (!s_bAutoScrollMode) { - KillTimer(hwnd, ID_AUTOSCROLLTIMER); - return; - } - - int const deltaY = s_ptAutoScrollMouse.y - s_ptAutoScrollOrigin.y; - - if (abs(deltaY) <= AUTOSCROLL_DEADZONE) { - s_dAutoScrollAccumY = 0.0; - return; - } - - // Speed: proportional to distance beyond dead zone - double const speed = (double)(deltaY - (deltaY > 0 ? AUTOSCROLL_DEADZONE : -AUTOSCROLL_DEADZONE)) / AUTOSCROLL_DIVISOR; - s_dAutoScrollAccumY += speed; - - DocLn const linesToScroll = (DocLn)s_dAutoScrollAccumY; - if (linesToScroll != 0) { - SciCall_LineScroll(0, linesToScroll); - s_dAutoScrollAccumY -= (double)linesToScroll; - } -} - -static void _AutoScrollStart(HWND hwndEdit, POINT pt) -{ - s_bAutoScrollMode = true; - s_ptAutoScrollOrigin = pt; - s_ptAutoScrollMouse = pt; - s_dAutoScrollAccumY = 0.0; - SetCapture(hwndEdit); - SetCursor(LoadCursor(NULL, IDC_SIZEALL)); // immediately show four-way arrow - SetTimer(hwndEdit, ID_AUTOSCROLLTIMER, AUTOSCROLL_TIMER_MS, _AutoScrollTimerProc); -} +// _AutoScrollStop, _AutoScrollTimerProc, _AutoScrollStart moved to Notepad3Util.c static LRESULT CALLBACK _EditSubclassProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, @@ -2534,31 +2385,30 @@ static LRESULT CALLBACK _EditSubclassProc( switch (uMsg) { case WM_MBUTTONDOWN: { - if (s_bAutoScrollMode) { - _AutoScrollStop(hwnd); + if (NP3Util_IsAutoScrollMode()) { + NP3Util_AutoScrollStop(hwnd); } else { POINT const pt = POINTFromLParam(lParam); DocPos const pos = SciCall_CharPositionFromPointClose(pt.x, pt.y); if ((pos >= 0) && SciCall_IndicatorValueAt(INDIC_NP3_HYPERLINK, pos)) { HandleHotSpotURLClicked(pos, OPEN_WITH_BROWSER); } else { - s_bAutoScrollHeld = true; - s_dwAutoScrollStartTick = GetTickCount64(); - _AutoScrollStart(hwnd, pt); + NP3Util_SetAutoScrollHeld(true); + NP3Util_AutoScrollStart(hwnd, pt); } } return 0; } case WM_MBUTTONUP: { - if (s_bAutoScrollMode && s_bAutoScrollHeld) { - ULONGLONG const elapsed = GetTickCount64() - s_dwAutoScrollStartTick; + if (NP3Util_IsAutoScrollMode() && NP3Util_IsAutoScrollHeld()) { + ULONGLONG const elapsed = GetTickCount64() - NP3Util_GetAutoScrollStartTick(); if (elapsed > AUTOSCROLL_CLICK_THRESHOLD_MS) { // Hold-to-scroll: stop on release - _AutoScrollStop(hwnd); + NP3Util_AutoScrollStop(hwnd); } else { // Quick click: keep scrolling (toggle mode) - s_bAutoScrollHeld = false; + NP3Util_SetAutoScrollHeld(false); } return 0; } @@ -2566,8 +2416,8 @@ static LRESULT CALLBACK _EditSubclassProc( } case WM_MOUSEMOVE: - if (s_bAutoScrollMode) { - s_ptAutoScrollMouse = POINTFromLParam(lParam); + if (NP3Util_IsAutoScrollMode()) { + NP3Util_AutoScrollUpdateMouse(POINTFromLParam(lParam)); SetCursor(LoadCursor(NULL, IDC_SIZEALL)); // maintain four-way arrow return 0; } @@ -2575,32 +2425,29 @@ static LRESULT CALLBACK _EditSubclassProc( case WM_LBUTTONDOWN: case WM_RBUTTONDOWN: - if (s_bAutoScrollMode) { - _AutoScrollStop(hwnd); + if (NP3Util_IsAutoScrollMode()) { + NP3Util_AutoScrollStop(hwnd); return 0; } break; case WM_MOUSEWHEEL: - if (s_bAutoScrollMode) { - _AutoScrollStop(hwnd); + if (NP3Util_IsAutoScrollMode()) { + NP3Util_AutoScrollStop(hwnd); return 0; } break; case WM_SETCURSOR: - if (s_bAutoScrollMode) { + if (NP3Util_IsAutoScrollMode()) { SetCursor(LoadCursor(NULL, IDC_SIZEALL)); return TRUE; } break; case WM_CAPTURECHANGED: - if (s_bAutoScrollMode && ((HWND)lParam != hwnd)) { - s_bAutoScrollMode = false; - KillTimer(hwnd, ID_AUTOSCROLLTIMER); - SciCall_SetCursor(SC_CURSORNORMAL); - s_dAutoScrollAccumY = 0.0; + if (NP3Util_IsAutoScrollMode() && ((HWND)lParam != hwnd)) { + NP3Util_AutoScrollStop(hwnd); } break; @@ -2765,8 +2612,8 @@ static void _InitializeSciEditCtrl(HWND hwndEditCtrl) Style_SetIndentGuides(hwndEditCtrl, Settings.ShowIndentGuides); // Word Wrap - _SetWrapIndentMode(hwndEditCtrl); - _SetWrapVisualFlags(hwndEditCtrl); + NP3Util_SetWrapIndentMode(); + NP3Util_SetWrapVisualFlags(hwndEditCtrl); // Long Lines if (Settings.MarkLongLines) { @@ -3087,119 +2934,7 @@ bool SelectExternalToolBar(HWND hwnd) } -//============================================================================= -// -// LoadBitmapFile() -// -static HBITMAP LoadBitmapFile(const HPATHL hpath) -{ - HBITMAP hbmp = NULL; - - if (Path_IsExistingFile(hpath)) { - - hbmp = (HBITMAP)LoadImage(NULL, Path_Get(hpath), IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION | LR_LOADFROMFILE); - - bool bDimOK = false; - int height = 16; - if (hbmp) { - BITMAP bmp = { 0 }; - GetObject(hbmp, sizeof(BITMAP), &bmp); - height = bmp.bmHeight; - bDimOK = (bmp.bmWidth >= (height * NUMTOOLBITMAPS)); - } - if (!bDimOK) { - InfoBoxLng(MB_ICONWARNING, L"NotSuitableToolbarDim", IDS_MUI_ERR_BITMAP, Path_Get(hpath), - (height * NUMTOOLBITMAPS), height, NUMTOOLBITMAPS); - } - } - else { - WCHAR displayName[80]; - Path_GetDisplayName(displayName, 80, hpath, L"", false); - InfoBoxLng(MB_ICONWARNING, NULL, IDS_MUI_ERR_LOADFILE, displayName); - } - - return hbmp; -} - - -//============================================================================= -// -// CreateScaledImageListFromBitmap() -// -static HIMAGELIST XXX_CreateScaledImageListFromBitmap(HWND hWnd, HBITMAP hBmp) -{ - BITMAP bmp = { 0 }; - GetObject(hBmp, sizeof(BITMAP), &bmp); - - int const mod = bmp.bmWidth % NUMTOOLBITMAPS; - int const cx = (bmp.bmWidth - mod) / NUMTOOLBITMAPS; - int const cy = bmp.bmHeight; - - HIMAGELIST himl = ImageList_Create(cx, cy, ILC_COLOR32 | ILC_MASK, NUMTOOLBITMAPS, NUMTOOLBITMAPS); - ImageList_AddMasked(himl, hBmp, CLR_DEFAULT); - - UINT const dpi = Scintilla_GetWindowDPI(hWnd); - if (!Settings.DpiScaleToolBar || (dpi == USER_DEFAULT_SCREEN_DPI)) { - return himl; // default DPI, we are done - } - - // Scale button icons/images - int const scx = ScaleIntToDPI(hWnd, cx); - int const scy = ScaleIntToDPI(hWnd, cy); - - HIMAGELIST hsciml = ImageList_Create(scx, scy, ILC_COLOR32 | ILC_MASK | ILC_HIGHQUALITYSCALE, NUMTOOLBITMAPS, NUMTOOLBITMAPS); - - for (int i = 0; i < NUMTOOLBITMAPS; ++i) { - HICON const hicon = ImageList_GetIcon(himl, i, ILD_TRANSPARENT | ILD_PRESERVEALPHA | ILD_SCALE); - ImageList_AddIcon(hsciml, hicon); - DestroyIcon(hicon); - } - - ImageList_Destroy(himl); - - return hsciml; -} - - -//============================================================================= -// -// CreateScaledImageListFromBitmap() -// -static HIMAGELIST CreateScaledImageListFromBitmap(HWND hWnd, HBITMAP hBmp) -{ - BITMAP bmp = { 0 }; - GetObject(hBmp, sizeof(BITMAP), &bmp); - - int const numOfToolBitmaps = (int)(bmp.bmWidth / bmp.bmHeight); - - int const mod = bmp.bmWidth % numOfToolBitmaps; - int const cx = (bmp.bmWidth - mod) / numOfToolBitmaps; - int const cy = bmp.bmHeight; - - HIMAGELIST himl = ImageList_Create(cx, cy, ILC_COLOR32 | ILC_MASK, numOfToolBitmaps, numOfToolBitmaps); - ImageList_AddMasked(himl, hBmp, CLR_DEFAULT); - - UINT const dpi = Scintilla_GetWindowDPI(hWnd); - if (!Settings.DpiScaleToolBar || (dpi == USER_DEFAULT_SCREEN_DPI)) { - return himl; // default DPI, we are done - } - - // Scale button icons/images - int const scx = ScaleIntToDPI(hWnd, cx); - int const scy = ScaleIntToDPI(hWnd, cy); - - HIMAGELIST hsciml = ImageList_Create(scx, scy, ILC_COLOR32 | ILC_MASK | ILC_HIGHQUALITYSCALE, numOfToolBitmaps, numOfToolBitmaps); - - for (int i = 0; i < numOfToolBitmaps; ++i) { - HICON const hicon = ImageList_GetIcon(himl, i, ILD_TRANSPARENT | ILD_PRESERVEALPHA | ILD_SCALE); - ImageList_AddIcon(hsciml, hicon); - DestroyIcon(hicon); - } - - ImageList_Destroy(himl); - - return hsciml; -} +// LoadBitmapFile, XXX_CreateScaledImageListFromBitmap, CreateScaledImageListFromBitmap moved to Notepad3Util.c //==== Toolbar Style ========================================================== @@ -3278,7 +3013,7 @@ void CreateBars(HWND hwnd, HINSTANCE hInstance) if ((Settings.ToolBarTheme == 2) && Path_IsNotEmpty(g_tchToolbarBitmap)) { HPATHL hfile_pth = Path_Copy(g_tchToolbarBitmap); Path_AbsoluteFromApp(hfile_pth, true); - hbmp = LoadBitmapFile(hfile_pth); + hbmp = NP3Util_LoadBitmapFile(hfile_pth); if (!hbmp) { Path_Reset(g_tchToolbarBitmap, L""); IniSectionDelete(L"Toolbar Images", L"BitmapDefault", false); @@ -3296,7 +3031,7 @@ void CreateBars(HWND hwnd, HINSTANCE hInstance) hbmpCopy = CopyImage(hbmp, IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION); - HIMAGELIST himl = CreateScaledImageListFromBitmap(hwnd, hbmp); + HIMAGELIST himl = NP3Util_CreateScaledImageListFromBitmap(hwnd, hbmp); DeleteObject(hbmp); hbmp = NULL; @@ -3310,7 +3045,7 @@ void CreateBars(HWND hwnd, HINSTANCE hInstance) if ((Settings.ToolBarTheme == 2) && Path_IsNotEmpty(g_tchToolbarBitmapHot)) { HPATHL hfile_pth = Path_Copy(g_tchToolbarBitmapHot); Path_AbsoluteFromApp(hfile_pth, true); - hbmp = Path_IsExistingFile(hfile_pth) ? LoadBitmapFile(hfile_pth) : NULL; + hbmp = Path_IsExistingFile(hfile_pth) ? NP3Util_LoadBitmapFile(hfile_pth) : NULL; if (!hbmp) { Path_Reset(g_tchToolbarBitmapHot, L""); IniSectionDelete(L"Toolbar Images", L"BitmapHot", false); @@ -3323,7 +3058,7 @@ void CreateBars(HWND hwnd, HINSTANCE hInstance) hbmp = LoadImage(hInstance, toolBarIntRes, IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION); } if (hbmp) { - himl = CreateScaledImageListFromBitmap(hwnd, hbmp); + himl = NP3Util_CreateScaledImageListFromBitmap(hwnd, hbmp); DeleteObject(hbmp); hbmp = NULL; SendMessage(Globals.hwndToolbar, TB_SETHOTIMAGELIST, 0, (LPARAM)himl); @@ -3339,7 +3074,7 @@ void CreateBars(HWND hwnd, HINSTANCE hInstance) if ((Settings.ToolBarTheme == 2) && Path_IsNotEmpty(g_tchToolbarBitmapDisabled)) { HPATHL hfile_pth = Path_Copy(g_tchToolbarBitmapDisabled); Path_AbsoluteFromApp(hfile_pth, true); - hbmp = Path_IsExistingFile(hfile_pth) ? LoadBitmapFile(hfile_pth) : NULL; + hbmp = Path_IsExistingFile(hfile_pth) ? NP3Util_LoadBitmapFile(hfile_pth) : NULL; if (!hbmp) { Path_Reset(g_tchToolbarBitmapDisabled, L""); IniSectionDelete(L"Toolbar Images", L"BitmapDisabled", false); @@ -3351,7 +3086,7 @@ void CreateBars(HWND hwnd, HINSTANCE hInstance) hbmp = LoadImage(hInstance, toolBarIntRes, IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION); } if (hbmp) { - himl = CreateScaledImageListFromBitmap(hwnd, hbmp); + himl = NP3Util_CreateScaledImageListFromBitmap(hwnd, hbmp); DeleteObject(hbmp); hbmp = NULL; SendMessage(Globals.hwndToolbar, TB_SETDISABLEDIMAGELIST, 0, (LPARAM)himl); @@ -3366,7 +3101,7 @@ void CreateBars(HWND hwnd, HINSTANCE hInstance) bProcessed = BitmapGrayScale(hbmpCopy); } if (bProcessed) { - himl = CreateScaledImageListFromBitmap(hwnd, hbmpCopy); + himl = NP3Util_CreateScaledImageListFromBitmap(hwnd, hbmpCopy); SendMessage(Globals.hwndToolbar, TB_SETDISABLEDIMAGELIST, 0, (LPARAM)himl); } } @@ -4184,6 +3919,29 @@ LRESULT MsgContextMenu(HWND hwnd, UINT umsg, WPARAM wParam, LPARAM lParam) return (imenu != MNU_NONE) ? !0 : 0; } +// --------------------------------------------------------------------------- +// Find the 0-based position of a POPUP submenu within hParent that contains +// childCmdId as a direct child MENUITEM. Returns -1 if not found. +// Replaces fragile hardcoded position numbers for POPUP items (which have +// no command ID in MENU resource format). +// --------------------------------------------------------------------------- +static int GetSubMenuPosByChildCmd(HMENU hParent, UINT childCmdId) +{ + int const count = GetMenuItemCount(hParent); + for (int i = 0; i < count; ++i) { + HMENU const hSub = GetSubMenu(hParent, i); + if (hSub) { + int const subCount = GetMenuItemCount(hSub); + for (int j = 0; j < subCount; ++j) { + if (GetMenuItemID(hSub, j) == childCmdId) { + return i; + } + } + } + } + return -1; +} + //============================================================================= // // MsgTrayMessage() - Handles WM_TRAYMESSAGE @@ -4196,7 +3954,7 @@ LRESULT MsgTrayMessage(HWND hwnd, WPARAM wParam, LPARAM lParam) switch (lParam) { case WM_RBUTTONUP: { HMENU hTrayMenu = LoadMenu(Globals.hLngResContainer, MAKEINTRESOURCE(IDR_MUI_POPUPMENU)); - HMENU hMenuPopup = GetSubMenu(hTrayMenu, 3); + HMENU hMenuPopup = GetSubMenu(hTrayMenu, GetSubMenuPosByChildCmd(hTrayMenu, IDM_TRAY_RESTORE)); SetForegroundWindow(hwnd); @@ -4557,7 +4315,8 @@ LRESULT MsgInitMenu(HWND hwnd, WPARAM wParam, LPARAM lParam) CheckCmd(hmenu, IDM_VIEW_HYPERLINKHOTSPOTS, Settings.HyperlinkHotspot); - int const _SUB_MNU_2 = 8; // menu:View -> base for parent of sub-menus (adj. offset accordingly) + // Resolve View menu by known child command (immune to top-level menu reordering) + HMENU const hViewMenu = GetSubMenu(hmenu, GetSubMenuPosByChildCmd(hmenu, IDM_VIEW_WORDWRAP)); int const chState = SciCall_GetChangeHistory(); assert(chState == Settings.ChangeHistoryMode); @@ -4565,18 +4324,18 @@ LRESULT MsgInitMenu(HWND hwnd, WPARAM wParam, LPARAM lParam) i += (chState & SC_CHANGE_HISTORY_MARKERS) ? 1 : 0; i += (chState & SC_CHANGE_HISTORY_INDICATORS) ? 2 : 0; CheckMenuRadioItem(hmenu, IDM_VIEW_CHGHIST_NONE, IDM_VIEW_CHGHIST_ALL, i, MF_BYCOMMAND); - CheckCmdPos(GetSubMenu(hmenu, 2), _SUB_MNU_2 + 0, (i != IDM_VIEW_CHGHIST_NONE)); + CheckCmdPos(hViewMenu, GetSubMenuPosByChildCmd(hViewMenu, IDM_VIEW_CHGHIST_NONE), (i != IDM_VIEW_CHGHIST_NONE)); i = IDM_VIEW_COLORDEFHOTSPOTS + Settings.ColorDefHotspot; CheckMenuRadioItem(hmenu, IDM_VIEW_COLORDEFHOTSPOTS, IDM_VIEW_COLOR_BGRA, i, MF_BYCOMMAND); - CheckCmdPos(GetSubMenu(hmenu, 2), _SUB_MNU_2 + 4, IsColorDefHotspotEnabled()); + CheckCmdPos(hViewMenu, GetSubMenuPosByChildCmd(hViewMenu, IDM_VIEW_COLORDEFHOTSPOTS), IsColorDefHotspotEnabled()); CheckCmd(hmenu, IDM_VIEW_UNICODE_POINTS, Settings.HighlightUnicodePoints); CheckCmd(hmenu, IDM_VIEW_MATCHBRACES, Settings.MatchBraces); i = IDM_VIEW_HILITCURLN_NONE + Settings.HighlightCurrentLine; CheckMenuRadioItem(hmenu, IDM_VIEW_HILITCURLN_NONE, IDM_VIEW_HILITCURLN_FRAME, i, MF_BYCOMMAND); - CheckCmdPos(GetSubMenu(hmenu, 2), _SUB_MNU_2 + 7, (i != IDM_VIEW_HILITCURLN_NONE)); + CheckCmdPos(hViewMenu, GetSubMenuPosByChildCmd(hViewMenu, IDM_VIEW_HILITCURLN_NONE), (i != IDM_VIEW_HILITCURLN_NONE)); #ifdef D_NP3_WIN10_DARK_MODE EnableCmd(hmenu, IDM_VIEW_WIN_DARK_MODE, IsDarkModeSupported()); @@ -4587,9 +4346,9 @@ LRESULT MsgInitMenu(HWND hwnd, WPARAM wParam, LPARAM lParam) // -------------------------------------------------------------------------- - int const mnuMain = 2; - int const mnuSubOcc = _SUB_MNU_2 + 8; - int const mnuSubSubWord = 6; + int const posOcc = GetSubMenuPosByChildCmd(hViewMenu, IDM_VIEW_MARKOCCUR_ONOFF); + HMENU const hOccMenu = (posOcc >= 0) ? GetSubMenu(hViewMenu, posOcc) : NULL; + int const posWholeWord = hOccMenu ? GetSubMenuPosByChildCmd(hOccMenu, IDM_VIEW_MARKOCCUR_WNONE) : -1; if (Settings.MarkOccurrencesMatchWholeWords) { i = IDM_VIEW_MARKOCCUR_WORD; @@ -4599,7 +4358,7 @@ LRESULT MsgInitMenu(HWND hwnd, WPARAM wParam, LPARAM lParam) i = IDM_VIEW_MARKOCCUR_WNONE; } CheckMenuRadioItem(hmenu, IDM_VIEW_MARKOCCUR_WNONE, IDM_VIEW_MARKOCCUR_CURRENT, i, MF_BYCOMMAND); - CheckCmdPos(GetSubMenu(GetSubMenu(hmenu, mnuMain), mnuSubOcc), mnuSubSubWord, (i != IDM_VIEW_MARKOCCUR_WNONE)); + if (posWholeWord >= 0) { CheckCmdPos(hOccMenu, posWholeWord, (i != IDM_VIEW_MARKOCCUR_WNONE)); } i = IsMarkOccurrencesEnabled(); EnableCmd(hmenu, IDM_VIEW_MARKOCCUR_VISIBLE, i); @@ -4607,8 +4366,8 @@ LRESULT MsgInitMenu(HWND hwnd, WPARAM wParam, LPARAM lParam) EnableCmd(hmenu, IDM_VIEW_MARKOCCUR_WNONE, i); EnableCmd(hmenu, IDM_VIEW_MARKOCCUR_WORD, i); EnableCmd(hmenu, IDM_VIEW_MARKOCCUR_CURRENT, i); - EnableCmdPos(GetSubMenu(GetSubMenu(hmenu, mnuMain), mnuSubOcc), mnuSubSubWord, i); - CheckCmdPos(GetSubMenu(hmenu, mnuMain), mnuSubOcc, i); + if (posWholeWord >= 0) { EnableCmdPos(hOccMenu, posWholeWord, i); } + if (posOcc >= 0) { CheckCmdPos(hViewMenu, posOcc, i); } // -------------------------------------------------------------------------- @@ -4623,7 +4382,7 @@ LRESULT MsgInitMenu(HWND hwnd, WPARAM wParam, LPARAM lParam) bool const bF = (SC_FOLDLEVELBASE < (SciCall_GetFoldLevel(iCurLine) & SC_FOLDLEVELNUMBERMASK)); bool const bH = (SciCall_GetFoldLevel(iCurLine) & SC_FOLDLEVELHEADERFLAG); EnableCmd(hmenu, IDM_VIEW_TOGGLE_CURRENT_FOLD, !te && fd && (bF || bH)); - CheckCmdPos(GetSubMenu(hmenu, 2), _SUB_MNU_2 + 14, fd); + CheckCmdPos(hViewMenu, GetSubMenuPosByChildCmd(hViewMenu, IDM_VIEW_FOLDING), fd); // -------------------------------------------------------------------------- @@ -4791,66 +4550,18 @@ static void _ApplyChangeHistoryMode() } + //============================================================================= // -// MsgCommand() - Handles WM_COMMAND +// _HandleFileCommands() - Handler for MsgCommand() // -LRESULT MsgCommand(HWND hwnd, UINT umsg, WPARAM wParam, LPARAM lParam) +static bool _HandleFileCommands(HWND hwnd, UINT umsg, WPARAM wParam, LPARAM lParam) { + UNREFERENCED_PARAMETER(umsg); + UNREFERENCED_PARAMETER(lParam); unsigned const iLoWParam = (unsigned)LOWORD(wParam); - #if defined(HAVE_DYN_LOAD_LIBS_MUI_LNGS) - bool const bIsLngMenuCmd = ((iLoWParam >= IDS_MUI_LANG_EN_US) && (iLoWParam < (IDS_MUI_LANG_EN_US + MuiLanguages_CountOf()))); - if (bIsLngMenuCmd) { - DynamicLanguageMenuCmd(iLoWParam); - Style_InsertThemesMenu(Globals.hMainMenu); - DrawMenuBar(Globals.hwndMain); - UpdateToolbar(); - return FALSE; - } - #endif - - bool const bIsThemesMenuCmd = ((iLoWParam >= IDM_THEMES_FACTORY_RESET) && (iLoWParam < (int)(IDM_THEMES_FACTORY_RESET + ThemeItems_CountOf()))); - if (bIsThemesMenuCmd) { - if (iLoWParam == IDM_THEMES_FACTORY_RESET) { - if (!IsYesOkay(InfoBoxLng(MB_OKCANCEL | MB_ICONWARNING, L"MsgResetScheme", IDS_MUI_WARN_STYLE_RESET))) { - return FALSE; - } - } - Style_DynamicThemesMenuCmd(iLoWParam); - return FALSE; - } - - switch(iLoWParam) { - - case SCEN_CHANGE: - EditUpdateVisibleIndicators(); - MarkAllOccurrences(-1, false); - break; - - case IDT_TIMER_UPDATE_STATUSBAR: - _UpdateStatusbarDelayed((bool)lParam); - break; - - case IDT_TIMER_UPDATE_TOOLBAR: - _UpdateToolbarDelayed(); - break; - - case IDT_TIMER_UPDATE_TITLEBAR: - _UpdateTitlebarDelayed((HWND)lParam); - break; - - case IDT_TIMER_CALLBACK_MRKALL: - EditMarkAllOccurrences(Globals.hwndEdit, (bool)lParam); - break; - - case IDT_TIMER_CLEAR_CALLTIP: - Sci_CallTipCancelEx(); - break; - - case IDT_TIMER_UNDO_TRANSACTION: - _SplitUndoTransaction(); - break; + switch (iLoWParam) { case IDM_FILE_NEW: { HPATHL hfile_pth = Path_Allocate(L""); @@ -5161,6 +4872,26 @@ LRESULT MsgCommand(HWND hwnd, UINT umsg, WPARAM wParam, LPARAM lParam) break; + + default: + return false; + } + return true; +} + + +//============================================================================= +// +// _HandleEncodingCommands() - Handler for MsgCommand() +// +static bool _HandleEncodingCommands(HWND hwnd, UINT umsg, WPARAM wParam, LPARAM lParam) +{ + UNREFERENCED_PARAMETER(umsg); + UNREFERENCED_PARAMETER(lParam); + unsigned const iLoWParam = (unsigned)LOWORD(wParam); + + switch (iLoWParam) { + case IDM_ENCODING_ANSI: case IDM_ENCODING_UNICODE: case IDM_ENCODING_UNICODEREV: @@ -5243,6 +4974,27 @@ LRESULT MsgCommand(HWND hwnd, UINT umsg, WPARAM wParam, LPARAM lParam) break; + + default: + return false; + } + return true; +} + + +//============================================================================= +// +// _HandleEditBasicCommands() - Handler for MsgCommand() +// +static bool _HandleEditBasicCommands(HWND hwnd, UINT umsg, WPARAM wParam, LPARAM lParam) +{ + UNREFERENCED_PARAMETER(hwnd); + UNREFERENCED_PARAMETER(umsg); + UNREFERENCED_PARAMETER(lParam); + unsigned const iLoWParam = (unsigned)LOWORD(wParam); + + switch (iLoWParam) { + case IDM_EDIT_UNDO: if (SciCall_CanUndo()) { //LimitNotifyEvents(EVM_UndoRedo); @@ -5509,6 +5261,26 @@ LRESULT MsgCommand(HWND hwnd, UINT umsg, WPARAM wParam, LPARAM lParam) SciCall_EditToggleOverType(); break; + + default: + return false; + } + return true; +} + + +//============================================================================= +// +// _HandleEditLineManipulation() - Handler for MsgCommand() +// +static bool _HandleEditLineManipulation(HWND hwnd, UINT umsg, WPARAM wParam, LPARAM lParam) +{ + UNREFERENCED_PARAMETER(umsg); + UNREFERENCED_PARAMETER(lParam); + unsigned const iLoWParam = (unsigned)LOWORD(wParam); + + switch (iLoWParam) { + case IDM_EDIT_ENCLOSESELECTION: { static ENCLOSESELDATA data = { 0 }; if (EditEncloseSelectionDlg(hwnd, &data)) { @@ -5801,6 +5573,26 @@ LRESULT MsgCommand(HWND hwnd, UINT umsg, WPARAM wParam, LPARAM lParam) break; + + default: + return false; + } + return true; +} + + +//============================================================================= +// +// _HandleEditTextTransform() - Handler for MsgCommand() +// +static bool _HandleEditTextTransform(HWND hwnd, UINT umsg, WPARAM wParam, LPARAM lParam) +{ + UNREFERENCED_PARAMETER(umsg); + UNREFERENCED_PARAMETER(lParam); + unsigned const iLoWParam = (unsigned)LOWORD(wParam); + + switch (iLoWParam) { + case IDM_EDIT_LINECOMMENT: case IDM_EDIT_LINECOMMENT_ADD: case IDM_EDIT_LINECOMMENT_REMOVE: { @@ -5917,6 +5709,26 @@ LRESULT MsgCommand(HWND hwnd, UINT umsg, WPARAM wParam, LPARAM lParam) break; + + default: + return false; + } + return true; +} + + +//============================================================================= +// +// _HandleEditFind() - Handler for MsgCommand() +// +static bool _HandleEditFind(HWND hwnd, UINT umsg, WPARAM wParam, LPARAM lParam) +{ + UNREFERENCED_PARAMETER(umsg); + UNREFERENCED_PARAMETER(lParam); + unsigned const iLoWParam = (unsigned)LOWORD(wParam); + + switch (iLoWParam) { + case IDM_EDIT_FINDMATCHINGBRACE: EditFindMatchingBrace(Globals.hwndEdit); break; @@ -6110,6 +5922,26 @@ LRESULT MsgCommand(HWND hwnd, UINT umsg, WPARAM wParam, LPARAM lParam) break; + + default: + return false; + } + return true; +} + + +//============================================================================= +// +// _HandleViewAndSettingsCommands() - Handler for MsgCommand() +// +static bool _HandleViewAndSettingsCommands(HWND hwnd, UINT umsg, WPARAM wParam, LPARAM lParam) +{ + UNREFERENCED_PARAMETER(umsg); + UNREFERENCED_PARAMETER(lParam); + unsigned const iLoWParam = (unsigned)LOWORD(wParam); + + switch (iLoWParam) { + case IDM_VIEW_SCHEME: Style_SelectLexerDlg(Globals.hwndEdit); break; @@ -6148,7 +5980,7 @@ LRESULT MsgCommand(HWND hwnd, UINT umsg, WPARAM wParam, LPARAM lParam) case IDM_VIEW_WORDWRAP: Globals.fvCurFile.bWordWrap = Settings.WordWrap = !Settings.WordWrap; - _SetWrapIndentMode(Globals.hwndEdit); + NP3Util_SetWrapIndentMode(); SciCall_ScrollVertical(Sci_GetCurrentLineNumber(), 0); UpdateToolbar(); break; @@ -6156,8 +5988,8 @@ LRESULT MsgCommand(HWND hwnd, UINT umsg, WPARAM wParam, LPARAM lParam) case IDM_SET_WORDWRAPSETTINGS: if (WordWrapSettingsDlg(hwnd,IDD_MUI_WORDWRAP, &Settings.WordWrapIndent)) { - _SetWrapIndentMode(Globals.hwndEdit); - _SetWrapVisualFlags(Globals.hwndEdit); + NP3Util_SetWrapIndentMode(); + NP3Util_SetWrapVisualFlags(Globals.hwndEdit); UpdateToolbar(); } break; @@ -6165,7 +5997,7 @@ LRESULT MsgCommand(HWND hwnd, UINT umsg, WPARAM wParam, LPARAM lParam) case IDM_VIEW_WORDWRAPSYMBOLS: Settings.ShowWordWrapSymbols = !Settings.ShowWordWrapSymbols; - _SetWrapVisualFlags(Globals.hwndEdit); + NP3Util_SetWrapVisualFlags(Globals.hwndEdit); UpdateToolbar(); break; @@ -6241,7 +6073,7 @@ LRESULT MsgCommand(HWND hwnd, UINT umsg, WPARAM wParam, LPARAM lParam) SciCall_SetTabWidth(Globals.fvCurFile.iTabWidth); SciCall_SetIndent(Globals.fvCurFile.iIndentWidth); if (SciCall_GetWrapIndentMode() == SC_WRAPINDENT_FIXED) { - _SetWrapStartIndent(Globals.hwndEdit); + NP3Util_SetWrapStartIndent(); } } break; @@ -6888,6 +6720,26 @@ LRESULT MsgCommand(HWND hwnd, UINT umsg, WPARAM wParam, LPARAM lParam) break; + + default: + return false; + } + return true; +} + + +//============================================================================= +// +// _HandleHelpCommands() - Handler for MsgCommand() +// +static bool _HandleHelpCommands(HWND hwnd, UINT umsg, WPARAM wParam, LPARAM lParam) +{ + UNREFERENCED_PARAMETER(umsg); + UNREFERENCED_PARAMETER(lParam); + unsigned const iLoWParam = (unsigned)LOWORD(wParam); + + switch (iLoWParam) { + case IDM_HELP_ONLINEDOCUMENTATION: ShellExecute(0, 0, ONLINE_HELP_WEBSITE, 0, 0, SW_SHOW); break; @@ -6913,6 +6765,26 @@ LRESULT MsgCommand(HWND hwnd, UINT umsg, WPARAM wParam, LPARAM lParam) break; + + default: + return false; + } + return true; +} + + +//============================================================================= +// +// _HandleCmdCommands() - Handler for MsgCommand() +// +static bool _HandleCmdCommands(HWND hwnd, UINT umsg, WPARAM wParam, LPARAM lParam) +{ + UNREFERENCED_PARAMETER(umsg); + UNREFERENCED_PARAMETER(lParam); + unsigned const iLoWParam = (unsigned)LOWORD(wParam); + + switch (iLoWParam) { + case CMD_ESCAPE: { DocLn const vis1stLine = SciCall_GetFirstVisibleLine(); @@ -7507,290 +7379,170 @@ LRESULT MsgCommand(HWND hwnd, UINT umsg, WPARAM wParam, LPARAM lParam) break; - case IDT_FILE_NEW: - if (IsCmdEnabled(hwnd,IDM_FILE_NEW)) { - SendWMCommand(hwnd, IDM_FILE_NEW); - } else { - SimpleBeep(); - } - break; + + default: + return false; + } + return true; +} - case IDT_VIEW_NEW_WINDOW: - if (IsCmdEnabled(hwnd, IDM_FILE_NEWWINDOW)) { - SendWMCommand(hwnd, IDM_FILE_NEWWINDOW); - } else { - SimpleBeep(); - } - break; - - - case IDT_FILE_OPEN: - if (IsCmdEnabled(hwnd,IDM_FILE_OPEN)) { - SendWMCommand(hwnd, IDM_FILE_OPEN); - } else { - SimpleBeep(); - } - break; - - - case IDT_FILE_BROWSE: - if (IsCmdEnabled(hwnd,IDM_FILE_BROWSE)) { - SendWMCommand(hwnd, IDM_FILE_BROWSE); - } else { - SimpleBeep(); - } - break; - - - case IDT_FILE_RECENT: - if (IsCmdEnabled(hwnd,IDM_FILE_RECENT)) { - SendWMCommand(hwnd, IDM_FILE_RECENT); - } else { - SimpleBeep(); - } - break; - - case IDT_FILE_SAVE: - if (IsCmdEnabled(hwnd,IDM_FILE_SAVE)) { - SendWMCommand(hwnd, IDM_FILE_SAVE); - } else { - SimpleBeep(); - } - break; - - - case IDT_EDIT_UNDO: - if (IsCmdEnabled(hwnd,IDM_EDIT_UNDO)) { - SendWMCommand(hwnd, IDM_EDIT_UNDO); - } else { - SimpleBeep(); - } - break; - - - case IDT_EDIT_REDO: - if (IsCmdEnabled(hwnd,IDM_EDIT_REDO)) { - SendWMCommand(hwnd, IDM_EDIT_REDO); - } else { - SimpleBeep(); - } - break; - - - case IDT_EDIT_CUT: - if (IsCmdEnabled(hwnd,IDM_EDIT_CUT)) { - SendWMCommand(hwnd, IDM_EDIT_CUT); - //~SendWMCommand(hwnd, IDM_EDIT_CUTLINE); - } else { - SimpleBeep(); - } - break; +//============================================================================= +// +// _HandleToolbarCommands() - Handler for MsgCommand() +// +static const struct { unsigned idt; unsigned idm; } s_ToolbarDispatch[] = { + { IDT_FILE_NEW, IDM_FILE_NEW }, + { IDT_VIEW_NEW_WINDOW, IDM_FILE_NEWWINDOW }, + { IDT_FILE_OPEN, IDM_FILE_OPEN }, + { IDT_FILE_BROWSE, IDM_FILE_BROWSE }, + { IDT_FILE_RECENT, IDM_FILE_RECENT }, + { IDT_FILE_SAVE, IDM_FILE_SAVE }, + { IDT_EDIT_UNDO, IDM_EDIT_UNDO }, + { IDT_EDIT_REDO, IDM_EDIT_REDO }, + { IDT_EDIT_CUT, IDM_EDIT_CUT }, + { IDT_EDIT_PASTE, IDM_EDIT_PASTE }, + { IDT_EDIT_FIND, IDM_EDIT_FIND }, + { IDT_EDIT_REPLACE, IDM_EDIT_REPLACE }, + { IDT_GREP_WIN_TOOL, IDM_GREP_WIN_SEARCH }, + { IDT_VIEW_WORDWRAP, IDM_VIEW_WORDWRAP }, + { IDT_VIEW_ZOOMIN, IDM_VIEW_ZOOMIN }, + { IDT_VIEW_RESETZOOM, IDM_VIEW_RESETZOOM }, + { IDT_VIEW_ZOOMOUT, IDM_VIEW_ZOOMOUT }, + { IDT_VIEW_CHASING_DOCTAIL, IDM_VIEW_CHASING_DOCTAIL }, + { IDT_VIEW_SCHEME, IDM_VIEW_SCHEME }, + { IDT_VIEW_SCHEMECONFIG, IDM_VIEW_SCHEMECONFIG }, + { IDT_FILE_SAVEAS, IDM_FILE_SAVEAS }, + { IDT_FILE_SAVECOPY, IDM_FILE_SAVECOPY }, + { IDT_FILE_PRINT, IDM_FILE_PRINT }, + { IDT_FILE_OPENFAV, IDM_FILE_OPENFAV }, + { IDT_FILE_ADDTOFAV, IDM_FILE_ADDTOFAV }, + { IDT_VIEW_TOGGLEFOLDS, IDM_VIEW_TOGGLEFOLDS }, + { IDT_VIEW_TOGGLE_VIEW, IDM_VIEW_TOGGLE_VIEW }, + { IDT_VIEW_PIN_ON_TOP, IDM_SET_ALWAYSONTOP }, + { IDT_FILE_LAUNCH, IDM_FILE_LAUNCH }, +}; +static bool _HandleToolbarCommands(HWND hwnd, UINT umsg, WPARAM wParam, LPARAM lParam) +{ + UNREFERENCED_PARAMETER(umsg); + UNREFERENCED_PARAMETER(lParam); + unsigned const iLoWParam = (unsigned)LOWORD(wParam); + switch (iLoWParam) { case IDT_EDIT_COPY: if (IsCmdEnabled(hwnd,IDM_EDIT_COPY)) { SendWMCommand(hwnd, IDM_EDIT_COPY); } else { SendWMCommand(hwnd, IDM_EDIT_COPYALL); } - break; - - - case IDT_EDIT_PASTE: - if (IsCmdEnabled(hwnd,IDM_EDIT_PASTE)) { - SendWMCommand(hwnd, IDM_EDIT_PASTE); - } else { - SimpleBeep(); - } - break; - - - case IDT_EDIT_FIND: - if (IsCmdEnabled(hwnd,IDM_EDIT_FIND)) { - SendWMCommand(hwnd, IDM_EDIT_FIND); - } else { - SimpleBeep(); - } - break; - - - case IDT_EDIT_REPLACE: - if (IsCmdEnabled(hwnd,IDM_EDIT_REPLACE)) { - SendWMCommand(hwnd, IDM_EDIT_REPLACE); - } else { - SimpleBeep(); - } - break; - - - case IDT_GREP_WIN_TOOL: - if (IsCmdEnabled(hwnd, IDM_GREP_WIN_SEARCH)) { - SendWMCommand(hwnd, IDM_GREP_WIN_SEARCH); - } else { - SimpleBeep(); - } - break; - - - case IDT_VIEW_WORDWRAP: - if (IsCmdEnabled(hwnd,IDM_VIEW_WORDWRAP)) { - SendWMCommand(hwnd, IDM_VIEW_WORDWRAP); - } else { - SimpleBeep(); - } - break; - - - case IDT_VIEW_ZOOMIN: - if (IsCmdEnabled(hwnd,IDM_VIEW_ZOOMIN)) { - SendWMCommand(hwnd, IDM_VIEW_ZOOMIN); - } else { - SimpleBeep(); - } - break; - - case IDT_VIEW_RESETZOOM: - if (IsCmdEnabled(hwnd, IDM_VIEW_RESETZOOM)) { - SendWMCommand(hwnd, IDM_VIEW_RESETZOOM); - } else { - SimpleBeep(); - } - break; - - - case IDT_VIEW_ZOOMOUT: - if (IsCmdEnabled(hwnd,IDM_VIEW_ZOOMOUT)) { - SendWMCommand(hwnd, IDM_VIEW_ZOOMOUT); - } else { - SimpleBeep(); - } - break; - - - case IDT_VIEW_CHASING_DOCTAIL: - if (IsCmdEnabled(hwnd, IDM_VIEW_CHASING_DOCTAIL)) { - SendWMCommand(hwnd, IDM_VIEW_CHASING_DOCTAIL); - } else { - SimpleBeep(); - } - break; - - - case IDT_VIEW_SCHEME: - if (IsCmdEnabled(hwnd,IDM_VIEW_SCHEME)) { - SendWMCommand(hwnd, IDM_VIEW_SCHEME); - } else { - SimpleBeep(); - } - break; - - - case IDT_VIEW_SCHEMECONFIG: - if (IsCmdEnabled(hwnd,IDM_VIEW_SCHEMECONFIG)) { - SendWMCommand(hwnd, IDM_VIEW_SCHEMECONFIG); - } else { - SimpleBeep(); - } - break; - - - case IDT_FILE_SAVEAS: - if (IsCmdEnabled(hwnd,IDM_FILE_SAVEAS)) { - SendWMCommand(hwnd, IDM_FILE_SAVEAS); - } else { - SimpleBeep(); - } - break; - - - case IDT_FILE_SAVECOPY: - if (IsCmdEnabled(hwnd,IDM_FILE_SAVECOPY)) { - SendWMCommand(hwnd, IDM_FILE_SAVECOPY); - } else { - SimpleBeep(); - } - break; - - + return true; case IDT_EDIT_CLEAR: if (IsCmdEnabled(hwnd,IDM_EDIT_CLEAR)) { SendWMCommand(hwnd, IDM_EDIT_CLEAR); } else { SciCall_ClearAll(); } + return true; + default: break; + } - - case IDT_FILE_PRINT: - if (IsCmdEnabled(hwnd,IDM_FILE_PRINT)) { - SendWMCommand(hwnd, IDM_FILE_PRINT); - } else { - SimpleBeep(); + for (int i = 0; i < (int)COUNTOF(s_ToolbarDispatch); ++i) { + if (s_ToolbarDispatch[i].idt == iLoWParam) { + if (IsCmdEnabled(hwnd, s_ToolbarDispatch[i].idm)) { + SendWMCommand(hwnd, s_ToolbarDispatch[i].idm); + } else { + SimpleBeep(); + } + return true; } - break; + } + return false; +} - case IDT_FILE_OPENFAV: - if (IsCmdEnabled(hwnd,IDM_FILE_OPENFAV)) { - SendWMCommand(hwnd, IDM_FILE_OPENFAV); - } else { - SimpleBeep(); +//============================================================================= +// +// MsgCommand() - Handles WM_COMMAND +// +LRESULT MsgCommand(HWND hwnd, UINT umsg, WPARAM wParam, LPARAM lParam) +{ + unsigned const iLoWParam = (unsigned)LOWORD(wParam); + + #if defined(HAVE_DYN_LOAD_LIBS_MUI_LNGS) + bool const bIsLngMenuCmd = ((iLoWParam >= IDS_MUI_LANG_EN_US) && (iLoWParam < (IDS_MUI_LANG_EN_US + MuiLanguages_CountOf()))); + if (bIsLngMenuCmd) { + DynamicLanguageMenuCmd(iLoWParam); + Style_InsertThemesMenu(Globals.hMainMenu); + DrawMenuBar(Globals.hwndMain); + UpdateToolbar(); + return FALSE; + } + #endif + + bool const bIsThemesMenuCmd = ((iLoWParam >= IDM_THEMES_FACTORY_RESET) && (iLoWParam < (int)(IDM_THEMES_FACTORY_RESET + ThemeItems_CountOf()))); + if (bIsThemesMenuCmd) { + if (iLoWParam == IDM_THEMES_FACTORY_RESET) { + if (!IsYesOkay(InfoBoxLng(MB_OKCANCEL | MB_ICONWARNING, L"MsgResetScheme", IDS_MUI_WARN_STYLE_RESET))) { + return FALSE; + } } - break; + Style_DynamicThemesMenuCmd(iLoWParam); + return FALSE; + } + switch(iLoWParam) { - case IDT_FILE_ADDTOFAV: - if (IsCmdEnabled(hwnd,IDM_FILE_ADDTOFAV)) { - SendWMCommand(hwnd, IDM_FILE_ADDTOFAV); - } else { - SimpleBeep(); - } - break; + case SCEN_CHANGE: + EditUpdateVisibleIndicators(); + MarkAllOccurrences(-1, false); + return FALSE; + case IDT_TIMER_UPDATE_STATUSBAR: + _UpdateStatusbarDelayed((bool)lParam); + return FALSE; - case IDT_VIEW_TOGGLEFOLDS: - if (IsCmdEnabled(hwnd,IDM_VIEW_TOGGLEFOLDS)) { - SendWMCommand(hwnd, IDM_VIEW_TOGGLEFOLDS); - } else { - SimpleBeep(); - } - break; + case IDT_TIMER_UPDATE_TOOLBAR: + _UpdateToolbarDelayed(); + return FALSE; + case IDT_TIMER_UPDATE_TITLEBAR: + _UpdateTitlebarDelayed((HWND)lParam); + return FALSE; - case IDT_VIEW_TOGGLE_VIEW: - if (IsCmdEnabled(hwnd,IDM_VIEW_TOGGLE_VIEW)) { - SendWMCommand(hwnd, IDM_VIEW_TOGGLE_VIEW); - } else { - SimpleBeep(); - } - break; + case IDT_TIMER_CALLBACK_MRKALL: + EditMarkAllOccurrences(Globals.hwndEdit, (bool)lParam); + return FALSE; + case IDT_TIMER_CLEAR_CALLTIP: + Sci_CallTipCancelEx(); + return FALSE; - case IDT_VIEW_PIN_ON_TOP: - if (IsCmdEnabled(hwnd, IDM_SET_ALWAYSONTOP)) { - SendWMCommand(hwnd, IDM_SET_ALWAYSONTOP); - } else { - SimpleBeep(); - } - break; - - - case IDT_FILE_LAUNCH: - if (IsCmdEnabled(hwnd,IDM_FILE_LAUNCH)) { - SendWMCommand(hwnd, IDM_FILE_LAUNCH); - } else { - SimpleBeep(); - } - break; + case IDT_TIMER_UNDO_TRANSACTION: + _SplitUndoTransaction(); + return FALSE; default: - return DefWindowProc(hwnd, umsg, wParam, lParam); + break; } - return FALSE; + + if (_HandleFileCommands(hwnd, umsg, wParam, lParam)) { return FALSE; } + if (_HandleEncodingCommands(hwnd, umsg, wParam, lParam)) { return FALSE; } + if (_HandleEditBasicCommands(hwnd, umsg, wParam, lParam)) { return FALSE; } + if (_HandleEditLineManipulation(hwnd, umsg, wParam, lParam)) { return FALSE; } + if (_HandleEditTextTransform(hwnd, umsg, wParam, lParam)) { return FALSE; } + if (_HandleEditFind(hwnd, umsg, wParam, lParam)) { return FALSE; } + if (_HandleViewAndSettingsCommands(hwnd, umsg, wParam, lParam)) { return FALSE; } + if (_HandleHelpCommands(hwnd, umsg, wParam, lParam)) { return FALSE; } + if (_HandleCmdCommands(hwnd, umsg, wParam, lParam)) { return FALSE; } + if (_HandleToolbarCommands(hwnd, umsg, wParam, lParam)) { return FALSE; } + + return DefWindowProc(hwnd, umsg, wParam, lParam); } + //============================================================================= // // MsgSysCommand() - Handles WM_SYSCOMMAND diff --git a/src/Notepad3.vcxproj b/src/Notepad3.vcxproj index ce22703de..c525af4b2 100644 --- a/src/Notepad3.vcxproj +++ b/src/Notepad3.vcxproj @@ -1077,6 +1077,7 @@ + @@ -1180,6 +1181,7 @@ + diff --git a/src/Notepad3.vcxproj.filters b/src/Notepad3.vcxproj.filters index 187446579..55a5453c0 100644 --- a/src/Notepad3.vcxproj.filters +++ b/src/Notepad3.vcxproj.filters @@ -72,6 +72,9 @@ Source Files + + Source Files + Source Files @@ -470,6 +473,9 @@ Header Files + + Header Files + Resource Files diff --git a/src/Notepad3Util.c b/src/Notepad3Util.c new file mode 100644 index 000000000..f7c226dcf --- /dev/null +++ b/src/Notepad3Util.c @@ -0,0 +1,349 @@ +// encoding: UTF-8 +/****************************************************************************** +* * +* * +* Notepad3 * +* * +* Notepad3Util.c * +* Utility functions extracted from Notepad3.c * +* Based on code from Notepad2, (c) Florian Balmer 1996-2011 * +* * +* (c) Rizonesoft 2008-2026 * +* https://rizonesoft.com * +* * +* * +*******************************************************************************/ + +#include "Helpers.h" + +#include +#include +#include + +#include "PathLib.h" +#include "Dialogs.h" +#include "Encoding.h" +#include "MuiLanguage.h" +#include "Notepad3.h" +#include "Notepad3Util.h" + + +// ============================================================================ +// --- Bitmap / Image Loading --- +// ============================================================================ + +//============================================================================= +// +// NP3Util_LoadBitmapFile() +// +HBITMAP NP3Util_LoadBitmapFile(const HPATHL hpath) +{ + HBITMAP hbmp = NULL; + + if (Path_IsExistingFile(hpath)) { + + hbmp = (HBITMAP)LoadImage(NULL, Path_Get(hpath), IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION | LR_LOADFROMFILE); + + bool bDimOK = false; + int height = 16; + if (hbmp) { + BITMAP bmp = { 0 }; + GetObject(hbmp, sizeof(BITMAP), &bmp); + height = bmp.bmHeight; + bDimOK = (bmp.bmWidth >= (height * NUMTOOLBITMAPS)); + } + if (!bDimOK) { + InfoBoxLng(MB_ICONWARNING, L"NotSuitableToolbarDim", IDS_MUI_ERR_BITMAP, Path_Get(hpath), + (height * NUMTOOLBITMAPS), height, NUMTOOLBITMAPS); + } + } + else { + WCHAR displayName[80]; + Path_GetDisplayName(displayName, 80, hpath, L"", false); + InfoBoxLng(MB_ICONWARNING, NULL, IDS_MUI_ERR_LOADFILE, displayName); + } + + return hbmp; +} + + +//============================================================================= +// +// NP3Util_XXX_CreateScaledImageListFromBitmap() +// +HIMAGELIST NP3Util_XXX_CreateScaledImageListFromBitmap(HWND hWnd, HBITMAP hBmp) +{ + BITMAP bmp = { 0 }; + GetObject(hBmp, sizeof(BITMAP), &bmp); + + int const mod = bmp.bmWidth % NUMTOOLBITMAPS; + int const cx = (bmp.bmWidth - mod) / NUMTOOLBITMAPS; + int const cy = bmp.bmHeight; + + HIMAGELIST himl = ImageList_Create(cx, cy, ILC_COLOR32 | ILC_MASK, NUMTOOLBITMAPS, NUMTOOLBITMAPS); + ImageList_AddMasked(himl, hBmp, CLR_DEFAULT); + + UINT const dpi = Scintilla_GetWindowDPI(hWnd); + if (!Settings.DpiScaleToolBar || (dpi == USER_DEFAULT_SCREEN_DPI)) { + return himl; // default DPI, we are done + } + + // Scale button icons/images + int const scx = ScaleIntToDPI(hWnd, cx); + int const scy = ScaleIntToDPI(hWnd, cy); + + HIMAGELIST hsciml = ImageList_Create(scx, scy, ILC_COLOR32 | ILC_MASK | ILC_HIGHQUALITYSCALE, NUMTOOLBITMAPS, NUMTOOLBITMAPS); + + for (int i = 0; i < NUMTOOLBITMAPS; ++i) { + HICON const hicon = ImageList_GetIcon(himl, i, ILD_TRANSPARENT | ILD_PRESERVEALPHA | ILD_SCALE); + ImageList_AddIcon(hsciml, hicon); + DestroyIcon(hicon); + } + + ImageList_Destroy(himl); + + return hsciml; +} + + +//============================================================================= +// +// NP3Util_CreateScaledImageListFromBitmap() +// +HIMAGELIST NP3Util_CreateScaledImageListFromBitmap(HWND hWnd, HBITMAP hBmp) +{ + BITMAP bmp = { 0 }; + GetObject(hBmp, sizeof(BITMAP), &bmp); + + int const numOfToolBitmaps = (int)(bmp.bmWidth / bmp.bmHeight); + + int const mod = bmp.bmWidth % numOfToolBitmaps; + int const cx = (bmp.bmWidth - mod) / numOfToolBitmaps; + int const cy = bmp.bmHeight; + + HIMAGELIST himl = ImageList_Create(cx, cy, ILC_COLOR32 | ILC_MASK, numOfToolBitmaps, numOfToolBitmaps); + ImageList_AddMasked(himl, hBmp, CLR_DEFAULT); + + UINT const dpi = Scintilla_GetWindowDPI(hWnd); + if (!Settings.DpiScaleToolBar || (dpi == USER_DEFAULT_SCREEN_DPI)) { + return himl; // default DPI, we are done + } + + // Scale button icons/images + int const scx = ScaleIntToDPI(hWnd, cx); + int const scy = ScaleIntToDPI(hWnd, cy); + + HIMAGELIST hsciml = ImageList_Create(scx, scy, ILC_COLOR32 | ILC_MASK | ILC_HIGHQUALITYSCALE, numOfToolBitmaps, numOfToolBitmaps); + + for (int i = 0; i < numOfToolBitmaps; ++i) { + HICON const hicon = ImageList_GetIcon(himl, i, ILD_TRANSPARENT | ILD_PRESERVEALPHA | ILD_SCALE); + ImageList_AddIcon(hsciml, hicon); + DestroyIcon(hicon); + } + + ImageList_Destroy(himl); + + return hsciml; +} + + +// ============================================================================ +// --- Word-Wrap Configuration --- +// ============================================================================ + +//============================================================================= +// +// NP3Util_SetWrapStartIndent() +// +void NP3Util_SetWrapStartIndent(void) +{ + int i = 0; + switch (Settings.WordWrapIndent) { + case 1: + i = 1; + break; + case 2: + i = 2; + break; + case 3: + i = (Globals.fvCurFile.iIndentWidth) ? 1 * Globals.fvCurFile.iIndentWidth : 1 * Globals.fvCurFile.iTabWidth; + break; + case 4: + i = (Globals.fvCurFile.iIndentWidth) ? 2 * Globals.fvCurFile.iIndentWidth : 2 * Globals.fvCurFile.iTabWidth; + break; + default: + break; + } + SciCall_SetWrapStartIndent(i); +} + + +//============================================================================= +// +// NP3Util_SetWrapIndentMode() +// +void NP3Util_SetWrapIndentMode(void) +{ + BeginWaitCursorUID(Flags.bHugeFileLoadState, IDS_MUI_SB_WRAP_LINES); + + Sci_SetWrapModeEx(GET_WRAP_MODE()); + + if (Settings.WordWrapIndent == 5) { + SciCall_SetWrapIndentMode(SC_WRAPINDENT_SAME); + } else if (Settings.WordWrapIndent == 6) { + SciCall_SetWrapIndentMode(SC_WRAPINDENT_INDENT); + } else if (Settings.WordWrapIndent == 7) { + SciCall_SetWrapIndentMode(SC_WRAPINDENT_DEEPINDENT); + } else { + NP3Util_SetWrapStartIndent(); + SciCall_SetWrapIndentMode(SC_WRAPINDENT_FIXED); + } + + EndWaitCursor(); +} + + +//============================================================================= +// +// NP3Util_SetWrapVisualFlags() +// +void NP3Util_SetWrapVisualFlags(HWND hwndEditCtrl) +{ + UNREFERENCED_PARAMETER(hwndEditCtrl); + + if (Settings.ShowWordWrapSymbols) { + int wrapVisualFlags = 0; + int wrapVisualFlagsLocation = 0; + if (Settings.WordWrapSymbols == 0) { + Settings.WordWrapSymbols = 22; + } + switch (Settings.WordWrapSymbols % 10) { + case 1: + wrapVisualFlags |= SC_WRAPVISUALFLAG_END; + wrapVisualFlagsLocation |= SC_WRAPVISUALFLAGLOC_END_BY_TEXT; + break; + case 2: + wrapVisualFlags |= SC_WRAPVISUALFLAG_END; + break; + } + switch (((Settings.WordWrapSymbols % 100) - (Settings.WordWrapSymbols % 10)) / 10) { + case 1: + wrapVisualFlags |= SC_WRAPVISUALFLAG_START; + wrapVisualFlagsLocation |= SC_WRAPVISUALFLAGLOC_START_BY_TEXT; + break; + case 2: + wrapVisualFlags |= SC_WRAPVISUALFLAG_START; + break; + } + SciCall_SetWrapVisualFlags(wrapVisualFlags); + SciCall_SetWrapVisualFlagsLocation(wrapVisualFlagsLocation); + } else { + SciCall_SetWrapVisualFlags(0); + } +} + + +// ============================================================================ +// --- Auto-Scroll (middle-click continuous scroll, Firefox-style) --- +// ============================================================================ + +static bool s_bAutoScrollMode = false; +static bool s_bAutoScrollHeld = false; +static ULONGLONG s_dwAutoScrollStartTick = 0; +static POINT s_ptAutoScrollOrigin = { 0, 0 }; +static POINT s_ptAutoScrollMouse = { 0, 0 }; +static double s_dAutoScrollAccumY = 0.0; + +bool NP3Util_IsAutoScrollMode(void) +{ + return s_bAutoScrollMode; +} + +bool NP3Util_IsAutoScrollHeld(void) +{ + return s_bAutoScrollHeld; +} + +ULONGLONG NP3Util_GetAutoScrollStartTick(void) +{ + return s_dwAutoScrollStartTick; +} + +void NP3Util_SetAutoScrollHeld(bool held) +{ + s_bAutoScrollHeld = held; +} + +void NP3Util_AutoScrollUpdateMouse(POINT pt) +{ + s_ptAutoScrollMouse = pt; +} + + +//============================================================================= +// +// NP3Util_AutoScrollStop() +// +void NP3Util_AutoScrollStop(HWND hwndEdit) +{ + if (s_bAutoScrollMode) { + KillTimer(hwndEdit, ID_AUTOSCROLLTIMER); + ReleaseCapture(); + SciCall_SetCursor(SC_CURSORNORMAL); + s_bAutoScrollMode = false; + s_bAutoScrollHeld = false; + s_dAutoScrollAccumY = 0.0; + } +} + + +//============================================================================= +// +// NP3Util_AutoScrollTimerProc() +// +void CALLBACK NP3Util_AutoScrollTimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime) +{ + UNREFERENCED_PARAMETER(uMsg); + UNREFERENCED_PARAMETER(idEvent); + UNREFERENCED_PARAMETER(dwTime); + + if (!s_bAutoScrollMode) { + KillTimer(hwnd, ID_AUTOSCROLLTIMER); + return; + } + + int const deltaY = s_ptAutoScrollMouse.y - s_ptAutoScrollOrigin.y; + + if (abs(deltaY) <= AUTOSCROLL_DEADZONE) { + s_dAutoScrollAccumY = 0.0; + return; + } + + // Speed: proportional to distance beyond dead zone + double const speed = (double)(deltaY - (deltaY > 0 ? AUTOSCROLL_DEADZONE : -AUTOSCROLL_DEADZONE)) / AUTOSCROLL_DIVISOR; + s_dAutoScrollAccumY += speed; + + DocLn const linesToScroll = (DocLn)s_dAutoScrollAccumY; + if (linesToScroll != 0) { + SciCall_LineScroll(0, linesToScroll); + s_dAutoScrollAccumY -= (double)linesToScroll; + } +} + + +//============================================================================= +// +// NP3Util_AutoScrollStart() +// +void NP3Util_AutoScrollStart(HWND hwndEdit, POINT pt) +{ + s_bAutoScrollMode = true; + s_bAutoScrollHeld = false; + s_dwAutoScrollStartTick = GetTickCount64(); + s_ptAutoScrollOrigin = pt; + s_ptAutoScrollMouse = pt; + s_dAutoScrollAccumY = 0.0; + SetCapture(hwndEdit); + SetCursor(LoadCursor(NULL, IDC_SIZEALL)); + SetTimer(hwndEdit, ID_AUTOSCROLLTIMER, AUTOSCROLL_TIMER_MS, NP3Util_AutoScrollTimerProc); +} diff --git a/src/Notepad3Util.h b/src/Notepad3Util.h new file mode 100644 index 000000000..4003fc613 --- /dev/null +++ b/src/Notepad3Util.h @@ -0,0 +1,50 @@ +// encoding: UTF-8 +/****************************************************************************** +* * +* * +* Notepad3 * +* * +* Notepad3Util.h * +* Utility functions extracted from Notepad3.c * +* Based on code from Notepad2, (c) Florian Balmer 1996-2011 * +* * +* (c) Rizonesoft 2008-2026 * +* https://rizonesoft.com * +* * +* * +*******************************************************************************/ +#pragma once +#ifndef _NP3_NOTEPAD3UTIL_H_ +#define _NP3_NOTEPAD3UTIL_H_ + +#include "TypeDefs.h" +#include "SciCall.h" + +// --- Bitmap / Image Loading --- +#define NUMTOOLBITMAPS (31) +HBITMAP NP3Util_LoadBitmapFile(const HPATHL hpath); +HIMAGELIST NP3Util_CreateScaledImageListFromBitmap(HWND hWnd, HBITMAP hBmp); +HIMAGELIST NP3Util_XXX_CreateScaledImageListFromBitmap(HWND hWnd, HBITMAP hBmp); + +// --- Word-Wrap Configuration --- +void NP3Util_SetWrapStartIndent(void); +void NP3Util_SetWrapIndentMode(void); +void NP3Util_SetWrapVisualFlags(HWND hwndEditCtrl); + +// --- Auto-Scroll (middle-click continuous scroll) --- +#define AUTOSCROLL_TIMER_MS 30 +#define AUTOSCROLL_DEADZONE 15 +#define AUTOSCROLL_DIVISOR 60.0 +#define AUTOSCROLL_CLICK_THRESHOLD_MS 200 + +bool NP3Util_IsAutoScrollMode(void); +void NP3Util_AutoScrollStop(HWND hwndEdit); +void NP3Util_AutoScrollStart(HWND hwndEdit, POINT pt); +void NP3Util_AutoScrollUpdateMouse(POINT pt); +bool NP3Util_IsAutoScrollHeld(void); +ULONGLONG NP3Util_GetAutoScrollStartTick(void); +void NP3Util_SetAutoScrollHeld(bool held); + +void CALLBACK NP3Util_AutoScrollTimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime); + +#endif // _NP3_NOTEPAD3UTIL_H_ diff --git a/src/Styles.c b/src/Styles.c index 40b4a4713..faa2bbbd8 100644 --- a/src/Styles.c +++ b/src/Styles.c @@ -555,7 +555,6 @@ bool Style_InsertThemesMenu(HMENU hMenuBar) WCHAR wchMenuItemStrg[128] = { L'\0' }; GetLngString(IDS_MUI_MENU_THEMES, wchMenuItemStrg, COUNTOF(wchMenuItemStrg)); - //bool const res = InsertMenu(hMenuBar, pos, MF_BYPOSITION | MF_POPUP | MF_STRING, (UINT_PTR)s_hmenuThemes, wchMenuItemStrg); bool const res = InsertMenu(hMenuBar, IDM_VIEW_SCHEMECONFIG, MF_BYCOMMAND | MF_POPUP | MF_STRING, (UINT_PTR)s_hmenuThemes, wchMenuItemStrg); unsigned const iTheme = Globals.uCurrentThemeIndex;