From b7011cdf9151ee8d6ae43f058928c8859b085a27 Mon Sep 17 00:00:00 2001 From: Maciej Zagozda Date: Sun, 12 Apr 2026 17:58:38 +0200 Subject: [PATCH] Initial version --- README.md | 201 +- src/{salamdr1.cpp => app_entry.cpp} | 575 +- src/app_globals.cpp | 597 ++ src/async_copy.cpp | 6215 ++++++++++++ src/cfgdlg.h | 20 +- src/dialogs.h | 10 +- src/{dialogs3.cpp => dialogs_attributes.cpp} | 20 +- ...gse.cpp => dialogs_config_environment.cpp} | 0 ...ialogs4.cpp => dialogs_config_general.cpp} | 56 +- ...ialogsp.cpp => dialogs_config_packing.cpp} | 0 ...dialogs6.cpp => dialogs_config_panels.cpp} | 0 ...ialogs5.cpp => dialogs_config_viewers.cpp} | 0 src/{dialogs.cpp => dialogs_file_ops.cpp} | 0 src/{dialogs2.cpp => dialogs_rename.cpp} | 0 src/drivelst.cpp | 4 +- src/{salamdr6.cpp => file_enumeration.cpp} | 20 +- src/filesbox.h | 32 +- src/{filesbx2.cpp => filesbox_input.cpp} | 24 +- src/{filesbx1.cpp => filesbox_rendering.cpp} | 62 +- ...fileswn7.cpp => fileswindow_archiving.cpp} | 4 +- src/{fileswn9.cpp => fileswindow_columns.cpp} | 4 +- src/{fileswn8.cpp => fileswindow_delete.cpp} | 0 ...leswna.cpp => fileswindow_dir_reading.cpp} | 28 +- src/{fileswn4.cpp => fileswindow_display.cpp} | 4 +- src/{fileswn2.cpp => fileswindow_execute.cpp} | 0 ...eswn5.cpp => fileswindow_file_actions.cpp} | 14 +- src/{fileswn1.cpp => fileswindow_init.cpp} | 0 ...ileswn3.cpp => fileswindow_navigation.cpp} | 0 ...ileswn6.cpp => fileswindow_operations.cpp} | 0 ...leswn0.cpp => fileswindow_quicksearch.cpp} | 6 +- src/{fileswnb.cpp => fileswindow_wndproc.cpp} | 8 +- src/fileswnd.h | 52 +- src/{finddlg1.cpp => find_dialog_ui.cpp} | 1296 +-- src/find_results.cpp | 1226 +++ src/gui.cpp | 3996 +------- src/gui_bitmap.h | 43 + src/gui_controls.cpp | 2608 +++++ src/gui_progressbar.cpp | 396 + src/gui_statictext.cpp | 987 ++ src/mainwnd.h | 16 +- src/mainwnd3.cpp | 7157 -------------- src/mainwnd4.cpp | 2075 ---- src/mainwnd_commands.cpp | 4636 +++++++++ src/{mainwnd2.cpp => mainwnd_config.cpp} | 0 src/mainwnd_help.cpp | 282 + src/{mainwnd1.cpp => mainwnd_init.cpp} | 4 +- src/mainwnd_messages.cpp | 3639 +++++++ src/{mainwnd5.cpp => mainwnd_panels.cpp} | 0 src/mainwnd_shutdown.cpp | 834 ++ src/{menu2.cpp => menu_popup.cpp} | 0 src/{menu3.cpp => menu_shared_resources.cpp} | 0 src/{menu4.cpp => menu_templates.cpp} | 0 src/{menu1.cpp => menu_window_queue.cpp} | 0 src/operations_core.cpp | 1606 ++++ src/{salamdr5.cpp => path_checking.cpp} | 0 src/{salamdr3.cpp => path_utils.cpp} | 10 +- .../automation/generated/salamander_p.c | 1173 +-- src/plugins/demoplug/demoplug.h | 10 +- src/plugins/demoplug/fs1.cpp | 8 +- src/plugins/ftp/fs1.cpp | 8 +- src/plugins/ftp/ftp.h | 10 +- src/plugins/pak/dll/pak_dll.cpp | 10 +- src/plugins/regedt/fs.cpp | 12 +- src/plugins/regedt/regedt.h | 10 +- src/plugins/undelete/library/miscstr.cpp | 10 +- src/plugins/undelete/undelete.cpp | 12 +- src/plugins/undelete/undelete.h | 10 +- src/plugins/wmobile/fs1.cpp | 8 +- src/plugins/wmobile/wmobile.h | 10 +- src/{plugins3.cpp => plugins_archiver.cpp} | 0 src/{plugins4.cpp => plugins_filesystem.cpp} | 0 src/{plugins2.cpp => plugins_interface.cpp} | 0 src/{plugins1.cpp => plugins_loading.cpp} | 0 src/salmon/salmon.cpp | 12 +- src/{salamdr7.cpp => shell_environment.cpp} | 0 src/{salamdr2.cpp => string_resources.cpp} | 12 +- src/stswnd.cpp | 112 +- src/stswnd.h | 8 +- src/{toolbar4.cpp => toolbar_button_defs.cpp} | 0 src/{toolbar1.cpp => toolbar_core.cpp} | 0 src/{toolbar3.cpp => toolbar_dnd.cpp} | 0 src/{toolbar6.cpp => toolbar_drivebar.cpp} | 0 src/{toolbar7.cpp => toolbar_hotpaths.cpp} | 0 src/{toolbar8.cpp => toolbar_pluginsbar.cpp} | 0 src/{toolbar2.cpp => toolbar_rendering.cpp} | 0 src/{toolbar5.cpp => toolbar_usermenu.cpp} | 0 src/transfer_speed.cpp | 448 + src/translator/translator.cpp | 10 +- src/{salamdr4.cpp => truncated_string.cpp} | 0 src/vcxproj/build_check.err | 0 src/vcxproj/build_check.wrn | 0 src/vcxproj/salamand.vcxproj | 132 +- src/worker.cpp | 8373 +---------------- src/worker.h | 131 + src/zip.cpp | 6505 +------------ src/zip_directory.cpp | 1316 +++ src/zip_general_api.cpp | 3020 ++++++ src/zip_progress.cpp | 439 + src/zip_utilities.cpp | 1850 ++++ 99 files changed, 31073 insertions(+), 31353 deletions(-) rename src/{salamdr1.cpp => app_entry.cpp} (88%) create mode 100644 src/app_globals.cpp create mode 100644 src/async_copy.cpp rename src/{dialogs3.cpp => dialogs_attributes.cpp} (99%) rename src/{dialogse.cpp => dialogs_config_environment.cpp} (100%) rename src/{dialogs4.cpp => dialogs_config_general.cpp} (99%) rename src/{dialogsp.cpp => dialogs_config_packing.cpp} (100%) rename src/{dialogs6.cpp => dialogs_config_panels.cpp} (100%) rename src/{dialogs5.cpp => dialogs_config_viewers.cpp} (100%) rename src/{dialogs.cpp => dialogs_file_ops.cpp} (100%) rename src/{dialogs2.cpp => dialogs_rename.cpp} (100%) rename src/{salamdr6.cpp => file_enumeration.cpp} (99%) rename src/{filesbx2.cpp => filesbox_input.cpp} (97%) rename src/{filesbx1.cpp => filesbox_rendering.cpp} (97%) rename src/{fileswn7.cpp => fileswindow_archiving.cpp} (99%) rename src/{fileswn9.cpp => fileswindow_columns.cpp} (99%) rename src/{fileswn8.cpp => fileswindow_delete.cpp} (100%) rename src/{fileswna.cpp => fileswindow_dir_reading.cpp} (98%) rename src/{fileswn4.cpp => fileswindow_display.cpp} (99%) rename src/{fileswn2.cpp => fileswindow_execute.cpp} (100%) rename src/{fileswn5.cpp => fileswindow_file_actions.cpp} (99%) rename src/{fileswn1.cpp => fileswindow_init.cpp} (100%) rename src/{fileswn3.cpp => fileswindow_navigation.cpp} (100%) rename src/{fileswn6.cpp => fileswindow_operations.cpp} (100%) rename src/{fileswn0.cpp => fileswindow_quicksearch.cpp} (99%) rename src/{fileswnb.cpp => fileswindow_wndproc.cpp} (99%) rename src/{finddlg1.cpp => find_dialog_ui.cpp} (75%) create mode 100644 src/find_results.cpp create mode 100644 src/gui_bitmap.h create mode 100644 src/gui_controls.cpp create mode 100644 src/gui_progressbar.cpp create mode 100644 src/gui_statictext.cpp delete mode 100644 src/mainwnd3.cpp delete mode 100644 src/mainwnd4.cpp create mode 100644 src/mainwnd_commands.cpp rename src/{mainwnd2.cpp => mainwnd_config.cpp} (100%) create mode 100644 src/mainwnd_help.cpp rename src/{mainwnd1.cpp => mainwnd_init.cpp} (99%) create mode 100644 src/mainwnd_messages.cpp rename src/{mainwnd5.cpp => mainwnd_panels.cpp} (100%) create mode 100644 src/mainwnd_shutdown.cpp rename src/{menu2.cpp => menu_popup.cpp} (100%) rename src/{menu3.cpp => menu_shared_resources.cpp} (100%) rename src/{menu4.cpp => menu_templates.cpp} (100%) rename src/{menu1.cpp => menu_window_queue.cpp} (100%) create mode 100644 src/operations_core.cpp rename src/{salamdr5.cpp => path_checking.cpp} (100%) rename src/{salamdr3.cpp => path_utils.cpp} (99%) rename src/{plugins3.cpp => plugins_archiver.cpp} (100%) rename src/{plugins4.cpp => plugins_filesystem.cpp} (100%) rename src/{plugins2.cpp => plugins_interface.cpp} (100%) rename src/{plugins1.cpp => plugins_loading.cpp} (100%) rename src/{salamdr7.cpp => shell_environment.cpp} (100%) rename src/{salamdr2.cpp => string_resources.cpp} (99%) rename src/{toolbar4.cpp => toolbar_button_defs.cpp} (100%) rename src/{toolbar1.cpp => toolbar_core.cpp} (100%) rename src/{toolbar3.cpp => toolbar_dnd.cpp} (100%) rename src/{toolbar6.cpp => toolbar_drivebar.cpp} (100%) rename src/{toolbar7.cpp => toolbar_hotpaths.cpp} (100%) rename src/{toolbar8.cpp => toolbar_pluginsbar.cpp} (100%) rename src/{toolbar2.cpp => toolbar_rendering.cpp} (100%) rename src/{toolbar5.cpp => toolbar_usermenu.cpp} (100%) create mode 100644 src/transfer_speed.cpp rename src/{salamdr4.cpp => truncated_string.cpp} (100%) create mode 100644 src/vcxproj/build_check.err create mode 100644 src/vcxproj/build_check.wrn create mode 100644 src/zip_directory.cpp create mode 100644 src/zip_general_api.cpp create mode 100644 src/zip_progress.cpp create mode 100644 src/zip_utilities.cpp diff --git a/README.md b/README.md index f683827e..7d743775 100644 --- a/README.md +++ b/README.md @@ -150,56 +150,177 @@ Open Salamander is a pure WinAPI C++ application with no external UI frameworks └─────────────────────────────────────────────────────────┘ ``` +### Source File Organisation + +The codebase is organised into focused file groups. Each group uses a common prefix so related files sort together: + +| Prefix | Files | What it contains | +|--------|-------|-----------------| +| `mainwnd_*` | 7 | Main window: init, config, messages, commands, panels, shutdown, HTML help | +| `fileswindow_*` | 12 | File panel implementation: navigation, display, operations, archiving, etc. | +| `filesbox_*` | 2 | Virtual list-box rendering and keyboard/mouse input | +| `dialogs_*` | 8 | All dialog boxes: file ops, rename, attributes, configuration pages | +| `plugins_*` | 4 | Plugin loader, interface layer, archiver and file-system adapters | +| `toolbar_*` | 8 | Toolbar core, rendering, drag-and-drop, drive bar, hot-paths, user menu | +| `menu_*` | 4 | Popup menus, shared resources, templates, window queue | +| `gui_*` | 3 | Reusable GUI controls: progress bar, static text, buttons and animations | +| `zip_*` | 4 | ZIP/archive API: progress, general API, utilities, directory management | +| `find_*` | 3 | Find dialog: result data structures, dialog UI, toolbar | +| `app_*` | 2 | Application entry point and global variable definitions | +| `transfer_speed`, `async_copy`, `operations_core`, `worker` | 4 | File-copy engine: speed meters, async copy, operation dispatch | + +**Rule of thumb adopted for new code:** one logical component or feature per file; aim for files under 1 500 lines. + ### Core Components -#### Main Window (`src/mainwnd*.cpp`, `src/mainwnd.h`) -`CMainWindow` is the top-level window that hosts the entire application. It contains: +#### Main Window (`src/mainwnd_*.cpp`, `src/mainwnd.h`) + +`CMainWindow` is the top-level window that hosts the entire application. Its implementation is split across seven focused files: + +| File | Contents | +|------|----------| +| `mainwnd_init.cpp` | Constructor, window creation, destruction, toolbar/panel layout | +| `mainwnd_config.cpp` | Configuration load/save, registry persistence | +| `mainwnd_messages.cpp` | `WindowProc` switch — delegates to the two extracted handlers below | +| `mainwnd_commands.cpp` | `HandleWmCommand` — all `WM_COMMAND` menu and toolbar dispatch (~2 500 cases) | +| `mainwnd_shutdown.cpp` | `HandleShutdown` — `WM_CLOSE`, `WM_ENDSESSION`, `WM_QUERYENDSESSION` | +| `mainwnd_help.cpp` | `CSalamanderHelp`, `OpenHtmlHelp`, `MessageBoxHelpCallback` | +| `mainwnd_panels.cpp` | Panel layout helpers, splitter, focus management | + +Key hosted objects: - Two `CFilesWindow` instances (left and right panels) - Drive bars, toolbars (`CMainToolBar`, `CPluginsBar`, `CBottomToolBar`, `CUserMenuBar`, `CHotPathsBar`) - Menu system (`CMenuBar`, `CMenuPopup`, `CMenuNew`) -- Status window (`CStatusWindow`) and tooltip window (`CToolTipWindow`) +- `CPanelStatusBar` (status bar below each panel) and `CToolTipWindow` + +`CMainWindowLock` (declared in `mainwnd.h`) serialises access to the main window during shutdown; `extern CMainWindowLock MainWindowCS` is defined in `app_globals.cpp`. + +#### File Panels (`src/fileswindow_*.cpp`, `src/filesbox_*.cpp`, `src/fileswnd.h`) + +`CFilesWindow` implements a single file panel. Its implementation is split across twelve focused files: + +| File | Contents | +|------|----------| +| `fileswindow_init.cpp` | Constructor, creation, destruction | +| `fileswindow_navigation.cpp` | `ReadDirectory`, `ChangeDir`, path history | +| `fileswindow_display.cpp` | Painting: `SetFontAndColors`, `DrawIcon`, `DrawItem` | +| `fileswindow_operations.cpp` | `MakeFileList`, `MoveFiles`, `BuildScriptMain` — copy/move scripting | +| `fileswindow_archiving.cpp` | Pack, unpack, panel enumeration data | +| `fileswindow_file_actions.cpp` | Convert, `ChangeAttr`, `FindFile`, `ViewFile` | +| `fileswindow_execute.cpp` | Open/execute files, view-template selection | +| `fileswindow_quicksearch.cpp` | Find-as-you-type quick search | +| `fileswindow_columns.cpp` | Column configuration and management | +| `fileswindow_delete.cpp` | `DeleteThroughRecycleBin`, `FilesAction` | +| `fileswindow_dir_reading.cpp` | `CVisibleFileItemsArray`, directory reading | +| `fileswindow_wndproc.cpp` | `WindowProc`, `LockUI`, `OpenDirHistory` | -#### File Panels (`src/fileswn*.cpp`, `src/filesbx*.cpp`) -`CFilesWindow` implements a single file panel. Key sub-components: -- `CFilesBox` — the virtual list control that renders file entries -- `CHeaderLine` — column headers with drag-to-resize -- `CPathHistory` — forward/back navigation history -- `CIconCache` — icon lookup cache to avoid redundant shell requests -- `CFilesArray` / `CFileData` — in-memory directory listing +The virtual list-box rendering lives in `filesbox_rendering.cpp` (`CFileListBox`) and keyboard/mouse input in `filesbox_input.cpp` (`CFileListHeader`). Each panel operates in one of three modes driven by the path type: `ptDisk` (local/network drive), `ptZIPArchive` (archive root), or `ptPluginFS` (virtual file system provided by a plugin). -#### File Operations (`src/worker.h`, `src/execute.cpp`) -Long-running operations (copy, move, delete, rename) execute on dedicated worker threads so the UI remains responsive. The operation pipeline is: +#### File Copy Engine (`src/worker.h`, `src/worker.cpp`, `src/async_copy.cpp`, `src/operations_core.cpp`, `src/transfer_speed.cpp`) +Long-running operations (copy, move, delete, rename) execute on dedicated worker threads. The implementation is split across four files: + +| File | Contents | +|------|----------| +| `transfer_speed.cpp` | `CTransferSpeedMeter`, `CProgressSpeedMeter` — real-time throughput measurement | +| `async_copy.cpp` | `CCopy_Context`, `DoCopyFile`, `DoMoveFile`, `DoDeleteFile`, `DoCreateDir` — the core async copy engine with overlapped I/O | +| `operations_core.cpp` | `DoConvert`, `DoChangeAttrs`, `ThreadWorker`, `StartWorker`, `COperationsQueue` | +| `worker.cpp` | `COperations` methods, UTF-8 file helpers, buffer-size heuristics | + +The operation pipeline: 1. User triggers a command → `CCriteriaData` is built with masks, attributes, and speed limits. -2. A `COperations` object is created and a worker thread is started. -3. The worker processes each file, updates a `CProgressData` structure, and communicates back via Windows messages. +2. A `COperations` object is created and a worker thread is started via `StartWorker`. +3. The worker processes each file, updates `CProgressData`, and communicates back via Windows messages. 4. On completion or cancellation, both source and target panels are refreshed. -`CTransferSpeedMeter` and `CProgressSpeedMeter` measure throughput and estimate time remaining in real time. +`CAsyncCopyParams` (declared in `worker.h`) manages overlapped-I/O buffers for the async path. `CWorkerData` and `CProgressDlgData` (also in `worker.h`) carry per-operation state across the four translation units. + +#### ZIP and Archive API (`src/zip_*.cpp`, `src/zip.h`) + +The plugin-facing archive API (`CSalamanderGeneral`, `CSalamanderDirectory`, etc.) is split into four files: + +| File | Contents | +|------|----------| +| `zip_progress.cpp` | `CZIPUnpackProgress` — progress dialog integration | +| `zip_general_api.cpp` | `CSalamanderGeneral` — the main archive API surface (~3 000 lines) | +| `zip_utilities.cpp` | `CSalamanderBMSearchDataImp`, `CSalamanderMD5Imp`, `CSalamanderPNG`, `CSalamanderCrypt` | +| `zip_directory.cpp` | `CSalamanderDirectory`, `CSalamanderForOperations`, `TestFreeSpace` | + +#### Dialog Boxes (`src/dialogs_*.cpp`, `src/dialogs.h`) + +All dialogs are grouped by function: + +| File | Contents | +|------|----------| +| `dialogs_file_ops.cpp` | Copy, move, delete confirmation dialogs | +| `dialogs_rename.cpp` | Rename and batch-rename dialogs | +| `dialogs_attributes.cpp` | File-attribute dialogs, `CZipSizeResultsDialog`, `CPasswordDialog` | +| `dialogs_config_general.cpp` | Configuration pages: `CConfigPageGeneral`, `CConfigPageRegional`, `CConfigPageView` | +| `dialogs_config_viewers.cpp` | Configuration pages: Viewers, Associations | +| `dialogs_config_panels.cpp` | Configuration pages: Panels, Colors | +| `dialogs_config_environment.cpp` | Environment/paths configuration page | +| `dialogs_config_packing.cpp` | Packing/archiving configuration page | -#### Plugin System (`src/plugins.h`, `src/plugins/`) -Plugins are DLLs that export a well-known entry point. The core defines abstract interface classes that plugins implement: +#### Find Dialog (`src/find_*.cpp`, `src/find.h`) + +| File | Contents | +|------|----------| +| `find_results.cpp` | `CFindOptions`, `CFoundFilesData`, `CFoundFilesListView` — data model and list control | +| `find_dialog_ui.cpp` | `CFindDialog` — search dialog UI and logic (~3 300 lines) | +| `finddlg2.cpp` | `CFindDialog` continuation methods, `CFindTBHeader` toolbar | + +#### GUI Controls (`src/gui_*.cpp`, `src/gui.h`) + +Reusable custom controls shared across all dialogs and the main window: + +| File | Contents | +|------|----------| +| `gui_progressbar.cpp` | `CProgressBar` — animated, self-moving progress bar | +| `gui_statictext.cpp` | `CStaticText` — text control with ellipsis, path compaction, tooltips | +| `gui_controls.cpp` | `CButton`, `CColorArrowButton`, `CToolbarHeader`, `CAnimate`, layout helpers | + +`CGuiBitmap` (a memory-DC helper for flicker-free button drawing) is declared in `gui_bitmap.h` and shared by `gui_progressbar.cpp` and `gui_controls.cpp`. + +#### Plugin System (`src/plugins_*.cpp`, `src/plugins.h`, `src/plugins/`) + +| File | Contents | +|------|----------| +| `plugins_loading.cpp` | Discovery, `LoadLibraryUtf8`, version check, activation | +| `plugins_interface.cpp` | `CPluginInterfaceEncapsulation` — thread-safety guards | +| `plugins_archiver.cpp` | Archiver plugin adapter | +| `plugins_filesystem.cpp` | Virtual file-system plugin adapter | + +Plugins implement abstract interface classes: | Interface | Purpose | |-----------|---------| -| `CPluginInterfaceAbstract` | Base interface; version negotiation | +| `CPluginInterfaceAbstract` | Base; version negotiation | | `CPluginInterfaceForArchiverAbstract` | List/pack/unpack archives | | `CPluginInterfaceForViewerAbstract` | File preview/viewer | | `CPluginInterfaceForFileSystemAbstract` | Virtual file systems (e.g. FTP) | | `CPluginInterfaceForThumbLoaderAbstract` | Thumbnail generation | -`CPluginInterfaceEncapsulation` wraps every plugin call with `EnterPlugin()` / `LeavePlugin()` guards for thread safety and call-stack tracking. Plugins are discovered from their subdirectories at startup, loaded with `LoadLibraryUtf8()`, and version-checked before activation. +Every call is wrapped with `EnterPlugin()` / `LeavePlugin()` guards for thread safety and call-stack tracking. + +#### Application Entry (`src/app_entry.cpp`, `src/app_globals.cpp`) + +| File | Contents | +|------|----------| +| `app_globals.cpp` | All global variable definitions — runtime flags, GDI handles, colour tables, enabler arrays, `CMainWindowLock MainWindowCS` | +| `app_entry.cpp` | `MyEntryPoint`, `WinMain`, `WinMainBody`, locale init, graphics init, CRC-32 helpers | #### Common Library (`src/common/`) -Shared infrastructure used by both the core and plugins: + +Shared infrastructure used by both the core and all plugins: | File | Purpose | |------|---------| -| `array.h` | `TIndirectArray` — typed dynamic array template (~4900 lines) | +| `array.h` | `TIndirectArray` — typed dynamic array template | +| `strutils.h/cpp` | UTF-8 ↔ wide string conversion, `CreateFileUtf8`, `DeleteFileUtf8`, etc. | | `handles.h/cpp` | Handle tracking and leak detection in debug builds | -| `messages.h/cpp` | Typed message box helpers | +| `messages.h/cpp` | Typed message-box helpers | | `allochan.h/cpp` | Allocation tracking wrappers | | `heap.h/cpp` | Custom heap management | | `crc32.h/cpp` | CRC-32 checksum | @@ -207,15 +328,20 @@ Shared infrastructure used by both the core and plugins: | `multimon.cpp` | Multi-monitor layout support | #### Crash Reporting (`src/salmon/`) -`SalmonInit()` is called before `WinMain` via `MyEntryPoint()` in `salamdr1.cpp`. It installs an unhandled-exception filter that captures a minidump and call-stack trace, enabling post-mortem analysis of field crashes. + +`SalmonInit()` is called before `WinMain` via `MyEntryPoint()` in `app_entry.cpp`. It installs an unhandled-exception filter that captures a minidump and call-stack trace, enabling post-mortem analysis of field crashes. ### Key Data Structures | Type | Description | |------|-------------| | `CFileData` | Metadata for one file or directory (name, size, time, attributes, plugin-specific data) | -| `CSalamanderDirectory` | Holds the full listing for a directory | -| `CCriteriaData` | Parameters for a copy/move operation: masks, date range, size limits, speed cap | +| `CSalamanderDirectory` | Full directory listing exposed to plugins via the archive API | +| `CCriteriaData` | Parameters for a copy/move: masks, date range, size limits, speed cap | +| `CAsyncCopyParams` | Overlapped-I/O buffer block for the async copy path (declared in `worker.h`) | +| `CWorkerData` / `CProgressDlgData` | Per-operation worker state shared across the copy-engine translation units | +| `CDirectorySizeCache` | Cached directory-size results (avoids redundant recursive scans) | +| `CVisibleFileItemsArray` | Tracks which file items are currently visible in the panel for incremental refresh | | `CChangeCaseData` | Options for a batch rename-case operation | | `CAttrsData` | Parameters for changing file attributes in bulk | @@ -227,15 +353,32 @@ All UI objects derive from a thin `CWindow` base that wraps a WinAPI `HWND` and CWindow ├── CMainWindow │ ├── CFilesWindow (×2, left/right panels) -│ │ ├── CFilesBox -│ │ └── CHeaderLine +│ │ ├── CFileListBox (virtual list-box rendering) +│ │ └── CFileListHeader (column headers with drag-to-resize) │ ├── CMenuBar -│ ├── CMainToolBar / CPluginsBar / ... +│ ├── CMainToolBar / CPluginsBar / CBottomToolBar / ... │ ├── CDriveBar -│ └── CStatusWindow +│ └── CPanelStatusBar (status bar below each panel) └── CDialog (modal/modeless dialogs) + ├── CFindDialog (find dialog with CFoundFilesListView) + ├── CConfigPageGeneral / CConfigPageRegional / CConfigPageView / ... + ├── CPasswordDialog + ├── CZipSizeResultsDialog + └── CInlineRenameEdit (in-place rename edit control) ``` +### Naming Conventions + +The codebase follows these conventions for new and refactored code: + +| Category | Convention | Example | +|----------|-----------|---------| +| Dialog classes | `C*Dialog` | `CPasswordDialog`, `CZipSizeResultsDialog` | +| Window classes | `C*Window` | `CMainWindow`, `CFilesWindow` | +| Data-holder structs | `C*Data` or `C*Info` | `CFileData`, `CCriteriaData` | +| Manager / cache classes | `C*Manager` or `C*Cache` | `CDirectorySizeCache`, `CIconCache` | +| Locks / critical sections | `C*Lock` | `CMainWindowLock`, `CStringResourceLock` | + ### Build System The solution (`src/vcxproj/salamand.sln`) targets MSVC v143 (VS 2022). MSBuild property sheets layer the configuration: diff --git a/src/salamdr1.cpp b/src/app_entry.cpp similarity index 88% rename from src/salamdr1.cpp rename to src/app_entry.cpp index 0818bd1d..98028175 100644 --- a/src/salamdr1.cpp +++ b/src/app_entry.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 Taskscape Ltd +// SPDX-FileCopyrightText: 2023 Taskscape Ltd // SPDX-License-Identifier: GPL-2.0-or-later #include "precomp.h" @@ -114,579 +114,12 @@ int MyEntryPoint() return ret; } -BOOL SalamanderBusy = TRUE; // je Salamander busy? -DWORD LastSalamanderIdleTime = 0; // GetTickCount() z okamziku, kdy SalamanderBusy naposledy presel na TRUE - -int PasteLinkIsRunning = 0; // if greater than zero, Past Shortcuts command is currently running in one of the panels - -BOOL CannotCloseSalMainWnd = FALSE; // TRUE = nesmi dojit k zavreni hlavniho okna - -DWORD MainThreadID = -1; - -int MenuNewExceptionHasOccured = 0; -int FGIExceptionHasOccured = 0; -int ICExceptionHasOccured = 0; -int QCMExceptionHasOccured = 0; -int OCUExceptionHasOccured = 0; -int GTDExceptionHasOccured = 0; -int SHLExceptionHasOccured = 0; -int RelExceptionHasOccured = 0; - -char DecimalSeparator[5] = "."; // "characters" (max. 4 characters) extracted from system -int DecimalSeparatorLen = 1; // length in characters without terminating zero -char ThousandsSeparator[5] = " "; -int ThousandsSeparatorLen = 1; - -BOOL WindowsXP64AndLater = FALSE; // JRYFIXME - zrusit -BOOL WindowsVistaAndLater = FALSE; // JRYFIXME - zrusit -BOOL Windows7AndLater = FALSE; // JRYFIXME - zrusit -BOOL Windows8AndLater = FALSE; -BOOL Windows8_1AndLater = FALSE; -BOOL Windows10AndLater = FALSE; - -BOOL Windows64Bit = FALSE; - -BOOL RunningAsAdmin = FALSE; - -DWORD CCVerMajor = 0; -DWORD CCVerMinor = 0; - -char ConfigurationName[MAX_PATH]; -BOOL ConfigurationNameIgnoreIfNotExists = TRUE; - -int StopRefresh = 0; - -BOOL ExecCmdsOrUnloadMarkedPlugins = FALSE; -BOOL OpenPackOrUnpackDlgForMarkedPlugins = FALSE; - -int StopIconRepaint = 0; -BOOL PostAllIconsRepaint = FALSE; - -int StopStatusbarRepaint = 0; -BOOL PostStatusbarRepaint = FALSE; - -int ChangeDirectoryAllowed = 0; -BOOL ChangeDirectoryRequest = FALSE; - -BOOL SkipOneActivateRefresh = FALSE; - -const char* DirColumnStr = NULL; -int DirColumnStrLen = 0; -const char* ColExtStr = NULL; -int ColExtStrLen = 0; -int TextEllipsisWidth = 0; -int TextEllipsisWidthEnv = 0; -const char* ProgDlgHoursStr = NULL; -const char* ProgDlgMinutesStr = NULL; -const char* ProgDlgSecsStr = NULL; - -char FolderTypeName[80] = ""; -int FolderTypeNameLen = 0; -const char* UpDirTypeName = NULL; -int UpDirTypeNameLen = 0; -const char* CommonFileTypeName = NULL; -int CommonFileTypeNameLen = 0; -const char* CommonFileTypeName2 = NULL; - -char WindowsDirectory[MAX_PATH] = ""; - -// to ensure escape from removed drives to fixed drive (after ejecting device - USB flash disk, etc.) -BOOL ChangeLeftPanelToFixedWhenIdleInProgress = FALSE; // TRUE = path is currently being changed, setting ChangeLeftPanelToFixedWhenIdle to TRUE is unnecessary -BOOL ChangeLeftPanelToFixedWhenIdle = FALSE; -BOOL ChangeRightPanelToFixedWhenIdleInProgress = FALSE; // TRUE = path is currently being changed, setting ChangeRightPanelToFixedWhenIdle to TRUE is unnecessary -BOOL ChangeRightPanelToFixedWhenIdle = FALSE; -BOOL OpenCfgToChangeIfPathIsInaccessibleGoTo = FALSE; // TRUE = in idle opens configuration on Drives and focuses "If path in panel is inaccessible, go to:" - -char IsSLGIncomplete[ISSLGINCOMPLETE_SIZE]; // if string is empty, SLG is completely translated; otherwise contains URL to forum section for the given language - -UINT TaskbarBtnCreatedMsg = 0; - -// **************************************************************************** - -C__MainWindowCS MainWindowCS; -BOOL CanDestroyMainWindow = FALSE; -CMainWindow* MainWindow = NULL; -CFilesWindow* DropSourcePanel = NULL; -BOOL OurClipDataObject = FALSE; -const char* SALCF_IDATAOBJECT = "SalIDataObject"; -const char* SALCF_FAKE_REALPATH = "SalFakeRealPath"; -const char* SALCF_FAKE_SRCTYPE = "SalFakeSrcType"; -const char* SALCF_FAKE_SRCFSPATH = "SalFakeSrcFSPath"; - -const char* MAINWINDOW_NAME = "Open Salamander"; -const char* CMAINWINDOW_CLASSNAME = "SalamanderMainWindowVer25"; -const char* SAVEBITS_CLASSNAME = "SalamanderSaveBits"; -const char* SHELLEXECUTE_CLASSNAME = "SalamanderShellExecute"; - -CAssociations Associations; // associations loaded from registry -CShares Shares; - -char DefaultDir['Z' - 'A' + 1][MAX_PATH]; - -HACCEL AccelTable1 = NULL; -HACCEL AccelTable2 = NULL; - -HINSTANCE NtDLL = NULL; // handle to ntdll.dll -HINSTANCE Shell32DLL = NULL; // handle to shell32.dll (icons) -HINSTANCE ImageResDLL = NULL; // handle to imageres.dll (icons - Vista) -HINSTANCE User32DLL = NULL; // handle to user32.dll (DisableProcessWindowsGhosting) -HINSTANCE HLanguage = NULL; // handle to language-dependent resources (.SPL file) -char CurrentHelpDir[MAX_PATH] = ""; // after first help usage, contains path to help directory (location of all .chm files) -WORD LanguageID = 0; // language-id of .SPL file - -char OpenReadmeInNotepad[MAX_PATH]; // used only when launched from installer: name of file to open in notepad during IDLE (launch notepad) - -BOOL UseCustomPanelFont = FALSE; -HFONT Font = NULL; -HFONT FontUL = NULL; -LOGFONT LogFont; -int FontCharHeight = 0; - -HFONT EnvFont = NULL; -HFONT EnvFontUL = NULL; -//LOGFONT EnvLogFont; -int EnvFontCharHeight = 0; -HFONT TooltipFont = NULL; - -HBRUSH HNormalBkBrush = NULL; -HBRUSH HFocusedBkBrush = NULL; -HBRUSH HSelectedBkBrush = NULL; -HBRUSH HFocSelBkBrush = NULL; -HBRUSH HDialogBrush = NULL; -HBRUSH HButtonTextBrush = NULL; -HBRUSH HDitherBrush = NULL; -HBRUSH HActiveCaptionBrush = NULL; -HBRUSH HInactiveCaptionBrush = NULL; - -HBRUSH HMenuSelectedBkBrush = NULL; -HBRUSH HMenuSelectedTextBrush = NULL; -HBRUSH HMenuHilightBrush = NULL; -HBRUSH HMenuGrayTextBrush = NULL; - -HPEN HActiveNormalPen = NULL; // pens for frame around item -HPEN HActiveSelectedPen = NULL; -HPEN HInactiveNormalPen = NULL; -HPEN HInactiveSelectedPen = NULL; - -HPEN HThumbnailNormalPen = NULL; // pens for frame around thumbnail -HPEN HThumbnailFucsedPen = NULL; -HPEN HThumbnailSelectedPen = NULL; -HPEN HThumbnailFocSelPen = NULL; - -HPEN BtnShadowPen = NULL; -HPEN BtnHilightPen = NULL; -HPEN Btn3DLightPen = NULL; -HPEN BtnFacePen = NULL; -HPEN WndFramePen = NULL; -HPEN WndPen = NULL; -HBITMAP HFilter = NULL; -HBITMAP HHeaderSort = NULL; - -HIMAGELIST HFindSymbolsImageList = NULL; -HIMAGELIST HMenuMarkImageList = NULL; -HIMAGELIST HGrayToolBarImageList = NULL; -HIMAGELIST HHotToolBarImageList = NULL; -HIMAGELIST HBottomTBImageList = NULL; -HIMAGELIST HHotBottomTBImageList = NULL; - -CBitmap ItemBitmap; - -HBITMAP HUpDownBitmap = NULL; -HBITMAP HZoomBitmap = NULL; - -//HBITMAP HWorkerBitmap = NULL; - -HCURSOR HHelpCursor = NULL; - -int SystemDPI = 0; // Global DPI across all monitors. Salamander does not support Per-Monitor DPI, see https://msdn.microsoft.com/library/windows/desktop/dn469266.aspx -int IconSizes[] = {16, 32, 48}; -int IconLRFlags = 0; -HICON HSharedOverlays[] = {0}; -HICON HShortcutOverlays[] = {0}; -HICON HSlowFileOverlays[] = {0}; -CIconList* SimpleIconLists[] = {0}; -CIconList* ThrobberFrames = NULL; -CIconList* LockFrames = NULL; // for simplicity declare and load as throbber - -HICON HGroupIcon = NULL; -HICON HFavoritIcon = NULL; -HICON HSlowFileIcon = NULL; - -RGBQUAD ColorTable[256] = {0}; - -DWORD MouseHoverTime = 0; - -SYSTEMTIME SalamanderStartSystemTime = {0}; // Salamander start time (GetSystemTime) - -BOOL WaitForESCReleaseBeforeTestingESC = FALSE; // should we wait for ESC release before starting to browse path in panel? - -int SPACE_WIDTH = 10; - -const char* LOW_MEMORY = "Low memory."; - -BOOL DragFullWindows = TRUE; - -CWindowQueue ViewerWindowQueue("Internal Viewers"); - -CFindSetDialog GlobalFindDialog(NULL /* ignored */, 0 /* ignored */, 0 /* ignored */); - -CNames GlobalSelection; -CDirectorySizesHolder DirectorySizesHolder; - -HWND PluginProgressDialog = NULL; -HWND PluginMsgBoxParent = NULL; - -BOOL CriticalShutdown = FALSE; - -HANDLE SalOpenFileMapping = NULL; -void* SalOpenSharedMem = NULL; - -// mutex pro synchronizaci load/save do Registry (dva procesy najednou nemuzou, ma to neblahe vysledky) -CLoadSaveToRegistryMutex LoadSaveToRegistryMutex; - -BOOL IsNotAlphaNorNum[256]; // array of TRUE/FALSE for characters (TRUE = not a letter or digit) -BOOL IsAlpha[256]; // array of TRUE/FALSE for characters (TRUE = letter) - -// defaultni useruv charset pro fonty; pod W2K+ uz by stacilo DEFAULT_CHARSET -// -// Pod WinXP lze v regionalnim nastaveni zvolit jako default napriklad cestinu, -// ale na zalozce Advanced nenainstalovat ceske fotny. Potom pri konstrukci -// fontu s kodovanim UserCharset operacni system vrati font s uplne -// jinym nazvem (face name), hlavne aby mel pozadovane kodovani. Proto je DULEZITE pri -// specifikaci parametru fontu spravne zvolit promennou lfPitchAndFamily, -// kde si lze volit mezi FF_SWISS a FF_ROMAN fonty (bezpatkove/patkove). -int UserCharset = DEFAULT_CHARSET; - -DWORD AllocationGranularity = 1; // allocation granularity (needed for using memory-mapped files) - -#ifdef USE_BETA_EXPIRATION_DATE - -// urcuje prvni den, kdy uz tato beta/PB verze nepobezi -// beta/PB verze 4.0 beta 1 pojede pouze do 1. unora 2020 -// YEAR MONTH DAY -SYSTEMTIME BETA_EXPIRATION_DATE = {2020, 2, 0, 1, 0, 0, 0, 0}; -#endif // USE_BETA_EXPIRATION_DATE - -//****************************************************************************** -// -// Rizeni Idle processingu (CMainWindow::OnEnterIdle) -// - -BOOL IdleRefreshStates = TRUE; // na uvod nechame nastavit promenne -BOOL IdleForceRefresh = FALSE; // vyradi cache Enabler* -BOOL IdleCheckClipboard = TRUE; // koukneme taky na clipboard - -DWORD EnablerUpDir = FALSE; -DWORD EnablerRootDir = FALSE; -DWORD EnablerForward = FALSE; -DWORD EnablerBackward = FALSE; -DWORD EnablerFileOnDisk = FALSE; -DWORD EnablerLeftFileOnDisk = FALSE; -DWORD EnablerRightFileOnDisk = FALSE; -DWORD EnablerFileOnDiskOrArchive = FALSE; -DWORD EnablerFileOrDirLinkOnDisk = FALSE; -DWORD EnablerFiles = FALSE; -DWORD EnablerFilesOnDisk = FALSE; -DWORD EnablerFilesOnDiskCompress = FALSE; -DWORD EnablerFilesOnDiskEncrypt = FALSE; -DWORD EnablerFilesOnDiskOrArchive = FALSE; -DWORD EnablerOccupiedSpace = FALSE; -DWORD EnablerFilesCopy = FALSE; -DWORD EnablerFilesMove = FALSE; -DWORD EnablerFilesDelete = FALSE; -DWORD EnablerFileDir = FALSE; -DWORD EnablerFileDirANDSelected = FALSE; -DWORD EnablerQuickRename = FALSE; -DWORD EnablerOnDisk = FALSE; -DWORD EnablerCalcDirSizes = FALSE; -DWORD EnablerPasteFiles = FALSE; -DWORD EnablerPastePath = FALSE; -DWORD EnablerPasteLinks = FALSE; -DWORD EnablerPasteSimpleFiles = FALSE; -DWORD EnablerPasteDefEffect = FALSE; -DWORD EnablerPasteFilesToArcOrFS = FALSE; -DWORD EnablerPaste = FALSE; -DWORD EnablerPasteLinksOnDisk = FALSE; -DWORD EnablerSelected = FALSE; -DWORD EnablerUnselected = FALSE; -DWORD EnablerHiddenNames = FALSE; -DWORD EnablerSelectionStored = FALSE; -DWORD EnablerGlobalSelStored = FALSE; -DWORD EnablerSelGotoPrev = FALSE; -DWORD EnablerSelGotoNext = FALSE; -DWORD EnablerLeftUpDir = FALSE; -DWORD EnablerRightUpDir = FALSE; -DWORD EnablerLeftRootDir = FALSE; -DWORD EnablerRightRootDir = FALSE; -DWORD EnablerLeftForward = FALSE; -DWORD EnablerRightForward = FALSE; -DWORD EnablerLeftBackward = FALSE; -DWORD EnablerRightBackward = FALSE; -DWORD EnablerFileHistory = FALSE; -DWORD EnablerDirHistory = FALSE; -DWORD EnablerCustomizeLeftView = FALSE; -DWORD EnablerCustomizeRightView = FALSE; -DWORD EnablerDriveInfo = FALSE; -DWORD EnablerCreateDir = FALSE; -DWORD EnablerViewFile = FALSE; -DWORD EnablerChangeAttrs = FALSE; -DWORD EnablerShowProperties = FALSE; -DWORD EnablerItemsContextMenu = FALSE; -DWORD EnablerOpenActiveFolder = FALSE; -DWORD EnablerPermissions = FALSE; - -COLORREF* CurrentColors = SalamanderColors; - -COLORREF UserColors[NUMBER_OF_COLORS]; - -SALCOLOR ViewerColors[NUMBER_OF_VIEWERCOLORS] = - { - RGBF(0, 0, 0, SCF_DEFAULT), // VIEWER_FG_NORMAL - RGBF(255, 255, 255, SCF_DEFAULT), // VIEWER_BK_NORMAL - RGBF(255, 255, 255, SCF_DEFAULT), // VIEWER_FG_SELECTED - RGBF(0, 0, 0, SCF_DEFAULT), // VIEWER_BK_SELECTED -}; - -COLORREF SalamanderColors[NUMBER_OF_COLORS] = - { - // barvy pera pro ramecek kolem polozky - RGBF(0, 0, 0, SCF_DEFAULT), // FOCUS_ACTIVE_NORMAL - RGBF(0, 0, 0, SCF_DEFAULT), // FOCUS_ACTIVE_SELECTED - RGBF(128, 128, 128, 0), // FOCUS_FG_INACTIVE_NORMAL - RGBF(128, 128, 128, 0), // FOCUS_FG_INACTIVE_SELECTED - RGBF(255, 255, 255, SCF_DEFAULT), // FOCUS_BK_INACTIVE_NORMAL - RGBF(255, 255, 255, SCF_DEFAULT), // FOCUS_BK_INACTIVE_SELECTED - - // barvy textu polozek v panelu - RGBF(0, 0, 0, SCF_DEFAULT), // ITEM_FG_NORMAL - RGBF(255, 0, 0, 0), // ITEM_FG_SELECTED - RGBF(0, 0, 0, SCF_DEFAULT), // ITEM_FG_FOCUSED - RGBF(255, 0, 0, 0), // ITEM_FG_FOCSEL - RGBF(0, 0, 0, SCF_DEFAULT), // ITEM_FG_HIGHLIGHT - - // barvy pozadi polozek v panelu - RGBF(255, 255, 255, SCF_DEFAULT), // ITEM_BK_NORMAL - RGBF(255, 255, 255, SCF_DEFAULT), // ITEM_BK_SELECTED - RGBF(232, 232, 232, 0), // ITEM_BK_FOCUSED - RGBF(232, 232, 232, 0), // ITEM_BK_FOCSEL - RGBF(0, 0, 0, SCF_DEFAULT), // ITEM_BK_HIGHLIGHT - - // barvy pro blend ikonek - RGBF(255, 128, 128, SCF_DEFAULT), // ICON_BLEND_SELECTED - RGBF(128, 128, 128, 0), // ICON_BLEND_FOCUSED - RGBF(255, 0, 0, 0), // ICON_BLEND_FOCSEL - - // barvy progress bary - RGBF(0, 0, 192, SCF_DEFAULT), // PROGRESS_FG_NORMAL - RGBF(255, 255, 255, SCF_DEFAULT), // PROGRESS_FG_SELECTED - RGBF(255, 255, 255, SCF_DEFAULT), // PROGRESS_BK_NORMAL - RGBF(0, 0, 192, SCF_DEFAULT), // PROGRESS_BK_SELECTED - - // barvy hot polozek - RGBF(0, 0, 255, SCF_DEFAULT), // HOT_PANEL - RGBF(128, 128, 128, SCF_DEFAULT), // HOT_ACTIVE - RGBF(128, 128, 128, SCF_DEFAULT), // HOT_INACTIVE - - // barvy titulku panelu - RGBF(255, 255, 255, SCF_DEFAULT), // ACTIVE_CAPTION_FG - RGBF(0, 0, 128, SCF_DEFAULT), // ACTIVE_CAPTION_BK - RGBF(255, 255, 255, SCF_DEFAULT), // INACTIVE_CAPTION_FG - RGBF(128, 128, 128, SCF_DEFAULT), // INACTIVE_CAPTION_BK - - // barvy pera pro ramecek kolem thumbnails - RGBF(192, 192, 192, 0), // THUMBNAIL_FRAME_NORMAL - RGBF(0, 0, 0, 0), // THUMBNAIL_FRAME_FOCUSED - RGBF(255, 0, 0, 0), // THUMBNAIL_FRAME_SELECTED - RGBF(128, 0, 0, 0), // THUMBNAIL_FRAME_FOCSEL -}; - -COLORREF ExplorerColors[NUMBER_OF_COLORS] = - { - // barvy pera pro ramecek kolem polozky - RGBF(0, 0, 0, SCF_DEFAULT), // FOCUS_ACTIVE_NORMAL - RGBF(255, 255, 0, 0), // FOCUS_ACTIVE_SELECTED - RGBF(128, 128, 128, 0), // FOCUS_FG_INACTIVE_NORMAL - RGBF(0, 0, 128, 0), // FOCUS_FG_INACTIVE_SELECTED - RGBF(255, 255, 255, SCF_DEFAULT), // FOCUS_BK_INACTIVE_NORMAL - RGBF(255, 255, 0, 0), // FOCUS_BK_INACTIVE_SELECTED - - // barvy textu polozek v panelu - RGBF(0, 0, 0, SCF_DEFAULT), // ITEM_FG_NORMAL - RGBF(255, 255, 255, 0), // ITEM_FG_SELECTED - RGBF(0, 0, 0, SCF_DEFAULT), // ITEM_FG_FOCUSED - RGBF(255, 255, 255, 0), // ITEM_FG_FOCSEL - RGBF(0, 0, 0, SCF_DEFAULT), // ITEM_FG_HIGHLIGHT - - // barvy pozadi polozek v panelu - RGBF(255, 255, 255, SCF_DEFAULT), // ITEM_BK_NORMAL - RGBF(0, 0, 128, 0), // ITEM_BK_SELECTED - RGBF(232, 232, 232, 0), // ITEM_BK_FOCUSED - RGBF(0, 0, 128, 0), // ITEM_BK_FOCSEL - RGBF(0, 0, 0, SCF_DEFAULT), // ITEM_BK_HIGHLIGHT - - // barvy pro blend ikonek - RGBF(0, 0, 128, SCF_DEFAULT), // ICON_BLEND_SELECTED - RGBF(128, 128, 128, 0), // ICON_BLEND_FOCUSED - RGBF(0, 0, 128, 0), // ICON_BLEND_FOCSEL - - // barvy progress bary - RGBF(0, 0, 192, SCF_DEFAULT), // PROGRESS_FG_NORMAL - RGBF(255, 255, 255, SCF_DEFAULT), // PROGRESS_FG_SELECTED - RGBF(255, 255, 255, SCF_DEFAULT), // PROGRESS_BK_NORMAL - RGBF(0, 0, 192, SCF_DEFAULT), // PROGRESS_BK_SELECTED - - // barvy hot polozek - RGBF(0, 0, 255, SCF_DEFAULT), // HOT_PANEL - RGBF(128, 128, 128, SCF_DEFAULT), // HOT_ACTIVE - RGBF(128, 128, 128, SCF_DEFAULT), // HOT_INACTIVE - - // barvy titulku panelu - RGBF(255, 255, 255, SCF_DEFAULT), // ACTIVE_CAPTION_FG - RGBF(0, 0, 128, SCF_DEFAULT), // ACTIVE_CAPTION_BK - RGBF(255, 255, 255, SCF_DEFAULT), // INACTIVE_CAPTION_FG - RGBF(128, 128, 128, SCF_DEFAULT), // INACTIVE_CAPTION_BK - - // barvy pera pro ramecek kolem thumbnails - RGBF(192, 192, 192, 0), // THUMBNAIL_FRAME_NORMAL - RGBF(0, 0, 128, 0), // THUMBNAIL_FRAME_FOCUSED - RGBF(0, 0, 128, 0), // THUMBNAIL_FRAME_SELECTED - RGBF(0, 0, 128, 0), // THUMBNAIL_FRAME_FOCSEL -}; - -COLORREF NortonColors[NUMBER_OF_COLORS] = - { - // barvy pera pro ramecek kolem polozky - RGBF(0, 128, 128, 0), // FOCUS_ACTIVE_NORMAL - RGBF(0, 128, 128, 0), // FOCUS_ACTIVE_SELECTED - RGBF(0, 128, 128, 0), // FOCUS_FG_INACTIVE_NORMAL - RGBF(0, 128, 128, 0), // FOCUS_FG_INACTIVE_SELECTED - RGBF(0, 0, 128, 0), // FOCUS_BK_INACTIVE_NORMAL - RGBF(0, 0, 128, 0), // FOCUS_BK_INACTIVE_SELECTED - - // barvy textu polozek v panelu - RGBF(0, 255, 255, 0), // ITEM_FG_NORMAL - RGBF(255, 255, 0, 0), // ITEM_FG_SELECTED - RGBF(0, 0, 0, SCF_DEFAULT), // ITEM_FG_FOCUSED - RGBF(255, 255, 0, 0), // ITEM_FG_FOCSEL - RGBF(0, 0, 0, SCF_DEFAULT), // ITEM_FG_HIGHLIGHT - - // barvy pozadi polozek v panelu - RGBF(0, 0, 128, 0), // ITEM_BK_NORMAL - RGBF(0, 0, 128, 0), // ITEM_BK_SELECTED - RGBF(0, 128, 128, 0), // ITEM_BK_FOCUSED - RGBF(0, 128, 128, 0), // ITEM_BK_FOCSEL - RGBF(0, 0, 0, SCF_DEFAULT), // ITEM_BK_HIGHLIGHT - - // barvy pro blend ikonek - RGBF(255, 255, 0, SCF_DEFAULT), // ICON_BLEND_SELECTED - RGBF(128, 128, 128, 0), // ICON_BLEND_FOCUSED - RGBF(255, 255, 0, 0), // ICON_BLEND_FOCSEL - - // barvy progress bary - RGBF(0, 0, 192, SCF_DEFAULT), // PROGRESS_FG_NORMAL - RGBF(255, 255, 255, SCF_DEFAULT), // PROGRESS_FG_SELECTED - RGBF(255, 255, 255, SCF_DEFAULT), // PROGRESS_BK_NORMAL - RGBF(0, 0, 192, SCF_DEFAULT), // PROGRESS_BK_SELECTED - - // barvy hot polozek - RGBF(0, 0, 255, SCF_DEFAULT), // HOT_PANEL - RGBF(128, 128, 128, SCF_DEFAULT), // HOT_ACTIVE - RGBF(128, 128, 128, SCF_DEFAULT), // HOT_INACTIVE - - // barvy titulku panelu - RGBF(255, 255, 255, SCF_DEFAULT), // ACTIVE_CAPTION_FG - RGBF(0, 0, 128, SCF_DEFAULT), // ACTIVE_CAPTION_BK - RGBF(255, 255, 255, SCF_DEFAULT), // INACTIVE_CAPTION_FG - RGBF(128, 128, 128, SCF_DEFAULT), // INACTIVE_CAPTION_BK - - // barvy pera pro ramecek kolem thumbnails - RGBF(192, 192, 192, 0), // THUMBNAIL_FRAME_NORMAL - RGBF(0, 128, 128, 0), // THUMBNAIL_FRAME_FOCUSED - RGBF(255, 255, 0, 0), // THUMBNAIL_FRAME_SELECTED - RGBF(255, 255, 0, 0), // THUMBNAIL_FRAME_FOCSEL -}; - -COLORREF NavigatorColors[NUMBER_OF_COLORS] = - { - // barvy pera pro ramecek kolem polozky - RGBF(0, 128, 128, 0), // FOCUS_ACTIVE_NORMAL - RGBF(0, 128, 128, 0), // FOCUS_ACTIVE_SELECTED - RGBF(0, 128, 128, 0), // FOCUS_FG_INACTIVE_NORMAL - RGBF(0, 128, 128, 0), // FOCUS_FG_INACTIVE_SELECTED - RGBF(0, 0, 128, 0), // FOCUS_BK_INACTIVE_NORMAL - RGBF(0, 0, 128, 0), // FOCUS_BK_INACTIVE_SELECTED - - // barvy textu polozek v panelu - RGBF(255, 255, 255, 0), // ITEM_FG_NORMAL - RGBF(255, 255, 0, 0), // ITEM_FG_SELECTED - RGBF(0, 0, 0, SCF_DEFAULT), // ITEM_FG_FOCUSED - RGBF(255, 255, 0, 0), // ITEM_FG_FOCSEL - RGBF(0, 0, 0, SCF_DEFAULT), // ITEM_FG_HIGHLIGHT - - // barvy pozadi polozek v panelu - RGBF(80, 80, 80, 0), // ITEM_BK_NORMAL - RGBF(80, 80, 80, 0), // ITEM_BK_SELECTED - RGBF(0, 128, 128, 0), // ITEM_BK_FOCUSED - RGBF(0, 128, 128, 0), // ITEM_BK_FOCSEL - RGBF(0, 0, 0, SCF_DEFAULT), // ITEM_BK_HIGHLIGHT - - // barvy pro blend ikonek - RGBF(255, 255, 0, SCF_DEFAULT), // ICON_BLEND_SELECTED - RGBF(128, 128, 128, 0), // ICON_BLEND_FOCUSED - RGBF(255, 255, 0, 0), // ICON_BLEND_FOCSEL - - // barvy progress bary - RGBF(0, 0, 192, SCF_DEFAULT), // PROGRESS_FG_NORMAL - RGBF(255, 255, 255, SCF_DEFAULT), // PROGRESS_FG_SELECTED - RGBF(255, 255, 255, SCF_DEFAULT), // PROGRESS_BK_NORMAL - RGBF(0, 0, 192, SCF_DEFAULT), // PROGRESS_BK_SELECTED - - // barvy hot polozek - RGBF(0, 0, 255, SCF_DEFAULT), // HOT_PANEL - RGBF(173, 182, 205, SCF_DEFAULT), // HOT_ACTIVE - RGBF(212, 212, 212, SCF_DEFAULT), // HOT_INACTIVE - - // barvy titulku panelu - RGBF(255, 255, 255, SCF_DEFAULT), // ACTIVE_CAPTION_FG - RGBF(0, 0, 128, SCF_DEFAULT), // ACTIVE_CAPTION_BK - RGBF(255, 255, 255, SCF_DEFAULT), // INACTIVE_CAPTION_FG - RGBF(128, 128, 128, SCF_DEFAULT), // INACTIVE_CAPTION_BK - - // barvy pera pro ramecek kolem thumbnails - RGBF(192, 192, 192, 0), // THUMBNAIL_FRAME_NORMAL - RGBF(0, 128, 128, 0), // THUMBNAIL_FRAME_FOCUSED - RGBF(255, 255, 0, 0), // THUMBNAIL_FRAME_SELECTED - RGBF(255, 255, 0, 0), // THUMBNAIL_FRAME_FOCSEL -}; - -COLORREF CustomColors[NUMBER_OF_CUSTOMCOLORS] = - { - RGB(255, 255, 255), - RGB(255, 255, 255), - RGB(255, 255, 255), - RGB(255, 255, 255), - RGB(255, 255, 255), - RGB(255, 255, 255), - RGB(255, 255, 255), - RGB(255, 255, 255), - RGB(255, 255, 255), - RGB(255, 255, 255), - RGB(255, 255, 255), - RGB(255, 255, 255), - RGB(255, 255, 255), - RGB(255, 255, 255), - RGB(255, 255, 255), - RGB(255, 255, 255), -}; - -//***************************************************************************** -// -// CRC32 -// - static DWORD Crc32Tab[256]; static BOOL Crc32TabInitialized = FALSE; +// Defined in app_globals.cpp +extern char OpenReadmeInNotepad[MAX_PATH]; + void MakeCrc32Table(DWORD* crcTab) { DWORD c; diff --git a/src/app_globals.cpp b/src/app_globals.cpp new file mode 100644 index 00000000..38cd26a0 --- /dev/null +++ b/src/app_globals.cpp @@ -0,0 +1,597 @@ +// SPDX-FileCopyrightText: 2023 Taskscape Ltd +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "precomp.h" + +#include +#include "allochan.h" +#include "menu.h" +#include "cfgdlg.h" +#include "plugins.h" +#include "fileswnd.h" +#include "mainwnd.h" +#include "shellib.h" +#include "worker.h" +#include "iconpool.h" +#include "snooper.h" +#include "viewer.h" +#include "editwnd.h" +#include "find.h" +#include "zip.h" +#include "pack.h" +#include "cache.h" +#include "dialogs.h" +#include "gui.h" +#include "tasklist.h" +#include "toolbar.h" +#include "usermenu.h" +#include "execute.h" +#include "drivelst.h" + +BOOL SalamanderBusy = TRUE; // je Salamander busy? +DWORD LastSalamanderIdleTime = 0; // GetTickCount() z okamziku, kdy SalamanderBusy naposledy presel na TRUE + +int PasteLinkIsRunning = 0; // if greater than zero, Past Shortcuts command is currently running in one of the panels + +BOOL CannotCloseSalMainWnd = FALSE; // TRUE = nesmi dojit k zavreni hlavniho okna + +DWORD MainThreadID = -1; + +int MenuNewExceptionHasOccured = 0; +int FGIExceptionHasOccured = 0; +int ICExceptionHasOccured = 0; +int QCMExceptionHasOccured = 0; +int OCUExceptionHasOccured = 0; +int GTDExceptionHasOccured = 0; +int SHLExceptionHasOccured = 0; +int RelExceptionHasOccured = 0; + +char DecimalSeparator[5] = "."; // "characters" (max. 4 characters) extracted from system +int DecimalSeparatorLen = 1; // length in characters without terminating zero +char ThousandsSeparator[5] = " "; +int ThousandsSeparatorLen = 1; + +BOOL WindowsXP64AndLater = FALSE; // JRYFIXME - zrusit +BOOL WindowsVistaAndLater = FALSE; // JRYFIXME - zrusit +BOOL Windows7AndLater = FALSE; // JRYFIXME - zrusit +BOOL Windows8AndLater = FALSE; +BOOL Windows8_1AndLater = FALSE; +BOOL Windows10AndLater = FALSE; + +BOOL Windows64Bit = FALSE; + +BOOL RunningAsAdmin = FALSE; + +DWORD CCVerMajor = 0; +DWORD CCVerMinor = 0; + +char ConfigurationName[MAX_PATH]; +BOOL ConfigurationNameIgnoreIfNotExists = TRUE; + +int StopRefresh = 0; + +BOOL ExecCmdsOrUnloadMarkedPlugins = FALSE; +BOOL OpenPackOrUnpackDlgForMarkedPlugins = FALSE; + +int StopIconRepaint = 0; +BOOL PostAllIconsRepaint = FALSE; + +int StopStatusbarRepaint = 0; +BOOL PostStatusbarRepaint = FALSE; + +int ChangeDirectoryAllowed = 0; +BOOL ChangeDirectoryRequest = FALSE; + +BOOL SkipOneActivateRefresh = FALSE; + +const char* DirColumnStr = NULL; +int DirColumnStrLen = 0; +const char* ColExtStr = NULL; +int ColExtStrLen = 0; +int TextEllipsisWidth = 0; +int TextEllipsisWidthEnv = 0; +const char* ProgDlgHoursStr = NULL; +const char* ProgDlgMinutesStr = NULL; +const char* ProgDlgSecsStr = NULL; + +char FolderTypeName[80] = ""; +int FolderTypeNameLen = 0; +const char* UpDirTypeName = NULL; +int UpDirTypeNameLen = 0; +const char* CommonFileTypeName = NULL; +int CommonFileTypeNameLen = 0; +const char* CommonFileTypeName2 = NULL; + +char WindowsDirectory[MAX_PATH] = ""; + +// to ensure escape from removed drives to fixed drive (after ejecting device - USB flash disk, etc.) +BOOL ChangeLeftPanelToFixedWhenIdleInProgress = FALSE; // TRUE = path is currently being changed, setting ChangeLeftPanelToFixedWhenIdle to TRUE is unnecessary +BOOL ChangeLeftPanelToFixedWhenIdle = FALSE; +BOOL ChangeRightPanelToFixedWhenIdleInProgress = FALSE; // TRUE = path is currently being changed, setting ChangeRightPanelToFixedWhenIdle to TRUE is unnecessary +BOOL ChangeRightPanelToFixedWhenIdle = FALSE; +BOOL OpenCfgToChangeIfPathIsInaccessibleGoTo = FALSE; // TRUE = in idle opens configuration on Drives and focuses "If path in panel is inaccessible, go to:" + +char IsSLGIncomplete[ISSLGINCOMPLETE_SIZE]; // if string is empty, SLG is completely translated; otherwise contains URL to forum section for the given language + +UINT TaskbarBtnCreatedMsg = 0; + +// **************************************************************************** + +CMainWindowLock MainWindowCS; +BOOL CanDestroyMainWindow = FALSE; +CMainWindow* MainWindow = NULL; +CFilesWindow* DropSourcePanel = NULL; +BOOL OurClipDataObject = FALSE; +const char* SALCF_IDATAOBJECT = "SalIDataObject"; +const char* SALCF_FAKE_REALPATH = "SalFakeRealPath"; +const char* SALCF_FAKE_SRCTYPE = "SalFakeSrcType"; +const char* SALCF_FAKE_SRCFSPATH = "SalFakeSrcFSPath"; + +const char* MAINWINDOW_NAME = "Open Salamander"; +const char* CMAINWINDOW_CLASSNAME = "SalamanderMainWindowVer25"; +const char* SAVEBITS_CLASSNAME = "SalamanderSaveBits"; +const char* SHELLEXECUTE_CLASSNAME = "SalamanderShellExecute"; + +CAssociations Associations; // associations loaded from registry +CShares Shares; + +char DefaultDir['Z' - 'A' + 1][MAX_PATH]; + +HACCEL AccelTable1 = NULL; +HACCEL AccelTable2 = NULL; + +HINSTANCE NtDLL = NULL; // handle to ntdll.dll +HINSTANCE Shell32DLL = NULL; // handle to shell32.dll (icons) +HINSTANCE ImageResDLL = NULL; // handle to imageres.dll (icons - Vista) +HINSTANCE User32DLL = NULL; // handle to user32.dll (DisableProcessWindowsGhosting) +HINSTANCE HLanguage = NULL; // handle to language-dependent resources (.SPL file) +char CurrentHelpDir[MAX_PATH] = ""; // after first help usage, contains path to help directory (location of all .chm files) +WORD LanguageID = 0; // language-id of .SPL file + +char OpenReadmeInNotepad[MAX_PATH]; // used only when launched from installer: name of file to open in notepad during IDLE (launch notepad) + +BOOL UseCustomPanelFont = FALSE; +HFONT Font = NULL; +HFONT FontUL = NULL; +LOGFONT LogFont; +int FontCharHeight = 0; + +HFONT EnvFont = NULL; +HFONT EnvFontUL = NULL; +//LOGFONT EnvLogFont; +int EnvFontCharHeight = 0; +HFONT TooltipFont = NULL; + +HBRUSH HNormalBkBrush = NULL; +HBRUSH HFocusedBkBrush = NULL; +HBRUSH HSelectedBkBrush = NULL; +HBRUSH HFocSelBkBrush = NULL; +HBRUSH HDialogBrush = NULL; +HBRUSH HButtonTextBrush = NULL; +HBRUSH HDitherBrush = NULL; +HBRUSH HActiveCaptionBrush = NULL; +HBRUSH HInactiveCaptionBrush = NULL; + +HBRUSH HMenuSelectedBkBrush = NULL; +HBRUSH HMenuSelectedTextBrush = NULL; +HBRUSH HMenuHilightBrush = NULL; +HBRUSH HMenuGrayTextBrush = NULL; + +HPEN HActiveNormalPen = NULL; // pens for frame around item +HPEN HActiveSelectedPen = NULL; +HPEN HInactiveNormalPen = NULL; +HPEN HInactiveSelectedPen = NULL; + +HPEN HThumbnailNormalPen = NULL; // pens for frame around thumbnail +HPEN HThumbnailFucsedPen = NULL; +HPEN HThumbnailSelectedPen = NULL; +HPEN HThumbnailFocSelPen = NULL; + +HPEN BtnShadowPen = NULL; +HPEN BtnHilightPen = NULL; +HPEN Btn3DLightPen = NULL; +HPEN BtnFacePen = NULL; +HPEN WndFramePen = NULL; +HPEN WndPen = NULL; +HBITMAP HFilter = NULL; +HBITMAP HHeaderSort = NULL; + +HIMAGELIST HFindSymbolsImageList = NULL; +HIMAGELIST HMenuMarkImageList = NULL; +HIMAGELIST HGrayToolBarImageList = NULL; +HIMAGELIST HHotToolBarImageList = NULL; +HIMAGELIST HBottomTBImageList = NULL; +HIMAGELIST HHotBottomTBImageList = NULL; + +CBitmap ItemBitmap; + +HBITMAP HUpDownBitmap = NULL; +HBITMAP HZoomBitmap = NULL; + +//HBITMAP HWorkerBitmap = NULL; + +HCURSOR HHelpCursor = NULL; + +int SystemDPI = 0; // Global DPI across all monitors. Salamander does not support Per-Monitor DPI, see https://msdn.microsoft.com/library/windows/desktop/dn469266.aspx +int IconSizes[] = {16, 32, 48}; +int IconLRFlags = 0; +HICON HSharedOverlays[] = {0}; +HICON HShortcutOverlays[] = {0}; +HICON HSlowFileOverlays[] = {0}; +CIconList* SimpleIconLists[] = {0}; +CIconList* ThrobberFrames = NULL; +CIconList* LockFrames = NULL; // for simplicity declare and load as throbber + +HICON HGroupIcon = NULL; +HICON HFavoritIcon = NULL; +HICON HSlowFileIcon = NULL; + +RGBQUAD ColorTable[256] = {0}; + +DWORD MouseHoverTime = 0; + +SYSTEMTIME SalamanderStartSystemTime = {0}; // Salamander start time (GetSystemTime) + +BOOL WaitForESCReleaseBeforeTestingESC = FALSE; // should we wait for ESC release before starting to browse path in panel? + +int SPACE_WIDTH = 10; + +const char* LOW_MEMORY = "Low memory."; + +BOOL DragFullWindows = TRUE; + +CWindowQueue ViewerWindowQueue("Internal Viewers"); + +CFindSetDialog GlobalFindDialog(NULL /* ignored */, 0 /* ignored */, 0 /* ignored */); + +CNames GlobalSelection; +CDirectorySizeCache DirectorySizesHolder; + +HWND PluginProgressDialog = NULL; +HWND PluginMsgBoxParent = NULL; + +BOOL CriticalShutdown = FALSE; + +HANDLE SalOpenFileMapping = NULL; +void* SalOpenSharedMem = NULL; + +// mutex pro synchronizaci load/save do Registry (dva procesy najednou nemuzou, ma to neblahe vysledky) +CLoadSaveToRegistryMutex LoadSaveToRegistryMutex; + +BOOL IsNotAlphaNorNum[256]; // array of TRUE/FALSE for characters (TRUE = not a letter or digit) +BOOL IsAlpha[256]; // array of TRUE/FALSE for characters (TRUE = letter) + +// defaultni useruv charset pro fonty; pod W2K+ uz by stacilo DEFAULT_CHARSET +// +// Pod WinXP lze v regionalnim nastaveni zvolit jako default napriklad cestinu, +// ale na zalozce Advanced nenainstalovat ceske fotny. Potom pri konstrukci +// fontu s kodovanim UserCharset operacni system vrati font s uplne +// jinym nazvem (face name), hlavne aby mel pozadovane kodovani. Proto je DULEZITE pri +// specifikaci parametru fontu spravne zvolit promennou lfPitchAndFamily, +// kde si lze volit mezi FF_SWISS a FF_ROMAN fonty (bezpatkove/patkove). +int UserCharset = DEFAULT_CHARSET; + +DWORD AllocationGranularity = 1; // allocation granularity (needed for using memory-mapped files) + +#ifdef USE_BETA_EXPIRATION_DATE + +// urcuje prvni den, kdy uz tato beta/PB verze nepobezi +// beta/PB verze 4.0 beta 1 pojede pouze do 1. unora 2020 +// YEAR MONTH DAY +SYSTEMTIME BETA_EXPIRATION_DATE = {2020, 2, 0, 1, 0, 0, 0, 0}; +#endif // USE_BETA_EXPIRATION_DATE + +//****************************************************************************** +// +// Rizeni Idle processingu (CMainWindow::OnEnterIdle) +// + +BOOL IdleRefreshStates = TRUE; // na uvod nechame nastavit promenne +BOOL IdleForceRefresh = FALSE; // vyradi cache Enabler* +BOOL IdleCheckClipboard = TRUE; // koukneme taky na clipboard + +DWORD EnablerUpDir = FALSE; +DWORD EnablerRootDir = FALSE; +DWORD EnablerForward = FALSE; +DWORD EnablerBackward = FALSE; +DWORD EnablerFileOnDisk = FALSE; +DWORD EnablerLeftFileOnDisk = FALSE; +DWORD EnablerRightFileOnDisk = FALSE; +DWORD EnablerFileOnDiskOrArchive = FALSE; +DWORD EnablerFileOrDirLinkOnDisk = FALSE; +DWORD EnablerFiles = FALSE; +DWORD EnablerFilesOnDisk = FALSE; +DWORD EnablerFilesOnDiskCompress = FALSE; +DWORD EnablerFilesOnDiskEncrypt = FALSE; +DWORD EnablerFilesOnDiskOrArchive = FALSE; +DWORD EnablerOccupiedSpace = FALSE; +DWORD EnablerFilesCopy = FALSE; +DWORD EnablerFilesMove = FALSE; +DWORD EnablerFilesDelete = FALSE; +DWORD EnablerFileDir = FALSE; +DWORD EnablerFileDirANDSelected = FALSE; +DWORD EnablerQuickRename = FALSE; +DWORD EnablerOnDisk = FALSE; +DWORD EnablerCalcDirSizes = FALSE; +DWORD EnablerPasteFiles = FALSE; +DWORD EnablerPastePath = FALSE; +DWORD EnablerPasteLinks = FALSE; +DWORD EnablerPasteSimpleFiles = FALSE; +DWORD EnablerPasteDefEffect = FALSE; +DWORD EnablerPasteFilesToArcOrFS = FALSE; +DWORD EnablerPaste = FALSE; +DWORD EnablerPasteLinksOnDisk = FALSE; +DWORD EnablerSelected = FALSE; +DWORD EnablerUnselected = FALSE; +DWORD EnablerHiddenNames = FALSE; +DWORD EnablerSelectionStored = FALSE; +DWORD EnablerGlobalSelStored = FALSE; +DWORD EnablerSelGotoPrev = FALSE; +DWORD EnablerSelGotoNext = FALSE; +DWORD EnablerLeftUpDir = FALSE; +DWORD EnablerRightUpDir = FALSE; +DWORD EnablerLeftRootDir = FALSE; +DWORD EnablerRightRootDir = FALSE; +DWORD EnablerLeftForward = FALSE; +DWORD EnablerRightForward = FALSE; +DWORD EnablerLeftBackward = FALSE; +DWORD EnablerRightBackward = FALSE; +DWORD EnablerFileHistory = FALSE; +DWORD EnablerDirHistory = FALSE; +DWORD EnablerCustomizeLeftView = FALSE; +DWORD EnablerCustomizeRightView = FALSE; +DWORD EnablerDriveInfo = FALSE; +DWORD EnablerCreateDir = FALSE; +DWORD EnablerViewFile = FALSE; +DWORD EnablerChangeAttrs = FALSE; +DWORD EnablerShowProperties = FALSE; +DWORD EnablerItemsContextMenu = FALSE; +DWORD EnablerOpenActiveFolder = FALSE; +DWORD EnablerPermissions = FALSE; + +COLORREF* CurrentColors = SalamanderColors; + +COLORREF UserColors[NUMBER_OF_COLORS]; + +SALCOLOR ViewerColors[NUMBER_OF_VIEWERCOLORS] = + { + RGBF(0, 0, 0, SCF_DEFAULT), // VIEWER_FG_NORMAL + RGBF(255, 255, 255, SCF_DEFAULT), // VIEWER_BK_NORMAL + RGBF(255, 255, 255, SCF_DEFAULT), // VIEWER_FG_SELECTED + RGBF(0, 0, 0, SCF_DEFAULT), // VIEWER_BK_SELECTED +}; + +COLORREF SalamanderColors[NUMBER_OF_COLORS] = + { + // barvy pera pro ramecek kolem polozky + RGBF(0, 0, 0, SCF_DEFAULT), // FOCUS_ACTIVE_NORMAL + RGBF(0, 0, 0, SCF_DEFAULT), // FOCUS_ACTIVE_SELECTED + RGBF(128, 128, 128, 0), // FOCUS_FG_INACTIVE_NORMAL + RGBF(128, 128, 128, 0), // FOCUS_FG_INACTIVE_SELECTED + RGBF(255, 255, 255, SCF_DEFAULT), // FOCUS_BK_INACTIVE_NORMAL + RGBF(255, 255, 255, SCF_DEFAULT), // FOCUS_BK_INACTIVE_SELECTED + + // barvy textu polozek v panelu + RGBF(0, 0, 0, SCF_DEFAULT), // ITEM_FG_NORMAL + RGBF(255, 0, 0, 0), // ITEM_FG_SELECTED + RGBF(0, 0, 0, SCF_DEFAULT), // ITEM_FG_FOCUSED + RGBF(255, 0, 0, 0), // ITEM_FG_FOCSEL + RGBF(0, 0, 0, SCF_DEFAULT), // ITEM_FG_HIGHLIGHT + + // barvy pozadi polozek v panelu + RGBF(255, 255, 255, SCF_DEFAULT), // ITEM_BK_NORMAL + RGBF(255, 255, 255, SCF_DEFAULT), // ITEM_BK_SELECTED + RGBF(232, 232, 232, 0), // ITEM_BK_FOCUSED + RGBF(232, 232, 232, 0), // ITEM_BK_FOCSEL + RGBF(0, 0, 0, SCF_DEFAULT), // ITEM_BK_HIGHLIGHT + + // barvy pro blend ikonek + RGBF(255, 128, 128, SCF_DEFAULT), // ICON_BLEND_SELECTED + RGBF(128, 128, 128, 0), // ICON_BLEND_FOCUSED + RGBF(255, 0, 0, 0), // ICON_BLEND_FOCSEL + + // barvy progress bary + RGBF(0, 0, 192, SCF_DEFAULT), // PROGRESS_FG_NORMAL + RGBF(255, 255, 255, SCF_DEFAULT), // PROGRESS_FG_SELECTED + RGBF(255, 255, 255, SCF_DEFAULT), // PROGRESS_BK_NORMAL + RGBF(0, 0, 192, SCF_DEFAULT), // PROGRESS_BK_SELECTED + + // barvy hot polozek + RGBF(0, 0, 255, SCF_DEFAULT), // HOT_PANEL + RGBF(128, 128, 128, SCF_DEFAULT), // HOT_ACTIVE + RGBF(128, 128, 128, SCF_DEFAULT), // HOT_INACTIVE + + // barvy titulku panelu + RGBF(255, 255, 255, SCF_DEFAULT), // ACTIVE_CAPTION_FG + RGBF(0, 0, 128, SCF_DEFAULT), // ACTIVE_CAPTION_BK + RGBF(255, 255, 255, SCF_DEFAULT), // INACTIVE_CAPTION_FG + RGBF(128, 128, 128, SCF_DEFAULT), // INACTIVE_CAPTION_BK + + // barvy pera pro ramecek kolem thumbnails + RGBF(192, 192, 192, 0), // THUMBNAIL_FRAME_NORMAL + RGBF(0, 0, 0, 0), // THUMBNAIL_FRAME_FOCUSED + RGBF(255, 0, 0, 0), // THUMBNAIL_FRAME_SELECTED + RGBF(128, 0, 0, 0), // THUMBNAIL_FRAME_FOCSEL +}; + +COLORREF ExplorerColors[NUMBER_OF_COLORS] = + { + // barvy pera pro ramecek kolem polozky + RGBF(0, 0, 0, SCF_DEFAULT), // FOCUS_ACTIVE_NORMAL + RGBF(255, 255, 0, 0), // FOCUS_ACTIVE_SELECTED + RGBF(128, 128, 128, 0), // FOCUS_FG_INACTIVE_NORMAL + RGBF(0, 0, 128, 0), // FOCUS_FG_INACTIVE_SELECTED + RGBF(255, 255, 255, SCF_DEFAULT), // FOCUS_BK_INACTIVE_NORMAL + RGBF(255, 255, 0, 0), // FOCUS_BK_INACTIVE_SELECTED + + // barvy textu polozek v panelu + RGBF(0, 0, 0, SCF_DEFAULT), // ITEM_FG_NORMAL + RGBF(255, 255, 255, 0), // ITEM_FG_SELECTED + RGBF(0, 0, 0, SCF_DEFAULT), // ITEM_FG_FOCUSED + RGBF(255, 255, 255, 0), // ITEM_FG_FOCSEL + RGBF(0, 0, 0, SCF_DEFAULT), // ITEM_FG_HIGHLIGHT + + // barvy pozadi polozek v panelu + RGBF(255, 255, 255, SCF_DEFAULT), // ITEM_BK_NORMAL + RGBF(0, 0, 128, 0), // ITEM_BK_SELECTED + RGBF(232, 232, 232, 0), // ITEM_BK_FOCUSED + RGBF(0, 0, 128, 0), // ITEM_BK_FOCSEL + RGBF(0, 0, 0, SCF_DEFAULT), // ITEM_BK_HIGHLIGHT + + // barvy pro blend ikonek + RGBF(0, 0, 128, SCF_DEFAULT), // ICON_BLEND_SELECTED + RGBF(128, 128, 128, 0), // ICON_BLEND_FOCUSED + RGBF(0, 0, 128, 0), // ICON_BLEND_FOCSEL + + // barvy progress bary + RGBF(0, 0, 192, SCF_DEFAULT), // PROGRESS_FG_NORMAL + RGBF(255, 255, 255, SCF_DEFAULT), // PROGRESS_FG_SELECTED + RGBF(255, 255, 255, SCF_DEFAULT), // PROGRESS_BK_NORMAL + RGBF(0, 0, 192, SCF_DEFAULT), // PROGRESS_BK_SELECTED + + // barvy hot polozek + RGBF(0, 0, 255, SCF_DEFAULT), // HOT_PANEL + RGBF(128, 128, 128, SCF_DEFAULT), // HOT_ACTIVE + RGBF(128, 128, 128, SCF_DEFAULT), // HOT_INACTIVE + + // barvy titulku panelu + RGBF(255, 255, 255, SCF_DEFAULT), // ACTIVE_CAPTION_FG + RGBF(0, 0, 128, SCF_DEFAULT), // ACTIVE_CAPTION_BK + RGBF(255, 255, 255, SCF_DEFAULT), // INACTIVE_CAPTION_FG + RGBF(128, 128, 128, SCF_DEFAULT), // INACTIVE_CAPTION_BK + + // barvy pera pro ramecek kolem thumbnails + RGBF(192, 192, 192, 0), // THUMBNAIL_FRAME_NORMAL + RGBF(0, 0, 128, 0), // THUMBNAIL_FRAME_FOCUSED + RGBF(0, 0, 128, 0), // THUMBNAIL_FRAME_SELECTED + RGBF(0, 0, 128, 0), // THUMBNAIL_FRAME_FOCSEL +}; + +COLORREF NortonColors[NUMBER_OF_COLORS] = + { + // barvy pera pro ramecek kolem polozky + RGBF(0, 128, 128, 0), // FOCUS_ACTIVE_NORMAL + RGBF(0, 128, 128, 0), // FOCUS_ACTIVE_SELECTED + RGBF(0, 128, 128, 0), // FOCUS_FG_INACTIVE_NORMAL + RGBF(0, 128, 128, 0), // FOCUS_FG_INACTIVE_SELECTED + RGBF(0, 0, 128, 0), // FOCUS_BK_INACTIVE_NORMAL + RGBF(0, 0, 128, 0), // FOCUS_BK_INACTIVE_SELECTED + + // barvy textu polozek v panelu + RGBF(0, 255, 255, 0), // ITEM_FG_NORMAL + RGBF(255, 255, 0, 0), // ITEM_FG_SELECTED + RGBF(0, 0, 0, SCF_DEFAULT), // ITEM_FG_FOCUSED + RGBF(255, 255, 0, 0), // ITEM_FG_FOCSEL + RGBF(0, 0, 0, SCF_DEFAULT), // ITEM_FG_HIGHLIGHT + + // barvy pozadi polozek v panelu + RGBF(0, 0, 128, 0), // ITEM_BK_NORMAL + RGBF(0, 0, 128, 0), // ITEM_BK_SELECTED + RGBF(0, 128, 128, 0), // ITEM_BK_FOCUSED + RGBF(0, 128, 128, 0), // ITEM_BK_FOCSEL + RGBF(0, 0, 0, SCF_DEFAULT), // ITEM_BK_HIGHLIGHT + + // barvy pro blend ikonek + RGBF(255, 255, 0, SCF_DEFAULT), // ICON_BLEND_SELECTED + RGBF(128, 128, 128, 0), // ICON_BLEND_FOCUSED + RGBF(255, 255, 0, 0), // ICON_BLEND_FOCSEL + + // barvy progress bary + RGBF(0, 0, 192, SCF_DEFAULT), // PROGRESS_FG_NORMAL + RGBF(255, 255, 255, SCF_DEFAULT), // PROGRESS_FG_SELECTED + RGBF(255, 255, 255, SCF_DEFAULT), // PROGRESS_BK_NORMAL + RGBF(0, 0, 192, SCF_DEFAULT), // PROGRESS_BK_SELECTED + + // barvy hot polozek + RGBF(0, 0, 255, SCF_DEFAULT), // HOT_PANEL + RGBF(128, 128, 128, SCF_DEFAULT), // HOT_ACTIVE + RGBF(128, 128, 128, SCF_DEFAULT), // HOT_INACTIVE + + // barvy titulku panelu + RGBF(255, 255, 255, SCF_DEFAULT), // ACTIVE_CAPTION_FG + RGBF(0, 0, 128, SCF_DEFAULT), // ACTIVE_CAPTION_BK + RGBF(255, 255, 255, SCF_DEFAULT), // INACTIVE_CAPTION_FG + RGBF(128, 128, 128, SCF_DEFAULT), // INACTIVE_CAPTION_BK + + // barvy pera pro ramecek kolem thumbnails + RGBF(192, 192, 192, 0), // THUMBNAIL_FRAME_NORMAL + RGBF(0, 128, 128, 0), // THUMBNAIL_FRAME_FOCUSED + RGBF(255, 255, 0, 0), // THUMBNAIL_FRAME_SELECTED + RGBF(255, 255, 0, 0), // THUMBNAIL_FRAME_FOCSEL +}; + +COLORREF NavigatorColors[NUMBER_OF_COLORS] = + { + // barvy pera pro ramecek kolem polozky + RGBF(0, 128, 128, 0), // FOCUS_ACTIVE_NORMAL + RGBF(0, 128, 128, 0), // FOCUS_ACTIVE_SELECTED + RGBF(0, 128, 128, 0), // FOCUS_FG_INACTIVE_NORMAL + RGBF(0, 128, 128, 0), // FOCUS_FG_INACTIVE_SELECTED + RGBF(0, 0, 128, 0), // FOCUS_BK_INACTIVE_NORMAL + RGBF(0, 0, 128, 0), // FOCUS_BK_INACTIVE_SELECTED + + // barvy textu polozek v panelu + RGBF(255, 255, 255, 0), // ITEM_FG_NORMAL + RGBF(255, 255, 0, 0), // ITEM_FG_SELECTED + RGBF(0, 0, 0, SCF_DEFAULT), // ITEM_FG_FOCUSED + RGBF(255, 255, 0, 0), // ITEM_FG_FOCSEL + RGBF(0, 0, 0, SCF_DEFAULT), // ITEM_FG_HIGHLIGHT + + // barvy pozadi polozek v panelu + RGBF(80, 80, 80, 0), // ITEM_BK_NORMAL + RGBF(80, 80, 80, 0), // ITEM_BK_SELECTED + RGBF(0, 128, 128, 0), // ITEM_BK_FOCUSED + RGBF(0, 128, 128, 0), // ITEM_BK_FOCSEL + RGBF(0, 0, 0, SCF_DEFAULT), // ITEM_BK_HIGHLIGHT + + // barvy pro blend ikonek + RGBF(255, 255, 0, SCF_DEFAULT), // ICON_BLEND_SELECTED + RGBF(128, 128, 128, 0), // ICON_BLEND_FOCUSED + RGBF(255, 255, 0, 0), // ICON_BLEND_FOCSEL + + // barvy progress bary + RGBF(0, 0, 192, SCF_DEFAULT), // PROGRESS_FG_NORMAL + RGBF(255, 255, 255, SCF_DEFAULT), // PROGRESS_FG_SELECTED + RGBF(255, 255, 255, SCF_DEFAULT), // PROGRESS_BK_NORMAL + RGBF(0, 0, 192, SCF_DEFAULT), // PROGRESS_BK_SELECTED + + // barvy hot polozek + RGBF(0, 0, 255, SCF_DEFAULT), // HOT_PANEL + RGBF(173, 182, 205, SCF_DEFAULT), // HOT_ACTIVE + RGBF(212, 212, 212, SCF_DEFAULT), // HOT_INACTIVE + + // barvy titulku panelu + RGBF(255, 255, 255, SCF_DEFAULT), // ACTIVE_CAPTION_FG + RGBF(0, 0, 128, SCF_DEFAULT), // ACTIVE_CAPTION_BK + RGBF(255, 255, 255, SCF_DEFAULT), // INACTIVE_CAPTION_FG + RGBF(128, 128, 128, SCF_DEFAULT), // INACTIVE_CAPTION_BK + + // barvy pera pro ramecek kolem thumbnails + RGBF(192, 192, 192, 0), // THUMBNAIL_FRAME_NORMAL + RGBF(0, 128, 128, 0), // THUMBNAIL_FRAME_FOCUSED + RGBF(255, 255, 0, 0), // THUMBNAIL_FRAME_SELECTED + RGBF(255, 255, 0, 0), // THUMBNAIL_FRAME_FOCSEL +}; + +COLORREF CustomColors[NUMBER_OF_CUSTOMCOLORS] = + { + RGB(255, 255, 255), + RGB(255, 255, 255), + RGB(255, 255, 255), + RGB(255, 255, 255), + RGB(255, 255, 255), + RGB(255, 255, 255), + RGB(255, 255, 255), + RGB(255, 255, 255), + RGB(255, 255, 255), + RGB(255, 255, 255), + RGB(255, 255, 255), + RGB(255, 255, 255), + RGB(255, 255, 255), + RGB(255, 255, 255), + RGB(255, 255, 255), + RGB(255, 255, 255), +}; + +//***************************************************************************** +// CRC32 tables are defined in app_entry.cpp alongside UpdateCrc32() diff --git a/src/async_copy.cpp b/src/async_copy.cpp new file mode 100644 index 00000000..236ee2b0 --- /dev/null +++ b/src/async_copy.cpp @@ -0,0 +1,6215 @@ +// SPDX-FileCopyrightText: 2023 Taskscape Ltd +// SPDX-License-Identifier: GPL-2.0-or-later +// CommentsTranslationProject: TRANSLATED + +#include "precomp.h" + +#include "cfgdlg.h" +#include "worker.h" +#include "execlog.h" + +#include +#include + +// CreateFileUtf8 / DeleteFileUtf8 / SetFileAttributesUtf8 / RemoveDirectoryUtf8 +// are globally declared in common/strutils.h and defined in common/strutils.cpp. +int GetOptimalSyncCopyBufferSize(COperations* script, DWORD opFlags); +extern NTQUERYINFORMATIONFILE DynNtQueryInformationFile; +extern NTFSCONTROLFILE DynNtFsControlFile; +// **************************************************************************** +// CAsyncCopyParams (declaration in worker.h, implementations below) +// + +CAsyncCopyParams::CAsyncCopyParams() +{ + memset(Buffers, 0, sizeof(Buffers)); + memset(Overlapped, 0, sizeof(Overlapped)); + UseAsyncAlg = FALSE; + HasFailed = FALSE; +} + +void CAsyncCopyParams::Init(BOOL useAsyncAlg) +{ + UseAsyncAlg = useAsyncAlg; + if (UseAsyncAlg && Buffers[0] == NULL) + { + for (int i = 0; i < 8; i++) + { + Buffers[i] = malloc(ASYNC_COPY_BUF_SIZE); + Overlapped[i].hEvent = HANDLES(CreateEvent(NULL, TRUE, FALSE, NULL)); + if (Overlapped[i].hEvent == NULL) + { + DWORD err = GetLastError(); + TRACE_E("Unable to create synchronization object for Copy rutine: " << GetErrorText(err)); + HasFailed = TRUE; + } + } + } +} + +CAsyncCopyParams::~CAsyncCopyParams() +{ + for (int i = 0; i < 8; i++) + { + if (Buffers[i] != NULL) + free(Buffers[i]); + if (Overlapped[i].hEvent != NULL) + HANDLES(CloseHandle(Overlapped[i].hEvent)); + } +} + +OVERLAPPED* +CAsyncCopyParams::InitOverlapped(int i) +{ + if (!UseAsyncAlg) + TRACE_C("CAsyncCopyParams::InitOverlapped(): unexpected call, UseAsyncAlg is FALSE!"); + Overlapped[i].Internal = 0; + Overlapped[i].InternalHigh = 0; + Overlapped[i].Offset = 0; + Overlapped[i].OffsetHigh = 0; + // Overlapped[i].Pointer = 0; // this is a union, Pointer overlaps with Offset and OffsetHigh + return &Overlapped[i]; +} + +OVERLAPPED* +CAsyncCopyParams::InitOverlappedWithOffset(int i, const CQuadWord& offset) +{ + if (!UseAsyncAlg) + TRACE_C("CAsyncCopyParams::InitOverlappedWithOffset(): unexpected call, UseAsyncAlg is FALSE!"); + Overlapped[i].Internal = 0; + Overlapped[i].InternalHigh = 0; + Overlapped[i].Offset = offset.LoDWord; + Overlapped[i].OffsetHigh = offset.HiDWord; + // Overlapped[i].Pointer = 0; // this is a union, Pointer overlaps with Offset and OffsetHigh + return &Overlapped[i]; +} + +void CAsyncCopyParams::SetOverlappedToEOF(int i, const CQuadWord& offset) +{ + if (!UseAsyncAlg) + TRACE_C("CAsyncCopyParams::SetOverlappedToEOF(): unexpected call, UseAsyncAlg is FALSE!"); + Overlapped[i].Internal = 0xC0000011 /* STATUS_END_OF_FILE */; // NTSTATUS code equivalent to system error code ERROR_HANDLE_EOF + Overlapped[i].InternalHigh = 0; + Overlapped[i].Offset = offset.LoDWord; + Overlapped[i].OffsetHigh = offset.HiDWord; + // Overlapped[i].Pointer = 0; // this is a union, Pointer overlaps with Offset and OffsetHigh + SetEvent(Overlapped[i].hEvent); +} + +// ********************************************************************************** + +BOOL HaveWriteOwnerRight = FALSE; // does the process have the WRITE_OWNER right? + +void InitWorker() +{ + if (NtDLL != NULL) // "always true" + { + DynNtQueryInformationFile = (NTQUERYINFORMATIONFILE)GetProcAddress(NtDLL, "NtQueryInformationFile"); // has no header + DynNtFsControlFile = (NTFSCONTROLFILE)GetProcAddress(NtDLL, "NtFsControlFile"); // has no header + } +} + +void ReleaseWorker() +{ + DynNtQueryInformationFile = NULL; + DynNtFsControlFile = NULL; +} + + +void SetProgressDialog(HWND hProgressDlg, CProgressData* data, CProgressDlgData& dlgData) +{ // wait for the response; the dialog must be updated + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + if (!*dlgData.CancelWorker) // we need to stop the main thread + SendMessage(hProgressDlg, WM_USER_SETDIALOG, (WPARAM)data, 0); +} + +int CaclProg(const CQuadWord& progressCurrent, const CQuadWord& progressTotal) +{ + return progressCurrent >= progressTotal ? (progressTotal.Value == 0 ? 0 : 1000) : (int)((progressCurrent * CQuadWord(1000, 0)) / progressTotal).Value; +} + +void SetProgress(HWND hProgressDlg, int operation, int summary, CProgressDlgData& dlgData) +{ // notify about the change and continue without waiting for a reply + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + if (!*dlgData.CancelWorker && + (*dlgData.OperationProgress != operation || *dlgData.SummaryProgress != summary)) + { + *dlgData.OperationProgress = operation; + *dlgData.SummaryProgress = summary; + SendMessage(hProgressDlg, WM_USER_SETDIALOG, 0, 0); + } +} + +void SetProgressWithoutSuspend(HWND hProgressDlg, int operation, int summary, CProgressDlgData& dlgData) +{ // notify about the change and continue without waiting for a reply + if (!*dlgData.CancelWorker && + (*dlgData.OperationProgress != operation || *dlgData.SummaryProgress != summary)) + { + *dlgData.OperationProgress = operation; + *dlgData.SummaryProgress = summary; + SendMessage(hProgressDlg, WM_USER_SETDIALOG, 0, 0); + } +} + +BOOL GetDirTime(const char* dirName, FILETIME* ftModified); +BOOL DoCopyDirTime(HWND hProgressDlg, const char* targetName, FILETIME* modified, CProgressDlgData& dlgData, BOOL quiet); + +void GetFileOverwriteInfo(char* buff, int buffLen, HANDLE file, const char* fileName, FILETIME* fileTime, BOOL* getTimeFailed) +{ + FILETIME lastWrite; + SYSTEMTIME st; + FILETIME ft; + char date[50], time[50]; + if (!GetFileTime(file, NULL, NULL, &lastWrite) || + !FileTimeToLocalFileTime(&lastWrite, &ft) || + !FileTimeToSystemTime(&ft, &st)) + { + if (getTimeFailed != NULL) + *getTimeFailed = TRUE; + date[0] = 0; + time[0] = 0; + } + else + { + if (fileTime != NULL) + *fileTime = ft; + if (GetTimeFormat(LOCALE_USER_DEFAULT, 0, &st, NULL, time, 50) == 0) + _snprintf_s(time, _countof(time), _TRUNCATE, "%u:%02u:%02u", st.wHour, st.wMinute, st.wSecond); + if (GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &st, NULL, date, 50) == 0) + _snprintf_s(date, _countof(date), _TRUNCATE, "%u.%u.%u", st.wDay, st.wMonth, st.wYear); + } + + char attr[30]; + lstrcpy(attr, ", "); + DWORD attrs = SalGetFileAttributes(fileName); + if (attrs != 0xFFFFFFFF) + GetAttrsString(attr + 2, attrs); + if (strlen(attr) == 2) + attr[0] = 0; + + char number[50]; + CQuadWord size; + DWORD err; + if (SalGetFileSize(file, size, err)) + NumberToStr(number, size); + else + number[0] = 0; // error - size unknown + + _snprintf_s(buff, buffLen, _TRUNCATE, "%s, %s, %s%s", number, date, time, attr); +} + +void GetDirInfo(char* buffer, int bufferLen, const char* dir) +{ + if (bufferLen <= 0) + return; + const char* dirFindFirst = dir; + char dirFindFirstCopy[3 * MAX_PATH]; + MakeCopyWithBackslashIfNeeded(dirFindFirst, dirFindFirstCopy); + + BOOL ok = FALSE; + FILETIME lastWrite; + if (NameEndsWithBackslash(dirFindFirst)) + { // FindFirstFile fails for a dir ending with a backslash (used for invalid directory names), + // so in this situation we handle it through CreateFile and GetFileTime + CStrP dirFindFirstW(ConvertAllocUtf8ToWide(dirFindFirst, -1)); + HANDLE file = dirFindFirstW != NULL + ? HANDLES_Q(CreateFileW(dirFindFirstW, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL)) + : INVALID_HANDLE_VALUE; + if (file != INVALID_HANDLE_VALUE) + { + if (GetFileTime(file, NULL, NULL, &lastWrite)) + ok = TRUE; + HANDLES(CloseHandle(file)); + } + } + else + { + WIN32_FIND_DATAW data; + CStrP dirFindFirstW(ConvertAllocUtf8ToWide(dirFindFirst, -1)); + HANDLE find = dirFindFirstW != NULL ? HANDLES_Q(FindFirstFileW(dirFindFirstW, &data)) : INVALID_HANDLE_VALUE; + if (find != INVALID_HANDLE_VALUE) + { + HANDLES(FindClose(find)); + lastWrite = data.ftLastWriteTime; + ok = TRUE; + } + } + if (ok) + { + SYSTEMTIME st; + FILETIME ft; + if (FileTimeToLocalFileTime(&lastWrite, &ft) && + FileTimeToSystemTime(&ft, &st)) + { + char date[50], time[50]; + if (GetTimeFormat(LOCALE_USER_DEFAULT, 0, &st, NULL, time, 50) == 0) + _snprintf_s(time, _countof(time), _TRUNCATE, "%u:%02u:%02u", st.wHour, st.wMinute, st.wSecond); + if (GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &st, NULL, date, 50) == 0) + _snprintf_s(date, _countof(date), _TRUNCATE, "%u.%u.%u", st.wDay, st.wMonth, st.wYear); + + _snprintf_s(buffer, bufferLen, _TRUNCATE, "%s, %s", date, time); + } + else + _snprintf_s(buffer, bufferLen, _TRUNCATE, "%s, %s", LoadStr(IDS_INVALID_DATEORTIME), LoadStr(IDS_INVALID_DATEORTIME)); + } + else + buffer[0] = 0; +} + +BOOL IsDirectoryEmpty(const char* name) // directories/subdirectories contain no files +{ + char dir[MAX_PATH + 5]; + int len = (int)strlen(name); + if (len <= 0 || len >= _countof(dir) - 2) + return FALSE; + memcpy(dir, name, len); + dir[len] = 0; + if (dir[len - 1] != '\\') + dir[len++] = '\\'; + char* end = dir + len; + *end++ = '*'; + *end = 0; + + WIN32_FIND_DATAW fileData; + HANDLE search; + CStrP dirW(ConvertAllocUtf8ToWide(dir, -1)); + search = dirW != NULL ? HANDLES_Q(FindFirstFileW(dirW, &fileData)) : INVALID_HANDLE_VALUE; + if (search != INVALID_HANDLE_VALUE) + { + do + { + if (fileData.cFileName[0] == 0 || + fileData.cFileName[0] == L'.' && (fileData.cFileName[1] == 0 || + fileData.cFileName[1] == L'.' && fileData.cFileName[2] == 0)) + continue; + + if (fileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + int remaining = (int)(_countof(dir) - (end - dir)); + if (remaining <= 1 || ConvertWideToUtf8(fileData.cFileName, -1, end, remaining) == 0) + continue; + if (!IsDirectoryEmpty(dir)) // the subdirectory is not empty + { + HANDLES(FindClose(search)); + return FALSE; + } + } + else + { + HANDLES(FindClose(search)); // a file exists here + return FALSE; + } + } while (FindNextFileW(search, &fileData)); + HANDLES(FindClose(search)); + } + return TRUE; +} + +BOOL CurrentProcessTokenUserValid = FALSE; +char CurrentProcessTokenUserBuf[200]; +TOKEN_USER* CurrentProcessTokenUser = (TOKEN_USER*)CurrentProcessTokenUserBuf; + +void GainWriteOwnerAccess() +{ + static BOOL firstCall = TRUE; + if (firstCall) + { + firstCall = FALSE; + + HANDLE tokenHandle; + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &tokenHandle)) + { + TRACE_E("GainWriteOwnerAccess(): OpenProcessToken failed!"); + return; + } + + DWORD reqSize; + if (GetTokenInformation(tokenHandle, TokenUser, CurrentProcessTokenUser, 200, &reqSize)) + CurrentProcessTokenUserValid = TRUE; + + int i; + for (i = 0; i < 3; i++) + { + const char* privName = NULL; + switch (i) + { + case 0: + privName = SE_RESTORE_NAME; + break; + case 1: + privName = SE_TAKE_OWNERSHIP_NAME; + break; + case 2: + privName = SE_SECURITY_NAME; + break; + } + + LUID value; + if (privName != NULL && LookupPrivilegeValue(NULL, privName, &value)) + { + TOKEN_PRIVILEGES tokenPrivileges; + tokenPrivileges.PrivilegeCount = 1; + tokenPrivileges.Privileges[0].Luid = value; + tokenPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + + AdjustTokenPrivileges(tokenHandle, FALSE, &tokenPrivileges, sizeof(tokenPrivileges), NULL, NULL); + if (GetLastError() != NO_ERROR) + { + DWORD err = GetLastError(); + TRACE_E("GainWriteOwnerAccess(): AdjustTokenPrivileges(" << privName << ") failed! error: " << GetErrorText(err)); + } + else + { + if (i == 0) + HaveWriteOwnerRight = TRUE; // successfully obtained SE_RESTORE_NAME, WRITE_OWNER is guaranteed + } + } + else + { + DWORD err = GetLastError(); + TRACE_E("GainWriteOwnerAccess(): LookupPrivilegeValue(" << (privName != NULL ? privName : "null") << ") failed! error: " << GetErrorText(err)); + } + } + CloseHandle(tokenHandle); + } +} +/* +// Purpose: Determines if the user is a member of the administrators group. +// Return: TRUE if user is a admin +// FALSE if not +#define STATUS_SUCCESS ((NTSTATUS)0x00000000L) // ntsubauth +#define STATUS_BUFFER_TOO_SMALL ((NTSTATUS)0xC0000023L) +#define NT_SUCCESS(Status) ((NTSTATUS)(Status) >= 0) +typedef NTSTATUS (WINAPI *FNtQueryInformationToken)( + HANDLE TokenHandle, // IN + TOKEN_INFORMATION_CLASS TokenInformationClass, // IN + PVOID TokenInformation, // OUT + ULONG TokenInformationLength, // IN + PULONG ReturnLength // OUT + ); + + +BOOL IsUserAdmin() +{ + if (NtDLL == NULL) + return TRUE; + + GainWriteOwnerAccess(); + + FNtQueryInformationToken DynNTNtQueryInformationToken = (FNtQueryInformationToken)GetProcAddress(NtDLL, "NtQueryInformationToken"); // has no header + if (DynNTNtQueryInformationToken == NULL) + { + TRACE_E("Getting NtQueryInformationToken export failed!"); + return FALSE; + } + + static int fIsUserAnAdmin = -1; // cache + + if (-1 == fIsUserAnAdmin) + { + SID_IDENTIFIER_AUTHORITY authNT = SECURITY_NT_AUTHORITY; + NTSTATUS Status; + ULONG InfoLength; + PTOKEN_GROUPS TokenGroupList; + ULONG GroupIndex; + BOOL FoundAdmins; + PSID AdminsDomainSid; + HANDLE hUserToken; + + // Open the user's token + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hUserToken)) + return FALSE; + + // Create Admins domain sid. + Status = AllocateAndInitializeSid( + &authNT, + 2, + SECURITY_BUILTIN_DOMAIN_RID, + DOMAIN_ALIAS_RID_ADMINS, + 0, 0, 0, 0, 0, 0, + &AdminsDomainSid + ); + + // Test if user is in the Admins domain + + // Get a list of groups in the token + Status = DynNTNtQueryInformationToken( + hUserToken, // Handle + TokenGroups, // TokenInformationClass + NULL, // TokenInformation + 0, // TokenInformationLength + &InfoLength // ReturnLength + ); + + if ((Status != STATUS_SUCCESS) && (Status != STATUS_BUFFER_TOO_SMALL)) + { + FreeSid(AdminsDomainSid); + CloseHandle(hUserToken); + return FALSE; + } + + TokenGroupList = (PTOKEN_GROUPS)GlobalAlloc(GPTR, InfoLength); + + if (TokenGroupList == NULL) + { + FreeSid(AdminsDomainSid); + CloseHandle(hUserToken); + return FALSE; + } + + Status = DynNTNtQueryInformationToken( + hUserToken, // Handle + TokenGroups, // TokenInformationClass + TokenGroupList, // TokenInformation + InfoLength, // TokenInformationLength + &InfoLength // ReturnLength + ); + + if (!NT_SUCCESS(Status)) + { + GlobalFree(TokenGroupList); + FreeSid(AdminsDomainSid); + CloseHandle(hUserToken); + return FALSE; + } + + + // Search group list for Admins alias + FoundAdmins = FALSE; + + for (GroupIndex=0; GroupIndex < TokenGroupList->GroupCount; GroupIndex++ ) + { + if (EqualSid(TokenGroupList->Groups[GroupIndex].Sid, AdminsDomainSid)) + { + FoundAdmins = TRUE; + break; + } + } + + // Tidy up + GlobalFree(TokenGroupList); + FreeSid(AdminsDomainSid); + CloseHandle(hUserToken); + + fIsUserAnAdmin = FoundAdmins ? 1 : 0; + } + + return (BOOL)fIsUserAnAdmin; +} + +*/ + +/* according to http://vcfaq.mvps.org/sdk/21.htm */ +#define BUFF_SIZE 1024 +BOOL IsUserAdmin() +{ + HANDLE hToken = NULL; + PSID pAdminSid = NULL; + BYTE buffer[BUFF_SIZE]; + PTOKEN_GROUPS pGroups = (PTOKEN_GROUPS)buffer; + DWORD dwSize; // buffer size + DWORD i; + BOOL bSuccess; + SID_IDENTIFIER_AUTHORITY siaNtAuth = SECURITY_NT_AUTHORITY; + + // get token handle + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) + return FALSE; + + bSuccess = GetTokenInformation(hToken, TokenGroups, (LPVOID)pGroups, BUFF_SIZE, &dwSize); + CloseHandle(hToken); + if (!bSuccess) + return FALSE; + + if (!AllocateAndInitializeSid(&siaNtAuth, 2, + SECURITY_BUILTIN_DOMAIN_RID, + DOMAIN_ALIAS_RID_ADMINS, + 0, 0, 0, 0, 0, 0, &pAdminSid)) + return FALSE; + + bSuccess = FALSE; + for (i = 0; (i < pGroups->GroupCount) && !bSuccess; i++) + { + if (EqualSid(pAdminSid, pGroups->Groups[i].Sid)) + bSuccess = TRUE; + } + FreeSid(pAdminSid); + + return bSuccess; +} + +struct CSrcSecurity // helper structure for keeping security info for MoveFile (the source disappears after the operation, its security info must be stored beforehand) +{ + PSID SrcOwner; + PSID SrcGroup; + PACL SrcDACL; + PSECURITY_DESCRIPTOR SrcSD; + DWORD SrcError; + + CSrcSecurity() { Clear(); } + ~CSrcSecurity() + { + if (SrcSD != NULL) + LocalFree(SrcSD); + } + void Clear() + { + SrcOwner = NULL; + SrcGroup = NULL; + SrcDACL = NULL; + SrcSD = NULL; + SrcError = NO_ERROR; + } +}; + +BOOL DoCopySecurity(const char* sourceName, const char* targetName, DWORD* err, CSrcSecurity* srcSecurity) +{ + // if the path ends with a space or dot, we must append '\\', otherwise + // GetNamedSecurityInfo (and others) trim the spaces/dots and then work + // with a different path + const char* sourceNameSec = sourceName; + char sourceNameSecCopy[3 * MAX_PATH]; + MakeCopyWithBackslashIfNeeded(sourceNameSec, sourceNameSecCopy); + const char* targetNameSec = targetName; + char targetNameSecCopy[3 * MAX_PATH]; + MakeCopyWithBackslashIfNeeded(targetNameSec, targetNameSecCopy); + + PSID srcOwner = NULL; + PSID srcGroup = NULL; + PACL srcDACL = NULL; + PSECURITY_DESCRIPTOR srcSD = NULL; + if (srcSecurity != NULL) // MoveFile: simply take over the security info + { + srcOwner = srcSecurity->SrcOwner; + srcGroup = srcSecurity->SrcGroup; + srcDACL = srcSecurity->SrcDACL; + srcSD = srcSecurity->SrcSD; + *err = srcSecurity->SrcError; + srcSecurity->Clear(); + } + else // obtain the security info from the source + { + CStrP sourceNameSecW(ConvertAllocUtf8ToWide(sourceNameSec, -1)); + if (sourceNameSecW != NULL) + { + *err = GetNamedSecurityInfoW(sourceNameSecW, SE_FILE_OBJECT, + DACL_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION, + &srcOwner, &srcGroup, &srcDACL, NULL, &srcSD); + } + else + { + *err = ERROR_NO_UNICODE_TRANSLATION; + } + } + BOOL ret = *err == ERROR_SUCCESS; + + if (ret) + { + SECURITY_DESCRIPTOR_CONTROL srcSDControl; + DWORD srcSDRevision; + if (!GetSecurityDescriptorControl(srcSD, &srcSDControl, &srcSDRevision)) + { + *err = GetLastError(); + ret = FALSE; + } + else + { + CStrP targetNameSecW(ConvertAllocUtf8ToWide(targetNameSec, -1)); + if (targetNameSecW == NULL) + { + *err = ERROR_NO_UNICODE_TRANSLATION; + ret = FALSE; + } + else + { + BOOL inheritedDACL = /*(srcSDControl & SE_DACL_AUTO_INHERITED) != 0 &&*/ (srcSDControl & SE_DACL_PROTECTED) == 0; // SE_DACL_AUTO_INHERITED unfortunately is not always set (for example Total Commander clears it after moving a file, so we ignore it) + DWORD attr = GetFileAttributesUtf8(targetNameSec); + *err = SetNamedSecurityInfoW(targetNameSecW, SE_FILE_OBJECT, + DACL_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION | + (inheritedDACL ? UNPROTECTED_DACL_SECURITY_INFORMATION : PROTECTED_DACL_SECURITY_INFORMATION), + srcOwner, srcGroup, srcDACL, NULL); + ret = *err == ERROR_SUCCESS; + + if (!ret) + { + // if the owner and group cannot be changed (we do not have the rights in the directory - for example we only have "change" rights), + // check whether the owner and group are already set (that would not be an error) + PSID tgtOwner = NULL; + PSID tgtGroup = NULL; + PACL tgtDACL = NULL; + PSECURITY_DESCRIPTOR tgtSD = NULL; + BOOL tgtRead = GetNamedSecurityInfoW(targetNameSecW, SE_FILE_OBJECT, + DACL_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION, + &tgtOwner, &tgtGroup, &tgtDACL, NULL, &tgtSD) == ERROR_SUCCESS; + // if the owner of the target file is not the current user, try to set it ("take ownership") - only + // provided we have the right to write the owner so that we can write back the original owner afterwards + BOOL ownerOfFile = FALSE; + if (!tgtRead || // if the security info cannot be read from the target, the owner is most likely not the current user (the owner has unblocked read rights) + tgtOwner == NULL || // probably nonsense, the file must have some owner; if it happens, try to set the owner to the current user + CurrentProcessTokenUserValid && CurrentProcessTokenUser->User.Sid != NULL && + !EqualSid(tgtOwner, CurrentProcessTokenUser->User.Sid)) + { + if (HaveWriteOwnerRight && + CurrentProcessTokenUserValid && CurrentProcessTokenUser->User.Sid != NULL && + SetNamedSecurityInfoW(targetNameSecW, SE_FILE_OBJECT, OWNER_SECURITY_INFORMATION, + CurrentProcessTokenUser->User.Sid, NULL, NULL, NULL) == ERROR_SUCCESS) + { // setting succeeded; we must retrieve 'tgtSD' again + ownerOfFile = TRUE; + if (tgtSD != NULL) + LocalFree(tgtSD); + tgtOwner = NULL; + tgtGroup = NULL; + tgtDACL = NULL; + tgtSD = NULL; + tgtRead = GetNamedSecurityInfoW(targetNameSecW, SE_FILE_OBJECT, + DACL_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION, + &tgtOwner, &tgtGroup, &tgtDACL, NULL, &tgtSD) == ERROR_SUCCESS; + } + } + else + { + ownerOfFile = tgtRead && tgtOwner != NULL && CurrentProcessTokenUserValid && + CurrentProcessTokenUser->User.Sid != NULL; + } + BOOL daclOK = FALSE; + BOOL ownerOK = FALSE; + BOOL groupOK = FALSE; + if (ownerOfFile && CurrentProcessTokenUserValid && CurrentProcessTokenUser->User.Sid != NULL) + { // we are the file owner -> the DACL can be written; try to allow owner/group/DACL write and set the required values + int allowChPermDACLSize = sizeof(ACL) + sizeof(ACCESS_ALLOWED_ACE) - sizeof(ACCESS_ALLOWED_ACE().SidStart) + + GetLengthSid(CurrentProcessTokenUser->User.Sid) + 200 /* +200 bytes is just paranoia */; + char buff3[500]; + PACL allowChPermDACL; + if (allowChPermDACLSize > 500) + allowChPermDACL = (PACL)malloc(allowChPermDACLSize); + else + allowChPermDACL = (PACL)buff3; + if (allowChPermDACL != NULL && InitializeAcl(allowChPermDACL, allowChPermDACLSize, ACL_REVISION) && + AddAccessAllowedAce(allowChPermDACL, ACL_REVISION, READ_CONTROL | WRITE_DAC | WRITE_OWNER, + CurrentProcessTokenUser->User.Sid) && + SetNamedSecurityInfoW(targetNameSecW, SE_FILE_OBJECT, + DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION, + NULL, NULL, allowChPermDACL, NULL) == ERROR_SUCCESS) + { + ownerOK = SetNamedSecurityInfoW(targetNameSecW, SE_FILE_OBJECT, + OWNER_SECURITY_INFORMATION, + srcOwner, NULL, NULL, NULL) == ERROR_SUCCESS; + groupOK = SetNamedSecurityInfoW(targetNameSecW, SE_FILE_OBJECT, + GROUP_SECURITY_INFORMATION, + NULL, srcGroup, NULL, NULL) == ERROR_SUCCESS; + daclOK = SetNamedSecurityInfoW(targetNameSecW, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION | (inheritedDACL ? UNPROTECTED_DACL_SECURITY_INFORMATION : PROTECTED_DACL_SECURITY_INFORMATION), + NULL, NULL, srcDACL, NULL) == ERROR_SUCCESS; + } + if (allowChPermDACL != (PACL)buff3 && allowChPermDACL != NULL) + free(allowChPermDACL); + } + if (!ownerOK && + (SetNamedSecurityInfoW(targetNameSecW, SE_FILE_OBJECT, OWNER_SECURITY_INFORMATION, + srcOwner, NULL, NULL, NULL) == ERROR_SUCCESS || + tgtRead && (srcOwner == NULL && tgtOwner == NULL || // if the owner is already set, ignore a potential error while setting + srcOwner != NULL && tgtOwner != NULL && EqualSid(srcOwner, tgtOwner)))) + { + ownerOK = TRUE; + } + if (!groupOK && + (SetNamedSecurityInfoW(targetNameSecW, SE_FILE_OBJECT, GROUP_SECURITY_INFORMATION, + NULL, srcGroup, NULL, NULL) == ERROR_SUCCESS || + tgtRead && (srcGroup == NULL && tgtGroup == NULL || // if the group is already set, ignore a potential error while setting + srcGroup != NULL && tgtGroup != NULL && EqualSid(srcGroup, tgtGroup)))) + { + groupOK = TRUE; + } + if (!daclOK && // the DACL must be set last because it depends on the owner (CREATOR OWNER is replaced with the real owner, etc.) + SetNamedSecurityInfoW(targetNameSecW, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION | (inheritedDACL ? UNPROTECTED_DACL_SECURITY_INFORMATION : PROTECTED_DACL_SECURITY_INFORMATION), + NULL, NULL, srcDACL, NULL) == ERROR_SUCCESS) + { + daclOK = TRUE; + } + if (ownerOK && groupOK && daclOK) + { + ret = TRUE; // all three components are OK -> the whole thing is OK + *err = NO_ERROR; + } + if (tgtSD != NULL) + LocalFree(tgtSD); + } + if (attr != INVALID_FILE_ATTRIBUTES) + SetFileAttributesUtf8(targetNameSec, attr); + } + } + } + if (srcSD != NULL) + LocalFree(srcSD); + return ret; +} + +DWORD CompressFile(char* fileName, DWORD attrs) +{ + DWORD ret = ERROR_SUCCESS; + if (attrs & FILE_ATTRIBUTE_COMPRESSED) + return ret; // already compressed + + // if the path ends with a space or dot, we must append '\\', otherwise CreateFile + // trims the spaces/dots and works with a different path + const char* fileNameCrFile = fileName; + char fileNameCrFileCopy[3 * MAX_PATH]; + MakeCopyWithBackslashIfNeeded(fileNameCrFile, fileNameCrFileCopy); + + BOOL attrsChange = FALSE; + if (attrs & FILE_ATTRIBUTE_READONLY) + { + attrsChange = TRUE; + SetFileAttributesUtf8(fileNameCrFile, attrs & ~FILE_ATTRIBUTE_READONLY); + } + HANDLE file = HANDLES_Q(CreateFileUtf8(fileNameCrFile, FILE_READ_DATA | FILE_WRITE_DATA, + FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, + OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, + NULL)); + if (file == INVALID_HANDLE_VALUE) + ret = GetLastError(); + else + { + USHORT state = COMPRESSION_FORMAT_DEFAULT; + ULONG length; + if (!DeviceIoControl(file, FSCTL_SET_COMPRESSION, &state, + sizeof(USHORT), NULL, 0, &length, FALSE)) + ret = GetLastError(); + HANDLES(CloseHandle(file)); + } + if (attrsChange) + SetFileAttributesUtf8(fileNameCrFile, attrs); // revert to the original attributes (on error the attributes would remain nonsensically changed) + return ret; +} + +DWORD UncompressFile(char* fileName, DWORD attrs) +{ + DWORD ret = ERROR_SUCCESS; + if ((attrs & FILE_ATTRIBUTE_COMPRESSED) == 0) + return ret; // not compressed + + // if the path ends with a space or dot, we must append '\\', otherwise CreateFile + // trims the spaces/dots and works with a different path + const char* fileNameCrFile = fileName; + char fileNameCrFileCopy[3 * MAX_PATH]; + MakeCopyWithBackslashIfNeeded(fileNameCrFile, fileNameCrFileCopy); + CStrP fileNameW(ConvertAllocUtf8ToWide(fileNameCrFile, -1)); + if (fileNameW == NULL) + return ERROR_NO_UNICODE_TRANSLATION; + + BOOL attrsChange = FALSE; + if (attrs & FILE_ATTRIBUTE_READONLY) + { + attrsChange = TRUE; + SetFileAttributesW(fileNameW, attrs & ~FILE_ATTRIBUTE_READONLY); + } + + HANDLE file = HANDLES_Q(CreateFileW(fileNameW, FILE_READ_DATA | FILE_WRITE_DATA, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, + NULL)); + if (file == INVALID_HANDLE_VALUE) + ret = GetLastError(); + else + { + USHORT state = COMPRESSION_FORMAT_NONE; + ULONG length; + if (!DeviceIoControl(file, FSCTL_SET_COMPRESSION, &state, + sizeof(USHORT), NULL, 0, &length, FALSE)) + ret = GetLastError(); + HANDLES(CloseHandle(file)); + } + if (attrsChange) + SetFileAttributesW(fileNameW, attrs); // revert to the original attributes (on error the attributes would remain nonsensically changed) + return ret; +} + +DWORD MyEncryptFile(HWND hProgressDlg, char* fileName, DWORD attrs, DWORD finalAttrs, + CProgressDlgData& dlgData, BOOL& cancelOper, BOOL preserveDate) +{ + DWORD retEnc = ERROR_SUCCESS; + cancelOper = FALSE; + if (attrs & FILE_ATTRIBUTE_ENCRYPTED) + return retEnc; // already encrypted + + // if the path ends with a space or dot, we must append '\\', otherwise CreateFile + // trims the spaces/dots and works with a different path + const char* fileNameCrFile = fileName; + char fileNameCrFileCopy[3 * MAX_PATH]; + MakeCopyWithBackslashIfNeeded(fileNameCrFile, fileNameCrFileCopy); + CStrP fileNameW(ConvertAllocUtf8ToWide(fileNameCrFile, -1)); + if (fileNameW == NULL) + return ERROR_NO_UNICODE_TRANSLATION; + + // if the file has the SYSTEM attribute, the EncryptFile API function reports "access denied"; handle it: + if ((attrs & FILE_ATTRIBUTE_SYSTEM) && (finalAttrs & FILE_ATTRIBUTE_SYSTEM)) + { // if it has and will keep the SYSTEM attribute, ask the user whether they really mean it + if (!dlgData.EncryptSystemAll) + { + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + if (*dlgData.CancelWorker) + return retEnc; + + if (dlgData.SkipAllEncryptSystem) + return retEnc; + + int ret = IDCANCEL; + char* data[4]; + data[0] = (char*)&ret; + data[1] = LoadStr(IDS_CONFIRMSFILEENCRYPT); + data[2] = fileName; + data[3] = LoadStr(IDS_ENCRYPTSFILE); + SendMessage(hProgressDlg, WM_USER_DIALOG, 2, (LPARAM)data); + switch (ret) + { + case IDB_ALL: + dlgData.EncryptSystemAll = TRUE; + case IDYES: + break; + + case IDB_SKIPALL: + dlgData.SkipAllEncryptSystem = TRUE; + case IDB_SKIP: + return retEnc; + + case IDCANCEL: + { + cancelOper = TRUE; + return retEnc; + } + } + } + } + + BOOL attrsChange = FALSE; + if (attrs & (FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_READONLY)) + { + attrsChange = TRUE; + SetFileAttributesW(fileNameW, attrs & ~(FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_READONLY)); + } + if (preserveDate) + { + HANDLE file; + file = HANDLES_Q(CreateFileW(fileNameW, GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, + (attrs & FILE_ATTRIBUTE_DIRECTORY) ? FILE_FLAG_BACKUP_SEMANTICS : 0, + NULL)); + if (file != INVALID_HANDLE_VALUE) + { + FILETIME ftCreated, /*ftAccessed,*/ ftModified; + GetFileTime(file, &ftCreated, NULL /*&ftAccessed*/, &ftModified); + HANDLES(CloseHandle(file)); + + if (!EncryptFileW(fileNameW)) + retEnc = GetLastError(); + + file = HANDLES_Q(CreateFileW(fileNameW, GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, + (attrs & FILE_ATTRIBUTE_DIRECTORY) ? FILE_FLAG_BACKUP_SEMANTICS : 0, + NULL)); + if (file != INVALID_HANDLE_VALUE) + { + SetFileTime(file, &ftCreated, NULL /*&ftAccessed*/, &ftModified); + HANDLES(CloseHandle(file)); + } + } + else + retEnc = GetLastError(); + } + else + { + if (!EncryptFileW(fileNameW)) + retEnc = GetLastError(); + } + if (attrsChange) + SetFileAttributesW(fileNameW, attrs); // revert to the original attributes (on error the attributes would remain nonsensically changed) + return retEnc; +} + +DWORD MyDecryptFile(char* fileName, DWORD attrs, BOOL preserveDate) +{ + DWORD ret = ERROR_SUCCESS; + if ((attrs & FILE_ATTRIBUTE_ENCRYPTED) == 0) + return ret; // not encrypted + + // if the path ends with a space or dot, we must append '\\', otherwise CreateFile + // trims the spaces/dots and works with a different path + const char* fileNameCrFile = fileName; + char fileNameCrFileCopy[3 * MAX_PATH]; + MakeCopyWithBackslashIfNeeded(fileNameCrFile, fileNameCrFileCopy); + CStrP fileNameW(ConvertAllocUtf8ToWide(fileNameCrFile, -1)); + if (fileNameW == NULL) + return ERROR_NO_UNICODE_TRANSLATION; + + BOOL attrsChange = FALSE; + if (attrs & FILE_ATTRIBUTE_READONLY) + { + attrsChange = TRUE; + SetFileAttributesW(fileNameW, attrs & ~FILE_ATTRIBUTE_READONLY); + } + if (preserveDate) + { + HANDLE file; + file = HANDLES_Q(CreateFileW(fileNameW, GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, + (attrs & FILE_ATTRIBUTE_DIRECTORY) ? FILE_FLAG_BACKUP_SEMANTICS : 0, + NULL)); + if (file != INVALID_HANDLE_VALUE) + { + FILETIME ftCreated, /*ftAccessed,*/ ftModified; + GetFileTime(file, &ftCreated, NULL /*&ftAccessed*/, &ftModified); + HANDLES(CloseHandle(file)); + + if (!DecryptFileW(fileNameW, 0)) + ret = GetLastError(); + + file = HANDLES_Q(CreateFileW(fileNameW, GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, + (attrs & FILE_ATTRIBUTE_DIRECTORY) ? FILE_FLAG_BACKUP_SEMANTICS : 0, + NULL)); + if (file != INVALID_HANDLE_VALUE) + { + SetFileTime(file, &ftCreated, NULL /*&ftAccessed*/, &ftModified); + HANDLES(CloseHandle(file)); + } + } + else + ret = GetLastError(); + } + else + { + if (!DecryptFileW(fileNameW, 0)) + ret = GetLastError(); + } + if (attrsChange) + SetFileAttributesW(fileNameW, attrs); // revert to the original attributes (on error the attributes would remain nonsensically changed) + return ret; +} + +BOOL CheckFileOrDirADS(const char* fileName, BOOL isDir, CQuadWord* adsSize, wchar_t*** streamNames, + int* streamNamesCount, BOOL* lowMemory, DWORD* winError, + DWORD bytesPerCluster, CQuadWord* adsOccupiedSpace, + BOOL* onlyDiscardableStreams) +{ + if (adsSize != NULL) + adsSize->SetUI64(0); + if (adsOccupiedSpace != NULL) + adsOccupiedSpace->SetUI64(0); + if (streamNames != NULL) + *streamNames = NULL; + if (streamNamesCount != NULL) + *streamNamesCount = 0; + if (lowMemory != NULL) + *lowMemory = FALSE; + if (winError != NULL) + *winError = NO_ERROR; + if (onlyDiscardableStreams != NULL) + *onlyDiscardableStreams = TRUE; + + if (DynNtQueryInformationFile != NULL) // "always true" + { + // if the path ends with a space or dot, we must append '\\', otherwise CreateFile + // trims the spaces/dots and works with a different path + const char* fileNameCrFile = fileName; + char fileNameCrFileCopy[3 * MAX_PATH]; + MakeCopyWithBackslashIfNeeded(fileNameCrFile, fileNameCrFileCopy); + + HANDLE file = HANDLES_Q(CreateFileUtf8(fileNameCrFile, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, + isDir ? FILE_FLAG_BACKUP_SEMANTICS : 0, NULL)); + if (file == INVALID_HANDLE_VALUE) + { + if (winError != NULL) + *winError = GetLastError(); + return FALSE; + } + + // get stream info + NTSTATUS uStatus; + IO_STATUS_BLOCK ioStatus; + BYTE buffer[65535]; // Windows XP cannot handle more than 65535 (no idea why) + uStatus = DynNtQueryInformationFile(file, &ioStatus, buffer, sizeof(buffer), FileStreamInformation); + HANDLES(CloseHandle(file)); + if (uStatus != 0 /* anything other than success is an error (including warnings) */) + { + if (winError != NULL) + { + if (uStatus == STATUS_BUFFER_OVERFLOW) + *winError = ERROR_INSUFFICIENT_BUFFER; + else + *winError = LsaNtStatusToWinError(uStatus); + } + return FALSE; + } + + TDirectArray* streamNamesAux = NULL; + if (streamNames != NULL) + { + streamNamesAux = new TDirectArray(10, 100); + if (streamNamesAux == NULL) + { + if (lowMemory != NULL) + *lowMemory = TRUE; + TRACE_E(LOW_MEMORY); + return FALSE; + } + } + + // iterate through the streams + PFILE_STREAM_INFORMATION psi = (PFILE_STREAM_INFORMATION)buffer; + BOOL ret = FALSE; + BOOL lowMem = FALSE; + if (ioStatus.Information > 0) // check whether we obtained any data at all + { + while (1) + { + if (psi->NameLength != 7 * 2 || _memicmp(psi->Name, L"::$DATA", 7 * 2)) // ignore default stream + { + ret = TRUE; + if (adsSize != NULL) + *adsSize += CQuadWord(psi->Size.LowPart, psi->Size.HighPart); // sum of the total size of all alternate data streams + if (adsOccupiedSpace != NULL && bytesPerCluster != 0) + { + CQuadWord fileSize(psi->Size.LowPart, psi->Size.HighPart); + *adsOccupiedSpace += fileSize - ((fileSize - CQuadWord(1, 0)) % CQuadWord(bytesPerCluster, 0)) + + CQuadWord(bytesPerCluster - 1, 0); + } + + if (onlyDiscardableStreams != NULL) + { // if an ADS appears that is unknown or indispensable, switch 'onlyDiscardableStreams' to FALSE + if ((psi->NameLength < 29 * 2 || _memicmp(psi->Name, L":\x05Q30lsldxJoudresxAaaqpcawXc:", 29 * 2) != 0) && // Win2K thumbnail in an ADS: 5952 bytes (depends on JPEG compression) + (psi->NameLength < 40 * 2 || _memicmp(psi->Name, L":{4c8cc155-6c1e-11d1-8e41-00c04fb9386d}:", 40 * 2) != 0) && // Win2K thumbnail in an ADS: 0 bytes + (psi->NameLength < 9 * 2 || _memicmp(psi->Name, L":KAVICHS:", 9 * 2) != 0)) // Kaspersky antivirus: 36/68 bytes + { + *onlyDiscardableStreams = FALSE; + } + } + + if (streamNamesAux != NULL) // collecting Unicode names of alternate data streams + { + wchar_t* str = (wchar_t*)malloc(psi->NameLength + 2); + if (str != NULL) + { + memcpy(str, psi->Name, psi->NameLength); + str[psi->NameLength / 2] = 0; + streamNamesAux->Add(str); + if (!streamNamesAux->IsGood()) + { + free(str); + streamNamesAux->ResetState(); + if (lowMemory != NULL) + *lowMemory = TRUE; + lowMem = TRUE; + break; + } + } + else + { + if (lowMemory != NULL) + *lowMemory = TRUE; + lowMem = TRUE; + TRACE_E(LOW_MEMORY); + break; + } + } + else + { + if (adsSize == NULL && adsOccupiedSpace == NULL && onlyDiscardableStreams == NULL) + break; // nothing else to find out (no names, stream sizes, or only-discardable-streams collected) + } + } + if (psi->NextEntry == 0) + break; + psi = (PFILE_STREAM_INFORMATION)((BYTE*)psi + psi->NextEntry); // move to next item + } + } + if (streamNamesAux != NULL) + { + if (lowMem || !ret) // lack of memory or no ADS, release all names + { + int i; + for (i = 0; i < streamNamesAux->Count; i++) + free(streamNamesAux->At(i)); + } + else // everything OK, pass the names to the caller + { + if (streamNamesCount != NULL) + *streamNamesCount = streamNamesAux->Count; + *streamNames = streamNamesAux->GetData(); + streamNamesAux->DetachArray(); + } + delete streamNamesAux; + } + return ret; + } + return FALSE; +} + +BOOL DeleteAllADS(HANDLE file, const char* fileName) +{ + if (DynNtQueryInformationFile != NULL) // "always true" + { + // get stream info + NTSTATUS uStatus; + IO_STATUS_BLOCK ioStatus; + BYTE buffer[65535]; // Windows XP cannot handle more than 65535 (no idea why) + uStatus = DynNtQueryInformationFile(file, &ioStatus, buffer, sizeof(buffer), FileStreamInformation); + if (uStatus != 0 /* anything other than success is an error (including warnings) */) + { + DWORD err; + if (uStatus == STATUS_BUFFER_OVERFLOW) + err = ERROR_INSUFFICIENT_BUFFER; + else + err = LsaNtStatusToWinError(uStatus); + TRACE_I("DeleteAllADS(" << fileName << "): NtQueryInformationFile failed: " << GetErrorText(err)); + return FALSE; + } + + // iterate through the streams + PFILE_STREAM_INFORMATION psi = (PFILE_STREAM_INFORMATION)buffer; + if (ioStatus.Information > 0) // verify that we received any data at all + { + WCHAR adsFullName[2 * MAX_PATH]; + adsFullName[0] = 0; + WCHAR* adsPart = NULL; + int adsPartSize = 0; + while (1) + { + if (psi->NameLength != 7 * 2 || _memicmp(psi->Name, L"::$DATA", 7 * 2)) // ignore default stream + { + if (adsFullName[0] == 0) // convert the file name only when needed for the first time to save CPU time + { + if (ConvertA2U(fileName, -1, adsFullName, 2 * MAX_PATH) == 0) + return FALSE; // "always false" + adsPart = adsFullName + wcslen(adsFullName); + adsPartSize = (int)((adsFullName + 2 * MAX_PATH) - adsPart); + if (adsPartSize > 0) + { + *adsPart++ = L':'; + adsPartSize--; + } + else + return FALSE; // "always false" + } + WCHAR* start = (WCHAR*)psi->Name; + WCHAR* nameEnd = (WCHAR*)((char*)psi->Name + psi->NameLength); + if (start < nameEnd && *start == L':') + start++; + WCHAR* end = start; + while (end < nameEnd && *end != L':') + end++; + if (end - start >= adsPartSize) + { + TRACE_I("DeleteAllADS(" << fileName << "): too long ADS name!"); + return FALSE; + } + if (end > start) + { + memcpy(adsPart, start, (end - start) * sizeof(WCHAR)); + adsPart[end - start] = 0; + if (!DeleteFileW(adsFullName)) + { + DWORD err = GetLastError(); + TRACE_IW(L"DeleteAllADS(" << adsFullName << L"): DeleteFile has failed: " << GetErrorTextW(err)); + return FALSE; + } + } + } + if (psi->NextEntry == 0) + break; + psi = (PFILE_STREAM_INFORMATION)((BYTE*)psi + psi->NextEntry); // move to next item + } + } + } + return TRUE; +} + +void MyStrCpyNW(wchar_t* s1, wchar_t* s2, int maxChars) +{ + if (maxChars == 0) + return; + while (--maxChars && *s2 != 0) + *s1++ = *s2++; + *s1 = 0; +} + +void CutADSNameSuffix(char* s) +{ + char* end = strrchr(s, ':'); + if (end != NULL && stricmp(end, ":$DATA") == 0) + *end = 0; +} + +// conversion to the extended-path variant, see the MSDN article "File Name Conventions" +void DoLongName(char* buf, const char* name, int bufSize) +{ + if (*name == '\\') + _snprintf_s(buf, bufSize, _TRUNCATE, "\\\\?\\UNC%s", name + 1); // UNC + else + _snprintf_s(buf, bufSize, _TRUNCATE, "\\\\?\\%s", name); // standard path +} + +BOOL SalSetFilePointer(HANDLE file, const CQuadWord& offset) +{ + LONG lo = offset.LoDWord; + LONG hi = offset.HiDWord; + lo = SetFilePointer(file, lo, &hi, FILE_BEGIN); + return (lo != INVALID_SET_FILE_POINTER || GetLastError() == NO_ERROR) && + lo == (LONG)offset.LoDWord && hi == (LONG)offset.HiDWord; +} + +#define RETRYCOPY_TAIL_MINSIZE (32 * 1024) // at least two blocks of this size are verified at the end of the file tested in CheckTailOfOutFile(); afterwards the block size grows up to ASYNC_COPY_BUF_SIZE (if reading is fast enough); NOTE: must be <= ASYNC_COPY_BUF_SIZE +#define RETRYCOPY_TESTINGTIME 3000 // duration of the CheckTailOfOutFile() test in [ms] + +void CheckTailOfOutFileShowErr(const char* txt) +{ + DWORD err = GetLastError(); + TRACE_I("CheckTailOfOutFile(): " << txt << " Error: " << GetErrorText(err)); +} + +BOOL CheckTailOfOutFile(CAsyncCopyParams* asyncPar, HANDLE in, HANDLE out, const CQuadWord& offset, + const CQuadWord& curInOffset, BOOL ignoreReadErrOnOut) +{ + char* bufIn = (char*)malloc(ASYNC_COPY_BUF_SIZE); + char* bufOut = (char*)malloc(ASYNC_COPY_BUF_SIZE); + + DWORD startTime = GetTickCount(); + DWORD rutineStartTime = startTime; + CQuadWord lastOffset = offset; + int roundNum = 1; + DWORD curBufSize = RETRYCOPY_TAIL_MINSIZE; + DWORD lastRoundStartTime = 0; + DWORD lastRoundBufSize = 0; + BOOL searchLongLastingBlock = TRUE; + BOOL ok; + while (1) + { + DWORD roundStartTime = GetTickCount(); + ok = FALSE; + CQuadWord start; + start.Value = lastOffset.Value > curBufSize ? lastOffset.Value - curBufSize : 0; + DWORD size = (DWORD)(lastOffset.Value - start.Value); + if (size == 0) + { + ok = TRUE; + break; // nothing to verify + } +#ifdef WORKER_COPY_DEBUG_MSG + TRACE_I("CheckTailOfOutFile(): check: " << start.Value << " - " << lastOffset.Value << ", size: " << size); +#endif // WORKER_COPY_DEBUG_MSG + if (asyncPar == NULL) + { + if (SalSetFilePointer(in, start)) + { // set the 'start' offset in the input + if (SalSetFilePointer(out, start)) + { // set the 'start' offset in the output + DWORD read; + if (ReadFile(out, bufOut, size, &read, NULL) && read == size) + { // read 'size' bytes into the output buffer (fails if opened without read access) + if (ReadFile(in, bufIn, size, &read, NULL) && read == size) + { // read 'size' bytes into the input buffer + if (memcmp(bufIn, bufOut, size) == 0) // compare whether the input/output buffers match + ok = TRUE; + else + TRACE_I("CheckTailOfOutFile(): tail of target file is different from source file, tail without differences: " << (offset.Value - lastOffset.Value)); + } + else + CheckTailOfOutFileShowErr("Unable to read IN file."); + } + else + { + if (ignoreReadErrOnOut) // if the input file failed earlier, ignore that we cannot read the output (input was reopened, output has remained open) + { + CheckTailOfOutFileShowErr("Unable to read OUT file, but it was not broken, so it's no problem."); + ok = TRUE; + break; + } + else + CheckTailOfOutFileShowErr("Unable to read OUT file."); + } + } + else + CheckTailOfOutFileShowErr("Unable to set file pointer to start offset in OUT file."); + } + else + CheckTailOfOutFileShowErr("Unable to set file pointer to start offset in IN file."); + } + else + { + // asynchronously read the block starting at 'start' of length 'size' bytes from in and out, then compare + DWORD readOut; + if ((ReadFile(out, bufOut, size, NULL, + asyncPar->InitOverlappedWithOffset(0, start)) || + GetLastError() == ERROR_IO_PENDING) && + GetOverlappedResult(out, asyncPar->GetOverlapped(0), &readOut, TRUE)) + { + DWORD readIn; + if ((ReadFile(in, bufIn, size, NULL, + asyncPar->InitOverlappedWithOffset(1, start)) || + GetLastError() == ERROR_IO_PENDING) && + GetOverlappedResult(in, asyncPar->GetOverlapped(1), &readIn, TRUE)) + { + if (readOut != size || readIn != size || + memcmp(bufIn, bufOut, size) != 0) // compare whether the input/output buffers match + { + TRACE_I("CheckTailOfOutFile(): tail of target file is different from source file (async), tail without differences: " << (offset.Value - lastOffset.Value)); + } + else + ok = TRUE; + } + else + CheckTailOfOutFileShowErr("Unable to read IN file (async)."); + } + else + { + if (ignoreReadErrOnOut) // if the input file failed earlier, ignore that we cannot read the output (input was reopened, output has remained open) + { + CheckTailOfOutFileShowErr("Unable to read OUT file (async), but it was not broken, so it's no problem."); + ok = TRUE; + break; + } + else + CheckTailOfOutFileShowErr("Unable to read OUT file (async)."); + } + } + if (!ok) + break; + lastOffset = start; + DWORD curBufSizeBackup = curBufSize; + if (roundNum > 1) + { + DWORD ti = GetTickCount(); + if (searchLongLastingBlock) + { + DWORD t1 = roundStartTime - lastRoundStartTime; + DWORD t2 = ti - roundStartTime; + if (roundNum == 2 && t1 > 300 && 10 * t2 < t1) // first iteration waits for the disk/network to be ready, shift the start time (so we spend the configured time reading instead of just waiting) + { +#ifdef WORKER_COPY_DEBUG_MSG + TRACE_I("CheckTailOfOutFile(): detected long lasting first block, start time shifted by " << ((roundStartTime - startTime) / 1000.0) << " secs."); +#endif // WORKER_COPY_DEBUG_MSG + startTime = roundStartTime; + } + else + { + if (t2 > 1000 && ((curBufSize * 10) / lastRoundBufSize) * t1 < t2) + { // unexpectedly long block read, likely waiting for disk "verification" or similar; ignore this block once so the overall check still behaves normally + searchLongLastingBlock = FALSE; + DWORD sh = t2 - ((unsigned __int64)curBufSize * t1) / lastRoundBufSize; +#ifdef WORKER_COPY_DEBUG_MSG + TRACE_I("CheckTailOfOutFile(): detected long lasting block, start time shifted by " << (sh / 1000.0) << " secs."); +#endif // WORKER_COPY_DEBUG_MSG + startTime += sh; + } + } + } + if (ti - startTime > RETRYCOPY_TESTINGTIME) + break; // we have been reading long enough; stop after the mandatory two rounds + if (ti - roundStartTime < 300 && curBufSize < ASYNC_COPY_BUF_SIZE) + { // when reading is fast enough, enlarge the buffer to avoid excessive reverse seeking (toward the beginning of the file) + curBufSize *= 2; + if (curBufSize > ASYNC_COPY_BUF_SIZE) + curBufSize = ASYNC_COPY_BUF_SIZE; + } + } + roundNum++; + lastRoundStartTime = roundStartTime; + lastRoundBufSize = curBufSizeBackup; + } + + if (ok && asyncPar == NULL) // reposition input/output to required offsets + { + if (!SalSetFilePointer(in, curInOffset)) + { + CheckTailOfOutFileShowErr("Unable to set file pointer back to current offset in IN file."); + ok = FALSE; + } + if (ok && !SalSetFilePointer(out, offset)) + { + CheckTailOfOutFileShowErr("Unable to set file pointer back to current offset in OUT file."); + ok = FALSE; + } + } +#ifdef WORKER_COPY_DEBUG_MSG + if (!ok) + TRACE_I("CheckTailOfOutFile(): aborting Retry..."); + else + { + TRACE_I("CheckTailOfOutFile(): " << (offset.Value - lastOffset.Value) / 1024.0 << " KB tested in " << (GetTickCount() - rutineStartTime) / 1000.0 << " secs (clear read time: " << (GetTickCount() - startTime) / 1000.0 << " secs)."); + } +#endif // WORKER_COPY_DEBUG_MSG + free(bufIn); + free(bufOut); + return ok; +} + +// copies ADS into the newly created file/directory +// returns FALSE only when cancelled; success + Skip both return TRUE; Skip sets 'skip' +// (when not NULL) to TRUE +// 'optimalBufferSize' is the pre-computed optimal buffer size (0 = use default based on script flags) +BOOL DoCopyADS(HWND hProgressDlg, const char* sourceName, BOOL isDir, const char* targetName, + CQuadWord const& totalDone, CQuadWord& operDone, CQuadWord const& operTotal, + CProgressDlgData& dlgData, COperations* script, BOOL* skip, void* buffer, + int optimalBufferSize = 0) +{ + BOOL doCopyADSRet = TRUE; + BOOL lowMemory; + DWORD adsWinError; + wchar_t** streamNames; + int streamNamesCount; + BOOL skipped = FALSE; + CQuadWord lastTransferredFileSize, finalTransferredFileSize; + script->GetTFSandResetTrSpeedIfNeeded(&lastTransferredFileSize); + finalTransferredFileSize = lastTransferredFileSize; + if (operTotal > operDone) // it should always be at least equal, but we play it safe... + finalTransferredFileSize += (operTotal - operDone); + +COPY_ADS_AGAIN: + + if (CheckFileOrDirADS(sourceName, isDir, NULL, &streamNames, &streamNamesCount, + &lowMemory, &adsWinError, 0, NULL, NULL) && + !lowMemory && streamNames != NULL) + { // we have the list of ADS, let's try to copy them to the target file/directory + wchar_t srcName[2 * MAX_PATH]; // MAX_PATH for the file name as well as the ADS name (no idea what the actual maximum lengths are) + wchar_t tgtName[2 * MAX_PATH]; + char longSourceName[MAX_PATH + 100]; + char longTargetName[MAX_PATH + 100]; + DoLongName(longSourceName, sourceName, MAX_PATH + 100); + DoLongName(longTargetName, targetName, MAX_PATH + 100); + if (!ConvertUtf8ToWide(longSourceName, -1, srcName, 2 * MAX_PATH)) + srcName[0] = 0; + if (!ConvertUtf8ToWide(longTargetName, -1, tgtName, 2 * MAX_PATH)) + tgtName[0] = 0; + wchar_t* srcEnd = srcName + lstrlenW(srcName); + if (srcEnd > srcName && *(srcEnd - 1) == L'\\') + *--srcEnd = 0; + wchar_t* tgtEnd = tgtName + lstrlenW(tgtName); + if (tgtEnd > tgtName && *(tgtEnd - 1) == L'\\') + *--tgtEnd = 0; + + // Use pre-computed buffer size if provided, otherwise fall back to default logic + int bufferSize = (optimalBufferSize > 0) ? optimalBufferSize : + (script->RemovableSrcDisk || script->RemovableTgtDisk ? REMOVABLE_DISK_COPY_BUFFER : OPERATION_BUFFER); + + char nameBuf[2 * MAX_PATH]; + BOOL endProcessing = FALSE; + CQuadWord operationDone; + int i; + for (i = 0; i < streamNamesCount; i++) + { + MyStrCpyNW(srcEnd, streamNames[i], (int)(2 * MAX_PATH - (srcEnd - srcName))); + MyStrCpyNW(tgtEnd, streamNames[i], (int)(2 * MAX_PATH - (tgtEnd - tgtName))); + + COPY_AGAIN_ADS: + + operationDone = CQuadWord(0, 0); + int limitBufferSize = bufferSize; + script->SetTFSandProgressSize(lastTransferredFileSize, totalDone + operDone, &limitBufferSize, bufferSize); + + BOOL doNextFile = FALSE; + while (1) + { + HANDLE in = CreateFileW(srcName, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, + OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL); + HANDLES_ADD_EX(__otQuiet, in != INVALID_HANDLE_VALUE, __htFile, + __hoCreateFile, in, GetLastError(), TRUE); + if (in != INVALID_HANDLE_VALUE) + { + CQuadWord fileSize; + fileSize.LoDWord = GetFileSize(in, &fileSize.HiDWord); + if (fileSize.LoDWord == INVALID_FILE_SIZE && GetLastError() != NO_ERROR) + { + DWORD err = GetLastError(); + TRACE_E("GetFileSize(some ADS of " << sourceName << "): unexpected error: " << GetErrorText(err)); + fileSize.SetUI64(0); + } + + while (1) + { + HANDLE out = CreateFileW(tgtName, GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_FLAG_SEQUENTIAL_SCAN, NULL); + HANDLES_ADD_EX(__otQuiet, out != INVALID_HANDLE_VALUE, __htFile, + __hoCreateFile, out, GetLastError(), TRUE); + + BOOL canOverwriteMACADSs = TRUE; + + COPY_OVERWRITE: + + if (out != INVALID_HANDLE_VALUE) + { + canOverwriteMACADSs = FALSE; + + // if possible, pre-allocate the required space (avoids disk fragmentation and smooths writes to floppies) + BOOL wholeFileAllocated = FALSE; + if (fileSize > CQuadWord(limitBufferSize, 0) && // pointless to pre-allocate below the copy buffer size + fileSize < CQuadWord(0, 0x80000000)) // file size must be positive (otherwise seeking fails � values above 8 EB, so practically never) + { + BOOL fatal = TRUE; + BOOL ignoreErr = FALSE; + if (SalSetFilePointer(out, fileSize)) + { + if (SetEndOfFile(out)) + { + if (SetFilePointer(out, 0, NULL, FILE_BEGIN) == 0) + { + fatal = FALSE; + wholeFileAllocated = TRUE; + } + } + else + { + if (GetLastError() == ERROR_DISK_FULL) + ignoreErr = TRUE; // low disk space + } + } + if (fatal) + { + if (!ignoreErr) + { + DWORD err = GetLastError(); + TRACE_E("DoCopyADS(): unable to allocate whole file size before copy operation, please report under what conditions this occurs! GetLastError(): " << GetErrorText(err)); + } + + // try truncating the file to zero so closing it does not trigger unnecessary writes + SetFilePointer(out, 0, NULL, FILE_BEGIN); + SetEndOfFile(out); + + HANDLES(CloseHandle(out)); + out = INVALID_HANDLE_VALUE; + if (DeleteFileW(tgtName)) + { + out = CreateFileW(tgtName, GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_FLAG_SEQUENTIAL_SCAN, NULL); + HANDLES_ADD_EX(__otQuiet, out != INVALID_HANDLE_VALUE, __htFile, + __hoCreateFile, out, GetLastError(), TRUE); + if (out == INVALID_HANDLE_VALUE) + goto CREATE_ERROR_ADS; + } + else + goto CREATE_ERROR_ADS; + } + } + + DWORD read; + DWORD written; + while (1) + { + if (ReadFile(in, buffer, limitBufferSize, &read, NULL)) + { + if (read == 0) + break; // EOF + if (!script->ChangeSpeedLimit) // if the speed limit can change, this is not a "suitable" place to wait + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + if (*dlgData.CancelWorker) + { + COPY_ERROR_ADS: + + if (in != NULL) + HANDLES(CloseHandle(in)); + if (out != NULL) + { + if (wholeFileAllocated) + SetEndOfFile(out); // otherwise on a floppy the remaining bytes would be written + HANDLES(CloseHandle(out)); + } + DeleteFileW(tgtName); + doCopyADSRet = FALSE; + endProcessing = TRUE; + break; + } + + while (1) + { + if (WriteFile(out, buffer, read, &written, NULL) && read == written) + break; + + WRITE_ERROR_ADS: + + DWORD err; + err = GetLastError(); + + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + if (*dlgData.CancelWorker) + goto COPY_ERROR_ADS; + + if (dlgData.SkipAllFileADSWrite) + goto SKIP_COPY_ADS; + + int ret; + ret = IDCANCEL; + char* data[4]; + data[0] = (char*)&ret; + data[1] = LoadStr(IDS_ERRORWRITINGADS); + ConvertWideToUtf8(tgtName, -1, nameBuf, 2 * MAX_PATH); + nameBuf[2 * MAX_PATH - 1] = 0; + CutADSNameSuffix(nameBuf); + data[2] = nameBuf; + if (err == NO_ERROR && read != written) + err = ERROR_DISK_FULL; + data[3] = GetErrorText(err); + SendMessage(hProgressDlg, WM_USER_DIALOG, 0, (LPARAM)data); + switch (ret) + { + case IDRETRY: // on a network we must reopen the handle; local access would not allow sharing + { + if (in == NULL && out == NULL) + { + DeleteFileW(tgtName); + goto COPY_AGAIN_ADS; + } + if (out != NULL) + { + if (wholeFileAllocated) + SetEndOfFile(out); // otherwise on a floppy the remaining bytes would be written + HANDLES(CloseHandle(out)); // close the invalid handle + } + out = CreateFileW(tgtName, GENERIC_WRITE | GENERIC_READ, 0, NULL, OPEN_ALWAYS, + FILE_FLAG_SEQUENTIAL_SCAN, NULL); + HANDLES_ADD_EX(__otQuiet, out != INVALID_HANDLE_VALUE, __htFile, + __hoCreateFile, out, GetLastError(), TRUE); + if (out != INVALID_HANDLE_VALUE) // opened successfully; now adjust the offset + { + LONG lo, hi; + lo = GetFileSize(out, (DWORD*)&hi); + if (lo == INVALID_FILE_SIZE && GetLastError() != NO_ERROR || + CQuadWord(lo, hi) < operationDone || + !CheckTailOfOutFile(NULL, in, out, operationDone, operationDone + CQuadWord(read, 0), FALSE)) + { // cannot determine the size or the file is too small; restart the entire copy + HANDLES(CloseHandle(in)); + HANDLES(CloseHandle(out)); + DeleteFileW(tgtName); + goto COPY_AGAIN_ADS; + } + } + else // still cannot open; problem persists + { + out = NULL; + goto WRITE_ERROR_ADS; + } + break; + } + + case IDB_SKIPALL: + dlgData.SkipAllFileADSWrite = TRUE; + case IDB_SKIP: + { + SKIP_COPY_ADS: + + if (in != NULL) + HANDLES(CloseHandle(in)); + if (out != NULL) + { + if (wholeFileAllocated) + SetEndOfFile(out); // otherwise on a floppy the remaining bytes would be written + HANDLES(CloseHandle(out)); + } + DeleteFileW(tgtName); + if (skip != NULL) + *skip = TRUE; + skipped = TRUE; + endProcessing = TRUE; + break; + } + + case IDCANCEL: + goto COPY_ERROR_ADS; + } + if (endProcessing) + break; + } + if (endProcessing) + break; + if (!script->ChangeSpeedLimit) // when the speed limit can change, this is not a suitable wait point + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + if (*dlgData.CancelWorker) + goto COPY_ERROR_ADS; + + script->AddBytesToSpeedMetersAndTFSandPS(read, FALSE, bufferSize, &limitBufferSize); + + if (!script->ChangeSpeedLimit) // when the speed limit can change, this is not a suitable wait point + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + operationDone += CQuadWord(read, 0); + SetProgressWithoutSuspend(hProgressDlg, CaclProg(operDone + operationDone, operTotal), + CaclProg(totalDone + operDone + operationDone, script->TotalSize), + dlgData); + + if (script->ChangeSpeedLimit) // speed limit may change; this is the right place to wait until the + { // worker resumes and fetch a fresh copy buffer size + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + script->GetNewBufSize(&limitBufferSize, bufferSize); + } + } + else + { + READ_ERROR_ADS: + + DWORD err; + err = GetLastError(); + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + if (*dlgData.CancelWorker) + goto COPY_ERROR_ADS; + + if (dlgData.SkipAllFileADSRead) + goto SKIP_COPY_ADS; + + int ret = IDCANCEL; + char* data[4]; + data[0] = (char*)&ret; + data[1] = LoadStr(IDS_ERRORREADINGADS); + ConvertWideToUtf8(srcName, -1, nameBuf, 2 * MAX_PATH); + nameBuf[2 * MAX_PATH - 1] = 0; + CutADSNameSuffix(nameBuf); + data[2] = nameBuf; + data[3] = GetErrorText(err); + SendMessage(hProgressDlg, WM_USER_DIALOG, 0, (LPARAM)data); + switch (ret) + { + case IDRETRY: + { + if (in != NULL) + HANDLES(CloseHandle(in)); // close the invalid handle + + in = CreateFileW(srcName, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, + OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL); + HANDLES_ADD_EX(__otQuiet, in != INVALID_HANDLE_VALUE, __htFile, + __hoCreateFile, in, GetLastError(), TRUE); + if (in != INVALID_HANDLE_VALUE) // opened successfully; now adjust the offset + { + LONG lo, hi; + lo = GetFileSize(in, (DWORD*)&hi); + if (lo == INVALID_FILE_SIZE && GetLastError() != NO_ERROR || + CQuadWord(lo, hi) < operationDone || + !CheckTailOfOutFile(NULL, in, out, operationDone, operationDone, TRUE)) + { // cannot obtain size or the file is too small; restart the entire operation + HANDLES(CloseHandle(in)); + if (wholeFileAllocated) + SetEndOfFile(out); // otherwise on a floppy the remaining bytes would be written + HANDLES(CloseHandle(out)); + DeleteFileW(tgtName); + goto COPY_AGAIN_ADS; + } + } + else // still cannot open; problem persists + { + in = NULL; + goto READ_ERROR_ADS; + } + break; + } + case IDB_SKIPALL: + dlgData.SkipAllFileADSRead = TRUE; + case IDB_SKIP: + goto SKIP_COPY_ADS; + case IDCANCEL: + goto COPY_ERROR_ADS; + } + } + } + if (endProcessing) + break; + + if (wholeFileAllocated && // the entire target layout was pre-allocated + operationDone < fileSize) // and the source file shrank + { + if (!SetEndOfFile(out)) // trim it here + { + written = read = 0; + goto WRITE_ERROR_ADS; + } + } + + // commented out because it sets the time of the file/directory that owns the ADS instead of the ADS timestamps + // FILETIME creation, lastAccess, lastWrite; + // GetFileTime(in, NULL /*&creation*/, NULL /*&lastAccess*/, &lastWrite); + // SetFileTime(out, NULL /*&creation*/, NULL /*&lastAccess*/, &lastWrite); + + HANDLES(CloseHandle(in)); + if (!HANDLES(CloseHandle(out))) // even after a failed call we assume the handle is closed, + { // see /viewtopic.php?f=6&t=8455 + in = out = NULL; // (reports that the target file can be deleted, so its handle was not left open) + written = read = 0; + goto WRITE_ERROR_ADS; + } + + // commented out because it sets the attributes of the file/directory that owns the ADS instead of the ADS attributes + // DWORD attr = DynGetFileAttributesW(srcName); + // if (attr != INVALID_FILE_ATTRIBUTES) DynSetFileAttributesW(tgtName, attr); + + operDone += operationDone; + lastTransferredFileSize += operationDone; + doNextFile = TRUE; + } + else + { + CREATE_ERROR_ADS: + + DWORD err = GetLastError(); + + // Macintosh compatibility: NTFS automatically creates ADS entries myFile:Afp_Resource and myFile:Afp_AfpInfo, + // overwrite them silently with the versions from the source file + if (canOverwriteMACADSs && + (err == ERROR_FILE_EXISTS || err == ERROR_ALREADY_EXISTS) && + (_wcsnicmp(streamNames[i], L":Afp_Resource", 13) == 0 && + (streamNames[i][13] == 0 || streamNames[i][13] == L':') || + _wcsnicmp(streamNames[i], L":Afp_AfpInfo", 12) == 0 && + (streamNames[i][12] == 0 || streamNames[i][12] == L':'))) + { + out = CreateFileW(tgtName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, + FILE_FLAG_SEQUENTIAL_SCAN, NULL); + HANDLES_ADD_EX(__otQuiet, out != INVALID_HANDLE_VALUE, __htFile, + __hoCreateFile, out, GetLastError(), TRUE); + + canOverwriteMACADSs = FALSE; + goto COPY_OVERWRITE; + } + + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + if (*dlgData.CancelWorker) + goto CANCEL_OPEN2_ADS; + + if (dlgData.SkipAllFileADSOpenOut) + goto SKIP_OPEN_OUT_ADS; + + if (dlgData.IgnoreAllADSOpenOutErr) + goto IGNORE_OPENOUTADS; + + int ret; + ret = IDCANCEL; + char* data[4]; + data[0] = (char*)&ret; + data[1] = LoadStr(IDS_ERROROPENINGADS); + ConvertWideToUtf8(tgtName, -1, nameBuf, 2 * MAX_PATH); + nameBuf[2 * MAX_PATH - 1] = 0; + CutADSNameSuffix(nameBuf); + data[2] = nameBuf; + data[3] = GetErrorText(err); + SendMessage(hProgressDlg, WM_USER_DIALOG, 8, (LPARAM)data); + switch (ret) + { + case IDRETRY: + break; + + case IDB_IGNOREALL: + dlgData.IgnoreAllADSOpenOutErr = TRUE; // break is intentionally omitted here + case IDB_IGNORE: + { + IGNORE_OPENOUTADS: + + HANDLES(CloseHandle(in)); + operDone += fileSize; + lastTransferredFileSize += fileSize; + script->SetTFSandProgressSize(lastTransferredFileSize, totalDone + operDone); + doNextFile = TRUE; + break; + } + + case IDB_SKIPALL: + dlgData.SkipAllFileADSOpenOut = TRUE; + case IDB_SKIP: + { + SKIP_OPEN_OUT_ADS: + + HANDLES(CloseHandle(in)); + if (skip != NULL) + *skip = TRUE; + skipped = TRUE; + endProcessing = TRUE; + break; + } + + case IDCANCEL: + { + CANCEL_OPEN2_ADS: + + HANDLES(CloseHandle(in)); + doCopyADSRet = FALSE; + endProcessing = TRUE; + break; + } + } + } + if (doNextFile || endProcessing) + break; + } + } + else + { + DWORD err = GetLastError(); + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + if (*dlgData.CancelWorker) + { + doCopyADSRet = FALSE; + endProcessing = TRUE; + break; + } + + if (dlgData.SkipAllFileADSOpenIn) + goto SKIP_OPEN_IN_ADS; + + int ret; + ret = IDCANCEL; + char* data[4]; + data[0] = (char*)&ret; + data[1] = LoadStr(IDS_ERROROPENINGADS); + ConvertWideToUtf8(srcName, -1, nameBuf, 2 * MAX_PATH); + nameBuf[2 * MAX_PATH - 1] = 0; + CutADSNameSuffix(nameBuf); + data[2] = nameBuf; + data[3] = GetErrorText(err); + SendMessage(hProgressDlg, WM_USER_DIALOG, 0, (LPARAM)data); + switch (ret) + { + case IDRETRY: + break; + + case IDB_SKIPALL: + dlgData.SkipAllFileADSOpenIn = TRUE; + case IDB_SKIP: + { + SKIP_OPEN_IN_ADS: + + if (skip != NULL) + *skip = TRUE; + skipped = TRUE; + endProcessing = TRUE; + break; + } + + case IDCANCEL: + { + doCopyADSRet = FALSE; + endProcessing = TRUE; + break; + } + } + } + if (doNextFile || endProcessing) + break; + } + if (endProcessing) + break; + } + + for (i = 0; i < streamNamesCount; i++) + free(streamNames[i]); + free(streamNames); + } + else + { + if (adsWinError != NO_ERROR) // display the Windows error (low-memory warning goes only to TRACE_E) + { + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + if (*dlgData.CancelWorker) + return FALSE; + + if (dlgData.IgnoreAllADSReadErr) + goto IGNORE_ADS; + + int ret; + ret = IDCANCEL; + char* data[3]; + data[0] = (char*)&ret; + data[1] = (char*)sourceName; + data[2] = GetErrorText(adsWinError); + SendMessage(hProgressDlg, WM_USER_DIALOG, 6, (LPARAM)data); + switch (ret) + { + case IDRETRY: + goto COPY_ADS_AGAIN; + + case IDB_IGNOREALL: + dlgData.IgnoreAllADSReadErr = TRUE; // break is intentionally omitted here + case IDB_IGNORE: + { + IGNORE_ADS: + + script->SetTFSandProgressSize(finalTransferredFileSize, totalDone + operTotal); + + SetProgress(hProgressDlg, 0, CaclProg(totalDone + operTotal, script->TotalSize), dlgData); + return TRUE; + } + + case IDCANCEL: + return FALSE; + } + } + if (lowMemory) + doCopyADSRet = FALSE; // lack of memory -> cancel the operation + } + if (doCopyADSRet && skipped) + { + script->SetTFSandProgressSize(finalTransferredFileSize, totalDone + operTotal); + + SetProgress(hProgressDlg, 0, CaclProg(totalDone + operTotal, script->TotalSize), dlgData); + } + return doCopyADSRet; +} + +HANDLE SalCreateFileEx(const char* fileName, DWORD desiredAccess, + DWORD shareMode, DWORD flagsAndAttributes, BOOL* encryptionNotSupported) +{ + CStrP fileNameW(ConvertAllocUtf8ToWide(fileName, -1)); + if (fileNameW == NULL) + { + SetLastError(ERROR_NO_UNICODE_TRANSLATION); + return INVALID_HANDLE_VALUE; + } + HANDLE out = NOHANDLES(CreateFileW(fileNameW, desiredAccess, shareMode, NULL, + CREATE_NEW, flagsAndAttributes, NULL)); + if (out == INVALID_HANDLE_VALUE) + { + DWORD err = GetLastError(); + if (encryptionNotSupported != NULL && (flagsAndAttributes & FILE_ATTRIBUTE_ENCRYPTED)) + { // when the target disk cannot create an Encrypted file (observed on NTFS network disk (tested on share from XP) while logged in under a different username than we have in the system (on the current console) - the remote machine has a same-named user without a password, so it cannot be used over the network) + out = NOHANDLES(CreateFileW(fileNameW, desiredAccess, shareMode, NULL, + CREATE_NEW, (flagsAndAttributes & ~(FILE_ATTRIBUTE_ENCRYPTED | FILE_ATTRIBUTE_READONLY)), NULL)); + if (out != INVALID_HANDLE_VALUE) + { + *encryptionNotSupported = TRUE; + NOHANDLES(CloseHandle(out)); + out = INVALID_HANDLE_VALUE; + if (!DeleteFileW(fileNameW)) // XP and Vista ignore this scenario, so do the same (at worst warn user that a zero-length file was added on disk and cannot be deleted) + TRACE_I("Unable to delete testing target file: " << fileName); + } + } + if (err == ERROR_FILE_EXISTS || // check whether this is merely overwriting the DOS name + err == ERROR_ALREADY_EXISTS || + err == ERROR_ACCESS_DENIED) + { + WIN32_FIND_DATAW data; + HANDLE find = HANDLES_Q(FindFirstFileW(fileNameW, &data)); + if (find != INVALID_HANDLE_VALUE) + { + HANDLES(FindClose(find)); + if (err != ERROR_ACCESS_DENIED || (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) + { + const char* tgtName = SalPathFindFileName(fileName); + char altName[MAX_PATH]; + char fullName[MAX_PATH]; + if (ConvertWideToUtf8(data.cAlternateFileName, -1, altName, _countof(altName)) == 0) + altName[0] = 0; + if (ConvertWideToUtf8(data.cFileName, -1, fullName, _countof(fullName)) == 0) + fullName[0] = 0; + if (StrICmp(tgtName, altName) == 0 && // match only for DOS name + StrICmp(tgtName, fullName) != 0) // (full name differs) + { + // rename ("tidy up") the file/directory with the conflicting DOS name to a temporary 8.3 name (no extra DOS name needed) + char tmpName[MAX_PATH + 20]; + if (strlen(fileName) >= _countof(tmpName)) + { + TRACE_E("SalCreateFileEx(): path too long for DOS-name collision workaround: " << fileName); + } + else + { + lstrcpyn(tmpName, fileName, _countof(tmpName)); + CutDirectory(tmpName); + SalPathAddBackslash(tmpName, _countof(tmpName)); + char* tmpNamePart = tmpName + strlen(tmpName); + char origFullName[MAX_PATH + 20]; + if (SalPathAppend(tmpName, fullName, _countof(tmpName))) + { + strcpy(origFullName, tmpName); + DWORD num = (GetTickCount() / 10) % 0xFFF; + DWORD origFullNameAttr = SalGetFileAttributes(origFullName); + while (1) + { + sprintf(tmpNamePart, "sal%03X", num++); + if (SalMoveFile(origFullName, tmpName)) + break; + DWORD e = GetLastError(); + if (e != ERROR_FILE_EXISTS && e != ERROR_ALREADY_EXISTS) + { + tmpName[0] = 0; + break; + } + } + if (tmpName[0] != 0) // if we successfully "tidied" the conflicting file, try creating + { // the target file again, then restore the original name + out = NOHANDLES(CreateFileW(fileNameW, desiredAccess, shareMode, NULL, + CREATE_NEW, flagsAndAttributes, NULL)); + if (out == INVALID_HANDLE_VALUE && encryptionNotSupported != NULL && + (flagsAndAttributes & FILE_ATTRIBUTE_ENCRYPTED)) + { // when the target disk cannot create an Encrypted file (observed on NTFS network disk (tested on share from XP) while logged in under a different username than we have in the system (on the current console) - the remote machine has a same-named user without a password, so it cannot be used over the network) + out = NOHANDLES(CreateFileW(fileNameW, desiredAccess, shareMode, NULL, + CREATE_NEW, (flagsAndAttributes & ~(FILE_ATTRIBUTE_ENCRYPTED | FILE_ATTRIBUTE_READONLY)), NULL)); + if (out != INVALID_HANDLE_VALUE) + { + *encryptionNotSupported = TRUE; + NOHANDLES(CloseHandle(out)); + out = INVALID_HANDLE_VALUE; + if (!DeleteFileW(fileNameW)) // XP and Vista ignore this scenario, so do the same (at worst warn user that a zero-length file was added on disk and cannot be deleted) + TRACE_E("Unable to delete testing target file: " << fileName); + } + } + if (!SalMoveFile(tmpName, origFullName)) + { // this apparently can happen; inexplicably, Windows creates a file named origFullName instead of fileName (the DOS name) + TRACE_I("Unexpected situation in SalCreateFileEx(): unable to rename file from tmp-name to original long file name! " << origFullName); + + if (out != INVALID_HANDLE_VALUE) + { + NOHANDLES(CloseHandle(out)); + out = INVALID_HANDLE_VALUE; + DeleteFileW(fileNameW); + if (!SalMoveFile(tmpName, origFullName)) + TRACE_E("Fatal unexpected situation in SalCreateFileEx(): unable to rename file from tmp-name to original long file name! " << origFullName); + } + } + else + { + if ((origFullNameAttr & FILE_ATTRIBUTE_ARCHIVE) == 0) + { + CStrP origFullNameW(ConvertAllocUtf8ToWide(origFullName, -1)); + if (origFullNameW != NULL) + SetFileAttributesW(origFullNameW, origFullNameAttr); // leave without extra handling or retry; not critical (normally toggles unpredictably) + } + } + } + } + else + TRACE_E("SalCreateFileEx(): Original full file name is too long, unable to bypass only-dos-name-overwrite problem!"); + } + } + } + } + } + if (out == INVALID_HANDLE_VALUE) + SetLastError(err); + } + return out; +} + +BOOL SyncOrAsyncDeviceIoControl(CAsyncCopyParams* asyncPar, HANDLE hDevice, DWORD dwIoControlCode, + LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer, + DWORD nOutBufferSize, LPDWORD lpBytesReturned, DWORD* err) +{ + if (asyncPar->UseAsyncAlg) // asynchronous variant + { + if (!DeviceIoControl(hDevice, dwIoControlCode, lpInBuffer, nInBufferSize, lpOutBuffer, + nOutBufferSize, NULL, asyncPar->InitOverlapped(0)) && + GetLastError() != ERROR_IO_PENDING || + !GetOverlappedResult(hDevice, asyncPar->GetOverlapped(0), lpBytesReturned, TRUE)) + { // error, return FALSE + *err = GetLastError(); + return FALSE; + } + } + else // synchronous variant + { + if (!DeviceIoControl(hDevice, dwIoControlCode, lpInBuffer, nInBufferSize, lpOutBuffer, + nOutBufferSize, lpBytesReturned, NULL)) + { // error, return FALSE + *err = GetLastError(); + return FALSE; + } + } + *err = NO_ERROR; + return TRUE; +} + +void SetCompressAndEncryptedAttrs(const char* name, DWORD attr, HANDLE* out, BOOL openAlsoForRead, + BOOL* encryptionNotSupported, CAsyncCopyParams* asyncPar) +{ + if (*out != INVALID_HANDLE_VALUE) + { + DWORD err = NO_ERROR; + DWORD curAttr = SalGetFileAttributes(name); + if ((curAttr == INVALID_FILE_ATTRIBUTES || + (attr & FILE_ATTRIBUTE_COMPRESSED) != (curAttr & FILE_ATTRIBUTE_COMPRESSED)) && + (attr & FILE_ATTRIBUTE_COMPRESSED) == 0) + { + USHORT state = COMPRESSION_FORMAT_NONE; + ULONG length; + if (!SyncOrAsyncDeviceIoControl(asyncPar, *out, FSCTL_SET_COMPRESSION, &state, + sizeof(USHORT), NULL, 0, &length, &err)) + { + TRACE_I("SetCompressAndEncryptedAttrs(): Unable to set Compressed attribute for " << name << "! error=" << GetErrorText(err)); + } + } + if (curAttr == INVALID_FILE_ATTRIBUTES || + (attr & FILE_ATTRIBUTE_ENCRYPTED) != (curAttr & FILE_ATTRIBUTE_ENCRYPTED)) + { // SalCreateFileEx above likely failed + err = NO_ERROR; + HANDLES(CloseHandle(*out)); // close the file; otherwise we cannot change its encrypted attribute + CStrP nameW(ConvertAllocUtf8ToWide(name, -1)); + if (nameW == NULL) + err = ERROR_NO_UNICODE_TRANSLATION; + if (attr & FILE_ATTRIBUTE_ENCRYPTED) + { + if (err == NO_ERROR && !EncryptFileW(nameW)) + { + err = GetLastError(); + if (encryptionNotSupported != NULL) + *encryptionNotSupported = TRUE; + } + } + else + { + if (err == NO_ERROR && !DecryptFileW(nameW, 0)) + err = GetLastError(); + } + if (err != NO_ERROR) + TRACE_I("SetCompressAndEncryptedAttrs(): Unable to set Encrypted attribute for " << name << "! error=" << GetErrorText(err)); + // reopen the existing file to continue writing + if (err == NO_ERROR) + { + *out = HANDLES_Q(CreateFileW(nameW, GENERIC_WRITE | (openAlsoForRead ? GENERIC_READ : 0), 0, NULL, OPEN_ALWAYS, + asyncPar->GetOverlappedFlag() | FILE_FLAG_SEQUENTIAL_SCAN, NULL)); + } + else + *out = INVALID_HANDLE_VALUE; + if (openAlsoForRead && *out == INVALID_HANDLE_VALUE) // problem: reopening failed, try write-only + { + *out = HANDLES_Q(CreateFileW(nameW, GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, + asyncPar->GetOverlappedFlag() | FILE_FLAG_SEQUENTIAL_SCAN, NULL)); + } + if (*out == INVALID_HANDLE_VALUE) // still a problem: cannot reopen; delete it + report an error + { + err = GetLastError(); + if (nameW != NULL) + DeleteFileW(nameW); + SetLastError(err); + } + } + if (*out != INVALID_HANDLE_VALUE && // only when reopening succeeded (and we did not delete the file) + (curAttr == INVALID_FILE_ATTRIBUTES || + (attr & FILE_ATTRIBUTE_COMPRESSED) != (curAttr & FILE_ATTRIBUTE_COMPRESSED)) && + (attr & FILE_ATTRIBUTE_COMPRESSED) != 0) + { + USHORT state = COMPRESSION_FORMAT_DEFAULT; + ULONG length; + if (!SyncOrAsyncDeviceIoControl(asyncPar, *out, FSCTL_SET_COMPRESSION, &state, + sizeof(USHORT), NULL, 0, &length, &err)) + { + TRACE_I("SetCompressAndEncryptedAttrs(): Unable to set Compressed attribute for " << name << "! error=" << GetErrorText(err)); + } + } + } +} + +void CorrectCaseOfTgtName(char* tgtName, BOOL dataRead, WIN32_FIND_DATAW* data) +{ + if (!dataRead) + { + CStrP tgtNameW(ConvertAllocUtf8ToWide(tgtName, -1)); + if (tgtNameW == NULL) + return; + HANDLE find = HANDLES_Q(FindFirstFileW(tgtNameW, data)); + if (find != INVALID_HANDLE_VALUE) + HANDLES(FindClose(find)); + else + return; // failed to read data for the target file; abort + } + char dataName[MAX_PATH]; + if (ConvertWideToUtf8(data->cFileName, -1, dataName, _countof(dataName)) == 0) + return; + int len = (int)strlen(dataName); + int tgtNameLen = (int)strlen(tgtName); + if (tgtNameLen >= len && StrICmp(tgtName + tgtNameLen - len, dataName) == 0) + memcpy(tgtName + tgtNameLen - len, dataName, len); +} + +void SetTFSandPSforSkippedFile(COperation* op, CQuadWord& lastTransferredFileSize, + COperations* script, const CQuadWord& pSize) +{ + if (op->FileSize < COPY_MIN_FILE_SIZE) + { + lastTransferredFileSize += op->FileSize; // file size + if (op->Size > COPY_MIN_FILE_SIZE) // should always be at least COPY_MIN_FILE_SIZE, but be safe... + lastTransferredFileSize += op->Size - COPY_MIN_FILE_SIZE; // add the ADS size + } + else + lastTransferredFileSize += op->Size; // file size + ADS + script->SetTFSandProgressSize(lastTransferredFileSize, pSize); +} + +void DoCopyFileLoopOrig(HANDLE& in, HANDLE& out, void* buffer, int& limitBufferSize, + COperations* script, CProgressDlgData& dlgData, BOOL wholeFileAllocated, + COperation* op, const CQuadWord& totalDone, BOOL& copyError, BOOL& skipCopy, + HWND hProgressDlg, CQuadWord& operationDone, CQuadWord& fileSize, + int bufferSize, int& allocWholeFileOnStart, BOOL& copyAgain) +{ + int autoRetryAttemptsSNAP = 0; + DWORD read; + DWORD written; + while (1) + { + if (ReadFile(in, buffer, limitBufferSize, &read, NULL)) + { + autoRetryAttemptsSNAP = 0; + if (read == 0) + break; // EOF + if (!script->ChangeSpeedLimit) // when the speed limit can change, this is not a suitable wait point + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + if (*dlgData.CancelWorker) + { + copyError = TRUE; // goto COPY_ERROR + return; + } + + while (1) + { + if (WriteFile(out, buffer, read, &written, NULL) && + read == written) + { + break; + } + + WRITE_ERROR: + + DWORD err; + err = GetLastError(); + + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + if (*dlgData.CancelWorker) + { + copyError = TRUE; // goto COPY_ERROR + return; + } + + if (dlgData.SkipAllFileWrite) + { + skipCopy = TRUE; // goto SKIP_COPY + return; + } + + int ret; + ret = IDCANCEL; + char* data[4]; + data[0] = (char*)&ret; + data[1] = LoadStr(IDS_ERRORWRITINGFILE); + data[2] = op->TargetName; + if (err == NO_ERROR && read != written) + err = ERROR_DISK_FULL; + data[3] = GetErrorText(err); + SendMessage(hProgressDlg, WM_USER_DIALOG, 0, (LPARAM)data); + switch (ret) + { + case IDRETRY: // on a network we must reopen the handle; local access forbids it due to sharing + { + if (out != NULL) + { + if (wholeFileAllocated) + SetEndOfFile(out); // otherwise on a floppy the remaining bytes would be written + HANDLES(CloseHandle(out)); // close the invalid handle + } + out = HANDLES_Q(CreateFileUtf8(op->TargetName, GENERIC_WRITE | GENERIC_READ, 0, NULL, + OPEN_ALWAYS, FILE_FLAG_SEQUENTIAL_SCAN, NULL)); + if (out != INVALID_HANDLE_VALUE) // opened successfully; now adjust the offset + { + LONG lo, hi; + lo = GetFileSize(out, (DWORD*)&hi); + if (lo == INVALID_FILE_SIZE && GetLastError() != NO_ERROR || // cannot obtain the size + CQuadWord(lo, hi) < operationDone || // file is too small + wholeFileAllocated && CQuadWord(lo, hi) > fileSize && + CQuadWord(lo, hi) > operationDone + CQuadWord(read, 0) || // pre-allocated file is too large (beyond the reserved size and beyond the written portion including the current block) = extra bytes were appended (allocWholeFileOnStart should be 0 /* need-test */) + !CheckTailOfOutFile(NULL, in, out, operationDone, operationDone + CQuadWord(read, 0), FALSE)) + { // restart the whole operation + HANDLES(CloseHandle(in)); + HANDLES(CloseHandle(out)); + DeleteFileUtf8(op->TargetName); + copyAgain = TRUE; // goto COPY_AGAIN; + return; + } + } + else // still cannot open; problem persists + { + out = NULL; + goto WRITE_ERROR; + } + break; + } + + case IDB_SKIPALL: + dlgData.SkipAllFileWrite = TRUE; + case IDB_SKIP: + { + skipCopy = TRUE; // goto SKIP_COPY + return; + } + + case IDCANCEL: + { + copyError = TRUE; // goto COPY_ERROR + return; + } + } + } + if (!script->ChangeSpeedLimit) // when the speed limit can change, this is not a suitable wait point + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + if (*dlgData.CancelWorker) + { + copyError = TRUE; // goto COPY_ERROR + return; + } + + script->AddBytesToSpeedMetersAndTFSandPS(read, FALSE, bufferSize, &limitBufferSize); + + if (!script->ChangeSpeedLimit) // when the speed limit can change, this is not a suitable wait point + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + operationDone += CQuadWord(read, 0); + SetProgressWithoutSuspend(hProgressDlg, CaclProg(operationDone, op->Size), + CaclProg(totalDone + operationDone, script->TotalSize), dlgData); + + if (script->ChangeSpeedLimit) // speed limit may change; this is the right place to wait until the + { // worker resumes and fetches a fresh copy buffer size + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + script->GetNewBufSize(&limitBufferSize, bufferSize); + } + } + else + { + READ_ERROR: + + DWORD err; + err = GetLastError(); + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + if (*dlgData.CancelWorker) + { + copyError = TRUE; // goto COPY_ERROR + return; + } + + if (dlgData.SkipAllFileRead) + { + skipCopy = TRUE; // goto SKIP_COPY + return; + } + + if (err == ERROR_NETNAME_DELETED && ++autoRetryAttemptsSNAP <= 3) + { // on SNAP server reading sometimes randomly fails with ERROR_NETNAME_DELETED; Retry button reportedly helps, so trigger it automatically + Sleep(100); + goto RETRY_COPY; + } + + int ret; + ret = IDCANCEL; + char* data[4]; + data[0] = (char*)&ret; + data[1] = LoadStr(IDS_ERRORREADINGFILE); + data[2] = op->SourceName; + data[3] = GetErrorText(err); + SendMessage(hProgressDlg, WM_USER_DIALOG, 0, (LPARAM)data); + switch (ret) + { + case IDRETRY: + { + RETRY_COPY: + + if (in != NULL) + HANDLES(CloseHandle(in)); // close the invalid handle + in = HANDLES_Q(CreateFileUtf8(op->SourceName, GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, + OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL)); + if (in != INVALID_HANDLE_VALUE) // opened successfully; now adjust the offset + { + LONG lo, hi; + lo = GetFileSize(in, (DWORD*)&hi); + if (lo == INVALID_FILE_SIZE && GetLastError() != NO_ERROR || + CQuadWord(lo, hi) < operationDone || + !CheckTailOfOutFile(NULL, in, out, operationDone, operationDone, TRUE)) + { // cannot obtain the size or the file is too small; restart the whole operation + HANDLES(CloseHandle(in)); + if (wholeFileAllocated) + SetEndOfFile(out); // otherwise on a floppy the remaining bytes would be written + HANDLES(CloseHandle(out)); + DeleteFileUtf8(op->TargetName); + copyAgain = TRUE; // goto COPY_AGAIN; + return; + } + } + else // still cannot open; problem persists + { + in = NULL; + goto READ_ERROR; + } + break; + } + + case IDB_SKIPALL: + dlgData.SkipAllFileRead = TRUE; + case IDB_SKIP: + { + skipCopy = TRUE; // goto SKIP_COPY + return; + } + + case IDCANCEL: + { + copyError = TRUE; // goto COPY_ERROR + return; + } + } + } + } + + if (wholeFileAllocated) // we pre-allocated the complete file layout (meaning the allocation was useful; for example, the file cannot be empty) + { + if (operationDone < fileSize) // and the source file shrank + { + if (!SetEndOfFile(out)) // trim it here + { + written = read = 0; + goto WRITE_ERROR; + } + } + + if (allocWholeFileOnStart == 0 /* need-test */) + { + CQuadWord curFileSize; + curFileSize.LoDWord = GetFileSize(out, &curFileSize.HiDWord); + BOOL getFileSizeSuccess = (curFileSize.LoDWord != INVALID_FILE_SIZE || GetLastError() == NO_ERROR); + if (getFileSizeSuccess && curFileSize == operationDone) + { // verify that no extra bytes were appended at the end and that truncation works + allocWholeFileOnStart = 1 /* yes */; + } + else + { +#ifdef _DEBUG + if (getFileSizeSuccess) + { + char num1[50]; + char num2[50]; + TRACE_E("DoCopyFileLoopOrig(): unable to allocate whole file size before copy operation, please report " + "under what conditions this occurs! Error: different file sizes: target=" + << NumberToStr(num1, curFileSize) << " bytes, source=" << NumberToStr(num2, operationDone) << " bytes"); + } + else + { + DWORD err = GetLastError(); + TRACE_E("DoCopyFileLoopOrig(): unable to test result of allocation of whole file size before copy operation, please report " + "under what conditions this occurs! GetFileSize(" + << op->TargetName << ") error: " << GetErrorText(err)); + } +#endif + allocWholeFileOnStart = 2 /* no */; // skip further attempts on this target disk + + HANDLES(CloseHandle(out)); + out = NULL; + ClearReadOnlyAttr(op->TargetName); // if it somehow became read-only (should never happen), so we know how to handle it + if (DeleteFileUtf8(op->TargetName)) + { + HANDLES(CloseHandle(in)); + copyAgain = TRUE; // goto COPY_AGAIN; + return; + } + else + { + written = read = 0; + goto WRITE_ERROR; + } + } + } + } +} + +enum CCopy_BlkState +{ + cbsFree, // block not in use + cbsRead, // reading source file blocks - completed (waiting to be written) + cbsInProgress, // --- states below mean "waiting for the operation to finish" (completed states are above) + cbsReading, // reading source file blocks - requested (in progress) + cbsTestingEOF, // checking the end of the source file + cbsWriting, // writing the block to the target file + cbsDiscarded, // attempted to read beyond the end of the source file (should only return error: EOF) +}; + +enum CCopy_ForceOp +{ + fopNotUsed, // free to read or write as needed + fopReading, // forced to read + fopWriting // forced to write +}; + +struct CCopy_Context +{ + CAsyncCopyParams* AsyncPar; + + CCopy_ForceOp ForceOp; // TRUE = must read now, FALSE = must write now + BOOL ReadingDone; // TRUE = the source file has been fully read + CCopy_BlkState BlockState[8]; // block state + DWORD BlockDataLen[8]; // for each block: expected data (cbsReading + cbsTestingEOF), valid data (cbsWriting) + CQuadWord BlockOffset[8]; // for each block: block offset in the source/target file (also stored in the 'AsyncPar' OVERLAPPED) + DWORD BlockTime[8]; // for each block: "time" when the last async operation in this block started + DWORD CurTime; // "time" counter for 'BlockTime', handles wrap-around (though unlikely) + int FreeBlocks; // current number of free blocks (cbsFree) + int FreeBlockIndex; // candidate index of a free block (cbsFree); must be verified + int ReadingBlocks; // current number of blocks being read(cbsReading and cbsTestingEOF) + int WritingBlocks; // current number of blocks being written (cbsWriting) + CQuadWord ReadOffset; // offset for reading the next block from the source file (previous one is already in progress) + CQuadWord WriteOffset; // offset for writing the next block to the target file (previous one is already in progress) + int AutoRetryAttemptsSNAP; // number of automatic Retry attempts (max 3): SNAP servers sporadically return ERROR_NETNAME_DELETED while reading, Retry button reportedly helps, so trigger it automatically + + // selected DoCopyFileLoopAsync parameters to avoid passing a long argument list everywhere + CProgressDlgData* DlgData; + COperation* Op; + HWND HProgressDlg; + HANDLE* In; + HANDLE* Out; + BOOL WholeFileAllocated; + COperations* Script; + CQuadWord* OperationDone; + const CQuadWord* TotalDone; + const CQuadWord* LastTransferredFileSize; + + CCopy_Context(CAsyncCopyParams* asyncPar, int numOfBlocks, CProgressDlgData* dlgData, COperation* op, + HWND hProgressDlg, HANDLE* in, HANDLE* out, BOOL wholeFileAllocated, COperations* script, + CQuadWord* operationDone, const CQuadWord* totalDone, const CQuadWord* lastTransferredFileSize) + { + AsyncPar = asyncPar; + ForceOp = fopNotUsed; + ReadingDone = FALSE; + CurTime = 0; + for (int i = 0; i < _countof(BlockState); i++) + BlockState[i] = cbsFree; + memset(BlockDataLen, 0, sizeof(BlockDataLen)); + memset(BlockOffset, 0, sizeof(BlockOffset)); + memset(BlockTime, 0, sizeof(BlockTime)); + FreeBlocks = numOfBlocks; + FreeBlockIndex = 0; + ReadingBlocks = 0; + WritingBlocks = 0; + ReadOffset.SetUI64(0); + WriteOffset.SetUI64(0); + AutoRetryAttemptsSNAP = 0; + + DlgData = dlgData; + Op = op; + HProgressDlg = hProgressDlg; + In = in; + Out = out; + WholeFileAllocated = wholeFileAllocated; + Script = script; + OperationDone = operationDone; + TotalDone = totalDone; + LastTransferredFileSize = lastTransferredFileSize; + } + + BOOL IsOperationDone(int numOfBlocks) + { + return ReadingDone && FreeBlocks == numOfBlocks; + } + + BOOL StartReading(int blkIndex, DWORD readSize, DWORD* err, BOOL testEOF); + BOOL StartWriting(int blkIndex, DWORD* err); + int FindBlock(CCopy_BlkState state); + void FreeBlock(int blkIndex); + void DiscardBlocksBehindEOF(const CQuadWord& fileSize, int excludeIndex); + void GetNewFileSize(const char* fileName, HANDLE file, CQuadWord* fileSize, const CQuadWord& minFileSize); + + BOOL HandleReadingErr(int blkIndex, DWORD err, BOOL* copyError, BOOL* skipCopy, BOOL* copyAgain); + BOOL HandleWritingErr(int blkIndex, DWORD err, BOOL* copyError, BOOL* skipCopy, BOOL* copyAgain, + const CQuadWord& allocFileSize, const CQuadWord& maxWriteOffset); + + // interrupts any pending asynchronous operations + void CancelOpPhase1(); + // ensures that all asynchronous operations have really finished + positions the pointer at the end of the contiguous + // portion of the target file so the file is truncated correctly (before a possible closing and deletion) + // WARNING: frees unnecessary blocks; only those with data read from the input file remain, and they still + // follow WriteOffset (usable for retry) + void CancelOpPhase2(int errBlkIndex); + BOOL RetryCopyReadErr(DWORD* err, BOOL* copyAgain, BOOL* errAgain); + BOOL RetryCopyWriteErr(DWORD* err, BOOL* copyAgain, BOOL* errAgain, const CQuadWord& allocFileSize, + const CQuadWord& maxWriteOffset); + BOOL HandleSuspModeAndCancel(BOOL* copyError); +}; + +BOOL DisableLocalBuffering(CAsyncCopyParams* asyncPar, HANDLE file, DWORD* err) +{ + CALL_STACK_MESSAGE1("DisableLocalBuffering()"); + if (DynNtFsControlFile != NULL) // "always true" + { + IO_STATUS_BLOCK ioStatus; + ResetEvent(asyncPar->Overlapped[0].hEvent); + ULONG status = DynNtFsControlFile(file, asyncPar->Overlapped[0].hEvent, NULL, + 0, &ioStatus, 0x00140390 /* IOCTL_LMR_DISABLE_LOCAL_BUFFERING */, + NULL, 0, NULL, 0); + if (status == STATUS_PENDING) // must wait for the operation to finish; it runs asynchronously + { + CALL_STACK_MESSAGE1("DisableLocalBuffering(): STATUS_PENDING"); + WaitForSingleObject(asyncPar->Overlapped[0].hEvent, INFINITE); + status = ioStatus.Status; + } + if (status == 0 /* STATUS_SUCCESS */) + return TRUE; + *err = LsaNtStatusToWinError(status); + } + else + *err = ERROR_INVALID_FUNCTION; + return FALSE; +} + +BOOL CCopy_Context::StartReading(int blkIndex, DWORD readSize, DWORD* err, BOOL testEOF) +{ +#ifdef ASYNC_COPY_DEBUG_MSG + char sss[1000]; + sprintf(sss, "ReadFile: %d 0x%08X 0x%08X", blkIndex, ReadOffset.LoDWord, readSize); + TRACE_I(sss); +#endif // ASYNC_COPY_DEBUG_MSG + + if (!ReadFile(*In, AsyncPar->Buffers[blkIndex], readSize, NULL, + AsyncPar->InitOverlappedWithOffset(blkIndex, ReadOffset)) && + GetLastError() != ERROR_IO_PENDING) + { // a read error occurred; handle it + *err = GetLastError(); + if (*err == ERROR_HANDLE_EOF) // synchronously reported EOF; convert it to an asynchronously reported EOF + AsyncPar->SetOverlappedToEOF(blkIndex, ReadOffset); + else + return FALSE; + } + // if the read was completed synchronously (or via cache, which we cannot detect), + // we must write something now; otherwise writing may idle and slow down the whole operation + BOOL opCompleted = HasOverlappedIoCompleted(AsyncPar->GetOverlapped(blkIndex)); + ForceOp = opCompleted ? fopWriting : fopNotUsed; + +#ifdef ASYNC_COPY_DEBUG_MSG + TRACE_I("ReadFile result: " << (opCompleted ? "DONE" : "ASYNC")); +#endif // ASYNC_COPY_DEBUG_MSG + + if (opCompleted && !Script->ChangeSpeedLimit) // when the speed limit can change, this is not a suitable wait point + WaitForSingleObject(DlgData->WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + if (*DlgData->CancelWorker) + { + *err = ERROR_CANCELLED; + return FALSE; // cancellation will be handled in the error-handling + } + + BlockOffset[blkIndex] = ReadOffset; + BlockDataLen[blkIndex] = readSize; + if (!testEOF) // block was cbsFree before calling this method + { + ReadOffset.Value += readSize; + BlockState[blkIndex] = cbsReading; + } + else + BlockState[blkIndex] = cbsTestingEOF; + BlockTime[blkIndex] = CurTime++; + FreeBlocks--; + ReadingBlocks++; + return TRUE; +} + +BOOL CCopy_Context::StartWriting(int blkIndex, DWORD* err) +{ +#ifdef ASYNC_COPY_DEBUG_MSG + char sss[1000]; + sprintf(sss, "WriteFile: %d 0x%08X 0x%08X", blkIndex, WriteOffset.LoDWord, BlockDataLen[blkIndex]); + TRACE_I(sss); +#endif // ASYNC_COPY_DEBUG_MSG + + if (!WriteFile(*Out, AsyncPar->Buffers[blkIndex], BlockDataLen[blkIndex], NULL, + AsyncPar->InitOverlappedWithOffset(blkIndex, WriteOffset)) && + GetLastError() != ERROR_IO_PENDING) + { // a write error occurred; handle it + *err = GetLastError(); + return FALSE; + } + // if the write was completed synchronously (or via cache, which we cannot detect), + // we must read something now; otherwise reading may idle and slow down the whole operation + BOOL opCompleted = HasOverlappedIoCompleted(AsyncPar->GetOverlapped(blkIndex)); + ForceOp = !ReadingDone && opCompleted ? fopReading : fopNotUsed; + +#ifdef ASYNC_COPY_DEBUG_MSG + TRACE_I("WriteFile result: " << (opCompleted ? "DONE" : "ASYNC")); +#endif // ASYNC_COPY_DEBUG_MSG + + if (opCompleted && !Script->ChangeSpeedLimit) // when the speed limit can change, this is not a suitable wait point + WaitForSingleObject(DlgData->WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + if (*DlgData->CancelWorker) + { + *err = ERROR_CANCELLED; + return FALSE; // cancellation will be handled in the error-handling + } + + WriteOffset.Value += BlockDataLen[blkIndex]; + BlockState[blkIndex] = cbsWriting; // block was cbsRead before calling this method + BlockTime[blkIndex] = CurTime++; + WritingBlocks++; + return TRUE; +} + +int CCopy_Context::FindBlock(CCopy_BlkState state) +{ + for (int i = 0; i < _countof(BlockState); i++) + if (BlockState[i] == state) + return i; + TRACE_C("CCopy_Context::FindBlock(): unable to find block with required state (" << (int)state << ")."); + return -1; // dead code, only for the compiler +} + +void CCopy_Context::FreeBlock(int blkIndex) +{ + if (BlockState[blkIndex] == cbsReading || BlockState[blkIndex] == cbsTestingEOF) + ReadingBlocks--; + if (BlockState[blkIndex] == cbsWriting) + WritingBlocks--; + BlockState[blkIndex] = cbsFree; + FreeBlockIndex = blkIndex; + FreeBlocks++; +} + +void CCopy_Context::DiscardBlocksBehindEOF(const CQuadWord& fileSize, int excludeIndex) +{ + for (int i = 0; i < _countof(BlockState); i++) + { + if (i == excludeIndex) + continue; + CCopy_BlkState st = BlockState[i]; + if ((st == cbsRead || st == cbsReading) && BlockOffset[i] >= fileSize) + { + if (st == cbsRead) // discard data read beyond the end of the file; they are useless + FreeBlock(i); + else + { + BlockState[i] = cbsDiscarded; // reading past the end of the file is pointless; no reason to adjust BlockTime + ReadingBlocks--; + } + } + } +} + +void CCopy_Context::GetNewFileSize(const char* fileName, HANDLE file, CQuadWord* fileSize, const CQuadWord& minFileSize) +{ + fileSize->LoDWord = GetFileSize(file, &fileSize->HiDWord); + if (fileSize->LoDWord == INVALID_FILE_SIZE && GetLastError() != NO_ERROR) + { + DWORD err = GetLastError(); + TRACE_E("CCopy_Context::GetNewFileSize(): GetFileSize(" << fileName << "): unexpected error: " << GetErrorText(err)); + *fileSize = minFileSize; + } + else + { + if (*fileSize < minFileSize) // if GetFileSize happened to return a shorter length than already read + *fileSize = minFileSize; + } +} + +void CCopy_Context::CancelOpPhase1() +{ + if (!CancelIo(*In)) + { + DWORD err = GetLastError(); + TRACE_E("CCopy_Context::CancelOpPhase1(): CancelIo(IN) failed, error: " << GetErrorText(err)); + } + if (*Out != NULL && !CancelIo(*Out)) + { + DWORD err = GetLastError(); + TRACE_E("CCopy_Context::CancelOpPhase1(): CancelIo(OUT) failed, error: " << GetErrorText(err)); + } +} + +void CCopy_Context::CancelOpPhase2(int errBlkIndex) +{ + // NOTE: errBlkIndex == -1 for errors when issuing an async reading (no block assigned), + // for errors while truncating the file after the main copy loop finished (no block assigned), + // or for Cancel in the progress dialog (no block assigned) + + DWORD bytes; + for (int i = 0; i < _countof(BlockState); i++) + { + if (BlockState[i] > cbsInProgress) + { // GetOverlappedResult should return results immediately because CancelIo() was called for both files + if (GetOverlappedResult(BlockState[i] == cbsWriting ? *Out : *In, AsyncPar->GetOverlapped(i), &bytes, TRUE)) + { + if (BlockState[i] == cbsReading && BlockDataLen[i] == bytes) // fully read -> convert to cbsRead block + { + BlockState[i] = cbsRead; + ReadingBlocks--; + } + else + { + if (BlockState[i] == cbsWriting && BlockDataLen[i] == bytes) // fully written -> convert to cbsRead block (might write again, so keep it) + { + BlockState[i] = cbsRead; + WritingBlocks--; + } + } + } + else + { + DWORD err = GetLastError(); + if (i != errBlkIndex && // already reporting the error for this block; no need to repeat it in TRACE + err != ERROR_OPERATION_ABORTED) // not an error, merely reports cancellation (CancelIo() call) + { // log issues in other blocks, usually harmless and best ignored + TRACE_I("CCopy_Context::CancelOpPhase2(): GetOverlappedResult(" << (BlockState[i] == cbsWriting ? "OUT" : "IN") << ", " << i << ") returned error: " << GetErrorText(err)); + } + } + switch (BlockState[i]) + { + case cbsReading: // not fully read + case cbsTestingEOF: // EOF test not finished + case cbsDiscarded: + FreeBlock(i); + break; + + case cbsWriting: // unwritten block + if (WriteOffset > BlockOffset[i]) // lower WriteOffset if needed + WriteOffset = BlockOffset[i]; + BlockState[i] = cbsRead; // not fully written but already read -> convert to cbsRead block (might write again, so keep it) + WritingBlocks--; + break; + } + } + } + + ReadOffset = WriteOffset; // determine how far we have contiguous data from the offset where writing should resume + for (int i = 0; i < _countof(BlockState); i++) + { + if (BlockState[i] == cbsRead && BlockOffset[i] == ReadOffset) // block read directly after ReadOffset + { + ReadOffset.Value += BlockDataLen[i]; + i = -1; // start the search from the beginning again (with 8 blocks this is affordable, max 36 loop iterations) + } + } + + // drop blocks that are already written or too far ahead (not contiguous) + // so they can be read again later + for (int i = 0; i < _countof(BlockState); i++) + if (BlockState[i] == cbsRead && (BlockOffset[i] < WriteOffset || BlockOffset[i] > ReadOffset)) + FreeBlock(i); + + // when deleting the target file, set the file pointer to the end of the written portion; + // the caller will truncate it with SetEndOfFile before deletion (otherwise zeroes might be written + // from the end of the written part to the end of the pre-allocated file - pre-allocation is + // used to prevent fragmentation) + if (*Out != NULL) // only if the target file was not closed meanwhile + { + if (!SalSetFilePointer(*Out, WriteOffset)) + { + DWORD err = GetLastError(); + TRACE_E("CCopy_Context::CancelOpPhase2(): unable to set file pointer in OUT file, error: " << GetErrorText(err)); + } + } +} + +BOOL CCopy_Context::RetryCopyReadErr(DWORD* err, BOOL* copyAgain, BOOL* errAgain) +{ + if (*In != NULL) + HANDLES(CloseHandle(*In)); // close the invalid handle + *In = HANDLES_Q(CreateFileUtf8(Op->SourceName, GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, + OPEN_EXISTING, AsyncPar->GetOverlappedFlag() | FILE_FLAG_SEQUENTIAL_SCAN, NULL)); + if (*In != INVALID_HANDLE_VALUE) // opened successfully; now adjust the offset + { + CQuadWord size; + size.LoDWord = GetFileSize(*In, (DWORD*)&size.HiDWord); + if ((size.LoDWord != INVALID_FILE_SIZE || GetLastError() == NO_ERROR) && size >= ReadOffset) + { // size obtained and the file is large enough + // if the source is on a network: disable local client-side in-memory caching + // http://msdn.microsoft.com/en-us/library/ee210753%28v=vs.85%29.aspx + // + // using Overlapped[0].hEvent from AsyncPar is OK; nothing is "in-progress" now, the event is unused + // (but WARNING: for example Buffers[0] from AsyncPar may still be in use) + if ((Op->OpFlags & OPFL_SRCPATH_IS_NET) && !DisableLocalBuffering(AsyncPar, *In, err)) + TRACE_E("CCopy_Context::RetryCopyReadErr(): IOCTL_LMR_DISABLE_LOCAL_BUFFERING failed for network source file: " << Op->SourceName << ", error: " << GetErrorText(*err)); + // using Overlapped[0 and 1].hEvent and Overlapped[0 and 1] from AsyncPar is OK; nothing is + // "in-progress", the event nor the overlapped structures are used (but WARNING: for example Buffers[0] + // from AsyncPar may still be in use) + if (CheckTailOfOutFile(AsyncPar, *In, *Out, WriteOffset, WriteOffset, TRUE)) + { + ForceOp = ReadOffset > WriteOffset ? fopWriting : fopNotUsed; // if the read side is ahead, resume with writing + *OperationDone = WriteOffset; + Script->SetTFSandProgressSize(*LastTransferredFileSize + *OperationDone, *TotalDone + *OperationDone); + SetProgressWithoutSuspend(HProgressDlg, CaclProg(*OperationDone, Op->Size), + CaclProg(*TotalDone + *OperationDone, Script->TotalSize), *DlgData); + return TRUE; // success: proceed with retry + } + } + // cannot obtain the size, the file is too small, or the last written part differs from the source -> restart from scratch + HANDLES(CloseHandle(*In)); + if (WholeFileAllocated) + SetEndOfFile(*Out); // otherwise on a floppy the remaining bytes would be written + HANDLES(CloseHandle(*Out)); + DeleteFileUtf8(Op->TargetName); + *copyAgain = TRUE; // goto COPY_AGAIN; + return FALSE; + } + else // still cannot open; problem persists + { + *err = GetLastError(); + *In = NULL; + *errAgain = TRUE; // goto READ_ERROR; + return FALSE; + } +} + +BOOL CCopy_Context::HandleReadingErr(int blkIndex, DWORD err, BOOL* copyError, BOOL* skipCopy, BOOL* copyAgain) +{ + // NOTE: blkIndex == -1 when the async read request failed (no block assigned) + + CancelOpPhase1(); + + while (1) + { + WaitForSingleObject(DlgData->WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + if (*DlgData->CancelWorker) + { + CancelOpPhase2(blkIndex); + *copyError = TRUE; // goto COPY_ERROR + return FALSE; + } + + if (DlgData->SkipAllFileRead) + { + CancelOpPhase2(blkIndex); + *skipCopy = TRUE; // goto SKIP_COPY + return FALSE; + } + + int ret = IDCANCEL; + if (err == ERROR_NETNAME_DELETED && ++AutoRetryAttemptsSNAP <= 3) + { // SNAP servers occasionally return ERROR_NETNAME_DELETED while reading; Retry button reportedly helps, so trigger it automatically + Sleep(100); + ret = IDRETRY; + } + else + { + char* data[4]; + data[0] = (char*)&ret; + data[1] = LoadStr(IDS_ERRORREADINGFILE); + data[2] = Op->SourceName; + data[3] = GetErrorText(err); + SendMessage(HProgressDlg, WM_USER_DIALOG, 0, (LPARAM)data); + } + CancelOpPhase2(blkIndex); + BOOL errAgain = FALSE; + switch (ret) + { + case IDRETRY: + { + if (RetryCopyReadErr(&err, copyAgain, &errAgain)) + return TRUE; // retry + else + { + if (errAgain) + break; // same problem again; repeat the message + return FALSE; // copyAgain==TRUE, goto COPY_AGAIN; + } + } + + case IDB_SKIPALL: + DlgData->SkipAllFileRead = TRUE; + case IDB_SKIP: + { + *skipCopy = TRUE; // goto SKIP_COPY + return FALSE; + } + + case IDCANCEL: + { + *copyError = TRUE; // goto COPY_ERROR + return FALSE; + } + } + if (errAgain) + continue; // IDRETRY: same problem again; repeat the message + TRACE_C("CCopy_Context::HandleReadingErr(): unexpected result of WM_USER_DIALOG(0)."); + return TRUE; + } +} + +BOOL CCopy_Context::RetryCopyWriteErr(DWORD* err, BOOL* copyAgain, BOOL* errAgain, + const CQuadWord& allocFileSize, const CQuadWord& maxWriteOffset) +{ + if (*Out != NULL) + { + if (WholeFileAllocated) + SetEndOfFile(*Out); // otherwise on a floppy the remaining bytes would be written + HANDLES(CloseHandle(*Out)); // close the invalid handle + } + *Out = HANDLES_Q(CreateFileUtf8(Op->TargetName, GENERIC_WRITE | GENERIC_READ, 0, NULL, + OPEN_ALWAYS, AsyncPar->GetOverlappedFlag() | FILE_FLAG_SEQUENTIAL_SCAN, NULL)); + if (*Out != INVALID_HANDLE_VALUE) // opened successfully; now adjust the offset + { + BOOL ok = TRUE; + CQuadWord size; + size.LoDWord = GetFileSize(*Out, (DWORD*)&size.HiDWord); + if (size.LoDWord == INVALID_FILE_SIZE && GetLastError() != NO_ERROR || // cannot obtain the size + size < WriteOffset || // file is too small + WholeFileAllocated && size > allocFileSize && size > maxWriteOffset) // pre-allocated file is too large (greater than the pre-allocated size and the written portion including the current block) = extra bytes appended (allocWholeFileOnStart should be 0 /* need-test */) + { // restart the entire thing + ok = FALSE; + } + // success (file size matches what we need) + // if the target is on a network: disable local client-side in-memory caching + // http://msdn.microsoft.com/en-us/library/ee210753%28v=vs.85%29.aspx + // + // using Overlapped[0].hEvent from AsyncPar is OK; nothing is "in-progress" now, the event is unused + // (but WARNING: for example Buffers[0] from AsyncPar may still be in use) + if (ok && (Op->OpFlags & OPFL_TGTPATH_IS_NET) && !DisableLocalBuffering(AsyncPar, *Out, err)) + TRACE_E("CCopy_Context::RetryCopyWriteErr(): IOCTL_LMR_DISABLE_LOCAL_BUFFERING failed for network target file: " << Op->TargetName << ", error: " << GetErrorText(*err)); + // using Overlapped[0 and 1].hEvent and Overlapped[0 and 1] from AsyncPar is OK; nothing is + // "in-progress", the event nor the overlapped structures are used (but WARNING: for example Buffers[0] + // from AsyncPar may still be in use) + if (!ok || !CheckTailOfOutFile(AsyncPar, *In, *Out, WriteOffset, WriteOffset, FALSE)) + { + HANDLES(CloseHandle(*In)); + HANDLES(CloseHandle(*Out)); + DeleteFileUtf8(Op->TargetName); + *copyAgain = TRUE; // goto COPY_AGAIN; + return FALSE; + } + ForceOp = ReadOffset > WriteOffset ? fopWriting : fopNotUsed; // if the read side is ahead, resume with writing + *OperationDone = WriteOffset; + Script->SetTFSandProgressSize(*LastTransferredFileSize + *OperationDone, *TotalDone + *OperationDone); + SetProgressWithoutSuspend(HProgressDlg, CaclProg(*OperationDone, Op->Size), + CaclProg(*TotalDone + *OperationDone, Script->TotalSize), *DlgData); + return TRUE; // success: proceed with retry + } + else // still cannot open; problem persists + { + *err = GetLastError(); + *Out = NULL; + *errAgain = TRUE; // goto WRITE_ERROR; + return FALSE; + } +} + +BOOL CCopy_Context::HandleWritingErr(int blkIndex, DWORD err, BOOL* copyError, BOOL* skipCopy, BOOL* copyAgain, + const CQuadWord& allocFileSize, const CQuadWord& maxWriteOffset) +{ + // NOTE: blkIndex == -1 for an error while truncating the file after the main copy loop finishes (it has no assigned block) + + CancelOpPhase1(); + + while (1) + { + WaitForSingleObject(DlgData->WorkerNotSuspended, INFINITE); // if we are supposed to be in suspend mode, wait ... + if (*DlgData->CancelWorker) + { + CancelOpPhase2(blkIndex); + *copyError = TRUE; // goto COPY_ERROR + return FALSE; + } + + if (DlgData->SkipAllFileWrite) + { + CancelOpPhase2(blkIndex); + *skipCopy = TRUE; // goto SKIP_COPY + return FALSE; + } + + int ret = IDCANCEL; + char* data[4]; + data[0] = (char*)&ret; + data[1] = LoadStr(IDS_ERRORWRITINGFILE); + data[2] = Op->TargetName; + data[3] = GetErrorText(err); + SendMessage(HProgressDlg, WM_USER_DIALOG, 0, (LPARAM)data); + CancelOpPhase2(blkIndex); + BOOL errAgain = FALSE; + switch (ret) + { + case IDRETRY: + { + if (RetryCopyWriteErr(&err, copyAgain, &errAgain, allocFileSize, maxWriteOffset)) + return TRUE; // retry + else + { + if (errAgain) + break; // same problem again, repeat the message + return FALSE; // copyAgain==TRUE, goto COPY_AGAIN; + } + } + + case IDB_SKIPALL: + DlgData->SkipAllFileWrite = TRUE; + case IDB_SKIP: + { + *skipCopy = TRUE; // goto SKIP_COPY + return FALSE; + } + + case IDCANCEL: + { + *copyError = TRUE; // goto COPY_ERROR + return FALSE; + } + } + if (errAgain) + continue; // IDRETRY: same problem again, repeat the message + TRACE_C("CCopy_Context::HandleWritingErr(): unexpected result of WM_USER_DIALOG(0)."); + return TRUE; + } +} + +BOOL CCopy_Context::HandleSuspModeAndCancel(BOOL* copyError) +{ + if (!Script->ChangeSpeedLimit) // if the speed limit cannot change (otherwise this is not a "suitable" place to wait) + WaitForSingleObject(DlgData->WorkerNotSuspended, INFINITE); // if we are supposed to be in suspend mode, wait ... + if (*DlgData->CancelWorker) + { + CancelOpPhase1(); + CancelOpPhase2(-1); + *copyError = TRUE; // goto COPY_ERROR + return TRUE; + } + return FALSE; +} + +void DoCopyFileLoopAsync(CAsyncCopyParams* asyncPar, HANDLE& in, HANDLE& out, void* buffer, int& limitBufferSize, + COperations* script, CProgressDlgData& dlgData, BOOL wholeFileAllocated, COperation* op, + const CQuadWord& totalDone, BOOL& copyError, BOOL& skipCopy, HWND hProgressDlg, + CQuadWord& operationDone, CQuadWord& fileSize, int bufferSize, + int& allocWholeFileOnStart, BOOL& copyAgain, const CQuadWord& lastTransferredFileSize) +{ + CQuadWord allocFileSize = fileSize; + DWORD err = NO_ERROR; + DWORD bytes = 0; // helper DWORD - how many bytes were read/written in the block + + // if the source/target is on the network: disable local client-side in-memory caching + // http://msdn.microsoft.com/en-us/library/ee210753%28v=vs.85%29.aspx + if ((op->OpFlags & OPFL_SRCPATH_IS_NET) && !DisableLocalBuffering(asyncPar, in, &err)) + TRACE_E("DoCopyFileLoopAsync(): IOCTL_LMR_DISABLE_LOCAL_BUFFERING failed for network source file: " << op->SourceName << ", error: " << GetErrorText(err)); + if ((op->OpFlags & OPFL_TGTPATH_IS_NET) && !DisableLocalBuffering(asyncPar, out, &err)) + TRACE_E("DoCopyFileLoopAsync(): IOCTL_LMR_DISABLE_LOCAL_BUFFERING failed for network target file: " << op->TargetName << ", error: " << GetErrorText(err)); + + // copy loop parameters + int numOfBlocks = 8; + + // Copy operation context (prevents passing heaps of parameters to helper functions, now context methods) + CCopy_Context ctx(asyncPar, numOfBlocks, &dlgData, op, hProgressDlg, &in, &out, wholeFileAllocated, script, + &operationDone, &totalDone, &lastTransferredFileSize); + BOOL doCopy = TRUE; + while (doCopy) + { + if (ctx.ForceOp != fopWriting && ctx.FreeBlocks > 0 && !ctx.ReadingDone && ctx.ReadingBlocks < (numOfBlocks + 1) / 2) // read in parallel at most up to half of the blocks + { + DWORD toRead = ctx.ReadOffset + CQuadWord(limitBufferSize, 0) <= fileSize ? limitBufferSize : (fileSize - ctx.ReadOffset).LoDWord; + BOOL testEOF = toRead == 0; + if (!testEOF || ctx.ReadingBlocks == 0) // data read or EOF test (the EOF test runs only when all reads are finished) + { + if (ctx.BlockState[ctx.FreeBlockIndex] != cbsFree) + ctx.FreeBlockIndex = ctx.FindBlock(cbsFree); + // EOF test = read the entire block, otherwise read the usual 'toRead' + if (ctx.StartReading(ctx.FreeBlockIndex, testEOF ? limitBufferSize : toRead, &err, testEOF)) + continue; // success (asynchronous read started), try to start another read + else + { // error (starting asynchronous read) + if (!ctx.HandleReadingErr(-1, err, ©Error, &skipCopy, ©Again)) + return; // cancel/skip(skip-all)/retry-complete + continue; // retry-resume + } + } + } + // reading has already been issued or is unnecessary, check whether something is completed + BOOL shouldWait = TRUE; // TRUE = nothing else can be queued asynchronously, we must wait for some pending operation to finish + BOOL retryCopy = FALSE; // TRUE = after an error we should run Retry = start over from the beginning of the "doCopy" loop + // two passes are needed only for synchronous writes (we want to mark it + // completed immediately and not after another read, mainly for progress reporting) + for (int afterWriting = 0; afterWriting < 2; afterWriting++) + { + for (int i = 0; i < _countof(ctx.BlockState); i++) + { + if (ctx.BlockState[i] > cbsInProgress && HasOverlappedIoCompleted(asyncPar->GetOverlapped(i))) + { + shouldWait = FALSE; // in the spirit of "keep it simple" (there are situations where it could remain TRUE, but we ignore them) + switch (ctx.BlockState[i]) + { + case cbsReading: // reading the source file into a block - requested (in progress) + case cbsTestingEOF: // testing for the end of the source file + { + BOOL testingEOF = ctx.BlockState[i] == cbsTestingEOF; + +#ifdef ASYNC_COPY_DEBUG_MSG + TRACE_I("READ done: " << i); +#endif // ASYNC_COPY_DEBUG_MSG + + BOOL res = GetOverlappedResult(in, asyncPar->GetOverlapped(i), &bytes, TRUE); + if (testingEOF && res && bytes == 0) + { + res = FALSE; // MSDN says it should return FALSE and ERROR_HANDLE_EOF at EOF, so enforce that (Novell Netware 6.5 disk returns TRUE) + SetLastError(ERROR_HANDLE_EOF); + } + if (res || GetLastError() == ERROR_HANDLE_EOF) + { + ctx.AutoRetryAttemptsSNAP = 0; + if (!res) // EOF at the beginning of the block (for cbsReading only: EOF can also be before this block and will be handled later in a block with a lower offset) + { + // when GetOverlappedResult() returns FALSE it does not have to return bytes==0 + // (TRACE_C existed for that and crashes happened), so zero the bytes explicitly + bytes = 0; + if (testingEOF) + ctx.ReadingDone = TRUE; // confirmed end of the source file, stop reading further + // we must not force fopWriting (we have not read anything, there is nothing to write), unless this is an EOF test, + // let the other asynchronous reads finish, then perform the EOF test, and only then continue with writing + ctx.ForceOp = fopNotUsed; + } + if (bytes < ctx.BlockDataLen[i]) // the file is shorter than expected -> set the new file size + { + if (!testingEOF || bytes != 0) + ctx.ReadOffset = fileSize = ctx.BlockOffset[i] + CQuadWord(bytes, 0); + if (!testingEOF) + ctx.DiscardBlocksBehindEOF(fileSize, i); + if (bytes == 0) // EOF = no data, free the block + { + ctx.FreeBlock(i); + if (testingEOF) + doCopy = !ctx.IsOperationDone(numOfBlocks); // verify whether this finished the copy + } + else + ctx.BlockDataLen[i] = bytes; // pretend we intended to read exactly this much + } + else + { + if (testingEOF) // we were looking for EOF and read a full block; the file probably grew significantly, determine the new size + { + ctx.ReadOffset = ctx.BlockOffset[i] + CQuadWord(bytes, 0); + ctx.GetNewFileSize(op->SourceName, in, &fileSize, ctx.ReadOffset); + } + } + if (ctx.BlockState[i] == cbsReading || ctx.BlockState[i] == cbsTestingEOF) + { + ctx.ReadingBlocks--; + ctx.BlockState[i] = cbsRead; + } + } + else // error + { + if (!ctx.HandleReadingErr(i, GetLastError(), ©Error, &skipCopy, ©Again)) + return; // cancel/skip(skip-all)/retry-complete + retryCopy = TRUE; // retry-resume + } + break; + } + + case cbsWriting: // writing a block to the target file + { +#ifdef ASYNC_COPY_DEBUG_MSG + TRACE_I("WRITE done: " << i); +#endif // ASYNC_COPY_DEBUG_MSG + + BOOL res = GetOverlappedResult(out, asyncPar->GetOverlapped(i), &bytes, TRUE); + if (!res || bytes != ctx.BlockDataLen[i]) // error + { + err = GetLastError(); + if (err == NO_ERROR && bytes != ctx.BlockDataLen[i]) + err = ERROR_DISK_FULL; + CQuadWord maxWriteOffset = ctx.WriteOffset; + if (!ctx.HandleWritingErr(i, err, ©Error, &skipCopy, ©Again, allocFileSize, maxWriteOffset)) + return; // cancel/skip(skip-all)/retry-complete + retryCopy = TRUE; // retry-resume + break; + } + + if (ctx.HandleSuspModeAndCancel(©Error)) + return; // cancel + + script->AddBytesToSpeedMetersAndTFSandPS(bytes, FALSE, bufferSize, &limitBufferSize); + + if (!script->ChangeSpeedLimit) // if the speed limit can change, this is not a "suitable" place to wait + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + operationDone += CQuadWord(bytes, 0); + SetProgressWithoutSuspend(hProgressDlg, CaclProg(operationDone, op->Size), + CaclProg(totalDone + operationDone, script->TotalSize), dlgData); + + if (script->ChangeSpeedLimit) // the speed limit is likely to change, this is a "suitable" place to wait until the + { // worker resumes so we can get the buffer size for copying again + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + script->GetNewBufSize(&limitBufferSize, bufferSize); + } + + // break; // the break is intentionally missing here... + } + case cbsDiscarded: // reading the source file beyond its end (should only return the EOF error) + { + ctx.FreeBlock(i); + doCopy = !ctx.IsOperationDone(numOfBlocks); + break; + } + } + } + if (!doCopy || retryCopy) + break; + } + if (!doCopy || retryCopy) + break; + + // we have read data into blocks, check whether they can be written to the target file; + // written/discarded blocks were freed (we will read into them again at the top of the loop) + CQuadWord nextReadBlkOffset; // lowest offset of a skipped cbsRead block + do + { + nextReadBlkOffset.SetUI64(0); + // write in parallel at most up to half of the blocks + for (int i = 0; ctx.ForceOp != fopReading && i < _countof(ctx.BlockState) && ctx.WritingBlocks < (numOfBlocks + 1) / 2; i++) + { + if (ctx.BlockState[i] == cbsRead) + { + if (ctx.WriteOffset == ctx.BlockOffset[i]) + { + if (!ctx.StartWriting(i, &err)) + { // error (asynchronous write) + CQuadWord maxWriteOffset = ctx.WriteOffset + CQuadWord(ctx.BlockDataLen[i], 0); + if (!ctx.HandleWritingErr(i, err, ©Error, &skipCopy, ©Again, allocFileSize, maxWriteOffset)) + return; // cancel/skip(skip-all)/retry-complete + retryCopy = TRUE; // retry-resume + break; + } + } + else + { + if (nextReadBlkOffset.Value == 0 || ctx.BlockOffset[i] < nextReadBlkOffset) + nextReadBlkOffset = ctx.BlockOffset[i]; + } + } + } // we have another cbsRead block adjoining the written portion of the target file -> keep writing + } while (!retryCopy && ctx.ForceOp != fopReading && nextReadBlkOffset.Value != 0 && nextReadBlkOffset == ctx.WriteOffset && + ctx.WritingBlocks < (numOfBlocks + 1) / 2); // write in parallel at most up to half of the blocks + if (retryCopy || ctx.ForceOp != fopReading) + break; // we are going to Retry or the write was not synchronous (finished in about 0 ms) or we only write now, so two passes are pointless + } + if (!doCopy || retryCopy) + continue; + + if (shouldWait) // another pass through the loop is pointless, no chance to start a new read or write, wait + { // for the oldest asynchronous operation to finish + DWORD oldestBlockTime = 0; + int oldestBlockIndex = -1; + for (int i = 0; i < _countof(ctx.BlockState); i++) + { + if (ctx.BlockState[i] > cbsInProgress) + { + DWORD ti = ctx.CurTime - ctx.BlockTime[i]; + if (oldestBlockTime < ti) + { + oldestBlockTime = ti; + oldestBlockIndex = i; + } + } + } + if (oldestBlockIndex == -1) + TRACE_C("DoCopyFileLoopAsync(): unexpected situation: unable to find any block with operation in progress!"); + +#ifdef ASYNC_COPY_DEBUG_MSG + TRACE_I("wait: GetOverlappedResult: " << oldestBlockIndex << (ctx.BlockState[oldestBlockIndex] == cbsWriting ? " WRITE" : " READ")); +#endif // ASYNC_COPY_DEBUG_MSG + + // wait for the oldest pending asynchronous operation to complete here + // for the source file ('in') this covers: cbsReading, cbsTestingEOF, and cbsDiscarded + // for the target file ('out') this covers only cbsWriting + GetOverlappedResult(ctx.BlockState[oldestBlockIndex] == cbsWriting ? out : in, + asyncPar->GetOverlapped(oldestBlockIndex), &bytes, TRUE); + +#ifdef ASYNC_COPY_DEBUG_MSG + char sss[1000]; + sprintf(sss, "wait done: 0x%08X 0x%08X", ctx.BlockOffset[oldestBlockIndex].LoDWord, bytes); + TRACE_I(sss); +#endif // ASYNC_COPY_DEBUG_MSG + + if (ctx.HandleSuspModeAndCancel(©Error)) + return; // cancel + } + } + if (ctx.ReadOffset != ctx.WriteOffset || operationDone != ctx.WriteOffset) + TRACE_C("DoCopyFileLoopAsync(): unexpected situation after copy: ReadOffset != WriteOffset || operationDone != ctx.WriteOffset"); + + if (wholeFileAllocated) // we allocated the full size of the file (meaning the allocation made sense, e.g. the file cannot be empty) + { + if (operationDone < allocFileSize) // and the source file shrank, trim it here + { + while (1) + { + CQuadWord off = ctx.WriteOffset; + off.LoDWord = SetFilePointer(out, off.LoDWord, (LONG*)&(off.HiDWord), FILE_BEGIN); + if (off.LoDWord == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR || + off != ctx.WriteOffset || + !SetEndOfFile(out)) + { + DWORD err2 = GetLastError(); + if ((off.LoDWord != INVALID_SET_FILE_POINTER || err2 == NO_ERROR) && off != ctx.WriteOffset) + err2 = ERROR_INVALID_FUNCTION; // successful SetFilePointer, but off != ctx.WriteOffset: will probably never happen, included for completeness + if (!ctx.HandleWritingErr(-1, err2, ©Error, &skipCopy, ©Again, allocFileSize, CQuadWord(0, 0))) + return; // cancel/skip(skip-all)/retry-complete + // retry-resume + } + else + break; // success + } + } + + if (allocWholeFileOnStart == 0 /* need-test */) + { + CQuadWord curFileSize; + curFileSize.LoDWord = GetFileSize(out, &curFileSize.HiDWord); + BOOL getFileSizeSuccess = (curFileSize.LoDWord != INVALID_FILE_SIZE || GetLastError() == NO_ERROR); + if (getFileSizeSuccess && curFileSize == operationDone) + { // verify that no extra bytes were appended to the end of the file + that we can truncate the file + allocWholeFileOnStart = 1 /* yes */; + } + else + { +#ifdef _DEBUG + if (getFileSizeSuccess) + { + char num1[50]; + char num2[50]; + TRACE_E("DoCopyFileLoopAsync(): unable to allocate whole file size before copy operation, please report " + "under what conditions this occurs! Error: different file sizes: target=" + << NumberToStr(num1, curFileSize) << " bytes, source=" << NumberToStr(num2, operationDone) << " bytes"); + } + else + { + DWORD err2 = GetLastError(); + TRACE_E("DoCopyFileLoopAsync(): unable to test result of allocation of whole file size before copy operation, please report " + "under what conditions this occurs! GetFileSize(" + << op->TargetName << ") error: " << GetErrorText(err2)); + } +#endif + allocWholeFileOnStart = 2 /* no */; // skip further attempts on this target disk + + while (1) + { + HANDLES(CloseHandle(out)); + out = NULL; + ClearReadOnlyAttr(op->TargetName); // in case it was created as read-only (should never happen) so we can handle it + if (DeleteFileUtf8(op->TargetName)) + { + HANDLES(CloseHandle(in)); + copyAgain = TRUE; // goto COPY_AGAIN; + return; + } + else + { + if (!ctx.HandleWritingErr(-1, GetLastError(), ©Error, &skipCopy, ©Again, allocFileSize, CQuadWord(0, 0))) + return; // cancel/skip(skip-all)/retry-complete + // retry-resume + } + } + } + } + } +} + +BOOL DoCopyFile(COperation* op, HWND hProgressDlg, void* buffer, + COperations* script, CQuadWord& totalDone, + DWORD clearReadonlyMask, BOOL* skip, BOOL lantasticCheck, + int& mustDeleteFileBeforeOverwrite, int& allocWholeFileOnStart, + CProgressDlgData& dlgData, BOOL copyADS, BOOL copyAsEncrypted, + BOOL isMove, CAsyncCopyParams*& asyncPar) +{ + if (script->CopyAttrs && copyAsEncrypted) + TRACE_E("DoCopyFile(): unexpected parameter value: copyAsEncrypted is TRUE when script->CopyAttrs is TRUE!"); + + // if the path ends with a space/dot, it is invalid and we must not copy it, + // CreateFile would trim the spaces/dots and copy a different file or under a different name + BOOL invalidSrcName = FileNameIsInvalid(op->SourceName, TRUE); + BOOL invalidTgtName = FileNameIsInvalid(op->TargetName, TRUE); + + // optimization: skipping all "older and identical" files is about 4x faster, + // slowing down when the file is newer is 5%, so it should be well worth it + // (it is safe to assume the user enables "Overwrite Older" when the skips occur) + BOOL tgtNameCaseCorrected = FALSE; // TRUE = the letter case in the target name was already adjusted to match the existing target file (so overwriting does not change it) + WIN32_FIND_DATAW dataIn, dataOut; + if ((op->OpFlags & OPFL_OVERWROLDERALRTESTED) == 0 && + !invalidSrcName && !invalidTgtName && script->OverwriteOlder) + { + HANDLE find; + CStrP targetNameW(ConvertAllocUtf8ToWide(op->TargetName, -1)); + find = targetNameW != NULL ? HANDLES_Q(FindFirstFileW(targetNameW, &dataOut)) : INVALID_HANDLE_VALUE; + if (find != INVALID_HANDLE_VALUE) + { + HANDLES(FindClose(find)); + + CorrectCaseOfTgtName(op->TargetName, TRUE, &dataOut); + tgtNameCaseCorrected = TRUE; + + const char* tgtName = SalPathFindFileName(op->TargetName); + char outName[MAX_PATH]; + if (ConvertWideToUtf8(dataOut.cFileName, -1, outName, _countof(outName)) == 0) + outName[0] = 0; + if (StrICmp(tgtName, outName) == 0 && // ensure it is not just a DOS-name match (that would change the DOS-name instead of overwriting) + (dataOut.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) // ensure it is not a directory (overwrite-older cannot help there) + { + CStrP sourceNameW(ConvertAllocUtf8ToWide(op->SourceName, -1)); + find = sourceNameW != NULL ? HANDLES_Q(FindFirstFileW(sourceNameW, &dataIn)) : INVALID_HANDLE_VALUE; + if (find != INVALID_HANDLE_VALUE) + { + HANDLES(FindClose(find)); + + // truncate times to seconds (different file systems store timestamps with different precision, leading to "differences" even between "identical" times) + *(unsigned __int64*)&dataIn.ftLastWriteTime = *(unsigned __int64*)&dataIn.ftLastWriteTime - (*(unsigned __int64*)&dataIn.ftLastWriteTime % 10000000); + *(unsigned __int64*)&dataOut.ftLastWriteTime = *(unsigned __int64*)&dataOut.ftLastWriteTime - (*(unsigned __int64*)&dataOut.ftLastWriteTime % 10000000); + + if ((dataIn.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0 && // verify the source is still a file + CompareFileTime(&dataIn.ftLastWriteTime, &dataOut.ftLastWriteTime) <= 0) // source file is not newer than the target file - skip the copy operation + { + CQuadWord fileSize(op->FileSize); + if (fileSize < COPY_MIN_FILE_SIZE) + { + if (op->Size > COPY_MIN_FILE_SIZE) // should always be at least COPY_MIN_FILE_SIZE, but play it safe... + fileSize += op->Size - COPY_MIN_FILE_SIZE; // add the size of ADS streams + } + else + fileSize = op->Size; + totalDone += op->Size; + script->AddBytesToTFSandSetProgressSize(fileSize, totalDone); + + SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); + if (skip != NULL) + *skip = TRUE; + return TRUE; + } + } + } + } + } + + // decide which algorithm to use for copying: the ancient synchronous one or + // the asynchronous one inspired by the Windows 7 CopyFileEx version: + // - under Vista it misbehaved badly; forget Vista, it is almost dead anyway, and + // when using the old algorithm against Win7 over the network I saw no speed difference + // for uploads, and downloads were only 15% slower (acceptable) + // - the asynchronous algorithm makes sense only over the network + when source/target is fast or network-based + // - with the old algorithm, copying on Win7 over the network is easily 2x-3x slower for downloads, + // almost 2x slower for uploads, and about 30% slower for network-to-network copies + BOOL useAsyncAlg = Windows7AndLater && Configuration.UseAsyncCopyAlg && + op->FileSize.Value > 0 && // empty files are copied synchronously (no data) + ((op->OpFlags & OPFL_SRCPATH_IS_NET) && ((op->OpFlags & OPFL_TGTPATH_IS_NET) || + (op->OpFlags & OPFL_TGTPATH_IS_FAST)) || + (op->OpFlags & OPFL_TGTPATH_IS_NET) && (op->OpFlags & OPFL_SRCPATH_IS_FAST)); + + if (asyncPar == NULL) + asyncPar = new CAsyncCopyParams; + + asyncPar->Init(useAsyncAlg); + script->EnableProgressBufferLimit(useAsyncAlg); + struct CDisableProgressBufferLimit // ensure Script->EnableProgressBufferLimit(FALSE) is called on every exit from this function + { + COperations* Script; + CDisableProgressBufferLimit(COperations* script) { Script = script; } + ~CDisableProgressBufferLimit() { Script->EnableProgressBufferLimit(FALSE); } + } DisableProgressBufferLimit(script); + + CQuadWord operationDone; + CQuadWord lastTransferredFileSize; + script->GetTFSandResetTrSpeedIfNeeded(&lastTransferredFileSize); + +COPY_AGAIN: + + operationDone = CQuadWord(0, 0); + HANDLE in; + + if (skip != NULL) + *skip = FALSE; + + int bufferSize; + if (useAsyncAlg) + { + if (op->FileSize.Value <= 512 * 1024) + bufferSize = ASYNC_COPY_BUF_SIZE_512KB; + else if (op->FileSize.Value <= 2 * 1024 * 1024) + bufferSize = ASYNC_COPY_BUF_SIZE_2MB; + else if (op->FileSize.Value <= 8 * 1024 * 1024) + bufferSize = ASYNC_COPY_BUF_SIZE_8MB; + else + bufferSize = ASYNC_COPY_BUF_SIZE; + } + else + bufferSize = GetOptimalSyncCopyBufferSize(script, op->OpFlags); + + int limitBufferSize = bufferSize; + script->SetTFSandProgressSize(lastTransferredFileSize, totalDone, &limitBufferSize, bufferSize); + + while (1) + { + if (!invalidSrcName && !asyncPar->Failed()) + { + in = HANDLES_Q(CreateFileUtf8(op->SourceName, GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, + OPEN_EXISTING, asyncPar->GetOverlappedFlag() | FILE_FLAG_SEQUENTIAL_SCAN, NULL)); + } + else + { + in = INVALID_HANDLE_VALUE; + } + if (in != INVALID_HANDLE_VALUE) + { + CQuadWord fileSize = op->FileSize; + + HANDLE out; + BOOL lossEncryptionAttr = FALSE; + BOOL skipAllocWholeFileOnStart = FALSE; + while (1) + { + OPEN_TGT_FILE: + + BOOL encryptionNotSupported = FALSE; + DWORD fileAttrs = asyncPar->GetOverlappedFlag() | FILE_FLAG_SEQUENTIAL_SCAN | + (!lossEncryptionAttr && copyAsEncrypted ? FILE_ATTRIBUTE_ENCRYPTED : 0) | + (script->CopyAttrs ? (op->Attr & (FILE_ATTRIBUTE_COMPRESSED | (lossEncryptionAttr ? 0 : FILE_ATTRIBUTE_ENCRYPTED))) : 0); + if (!invalidTgtName) + { + // GENERIC_READ for 'out' slows asynchronous copying from disk to network (measured 95 MB/s instead of 111 MB/s on Win7 x64 GLAN) + out = SalCreateFileEx(op->TargetName, GENERIC_WRITE | (script->CopyAttrs ? GENERIC_READ : 0), 0, fileAttrs, &encryptionNotSupported); + if (!encryptionNotSupported && script->CopyAttrs && out == INVALID_HANDLE_VALUE) // in case read access to the directory is not allowed (we added it only for setting the Compressed attribute), try creating a write-only file + out = SalCreateFileEx(op->TargetName, GENERIC_WRITE, 0, fileAttrs, &encryptionNotSupported); + + if (out == INVALID_HANDLE_VALUE && encryptionNotSupported && dlgData.FileOutLossEncrAll && !lossEncryptionAttr) + { // the user agreed to lose the Encrypted attribute for all problematic files, so make that happen here + lossEncryptionAttr = TRUE; + continue; + } + HANDLES_ADD_EX(__otQuiet, out != INVALID_HANDLE_VALUE, __htFile, + __hoCreateFile, out, GetLastError(), TRUE); + if (script->CopyAttrs) + { + fileAttrs = lossEncryptionAttr ? (op->Attr & ~FILE_ATTRIBUTE_ENCRYPTED) : op->Attr; + SetCompressAndEncryptedAttrs(op->TargetName, fileAttrs, &out, TRUE, NULL, asyncPar); + } + + if (out != INVALID_HANDLE_VALUE && (fileAttrs & FILE_ATTRIBUTE_ENCRYPTED)) + { // verify that the Encrypted attribute is really set (on FAT it is simply ignored, the system does not return an error (for CreateFile specifically)) + DWORD attrs; + attrs = SalGetFileAttributes(op->TargetName); + if (attrs != INVALID_FILE_ATTRIBUTES && (attrs & FILE_ATTRIBUTE_ENCRYPTED) == 0) + { // unable to apply the Encrypted attribute, ask the user what to do... + if (dlgData.FileOutLossEncrAll) + lossEncryptionAttr = TRUE; + else + { + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + if (*dlgData.CancelWorker) + goto CANCEL_ENCNOTSUP; + + if (dlgData.SkipAllFileOutLossEncr) + goto SKIP_ENCNOTSUP; + + int ret; + ret = IDCANCEL; + char* data[4]; + data[0] = (char*)&ret; + data[1] = (char*)TRUE; + data[2] = op->TargetName; + data[3] = (char*)(INT_PTR)isMove; + SendMessage(hProgressDlg, WM_USER_DIALOG, 12, (LPARAM)data); + switch (ret) + { + case IDB_ALL: + dlgData.FileOutLossEncrAll = TRUE; // the break; is intentionally missing here + case IDYES: + lossEncryptionAttr = TRUE; + break; + + case IDB_SKIPALL: + dlgData.SkipAllFileOutLossEncr = TRUE; + case IDB_SKIP: + { + SKIP_ENCNOTSUP: + + HANDLES(CloseHandle(out)); + DeleteFileUtf8(op->TargetName); + goto SKIP_OPEN_OUT; + } + + case IDCANCEL: + { + CANCEL_ENCNOTSUP: + + HANDLES(CloseHandle(out)); + DeleteFileUtf8(op->TargetName); + goto CANCEL_OPEN2; + } + } + } + } + } + } + else + { + out = INVALID_HANDLE_VALUE; + } + + if (out != INVALID_HANDLE_VALUE) + { + + COPY: + + // if possible, allocate the required space for the file (prevents disk fragmentation + smoother writes to floppies) + BOOL wholeFileAllocated = FALSE; + if (!skipAllocWholeFileOnStart && // last time failed, so the same would probably happen now + allocWholeFileOnStart != 2 /* no */ && // allocating the whole file is not forbidden + fileSize > CQuadWord(limitBufferSize, 0) && // allocation is pointless below the copy buffer size + fileSize < CQuadWord(0, 0x80000000)) // file size is positive number (otherwise seeking is impossible - numbers above 8EB, so likely never happens) + { + BOOL fatal = TRUE; + BOOL ignoreErr = FALSE; + if (SalSetFilePointer(out, fileSize)) + { + if (SetEndOfFile(out)) + { + if (SetFilePointer(out, 0, NULL, FILE_BEGIN) == 0) + { + fatal = FALSE; + wholeFileAllocated = TRUE; + } + } + else + { + if (GetLastError() == ERROR_DISK_FULL) + ignoreErr = TRUE; // not enough space on the disk + } + } + if (fatal) + { + if (!ignoreErr) + { + DWORD err = GetLastError(); + TRACE_E("DoCopyFile(): unable to allocate whole file size before copy operation, please report under what conditions this occurs! GetLastError(): " << GetErrorText(err)); + allocWholeFileOnStart = 2 /* no */; // we will forego further attempts on this target disk + } + + // try truncating the file to zero so closing it does not trigger any unnecessary writes + SetFilePointer(out, 0, NULL, FILE_BEGIN); + SetEndOfFile(out); + + HANDLES(CloseHandle(out)); + out = INVALID_HANDLE_VALUE; + ClearReadOnlyAttr(op->TargetName); // in case it was created as read-only (should never happen) so we can handle it + if (DeleteFileUtf8(op->TargetName)) + { + skipAllocWholeFileOnStart = TRUE; + goto OPEN_TGT_FILE; + } + else + goto CREATE_ERROR; + } + } + + script->SetFileStartParams(); + + BOOL copyError = FALSE; + BOOL skipCopy = FALSE; + BOOL copyAgain = FALSE; + if (useAsyncAlg) + { + DoCopyFileLoopAsync(asyncPar, in, out, buffer, limitBufferSize, script, dlgData, wholeFileAllocated, op, + totalDone, copyError, skipCopy, hProgressDlg, operationDone, fileSize, + bufferSize, allocWholeFileOnStart, copyAgain, lastTransferredFileSize); + // NOTE: neither 'in' nor 'out' has the file pointer (SetFilePointer) positioned at the end of the file, + // 'out' has it set only when (copyError || skipCopy) + } + else + { + DoCopyFileLoopOrig(in, out, buffer, limitBufferSize, script, dlgData, wholeFileAllocated, op, + totalDone, copyError, skipCopy, hProgressDlg, operationDone, fileSize, + bufferSize, allocWholeFileOnStart, copyAgain); + } + + if (copyError) + { + COPY_ERROR: + + if (in != NULL) + HANDLES(CloseHandle(in)); + if (out != NULL) + { + if (wholeFileAllocated) + SetEndOfFile(out); // otherwise on a floppy the remaining part of the file would be written + HANDLES(CloseHandle(out)); + } + DeleteFileUtf8(op->TargetName); + return FALSE; + } + if (skipCopy) + { + SKIP_COPY: + + totalDone += op->Size; + SetTFSandPSforSkippedFile(op, lastTransferredFileSize, script, totalDone); + + if (in != NULL) + HANDLES(CloseHandle(in)); + if (out != NULL) + { + if (wholeFileAllocated) + SetEndOfFile(out); // otherwise on a floppy the remaining part of the file would be written + HANDLES(CloseHandle(out)); + } + DeleteFileUtf8(op->TargetName); + SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); + if (skip != NULL) + *skip = TRUE; + return TRUE; + } + if (copyAgain) + goto COPY_AGAIN; + + if (lantasticCheck) + { + CQuadWord inSize, outSize; + inSize.LoDWord = GetFileSize(in, &inSize.HiDWord); + outSize.LoDWord = GetFileSize(out, &outSize.HiDWord); + if (inSize != outSize) + { // Lantastic 7.0: everything seems fine, but the result is wrong + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + if (*dlgData.CancelWorker) + goto COPY_ERROR; + + if (dlgData.SkipAllFileWrite) + goto SKIP_COPY; + + int ret = IDCANCEL; + char* data[4]; + data[0] = (char*)&ret; + data[1] = LoadStr(IDS_ERRORWRITINGFILE); + data[2] = op->TargetName; + data[3] = GetErrorText(ERROR_DISK_FULL); + SendMessage(hProgressDlg, WM_USER_DIALOG, 0, (LPARAM)data); + switch (ret) + { + case IDRETRY: + { + operationDone = CQuadWord(0, 0); + script->SetTFSandProgressSize(lastTransferredFileSize, totalDone); + SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); + SetFilePointer(in, 0, NULL, FILE_BEGIN); // read again + SetFilePointer(out, 0, NULL, FILE_BEGIN); // write again + SetEndOfFile(out); // truncate the output file + goto COPY; + } + + case IDB_SKIPALL: + dlgData.SkipAllFileWrite = TRUE; + case IDB_SKIP: + goto SKIP_COPY; + + case IDCANCEL: + goto COPY_ERROR; + } + } + } + + FILETIME /*creation, lastAccess,*/ lastWrite; + BOOL ignoreGetFileTimeErr = FALSE; + while (!ignoreGetFileTimeErr && + !GetFileTime(in, NULL /*&creation*/, NULL /*&lastAccess*/, &lastWrite)) + { + DWORD err = GetLastError(); + + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + if (*dlgData.CancelWorker) + goto COPY_ERROR; + + if (dlgData.SkipAllGetFileTime) + goto SKIP_COPY; + + if (dlgData.IgnoreAllGetFileTimeErr) + goto IGNORE_GETFILETIME; + + int ret; + ret = IDCANCEL; + char* data[4]; + data[0] = (char*)&ret; + data[1] = LoadStr(IDS_ERRORGETTINGFILETIME); + data[2] = op->SourceName; + data[3] = GetErrorText(err); + SendMessage(hProgressDlg, WM_USER_DIALOG, 8, (LPARAM)data); + switch (ret) + { + case IDRETRY: + break; + + case IDB_IGNOREALL: + dlgData.IgnoreAllGetFileTimeErr = TRUE; // the break; is intentionally missing here + case IDB_IGNORE: + { + IGNORE_GETFILETIME: + + ignoreGetFileTimeErr = TRUE; + break; + } + + case IDB_SKIPALL: + dlgData.SkipAllGetFileTime = TRUE; + case IDB_SKIP: + goto SKIP_COPY; + + case IDCANCEL: + goto COPY_ERROR; + } + } + + HANDLES(CloseHandle(in)); + in = NULL; + + if (operationDone < COPY_MIN_FILE_SIZE) // zero/small files take at least as long as files of size COPY_MIN_FILE_SIZE + script->AddBytesToSpeedMetersAndTFSandPS((DWORD)(COPY_MIN_FILE_SIZE - operationDone).Value, TRUE, 0, NULL, MAX_OP_FILESIZE); + + DWORD attr = op->Attr & clearReadonlyMask; + if (copyADS) // copy ADS streams if needed + { + SetFileAttributesUtf8(op->TargetName, FILE_ATTRIBUTE_ARCHIVE); // probably unnecessary, it hardly slows copying; reason: the file must not be read-only to work with it + CQuadWord operDone = operationDone; // the file is already copied + if (operDone < COPY_MIN_FILE_SIZE) + operDone = COPY_MIN_FILE_SIZE; // zero/small files take at least as long as files of size COPY_MIN_FILE_SIZE + BOOL adsSkip = FALSE; + // Pass the optimal buffer size computed from op->OpFlags for ADS copy + int adsBufferSize = GetOptimalSyncCopyBufferSize(script, op->OpFlags); + if (!DoCopyADS(hProgressDlg, op->SourceName, FALSE, op->TargetName, totalDone, + operDone, op->Size, dlgData, script, &adsSkip, buffer, adsBufferSize) || + adsSkip) // user hit cancel or skipped at least one ADS + { + if (out != NULL) + HANDLES(CloseHandle(out)); + out = NULL; + if (DeleteFileUtf8(op->TargetName) == 0) + { + DWORD err = GetLastError(); + TRACE_E("DoCopyFile(): Unable to remove newly created file: " << op->TargetName << ", error: " << GetErrorText(err)); + } + if (!adsSkip) + return FALSE; // cancel the entire operation + if (skip != NULL) + *skip = TRUE; // it is a Skip, must report higher up (Move must not delete the source file) + } + } + + if (out != NULL) + { + if (!ignoreGetFileTimeErr) // only if we did not ignore the error while reading the file time (nothing to set otherwise) + { + BOOL ignoreSetFileTimeErr = FALSE; + while (!ignoreSetFileTimeErr && + !SetFileTime(out, NULL /*&creation*/, NULL /*&lastAccess*/, &lastWrite)) + { + DWORD err = GetLastError(); + + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + if (*dlgData.CancelWorker) + goto COPY_ERROR; + + if (dlgData.SkipAllSetFileTime) + goto SKIP_COPY; + + if (dlgData.IgnoreAllSetFileTimeErr) + goto IGNORE_SETFILETIME; + + int ret; + ret = IDCANCEL; + char* data[4]; + data[0] = (char*)&ret; + data[1] = LoadStr(IDS_ERRORSETTINGFILETIME); + data[2] = op->TargetName; + data[3] = GetErrorText(err); + SendMessage(hProgressDlg, WM_USER_DIALOG, 8, (LPARAM)data); + switch (ret) + { + case IDRETRY: + break; + + case IDB_IGNOREALL: + dlgData.IgnoreAllSetFileTimeErr = TRUE; // the break; is intentionally missing here + case IDB_IGNORE: + { + IGNORE_SETFILETIME: + + ignoreSetFileTimeErr = TRUE; + break; + } + + case IDB_SKIPALL: + dlgData.SkipAllSetFileTime = TRUE; + case IDB_SKIP: + goto SKIP_COPY; + + case IDCANCEL: + goto COPY_ERROR; + } + } + } + if (!HANDLES(CloseHandle(out))) + { + out = NULL; + DWORD err = GetLastError(); + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + if (*dlgData.CancelWorker) + goto COPY_ERROR; + + if (dlgData.SkipAllFileWrite) + goto SKIP_COPY; + + int ret = IDCANCEL; + char* data[4]; + data[0] = (char*)&ret; + data[1] = LoadStr(IDS_ERRORWRITINGFILE); + data[2] = op->TargetName; + data[3] = GetErrorText(err); + SendMessage(hProgressDlg, WM_USER_DIALOG, 0, (LPARAM)data); + switch (ret) + { + case IDRETRY: + { + if (DeleteFileUtf8(op->TargetName) == 0) + { + DWORD err2 = GetLastError(); + TRACE_E("DoCopyFile(): Unable to remove newly created file: " << op->TargetName << ", error: " << GetErrorText(err2)); + } + goto COPY_AGAIN; + } + + case IDB_SKIPALL: + dlgData.SkipAllFileWrite = TRUE; + case IDB_SKIP: + goto SKIP_COPY; + + case IDCANCEL: + goto COPY_ERROR; + } + } + + SetFileAttributesUtf8(op->TargetName, script->CopyAttrs ? attr : (attr | FILE_ATTRIBUTE_ARCHIVE)); + } + + if (script->CopyAttrs) // verify whether the source file attributes were preserved + { + DWORD curAttrs; + curAttrs = SalGetFileAttributes(op->TargetName); + if (curAttrs == INVALID_FILE_ATTRIBUTES || (curAttrs & DISPLAYED_ATTRIBUTES) != (attr & DISPLAYED_ATTRIBUTES)) + { // attributes probably were not preserved, warn the user + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + if (*dlgData.CancelWorker) + goto COPY_ERROR_2; + + int ret; + ret = IDCANCEL; + if (dlgData.IgnoreAllSetAttrsErr) + ret = IDB_IGNORE; + else + { + char* data[4]; + data[0] = (char*)&ret; + data[1] = op->TargetName; + data[2] = (char*)(DWORD_PTR)(attr & DISPLAYED_ATTRIBUTES); + data[3] = (char*)(DWORD_PTR)(curAttrs == INVALID_FILE_ATTRIBUTES ? 0 : (curAttrs & DISPLAYED_ATTRIBUTES)); + SendMessage(hProgressDlg, WM_USER_DIALOG, 9, (LPARAM)data); + } + switch (ret) + { + case IDB_IGNOREALL: + dlgData.IgnoreAllSetAttrsErr = TRUE; // break is intentional here; nothing is missing + case IDB_IGNORE: + break; + + case IDCANCEL: + { + COPY_ERROR_2: + + ClearReadOnlyAttr(op->TargetName); // the file must not be read-only if it is to be deleted + DeleteFileUtf8(op->TargetName); + return FALSE; + } + } + } + } + + if (script->CopySecurity) // should we copy NTFS security permissions? + { + DWORD err; + if (!DoCopySecurity(op->SourceName, op->TargetName, &err, NULL)) + { + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + if (*dlgData.CancelWorker) + goto COPY_ERROR_2; + + int ret; + ret = IDCANCEL; + if (dlgData.IgnoreAllCopyPermErr) + ret = IDB_IGNORE; + else + { + char* data[4]; + data[0] = (char*)&ret; + data[1] = op->SourceName; + data[2] = op->TargetName; + data[3] = (char*)(DWORD_PTR)err; + SendMessage(hProgressDlg, WM_USER_DIALOG, 10, (LPARAM)data); + } + switch (ret) + { + case IDB_IGNOREALL: + dlgData.IgnoreAllCopyPermErr = TRUE; // the break; is intentionally missing here + case IDB_IGNORE: + break; + + case IDCANCEL: + goto COPY_ERROR_2; + } + } + } + + totalDone += op->Size; + script->SetProgressSize(totalDone); + return TRUE; + } + else + { + if (!invalidTgtName && encryptionNotSupported) + { + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + if (*dlgData.CancelWorker) + goto CANCEL_OPEN2; + + if (dlgData.SkipAllFileOutLossEncr) + goto SKIP_OPEN_OUT; + + int ret; + ret = IDCANCEL; + char* data[4]; + data[0] = (char*)&ret; + data[1] = (char*)TRUE; + data[2] = op->TargetName; + data[3] = (char*)(INT_PTR)isMove; + SendMessage(hProgressDlg, WM_USER_DIALOG, 12, (LPARAM)data); + switch (ret) + { + case IDB_ALL: + dlgData.FileOutLossEncrAll = TRUE; // the break; is intentionally missing here + case IDYES: + lossEncryptionAttr = TRUE; + break; + + case IDB_SKIPALL: + dlgData.SkipAllFileOutLossEncr = TRUE; + case IDB_SKIP: + { + SKIP_OPEN_OUT: + + totalDone += op->Size; + SetTFSandPSforSkippedFile(op, lastTransferredFileSize, script, totalDone); + + HANDLES(CloseHandle(in)); + SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); + if (skip != NULL) + *skip = TRUE; + return TRUE; + } + + case IDCANCEL: + { + CANCEL_OPEN2: + + HANDLES(CloseHandle(in)); + return FALSE; + } + } + } + else + { + CREATE_ERROR: + + DWORD err = GetLastError(); + if (invalidTgtName) + err = ERROR_INVALID_NAME; + BOOL errDeletingFile = FALSE; + if (err == ERROR_FILE_EXISTS || // overwrite the file? + err == ERROR_ALREADY_EXISTS) + { + if (!dlgData.OverwriteAll && (dlgData.CnfrmFileOver || script->OverwriteOlder)) + { + char sAttr[101], tAttr[101]; + BOOL getTimeFailed; + getTimeFailed = FALSE; + FILETIME sFileTime, tFileTime; + GetFileOverwriteInfo(sAttr, _countof(sAttr), in, op->SourceName, &sFileTime, &getTimeFailed); + HANDLES(CloseHandle(in)); + in = NULL; + out = HANDLES_Q(CreateFileUtf8(op->TargetName, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, + OPEN_EXISTING, 0, NULL)); + if (out != INVALID_HANDLE_VALUE) + { + GetFileOverwriteInfo(tAttr, _countof(tAttr), out, op->TargetName, &tFileTime, &getTimeFailed); + HANDLES(CloseHandle(out)); + } + else + { + getTimeFailed = TRUE; + lstrcpyn(tAttr, LoadStr(IDS_ERR_FILEOPEN), _countof(tAttr)); + } + out = NULL; + + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + if (*dlgData.CancelWorker) + goto CANCEL_OPEN; + + if (dlgData.SkipAllOverwrite) + goto SKIP_OPEN; + + int ret; + ret = IDCANCEL; + + if (!getTimeFailed && script->OverwriteOlder) // option from the Copy/Move dialog + { + // trim times to seconds (different file systems store times with different precision, so "differences" occurred even between "identical" times) + *(unsigned __int64*)&sFileTime = *(unsigned __int64*)&sFileTime - (*(unsigned __int64*)&sFileTime % 10000000); + *(unsigned __int64*)&tFileTime = *(unsigned __int64*)&tFileTime - (*(unsigned __int64*)&tFileTime % 10000000); + + if (CompareFileTime(&sFileTime, &tFileTime) > 0) + ret = IDYES; // overwrite older files without asking + else + ret = IDB_SKIP; // skip other existing files + } + else + { + // show the prompt + char* data[5]; + data[0] = (char*)&ret; + data[1] = op->TargetName; + data[2] = tAttr; + data[3] = op->SourceName; + data[4] = sAttr; + SendMessage(hProgressDlg, WM_USER_DIALOG, 1, (LPARAM)data); + } + switch (ret) + { + case IDB_ALL: + dlgData.OverwriteAll = TRUE; + case IDYES: + default: // for safety (to prevent exiting this block with the 'in' handle closed) + { + in = HANDLES_Q(CreateFileUtf8(op->SourceName, GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, + OPEN_EXISTING, asyncPar->GetOverlappedFlag() | FILE_FLAG_SEQUENTIAL_SCAN, NULL)); + if (in == INVALID_HANDLE_VALUE) + goto OPEN_IN_ERROR; + break; + } + + case IDB_SKIPALL: + dlgData.SkipAllOverwrite = TRUE; + case IDB_SKIP: + { + SKIP_OPEN: + + totalDone += op->Size; + SetTFSandPSforSkippedFile(op, lastTransferredFileSize, script, totalDone); + + SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); + if (skip != NULL) + *skip = TRUE; + return TRUE; + } + + case IDCANCEL: + { + CANCEL_OPEN: + + return FALSE; + } + } + } + + DWORD attr = SalGetFileAttributes(op->TargetName); + if (attr != INVALID_FILE_ATTRIBUTES && (attr & (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM))) + { + if (!dlgData.OverwriteHiddenAll && dlgData.CnfrmSHFileOver) // ignore script->OverwriteOlder here; user wants to see that this is a SYSTEM or HIDDEN file even with the option enabled + { + HANDLES(CloseHandle(in)); + in = NULL; + + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + if (*dlgData.CancelWorker) + goto CANCEL_OPEN; + + if (dlgData.SkipAllSystemOrHidden) + goto SKIP_OPEN; + + int ret = IDCANCEL; + char* data[4]; + data[0] = (char*)&ret; + data[1] = LoadStr(IDS_CONFIRMFILEOVERWRITING); + data[2] = op->TargetName; + data[3] = LoadStr(IDS_WANTOVERWRITESHFILE); + SendMessage(hProgressDlg, WM_USER_DIALOG, 2, (LPARAM)data); + switch (ret) + { + case IDB_ALL: + dlgData.OverwriteHiddenAll = TRUE; + case IDYES: + default: // for safety (to prevent exiting this block with the 'in' handle closed) + { + in = HANDLES_Q(CreateFileUtf8(op->SourceName, GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, + OPEN_EXISTING, asyncPar->GetOverlappedFlag() | FILE_FLAG_SEQUENTIAL_SCAN, NULL)); + if (in == INVALID_HANDLE_VALUE) + goto OPEN_IN_ERROR; + attr = SalGetFileAttributes(op->TargetName); // refresh attributes in case the user changed them + break; + } + + case IDB_SKIPALL: + dlgData.SkipAllSystemOrHidden = TRUE; + case IDB_SKIP: + goto SKIP_OPEN; + + case IDCANCEL: + goto CANCEL_OPEN; + } + } + } + + BOOL targetCannotOpenForWrite = FALSE; + while (1) + { + if (targetCannotOpenForWrite || mustDeleteFileBeforeOverwrite == 1 /* yes */) + { // the file must be deleted first + BOOL chAttr = ClearReadOnlyAttr(op->TargetName, attr); + + if (!tgtNameCaseCorrected) + { + CorrectCaseOfTgtName(op->TargetName, FALSE, &dataOut); + tgtNameCaseCorrected = TRUE; + } + + if (DeleteFileUtf8(op->TargetName)) + goto OPEN_TGT_FILE; // if it is read-only (clearing the attribute may have failed), it can be deleted only on Samba with "delete readonly" enabled + else // cannot delete either, end with an error... + { + err = GetLastError(); + if (chAttr) + SetFileAttributesUtf8(op->TargetName, attr); + errDeletingFile = TRUE; + goto NORMAL_ERROR; + } + } + else // overwrite the file in place + { + // if we have not yet tested truncating the file to zero, obtain the current file size + CQuadWord origFileSize(0, 0); // file size before truncation + if (mustDeleteFileBeforeOverwrite == 0 /* need test */) + { + out = HANDLES_Q(CreateFileUtf8(op->TargetName, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, + OPEN_EXISTING, 0, NULL)); + if (out != INVALID_HANDLE_VALUE) + { + origFileSize.LoDWord = GetFileSize(out, &origFileSize.HiDWord); + if (origFileSize.LoDWord == INVALID_FILE_SIZE && GetLastError() == NO_ERROR) + origFileSize.Set(0, 0); // error => set the size to zero and test it on another file + HANDLES(CloseHandle(out)); + } + } + + // open the file with ADS removal and truncation to zero + BOOL chAttr = FALSE; + if (attr != INVALID_FILE_ATTRIBUTES && + (attr & (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM))) + { // CREATE_ALWAYS does not play well with read-only, hidden, or system attributes, so drop them if needed + chAttr = TRUE; + SetFileAttributesUtf8(op->TargetName, 0); + } + // GENERIC_READ for 'out' slows asynchronous copying from disk to network (measured 95 MB/s instead of 111 MB/s on Win7 x64 GLAN) + DWORD access = GENERIC_WRITE | (script->CopyAttrs ? GENERIC_READ : 0); + fileAttrs = asyncPar->GetOverlappedFlag() | FILE_FLAG_SEQUENTIAL_SCAN | + (!lossEncryptionAttr && copyAsEncrypted ? FILE_ATTRIBUTE_ENCRYPTED : 0) | // setting attributes during CREATE_ALWAYS works since XP and is the only way to apply Encrypted attribute when the file denies read access + (script->CopyAttrs ? (op->Attr & (FILE_ATTRIBUTE_COMPRESSED | (lossEncryptionAttr ? 0 : FILE_ATTRIBUTE_ENCRYPTED))) : 0); + out = HANDLES_Q(CreateFileUtf8(op->TargetName, access, 0, NULL, CREATE_ALWAYS, fileAttrs, NULL)); + if (out == INVALID_HANDLE_VALUE && fileAttrs != (asyncPar->GetOverlappedFlag() | FILE_FLAG_SEQUENTIAL_SCAN)) // when the target disk cannot create an Encrypted file (observed on NTFS network disk (tested on share from XP) while logged in under a different username than we have in the system (on the current console) - the remote machine has a same-named user without a password, so it cannot be used over the network) + out = HANDLES_Q(CreateFileUtf8(op->TargetName, access, 0, NULL, CREATE_ALWAYS, asyncPar->GetOverlappedFlag() | FILE_FLAG_SEQUENTIAL_SCAN, NULL)); + if (script->CopyAttrs && out == INVALID_HANDLE_VALUE) + { // if read access to the directory is denied (we added it only for setting the Compressed attribute), try opening the file for write only + access = GENERIC_WRITE; + out = HANDLES_Q(CreateFileUtf8(op->TargetName, access, 0, NULL, CREATE_ALWAYS, fileAttrs, NULL)); + if (out == INVALID_HANDLE_VALUE && fileAttrs != (asyncPar->GetOverlappedFlag() | FILE_FLAG_SEQUENTIAL_SCAN)) // when the target disk cannot create an Encrypted file (observed on NTFS network disk (tested on share from XP) while logged in under a different username than we have in the system (on the current console) - the remote machine has a same-named user without a password, so it cannot be used over the network) + out = HANDLES_Q(CreateFileUtf8(op->TargetName, access, 0, NULL, CREATE_ALWAYS, asyncPar->GetOverlappedFlag() | FILE_FLAG_SEQUENTIAL_SCAN, NULL)); + } + if (out == INVALID_HANDLE_VALUE) // target file cannot be opened for writing, so delete it and create it again + { + // handles the situation when a Samba file must be overwritten: + // the file has mode 440+different_owner and sits in a directory where the current user has write access + // (deletion works, but direct overwrite does not (cannot open for writing) - workaround: + // delete and recreate the file) + // (Samba can allow deleting read-only files, which enables deleting them, + // otherwise Windows cannot delete a read-only file and we cannot drop + // the "read-only" attribute because the current user is not the owner) + if (chAttr) + SetFileAttributesUtf8(op->TargetName, attr); + targetCannotOpenForWrite = TRUE; + continue; + } + + // on target paths that support ADS also delete ADS on the target file (CREATE_ALWAYS should remove them, but on home W2K and XP they simply stay; no idea why, W2K and XP in VMWare delete ADS normally) + if (script->TargetPathSupADS && !DeleteAllADS(out, op->TargetName)) + { + HANDLES(CloseHandle(out)); + out = INVALID_HANDLE_VALUE; + if (chAttr) + SetFileAttributesUtf8(op->TargetName, attr); + targetCannotOpenForWrite = TRUE; + continue; + } + + // if we have not yet tested truncating the file to zero, obtain the new file size + if (mustDeleteFileBeforeOverwrite == 0 /* need test */) + { + HANDLES(CloseHandle(out)); + out = HANDLES_Q(CreateFileUtf8(op->TargetName, access, 0, NULL, OPEN_ALWAYS, asyncPar->GetOverlappedFlag() | FILE_FLAG_SEQUENTIAL_SCAN, NULL)); + if (out == INVALID_HANDLE_VALUE) // cannot reopen the target file we just opened, unlikely, try deleting and recreating it + { + targetCannotOpenForWrite = TRUE; + continue; + } + CQuadWord newFileSize(0, 0); // file size after truncation + newFileSize.LoDWord = GetFileSize(out, &newFileSize.HiDWord); + if ((newFileSize.LoDWord != INVALID_FILE_SIZE || GetLastError() == NO_ERROR) && // we have the new size + newFileSize == CQuadWord(0, 0)) // file really has 0 bytes + { + if (origFileSize != CQuadWord(0, 0)) // truncation can only be tested on a non-zero file + mustDeleteFileBeforeOverwrite = 2; /* no */ // success (not a SNAP server - NSA drive, truncation does not work there) + } + else + { + HANDLES(CloseHandle(out)); + out = INVALID_HANDLE_VALUE; + mustDeleteFileBeforeOverwrite = 1 /* yes */; // on error or when the size is non-zero, play it safe... + continue; + } + } + + if (script->CopyAttrs || !lossEncryptionAttr && copyAsEncrypted) + { + encryptionNotSupported = FALSE; + SetCompressAndEncryptedAttrs(op->TargetName, (!lossEncryptionAttr && copyAsEncrypted ? FILE_ATTRIBUTE_ENCRYPTED : 0) | (script->CopyAttrs ? (op->Attr & (FILE_ATTRIBUTE_COMPRESSED | (lossEncryptionAttr ? 0 : FILE_ATTRIBUTE_ENCRYPTED))) : 0), + &out, script->CopyAttrs, &encryptionNotSupported, asyncPar); + if (encryptionNotSupported) // unable to apply the Encrypted attribute, ask the user what to do... + { + if (dlgData.FileOutLossEncrAll) + lossEncryptionAttr = TRUE; + else + { + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + if (*dlgData.CancelWorker) + goto CANCEL_ENCNOTSUP; + + if (dlgData.SkipAllFileOutLossEncr) + goto SKIP_ENCNOTSUP; + + int ret; + ret = IDCANCEL; + char* data[4]; + data[0] = (char*)&ret; + data[1] = (char*)TRUE; + data[2] = op->TargetName; + data[3] = (char*)(INT_PTR)isMove; + SendMessage(hProgressDlg, WM_USER_DIALOG, 12, (LPARAM)data); + switch (ret) + { + case IDB_ALL: + dlgData.FileOutLossEncrAll = TRUE; // the break; is intentionally missing here + case IDYES: + lossEncryptionAttr = TRUE; + break; + + case IDB_SKIPALL: + dlgData.SkipAllFileOutLossEncr = TRUE; + case IDB_SKIP: + goto SKIP_ENCNOTSUP; + + case IDCANCEL: + goto CANCEL_ENCNOTSUP; + } + } + } + } + } + break; + } + + goto COPY; + } + else // regular error + { + NORMAL_ERROR: + + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + if (*dlgData.CancelWorker) + goto CANCEL_OPEN2; + + if (dlgData.SkipAllFileOpenOut) + goto SKIP_OPEN_OUT; + + int ret; + ret = IDCANCEL; + char* data[4]; + data[0] = (char*)&ret; + data[1] = LoadStr(errDeletingFile ? IDS_ERRORDELETINGFILE : IDS_ERROROPENINGFILE); + data[2] = op->TargetName; + data[3] = GetErrorText(err); + SendMessage(hProgressDlg, WM_USER_DIALOG, 0, (LPARAM)data); + switch (ret) + { + case IDRETRY: + break; + + case IDB_SKIPALL: + dlgData.SkipAllFileOpenOut = TRUE; + case IDB_SKIP: + goto SKIP_OPEN_OUT; + + case IDCANCEL: + goto CANCEL_OPEN2; + } + } + } + } + } + } + else + { + OPEN_IN_ERROR: + + DWORD err = GetLastError(); + if (invalidSrcName) + err = ERROR_INVALID_NAME; + if (asyncPar->Failed()) + err = ERROR_NOT_ENOUGH_MEMORY; // cannot create the synchronization event = lack of resources (will probably never happens, so we do not bother) + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + if (*dlgData.CancelWorker) + return FALSE; + + if (dlgData.SkipAllFileOpenIn) + goto SKIP_OPEN_IN; + + int ret; + ret = IDCANCEL; + char* data[4]; + data[0] = (char*)&ret; + data[1] = LoadStr(IDS_ERROROPENINGFILE); + data[2] = op->SourceName; + data[3] = GetErrorText(err); + SendMessage(hProgressDlg, WM_USER_DIALOG, 0, (LPARAM)data); + switch (ret) + { + case IDRETRY: + break; + + case IDB_SKIPALL: + dlgData.SkipAllFileOpenIn = TRUE; + case IDB_SKIP: + { + SKIP_OPEN_IN: + + totalDone += op->Size; + SetTFSandPSforSkippedFile(op, lastTransferredFileSize, script, totalDone); + + SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); + if (skip != NULL) + *skip = TRUE; + return TRUE; + } + + case IDCANCEL: + return FALSE; + } + } + } +} + +BOOL DoMoveFile(COperation* op, HWND hProgressDlg, void* buffer, + COperations* script, CQuadWord& totalDone, BOOL dir, + DWORD clearReadonlyMask, BOOL* novellRenamePatch, BOOL lantasticCheck, + int& mustDeleteFileBeforeOverwrite, int& allocWholeFileOnStart, + CProgressDlgData& dlgData, BOOL copyADS, BOOL copyAsEncrypted, + BOOL* setDirTimeAfterMove, CAsyncCopyParams*& asyncPar, + BOOL ignInvalidName) +{ + char log_buffer[1024]; + _snprintf_s(log_buffer, sizeof(log_buffer), _TRUNCATE, "DoMoveFile: Source='%s', Target='%s'", op->SourceName ? op->SourceName : "NULL", op->TargetName ? op->TargetName : "NULL"); + OutputDebugStringA(log_buffer); + + if (script->CopyAttrs && copyAsEncrypted) + TRACE_E("DoMoveFile(): unexpected parameter value: copyAsEncrypted is TRUE when script->CopyAttrs is TRUE!"); + + // if the path ends with a space/dot, it is invalid and we must not move it, + // MoveFile would trim the spaces/dots and move a different file or under a different name, + // directories fare better: appending a backslash helps there, we block the move + // only when a new directory name would be invalid (when moving under the old + // name, 'ignInvalidName' is TRUE) + BOOL invalidName = FileNameIsInvalid(op->SourceName, TRUE, dir) || + FileNameIsInvalid(op->TargetName, TRUE, dir && ignInvalidName); + + if (!copyAsEncrypted && !script->SameRootButDiffVolume && HasTheSameRootPath(op->SourceName, op->TargetName)) + { + // if the path ends with a space or dot, we must append '\\', otherwise GetNamedSecurityInfo, + // GetDirTime, SetFileAttributes, and MoveFile trim the spaces/dots and operate on a different path + const char* sourceNameMvDir = op->SourceName; + char sourceNameMvDirCopy[3 * MAX_PATH]; + MakeCopyWithBackslashIfNeeded(sourceNameMvDir, sourceNameMvDirCopy); + const char* targetNameMvDir = op->TargetName; + char targetNameMvDirCopy[3 * MAX_PATH]; + MakeCopyWithBackslashIfNeeded(targetNameMvDir, targetNameMvDirCopy); + + int autoRetryAttempts = 0; + CSrcSecurity srcSecurity; + BOOL srcSecurityErr = FALSE; + if (!invalidName && script->CopySecurity) // should we copy NTFS security permissions? + { + CStrP sourceNameMvDirW(ConvertAllocUtf8ToWide(sourceNameMvDir, -1)); + if (sourceNameMvDirW != NULL) + { + srcSecurity.SrcError = GetNamedSecurityInfoW(sourceNameMvDirW, SE_FILE_OBJECT, + DACL_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION, + &srcSecurity.SrcOwner, &srcSecurity.SrcGroup, &srcSecurity.SrcDACL, + NULL, &srcSecurity.SrcSD); + } + else + { + srcSecurity.SrcError = ERROR_NO_UNICODE_TRANSLATION; + } + if (srcSecurity.SrcError != ERROR_SUCCESS) // failed to read security info from the source file -> nothing to apply on the target + { + srcSecurityErr = TRUE; + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + if (*dlgData.CancelWorker) + return FALSE; + + int ret; + ret = IDCANCEL; + if (dlgData.IgnoreAllCopyPermErr) + ret = IDB_IGNORE; + else + { + char* data[4]; + data[0] = (char*)&ret; + data[1] = op->SourceName; + data[2] = op->TargetName; + data[3] = (char*)(DWORD_PTR)srcSecurity.SrcError; + SendMessage(hProgressDlg, WM_USER_DIALOG, 10, (LPARAM)data); + } + switch (ret) + { + case IDB_IGNOREALL: + dlgData.IgnoreAllCopyPermErr = TRUE; // the break; is intentionally missing here + case IDB_IGNORE: + break; + + case IDCANCEL: + return FALSE; + } + } + } + FILETIME dirTimeModified; + BOOL dirTimeModifiedIsValid = FALSE; + if (!invalidName && dir && !*novellRenamePatch && *setDirTimeAfterMove != 2 /* no */) // the issue apparently does not apply to Novell Netware, so ignore it there (affects e.g. Samba) + dirTimeModifiedIsValid = GetDirTime(sourceNameMvDir, &dirTimeModified); + while (1) + { + if (!invalidName && !*novellRenamePatch && SalMoveFile(sourceNameMvDir, targetNameMvDir)) + { + if (script->CopyAttrs && (op->Attr & FILE_ATTRIBUTE_ARCHIVE) == 0) // Archive attribute was not set, MoveFile turned it on, clear it again + SetFileAttributesUtf8(targetNameMvDir, op->Attr); // leave without handling or retry, not important (it normally toggles chaotically) + + OPERATION_DONE: + + if (script->CopyAttrs) // check whether the source file attributes were preserved + { + DWORD curAttrs; + curAttrs = SalGetFileAttributes(targetNameMvDir); + if (curAttrs == INVALID_FILE_ATTRIBUTES || (curAttrs & DISPLAYED_ATTRIBUTES) != (op->Attr & DISPLAYED_ATTRIBUTES)) + { // attributes probably were not preserved, warn the user + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + if (*dlgData.CancelWorker) + goto MOVE_ERROR_2; + + int ret; + ret = IDCANCEL; + if (dlgData.IgnoreAllSetAttrsErr) + ret = IDB_IGNORE; + else + { + char* data[4]; + data[0] = (char*)&ret; + data[1] = op->TargetName; + data[2] = (char*)(DWORD_PTR)(op->Attr & DISPLAYED_ATTRIBUTES); + data[3] = (char*)(DWORD_PTR)(curAttrs == INVALID_FILE_ATTRIBUTES ? 0 : (curAttrs & DISPLAYED_ATTRIBUTES)); + SendMessage(hProgressDlg, WM_USER_DIALOG, 9, (LPARAM)data); + } + switch (ret) + { + case IDB_IGNOREALL: + dlgData.IgnoreAllSetAttrsErr = TRUE; // the break; is intentionally missing here + case IDB_IGNORE: + break; + + case IDCANCEL: + { + MOVE_ERROR_2: + + return FALSE; // the file was moved to the target + cancel occurred; but we would rather not move it back, nobody should mind much + } + } + } + } + + if (script->CopySecurity && !srcSecurityErr) // should we copy NTFS security permissions? + { + DWORD err; + if (!DoCopySecurity(sourceNameMvDir, targetNameMvDir, &err, &srcSecurity)) + { + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + if (*dlgData.CancelWorker) + goto MOVE_ERROR_2; + + int ret; + ret = IDCANCEL; + if (dlgData.IgnoreAllCopyPermErr) + ret = IDB_IGNORE; + else + { + char* data[4]; + data[0] = (char*)&ret; + data[1] = op->SourceName; + data[2] = op->TargetName; + data[3] = (char*)(DWORD_PTR)err; + SendMessage(hProgressDlg, WM_USER_DIALOG, 10, (LPARAM)data); + } + switch (ret) + { + case IDB_IGNOREALL: + dlgData.IgnoreAllCopyPermErr = TRUE; // the break; is intentionally missing here + case IDB_IGNORE: + break; + + case IDCANCEL: + goto MOVE_ERROR_2; + } + } + } + + if (dir && dirTimeModifiedIsValid && *setDirTimeAfterMove != 2 /* no */) + { + FILETIME movedDirTimeModified; + if (GetDirTime(targetNameMvDir, &movedDirTimeModified)) + { + if (CompareFileTime(&dirTimeModified, &movedDirTimeModified) == 0) + { + if (*setDirTimeAfterMove == 0 /* need test */) + *setDirTimeAfterMove = 2 /* no */; + } + else + { + if (*setDirTimeAfterMove == 0 /* need test */) + *setDirTimeAfterMove = 1 /* yes */; + DoCopyDirTime(hProgressDlg, targetNameMvDir, &dirTimeModified, dlgData, TRUE); // ignore any failure, this is just a hack (we already ignore time read errors from the directory); MoveFile should not change times + } + } + } + + script->AddBytesToSpeedMetersAndTFSandPS((DWORD)op->Size.Value, TRUE, 0, NULL, MAX_OP_FILESIZE); + + totalDone += op->Size; + SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); + return TRUE; + } + else + { + DWORD err = GetLastError(); + if (invalidName) + err = ERROR_INVALID_NAME; + // Novell patch - before calling MoveFile we need to drop the read-only attribute + if (!invalidName && *novellRenamePatch || err == ERROR_ACCESS_DENIED) + { + DWORD attr = SalGetFileAttributes(sourceNameMvDir); + BOOL setAttr = ClearReadOnlyAttr(sourceNameMvDir, attr); + if (SalMoveFile(sourceNameMvDir, targetNameMvDir)) + { + if (!*novellRenamePatch) + *novellRenamePatch = TRUE; // the next operations will go straight through here + if (setAttr || script->CopyAttrs && (attr & FILE_ATTRIBUTE_ARCHIVE) == 0) + { + CStrP targetNameW(ConvertAllocUtf8ToWide(targetNameMvDir, -1)); + if (targetNameW != NULL) + SetFileAttributesW(targetNameW, attr); + } + + goto OPERATION_DONE; + } + err = GetLastError(); + if (setAttr) + { + CStrP sourceNameW(ConvertAllocUtf8ToWide(sourceNameMvDir, -1)); + if (sourceNameW != NULL) + SetFileAttributesW(sourceNameW, attr); + } + } + + if (StrICmp(op->SourceName, op->TargetName) != 0 && // provided this is not just a change of case + (err == ERROR_FILE_EXISTS || // verify whether this is only overwriting the DOS name of the file/directory + err == ERROR_ALREADY_EXISTS) && + targetNameMvDir == op->TargetName) // no invalid names are allowed here + { + WIN32_FIND_DATAW findData; + CStrP targetNameW(ConvertAllocUtf8ToWide(op->TargetName, -1)); + HANDLE find = targetNameW != NULL ? HANDLES_Q(FindFirstFileW(targetNameW, &findData)) : INVALID_HANDLE_VALUE; + if (find != INVALID_HANDLE_VALUE) + { + HANDLES(FindClose(find)); + const char* tgtName = SalPathFindFileName(op->TargetName); + char altName[MAX_PATH]; + char fullName[MAX_PATH]; + if (ConvertWideToUtf8(findData.cAlternateFileName, -1, altName, _countof(altName)) == 0) + altName[0] = 0; + if (ConvertWideToUtf8(findData.cFileName, -1, fullName, _countof(fullName)) == 0) + fullName[0] = 0; + if (StrICmp(tgtName, altName) == 0 && // match only on the DOS name + StrICmp(tgtName, fullName) != 0) // (the full name is different) + { + // rename ("tidy up") the file/directory with the conflicting DOS name to a temporary 8.3 name (does not need an extra DOS name) + char tmpName[MAX_PATH + 20]; + if (strlen(op->TargetName) >= _countof(tmpName)) + { + TRACE_E("DoMoveFile(): target path too long for DOS-name collision workaround: " << op->TargetName); + } + else + { + lstrcpyn(tmpName, op->TargetName, _countof(tmpName)); + CutDirectory(tmpName); + SalPathAddBackslash(tmpName, _countof(tmpName)); + char* tmpNamePart = tmpName + strlen(tmpName); + char origFullName[MAX_PATH + 20]; + if (SalPathAppend(tmpName, fullName, _countof(tmpName))) + { + strcpy(origFullName, tmpName); + DWORD num = (GetTickCount() / 10) % 0xFFF; + DWORD origFullNameAttr = SalGetFileAttributes(origFullName); + while (1) + { + sprintf(tmpNamePart, "sal%03X", num++); + if (SalMoveFile(origFullName, tmpName)) + break; + DWORD e = GetLastError(); + if (e != ERROR_FILE_EXISTS && e != ERROR_ALREADY_EXISTS) + { + tmpName[0] = 0; + break; + } + } + if (tmpName[0] != 0) // if we managed to "tidy up" the conflicting file/directory, try moving it again + { // then restore the original name of the "tidied" file/directory + BOOL moveDone = SalMoveFile(sourceNameMvDir, op->TargetName); + if (script->CopyAttrs && (op->Attr & FILE_ATTRIBUTE_ARCHIVE) == 0) // the Archive attribute was not set; MoveFile turned it on, clear it again + { + CStrP targetNameW2(ConvertAllocUtf8ToWide(op->TargetName, -1)); + if (targetNameW2 != NULL) + SetFileAttributesW(targetNameW2, op->Attr); // leave without handling or retry, not important (it normally toggles chaotically) + } + if (!SalMoveFile(tmpName, origFullName)) + { // this apparently can happen; inexplicably, Windows creates a file named origFullName instead of op->TargetName (the DOS name) + TRACE_I("DoMoveFile(): Unexpected situation: unable to rename file/dir from tmp-name to original long file name! " << origFullName); + if (moveDone) + { + if (SalMoveFile(op->TargetName, sourceNameMvDir)) + moveDone = FALSE; + if (!SalMoveFile(tmpName, origFullName)) + TRACE_E("DoMoveFile(): Fatal unexpected situation: unable to rename file/dir from tmp-name to original long file name! " << origFullName); + } + } + else + { + if ((origFullNameAttr & FILE_ATTRIBUTE_ARCHIVE) == 0) + SetFileAttributesUtf8(origFullName, origFullNameAttr); // leave without handling or retry, not important (it normally toggles chaotically) + } + + if (moveDone) + goto OPERATION_DONE; + } + } + else + TRACE_E("DoMoveFile(): Original full file/dir name is too long, unable to bypass only-dos-name-overwrite problem!"); + } + } + } + } + + if ((err == ERROR_ALREADY_EXISTS || // theoretically can happen for directories; prevent that (overwrite prompt is only for files) + err == ERROR_FILE_EXISTS) && + !dir && StrICmp(op->SourceName, op->TargetName) != 0 && + sourceNameMvDir == op->SourceName && targetNameMvDir == op->TargetName) // no invalid names allowed here (files only, and their names are validated) + { + HANDLE in, out; + in = HANDLES_Q(CreateFileUtf8(op->SourceName, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)); + if (in == INVALID_HANDLE_VALUE) + { + err = GetLastError(); + goto NORMAL_ERROR; + } + out = HANDLES_Q(CreateFileUtf8(op->TargetName, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)); + if (out == INVALID_HANDLE_VALUE) + { + err = GetLastError(); + HANDLES(CloseHandle(in)); + goto NORMAL_ERROR; + } + + if (!dlgData.OverwriteAll && (dlgData.CnfrmFileOver || script->OverwriteOlder)) + { + char sAttr[101], tAttr[101]; + BOOL getTimeFailed; + getTimeFailed = FALSE; + FILETIME sFileTime, tFileTime; + GetFileOverwriteInfo(sAttr, _countof(sAttr), in, op->SourceName, &sFileTime, &getTimeFailed); + GetFileOverwriteInfo(tAttr, _countof(tAttr), out, op->TargetName, &tFileTime, &getTimeFailed); + HANDLES(CloseHandle(in)); + HANDLES(CloseHandle(out)); + + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + if (*dlgData.CancelWorker) + goto CANCEL_OPEN; + + if (dir) + TRACE_E("Error in script."); + + if (dlgData.SkipAllOverwrite) + goto SKIP_OPEN; + + int ret; + ret = IDCANCEL; + + if (!getTimeFailed && script->OverwriteOlder) // option from the Copy/Move dialog + { + // trim timestamps to seconds (different file systems store times with different precision, leading to "differences" even between "matching" times) + *(unsigned __int64*)&sFileTime = *(unsigned __int64*)&sFileTime - (*(unsigned __int64*)&sFileTime % 10000000); + *(unsigned __int64*)&tFileTime = *(unsigned __int64*)&tFileTime - (*(unsigned __int64*)&tFileTime % 10000000); + + if (CompareFileTime(&sFileTime, &tFileTime) > 0) + ret = IDYES; // older ones should be overwritten without asking + else + ret = IDB_SKIP; // skip the other existing ones + } + else + { + // display the prompt + char* data[5]; + data[0] = (char*)&ret; + data[1] = op->TargetName; + data[2] = tAttr; + data[3] = op->SourceName; + data[4] = sAttr; + SendMessage(hProgressDlg, WM_USER_DIALOG, 1, (LPARAM)data); + } + switch (ret) + { + case IDB_ALL: + dlgData.OverwriteAll = TRUE; + case IDYES: + break; + + case IDB_SKIPALL: + dlgData.SkipAllOverwrite = TRUE; + case IDB_SKIP: + { + SKIP_OPEN: + + totalDone += op->Size; + script->SetProgressSize(totalDone); + SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); + return TRUE; + } + + case IDCANCEL: + { + CANCEL_OPEN: + + return FALSE; + } + } + } + else + { + HANDLES(CloseHandle(in)); + HANDLES(CloseHandle(out)); + } + + DWORD attr = SalGetFileAttributes(op->TargetName); + if (attr != INVALID_FILE_ATTRIBUTES && (attr & (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM))) + { + if (!dlgData.OverwriteHiddenAll && dlgData.CnfrmSHFileOver) // ignore script->OverwriteOlder here; user wants to see that this is a SYSTEM or HIDDEN file even with the option enabled + { + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + if (*dlgData.CancelWorker) + goto CANCEL_OPEN; + + if (dir) + TRACE_E("Error in script."); + + if (dlgData.SkipAllSystemOrHidden) + goto SKIP_OPEN; + + int ret = IDCANCEL; + char* data[4]; + data[0] = (char*)&ret; + data[1] = LoadStr(IDS_CONFIRMFILEOVERWRITING); + data[2] = op->TargetName; + data[3] = LoadStr(IDS_WANTOVERWRITESHFILE); + SendMessage(hProgressDlg, WM_USER_DIALOG, 2, (LPARAM)data); + switch (ret) + { + case IDB_ALL: + dlgData.OverwriteHiddenAll = TRUE; + case IDYES: + break; + + case IDB_SKIPALL: + dlgData.SkipAllSystemOrHidden = TRUE; + case IDB_SKIP: + goto SKIP_OPEN; + + case IDCANCEL: + goto CANCEL_OPEN; + } + attr = SalGetFileAttributes(op->TargetName); // may also fail (returns INVALID_FILE_ATTRIBUTES) + } + } + + ClearReadOnlyAttr(op->TargetName, attr); // make sure it can be deleted ... + while (1) + { + if (DeleteFileUtf8(op->TargetName)) + break; + else + { + DWORD err2 = GetLastError(); + if (err2 == ERROR_FILE_NOT_FOUND) + break; // if the user already deleted the file manually, everything is fine + + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + if (*dlgData.CancelWorker) + return FALSE; + + if (dir) + TRACE_E("Error in script."); + + if (dlgData.SkipAllOverwriteErr) + goto SKIP_OVERWRITE_ERROR; + + int ret; + ret = IDCANCEL; + char* data[4]; + data[0] = (char*)&ret; + data[1] = LoadStr(IDS_ERROROVERWRITINGFILE); + data[2] = op->TargetName; + data[3] = GetErrorText(err2); + SendMessage(hProgressDlg, WM_USER_DIALOG, 0, (LPARAM)data); + switch (ret) + { + case IDRETRY: + break; + + case IDB_SKIPALL: + dlgData.SkipAllOverwriteErr = TRUE; + case IDB_SKIP: + { + SKIP_OVERWRITE_ERROR: + + totalDone += op->Size; + script->SetProgressSize(totalDone); + SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); + return TRUE; + } + + case IDCANCEL: + return FALSE; + } + } + } + } + else + { + NORMAL_ERROR: + + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + if (*dlgData.CancelWorker) + return FALSE; + + if (dlgData.SkipAllMoveErrors) + goto SKIP_MOVE_ERROR; + + if (err == ERROR_SHARING_VIOLATION && ++autoRetryAttempts <= 2) + { // auto-retry added to handle move errors while directory icons are being read (SHGetFileInfo running in parallel with MoveFile) + Sleep(100); // wait a moment before the next attempt + } + else + { + int ret; + ret = IDCANCEL; + char* data[4]; + data[0] = (char*)&ret; + data[1] = op->SourceName; + data[2] = op->TargetName; + data[3] = GetErrorText(err); + SendMessage(hProgressDlg, WM_USER_DIALOG, dir ? 4 : 3, (LPARAM)data); + switch (ret) + { + case IDRETRY: + break; + + case IDB_SKIPALL: + dlgData.SkipAllMoveErrors = TRUE; + case IDB_SKIP: + { + SKIP_MOVE_ERROR: + + totalDone += op->Size; + script->SetProgressSize(totalDone); + SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); + return TRUE; + } + + case IDCANCEL: + return FALSE; + } + } + } + } + } + } + else + { + if (dir) + { + TRACE_E("Error in script."); + return FALSE; + } + + BOOL skip; + BOOL notError = DoCopyFile(op, hProgressDlg, buffer, script, totalDone, + clearReadonlyMask, &skip, lantasticCheck, + mustDeleteFileBeforeOverwrite, allocWholeFileOnStart, + dlgData, copyADS, copyAsEncrypted, TRUE, asyncPar); + if (notError && !skip) // still need to clean up the file from the source + { + ClearReadOnlyAttr(op->SourceName); // ensure it can be deleted + while (1) + { + if (DeleteFileUtf8(op->SourceName)) + break; + { + DWORD err = GetLastError(); + + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + if (*dlgData.CancelWorker) + return FALSE; + + if (dlgData.SkipAllDeleteErr) + return TRUE; + + int ret = IDCANCEL; + char* data[4]; + data[0] = (char*)&ret; + data[1] = LoadStr(IDS_ERRORDELETINGFILE); + data[2] = op->SourceName; + data[3] = GetErrorText(err); + SendMessage(hProgressDlg, WM_USER_DIALOG, 0, (LPARAM)data); + switch (ret) + { + case IDRETRY: + break; + case IDB_SKIPALL: + dlgData.SkipAllDeleteErr = TRUE; + case IDB_SKIP: + return TRUE; + case IDCANCEL: + return FALSE; + } + } + } + } + return notError; + } +} + +BOOL DoDeleteFile(HWND hProgressDlg, char* name, const CQuadWord& size, COperations* script, + CQuadWord& totalDone, DWORD attr, CProgressDlgData& dlgData) +{ + // if the path ends with a space/dot it is invalid and we must not delete it, + // DeleteFile would trim the spaces/dots and remove a different file + BOOL invalidName = FileNameIsInvalid(name, TRUE); + + DWORD err; + while (1) + { + if (!invalidName) + { + if (attr & (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM)) + { + if (!dlgData.DeleteHiddenAll && dlgData.CnfrmSHFileDel) + { + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + if (*dlgData.CancelWorker) + return FALSE; + + if (dlgData.SkipAllSystemOrHidden) + goto SKIP_DELETE; + + int ret = IDCANCEL; + char* data[4]; + data[0] = (char*)&ret; + data[1] = LoadStr(IDS_CONFIRMSHFILEDELETE); + data[2] = name; + data[3] = LoadStr(IDS_DELETESHFILE); + SendMessage(hProgressDlg, WM_USER_DIALOG, 2, (LPARAM)data); + switch (ret) + { + case IDB_ALL: + dlgData.DeleteHiddenAll = TRUE; + case IDYES: + break; + + case IDB_SKIPALL: + dlgData.SkipAllSystemOrHidden = TRUE; + case IDB_SKIP: + goto SKIP_DELETE; + + case IDCANCEL: + return FALSE; + } + } + } + ClearReadOnlyAttr(name, attr); // ensure it can be deleted + + err = ERROR_SUCCESS; + BOOL useRecycleBin; + switch (dlgData.UseRecycleBin) + { + case 0: + useRecycleBin = script->CanUseRecycleBin && script->InvertRecycleBin; + break; + case 1: + useRecycleBin = script->CanUseRecycleBin && !script->InvertRecycleBin; + break; + case 2: + { + if (!script->CanUseRecycleBin || script->InvertRecycleBin) + useRecycleBin = FALSE; + else + { + const char* fileName = strrchr(name, '\\'); + if (fileName != NULL) // "always true" + { + fileName++; + int tmpLen = lstrlen(fileName); + const char* ext = fileName + tmpLen; + // while (ext > fileName && *ext != '.') ext--; + while (--ext >= fileName && *ext != '.') + ; + // if (ext == fileName) // ".cvspass" is treated as an extension in Windows ... + if (ext < fileName) + ext = fileName + tmpLen; + else + ext++; + useRecycleBin = dlgData.AgreeRecycleMasks(fileName, ext); + } + else + { + useRecycleBin = TRUE; // choose the safe option on error and delete via the Recycle Bin + TRACE_E("DoDeleteFile(): unexpected situation: filename does not contain backslash: " << name); + } + } + break; + } + } + if (useRecycleBin) + { + if (!PathContainsValidComponents((char*)name, FALSE)) + { + err = ERROR_INVALID_NAME; + } + else + { + CStrP nameW(ConvertAllocUtf8ToWide(name, -1)); + if (nameW == NULL) + { + err = ERROR_NO_UNICODE_TRANSLATION; + } + else + { + int nameLen = lstrlenW(nameW); + WCHAR* nameListW = (WCHAR*)malloc((nameLen + 2) * sizeof(WCHAR)); + if (nameListW == NULL) + { + err = ERROR_NOT_ENOUGH_MEMORY; + } + else + { + memcpy(nameListW, nameW, (nameLen + 1) * sizeof(WCHAR)); + nameListW[nameLen + 1] = 0; // double null + + CShellExecuteWnd shellExecuteWnd; + SHFILEOPSTRUCTW opCode; + memset(&opCode, 0, sizeof(opCode)); + + opCode.hwnd = shellExecuteWnd.Create(hProgressDlg, "SEW: DoDeleteFile"); + + opCode.wFunc = FO_DELETE; + opCode.pFrom = nameListW; + opCode.fFlags = FOF_ALLOWUNDO | FOF_SILENT | FOF_NOCONFIRMATION; + opCode.lpszProgressTitle = L""; + err = SHFileOperationW(&opCode); + free(nameListW); + } + } + } + } + else + { + if (DeleteFileUtf8(name) == 0) + err = GetLastError(); + } + } + else + { + err = ERROR_INVALID_NAME; + } + if (err == ERROR_SUCCESS) + { + totalDone += size; + SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); + return TRUE; + } + else + { + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + if (*dlgData.CancelWorker) + return FALSE; + + if (dlgData.SkipAllDeleteErr) + goto SKIP_DELETE; + + int ret; + ret = IDCANCEL; + char* data[4]; + data[0] = (char*)&ret; + data[1] = LoadStr(IDS_ERRORDELETINGFILE); + data[2] = name; + data[3] = GetErrorText(err); + SendMessage(hProgressDlg, WM_USER_DIALOG, 0, (LPARAM)data); + switch (ret) + { + case IDRETRY: + break; + + case IDB_SKIPALL: + dlgData.SkipAllDeleteErr = TRUE; + case IDB_SKIP: + { + SKIP_DELETE: + + totalDone += size; + SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); + return TRUE; + } + + case IDCANCEL: + return FALSE; + } + } + if (!invalidName) + { + DWORD attr2 = SalGetFileAttributes(name); // get the current attribute state + if (attr2 != INVALID_FILE_ATTRIBUTES) + attr = attr2; + } + } +} + +BOOL SalCreateDirectoryEx(const char* name, DWORD* err) +{ + if (err != NULL) + *err = 0; + // if the name ends with a space/dot we must append '\\', otherwise CreateDirectory + // quietly trims the trailing spaces/dots and creates a different directory + const char* nameCrDir = name; + char nameCrDirBuf[3 * MAX_PATH]; + MakeCopyWithBackslashIfNeeded(nameCrDir, nameCrDirBuf); + CStrP nameCrDirW(ConvertAllocUtf8ToWide(nameCrDir, -1)); + if (nameCrDirW == NULL) + { + if (err != NULL) + *err = ERROR_NO_UNICODE_TRANSLATION; + SetLastError(ERROR_NO_UNICODE_TRANSLATION); + return FALSE; + } + if (CreateDirectoryW(nameCrDirW, NULL)) + return TRUE; + else + { + DWORD errLoc = GetLastError(); + if (name == nameCrDir && // a name ending with a space/dot cannot collide with a DOS name + (errLoc == ERROR_FILE_EXISTS || // check whether this is only overwriting the file's DOS name + errLoc == ERROR_ALREADY_EXISTS)) + { + WIN32_FIND_DATAW data; + CStrP nameW(ConvertAllocUtf8ToWide(name, -1)); + HANDLE find = nameW != NULL ? HANDLES_Q(FindFirstFileW(nameW, &data)) : INVALID_HANDLE_VALUE; + if (find != INVALID_HANDLE_VALUE) + { + HANDLES(FindClose(find)); + const char* tgtName = SalPathFindFileName(name); + char altName[MAX_PATH]; + char fullName[MAX_PATH]; + if (ConvertWideToUtf8(data.cAlternateFileName, -1, altName, _countof(altName)) == 0) + altName[0] = 0; + if (ConvertWideToUtf8(data.cFileName, -1, fullName, _countof(fullName)) == 0) + fullName[0] = 0; + if (StrICmp(tgtName, altName) == 0 && // match only for the DOS name + StrICmp(tgtName, fullName) != 0) // (the full name differs) + { + // rename ("tidy up") the file/directory whose DOS name conflicts to a temporary 8.3 name (no extra DOS name needed) + char tmpName[MAX_PATH + 20]; + if (strlen(name) >= _countof(tmpName)) + { + TRACE_E("SalCreateDirectoryEx(): path too long for DOS-name collision workaround: " << name); + } + else + { + lstrcpyn(tmpName, name, _countof(tmpName)); + CutDirectory(tmpName); + SalPathAddBackslash(tmpName, _countof(tmpName)); + char* tmpNamePart = tmpName + strlen(tmpName); + char origFullName[MAX_PATH + 20]; + if (SalPathAppend(tmpName, fullName, _countof(tmpName))) + { + strcpy(origFullName, tmpName); + DWORD num = (GetTickCount() / 10) % 0xFFF; + DWORD origFullNameAttr = SalGetFileAttributes(origFullName); + while (1) + { + sprintf(tmpNamePart, "sal%03X", num++); + if (SalMoveFile(origFullName, tmpName)) + break; + DWORD e = GetLastError(); + if (e != ERROR_FILE_EXISTS && e != ERROR_ALREADY_EXISTS) + { + tmpName[0] = 0; + break; + } + } + if (tmpName[0] != 0) // if we managed to "tidy up" the conflicting file, retry the move + { // and then restore the original name of the "tidied" file + BOOL createDirDone = nameW != NULL ? CreateDirectoryW(nameW, NULL) : FALSE; + if (!SalMoveFile(tmpName, origFullName)) + { // this can apparently happen: inexplicably Windows creates a file named origFullName instead of name (the DOS name) + TRACE_I("Unexpected situation: unable to rename file from tmp-name to original long file name! " << origFullName); + if (createDirDone) + { + if (nameW != NULL && RemoveDirectoryW(nameW)) + createDirDone = FALSE; + if (!SalMoveFile(tmpName, origFullName)) + TRACE_E("Fatal unexpected situation: unable to rename file from tmp-name to original long file name! " << origFullName); + } + } + else + { + if ((origFullNameAttr & FILE_ATTRIBUTE_ARCHIVE) == 0) + { + CStrP origFullNameW(ConvertAllocUtf8ToWide(origFullName, -1)); + if (origFullNameW != NULL) + SetFileAttributesW(origFullNameW, origFullNameAttr); // leave it without extra handling or retries; not important (normally toggles unpredictably) + } + } + + if (createDirDone) + return TRUE; + } + } + else + TRACE_E("Original full file name is too long, unable to bypass only-dos-name-overwrite problem!"); + } + } + } + } + if (err != NULL) + *err = errLoc; + } + return FALSE; +} + +BOOL GetDirTime(const char* dirName, FILETIME* ftModified) +{ + HANDLE dir; + CStrP dirNameW(ConvertAllocUtf8ToWide(dirName, -1)); + dir = dirNameW != NULL + ? HANDLES_Q(CreateFileW(dirNameW, GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, + NULL)) + : INVALID_HANDLE_VALUE; + if (dir != INVALID_HANDLE_VALUE) + { + BOOL ret = GetFileTime(dir, NULL /*ftCreated*/, NULL /*ftAccessed*/, ftModified); + HANDLES(CloseHandle(dir)); + return ret; + } + return FALSE; +} + +BOOL DoCopyDirTime(HWND hProgressDlg, const char* targetName, FILETIME* modified, CProgressDlgData& dlgData, BOOL quiet) +{ + // if the path ends with a space/dot, we must append '\\', otherwise CreateFile + // trims the spaces/dots and works with a different path + const char* targetNameCrFile = targetName; + char targetNameCrFileCopy[3 * MAX_PATH]; + MakeCopyWithBackslashIfNeeded(targetNameCrFile, targetNameCrFileCopy); + CStrP targetNameW(ConvertAllocUtf8ToWide(targetNameCrFile, -1)); + + BOOL showError = !quiet; + DWORD error = NO_ERROR; + DWORD attr = targetNameW != NULL ? GetFileAttributesW(targetNameW) : INVALID_FILE_ATTRIBUTES; + BOOL setAttr = FALSE; + if (attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_READONLY)) + { + if (targetNameW != NULL) + SetFileAttributesW(targetNameW, attr & ~FILE_ATTRIBUTE_READONLY); + setAttr = TRUE; + } + HANDLE file; + file = targetNameW != NULL + ? HANDLES_Q(CreateFileW(targetNameW, GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, + NULL)) + : INVALID_HANDLE_VALUE; + if (file != INVALID_HANDLE_VALUE) + { + if (SetFileTime(file, NULL /*&ftCreated*/, NULL /*&ftAccessed*/, modified)) + showError = FALSE; // success! + else + error = GetLastError(); + HANDLES(CloseHandle(file)); + } + else + error = GetLastError(); + if (setAttr) + { + if (targetNameW != NULL) + SetFileAttributesW(targetNameW, attr); + } + + if (showError) + { + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + if (*dlgData.CancelWorker) + return FALSE; + + int ret; + ret = IDCANCEL; + if (dlgData.IgnoreAllCopyDirTimeErr) + ret = IDB_IGNORE; + else + { + char* data[4]; + data[0] = (char*)&ret; + data[1] = (char*)targetNameCrFile; + data[2] = (char*)(DWORD_PTR)error; + SendMessage(hProgressDlg, WM_USER_DIALOG, 11, (LPARAM)data); + } + switch (ret) + { + case IDB_IGNOREALL: + dlgData.IgnoreAllCopyDirTimeErr = TRUE; // break intentionally omitted here + case IDB_IGNORE: + break; + + case IDCANCEL: + return FALSE; + } + } + return TRUE; +} + +BOOL DoCreateDir(HWND hProgressDlg, char* name, DWORD attr, + DWORD clearReadonlyMask, CProgressDlgData& dlgData, + CQuadWord& totalDone, CQuadWord& operTotal, + const char* sourceDir, BOOL adsCopy, COperations* script, + void* buffer, BOOL& skip, BOOL& alreadyExisted, + BOOL createAsEncrypted, BOOL ignInvalidName) +{ + if (script->CopyAttrs && createAsEncrypted) + TRACE_E("DoCreateDir(): unexpected parameter value: createAsEncrypted is TRUE when script->CopyAttrs is TRUE!"); + + skip = FALSE; + alreadyExisted = FALSE; + CQuadWord lastTransferredFileSize; + script->GetTFS(&lastTransferredFileSize); + + BOOL invalidName = FileNameIsInvalid(name, TRUE, ignInvalidName); + + // if the path ends with a space/dot, we must append '\\'; otherwise SetFileAttributes + // and RemoveDirectory trim the spaces/dots and operate on a different path + const char* nameCrDir = name; + char nameCrDirCopy[3 * MAX_PATH]; + MakeCopyWithBackslashIfNeeded(nameCrDir, nameCrDirCopy); + const char* sourceDirCrDir = sourceDir; + char sourceDirCrDirCopy[3 * MAX_PATH]; + if (sourceDirCrDir != NULL) + MakeCopyWithBackslashIfNeeded(sourceDirCrDir, sourceDirCrDirCopy); + + while (1) + { + DWORD err; + if (!invalidName && SalCreateDirectoryEx(name, &err)) + { + script->AddBytesToSpeedMetersAndTFSandPS((DWORD)CREATE_DIR_SIZE.Value, TRUE, 0, NULL, MAX_OP_FILESIZE); // directory already created + + DWORD newAttr = attr & clearReadonlyMask; + if (sourceDir != NULL && adsCopy) // copy ADS when required + { + CQuadWord operDone = CREATE_DIR_SIZE; // directory already created + BOOL adsSkip = FALSE; + if (!DoCopyADS(hProgressDlg, sourceDir, TRUE, name, totalDone, + operDone, operTotal, dlgData, script, &adsSkip, buffer) || + adsSkip) // user cancelled or skipped at least one ADS + { + if (RemoveDirectoryUtf8(nameCrDir) == 0) + { + DWORD err2 = GetLastError(); + TRACE_E("Unable to remove newly created directory: " << name << ", error: " << GetErrorText(err2)); + } + if (!adsSkip) + return FALSE; // cancel the entire operation (Skip must return TRUE) + skip = TRUE; + newAttr = -1; // the directory should no longer exist, so do not apply attributes + } + } + if (newAttr != -1) + { + if (script->CopyAttrs || createAsEncrypted) // set Compressed & Encrypted attributes based on the source directory + { + if (createAsEncrypted) + { + newAttr &= ~FILE_ATTRIBUTE_COMPRESSED; + newAttr |= FILE_ATTRIBUTE_ENCRYPTED; + } + DWORD changeAttrErr = NO_ERROR; + DWORD currentAttrs = SalGetFileAttributes(name); + if (currentAttrs != INVALID_FILE_ATTRIBUTES) + { + if ((newAttr & FILE_ATTRIBUTE_COMPRESSED) != (currentAttrs & FILE_ATTRIBUTE_COMPRESSED) && + (newAttr & FILE_ATTRIBUTE_COMPRESSED) == 0) + { + changeAttrErr = UncompressFile(name, currentAttrs); + } + if (changeAttrErr == NO_ERROR && + (newAttr & FILE_ATTRIBUTE_ENCRYPTED) != (currentAttrs & FILE_ATTRIBUTE_ENCRYPTED)) + { + BOOL dummyCancelOper = FALSE; + if (newAttr & FILE_ATTRIBUTE_ENCRYPTED) + { + changeAttrErr = MyEncryptFile(hProgressDlg, name, currentAttrs, 0 /* allow encrypting directories with the SYSTEM attribute */, + dlgData, dummyCancelOper, FALSE); + + if ( //(WindowsVistaAndLater || script->TargetPathSupEFS) && // complain regardless of OS version and EFS support; originally directories on FAT could not be encrypted before Vista, we behave the same (to match Explorer, the Encrypted attribute is not that important) + !dlgData.DirCrLossEncrAll && changeAttrErr != ERROR_SUCCESS) + { // failed to set the Encrypted attribute on the directory, ask the user what to do + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + if (*dlgData.CancelWorker) + goto CANCEL_CRDIR; + + int ret; + if (dlgData.SkipAllDirCrLossEncr) + ret = IDB_SKIP; + else + { + ret = IDCANCEL; + char* data[4]; + data[0] = (char*)&ret; + data[1] = (char*)FALSE; + data[2] = name; + data[3] = (char*)(!script->IsCopyOperation); + SendMessage(hProgressDlg, WM_USER_DIALOG, 12, (LPARAM)data); + } + switch (ret) + { + case IDB_ALL: + dlgData.DirCrLossEncrAll = TRUE; // break intentionally omitted here + case IDYES: + break; + + case IDB_SKIPALL: + dlgData.SkipAllDirCrLossEncr = TRUE; + case IDB_SKIP: + { + ClearReadOnlyAttr(nameCrDir); // remove read-only attribute so the file can be deleted + RemoveDirectoryUtf8(nameCrDir); + script->SetTFS(lastTransferredFileSize); // add TFS only after the directory is fully outside; ProgressSize will be synced outside (no point in adjusting it here) + skip = TRUE; + return TRUE; + } + + case IDCANCEL: + goto CANCEL_CRDIR; + } + } + } + else + changeAttrErr = MyDecryptFile(name, currentAttrs, FALSE); + } + if (changeAttrErr == NO_ERROR && + (newAttr & FILE_ATTRIBUTE_COMPRESSED) != (currentAttrs & FILE_ATTRIBUTE_COMPRESSED) && + (newAttr & FILE_ATTRIBUTE_COMPRESSED) != 0) + { + changeAttrErr = CompressFile(name, currentAttrs); + } + } + else + changeAttrErr = GetLastError(); + if (changeAttrErr != NO_ERROR) + { + TRACE_I("DoCreateDir(): Unable to set Encrypted or Compressed attributes for " << name << "! error=" << GetErrorText(changeAttrErr)); + } + } + SetFileAttributesUtf8(nameCrDir, newAttr); + + if (script->CopyAttrs) // verify whether the source file attributes were preserved + { + DWORD curAttrs; + curAttrs = SalGetFileAttributes(name); + if (curAttrs == INVALID_FILE_ATTRIBUTES || (curAttrs & DISPLAYED_ATTRIBUTES) != (newAttr & DISPLAYED_ATTRIBUTES)) + { // attributes probably did not transfer; warn the user + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + if (*dlgData.CancelWorker) + goto CANCEL_CRDIR; + + int ret; + ret = IDCANCEL; + if (dlgData.IgnoreAllSetAttrsErr) + ret = IDB_IGNORE; + else + { + char* data[4]; + data[0] = (char*)&ret; + data[1] = name; + data[2] = (char*)(DWORD_PTR)(newAttr & DISPLAYED_ATTRIBUTES); + data[3] = (char*)(DWORD_PTR)(curAttrs == INVALID_FILE_ATTRIBUTES ? 0 : (curAttrs & DISPLAYED_ATTRIBUTES)); + SendMessage(hProgressDlg, WM_USER_DIALOG, 9, (LPARAM)data); + } + switch (ret) + { + case IDB_IGNOREALL: + dlgData.IgnoreAllSetAttrsErr = TRUE; // break intentionally omitted here + case IDB_IGNORE: + break; + + case IDCANCEL: + { + CANCEL_CRDIR: + + ClearReadOnlyAttr(nameCrDir); // remove read-only so the file can be deleted + RemoveDirectoryUtf8(nameCrDir); + return FALSE; + } + } + } + } + + if (sourceDir != NULL && script->CopySecurity) // should NTFS security permissions be copied? + { + DWORD err2; + if (!DoCopySecurity(sourceDir, name, &err2, NULL)) + { + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + if (*dlgData.CancelWorker) + goto CANCEL_CRDIR; + + int ret; + ret = IDCANCEL; + if (dlgData.IgnoreAllCopyPermErr) + ret = IDB_IGNORE; + else + { + char* data[4]; + data[0] = (char*)&ret; + data[1] = (char*)sourceDir; + data[2] = name; + data[3] = (char*)(DWORD_PTR)err2; + SendMessage(hProgressDlg, WM_USER_DIALOG, 10, (LPARAM)data); + } + switch (ret) + { + case IDB_IGNOREALL: + dlgData.IgnoreAllCopyPermErr = TRUE; // break intentionally omitted here + case IDB_IGNORE: + break; + + case IDCANCEL: + goto CANCEL_CRDIR; + } + } + } + } + return TRUE; + } + else + { + if (invalidName) + err = ERROR_INVALID_NAME; + if (err == ERROR_ALREADY_EXISTS || + err == ERROR_FILE_EXISTS) + { + DWORD attr2 = SalGetFileAttributes(name); + if (attr2 & FILE_ATTRIBUTE_DIRECTORY) // "directory overwrite" + { + if (dlgData.CnfrmDirOver && !dlgData.DirOverwriteAll) // should we ask the user about overwriting the directory? + { + char sAttr[101], tAttr[101]; + GetDirInfo(sAttr, _countof(sAttr), sourceDir); + GetDirInfo(tAttr, _countof(tAttr), name); + + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + if (*dlgData.CancelWorker) + return FALSE; + + if (dlgData.SkipAllDirOver) + goto SKIP_CREATE_ERROR; + + int ret = IDCANCEL; + char* data[5]; + data[0] = (char*)&ret; + data[1] = name; + data[2] = tAttr; + data[3] = (char*)sourceDir; + data[4] = sAttr; + SendMessage(hProgressDlg, WM_USER_DIALOG, 7, (LPARAM)data); + switch (ret) + { + case IDB_ALL: + dlgData.DirOverwriteAll = TRUE; + case IDYES: + break; + + case IDB_SKIPALL: + dlgData.SkipAllDirOver = TRUE; + case IDB_SKIP: + goto SKIP_CREATE_ERROR; + + case IDCANCEL: + return FALSE; + } + } + alreadyExisted = TRUE; + return TRUE; // o.k. + } + + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + if (*dlgData.CancelWorker) + return FALSE; + + if (dlgData.SkipAllDirCreate) + goto SKIP_CREATE_ERROR; + + int ret = IDCANCEL; + char* data[4]; + data[0] = (char*)&ret; + data[1] = LoadStr(IDS_ERRORCREATINGDIR); + data[2] = name; + data[3] = LoadStr(IDS_NAMEALREADYUSED); + SendMessage(hProgressDlg, WM_USER_DIALOG, 0, (LPARAM)data); + switch (ret) + { + case IDRETRY: + break; + + case IDB_SKIPALL: + dlgData.SkipAllDirCreate = TRUE; + case IDB_SKIP: + goto SKIP_CREATE_ERROR; + + case IDCANCEL: + return FALSE; + } + continue; + } + + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + if (*dlgData.CancelWorker) + return FALSE; + + if (dlgData.SkipAllDirCreateErr) + goto SKIP_CREATE_ERROR; + + int ret; + ret = IDCANCEL; + char* data[4]; + data[0] = (char*)&ret; + data[1] = LoadStr(IDS_ERRORCREATINGDIR); + data[2] = name; + data[3] = GetErrorText(err); + SendMessage(hProgressDlg, WM_USER_DIALOG, 0, (LPARAM)data); + switch (ret) + { + case IDRETRY: + break; + + case IDB_SKIPALL: + dlgData.SkipAllDirCreateErr = TRUE; + case IDB_SKIP: + { + SKIP_CREATE_ERROR: + + skip = TRUE; // this is a skip (all operations within the directory must be skipped) + return TRUE; + } + case IDCANCEL: + return FALSE; + } + } + } +} + +BOOL DoDeleteDir(HWND hProgressDlg, char* name, const CQuadWord& size, COperations* script, + CQuadWord& totalDone, DWORD attr, BOOL dontUseRecycleBin, CProgressDlgData& dlgData) +{ + DWORD err; + int AutoRetryCounter = 0; + DWORD startTime = GetTickCount(); + + // if the path ends with a space/dot, we must append '\\'; otherwise SetFileAttributes + // and RemoveDirectory trim the spaces/dots and operate on a different path + const char* nameRmDir = name; + char nameRmDirCopy[3 * MAX_PATH]; + MakeCopyWithBackslashIfNeeded(nameRmDir, nameRmDirCopy); + + while (1) + { + ClearReadOnlyAttr(nameRmDir, attr); // ensure it can be deleted + + err = ERROR_SUCCESS; + if (script->CanUseRecycleBin && !dontUseRecycleBin && + (script->InvertRecycleBin && dlgData.UseRecycleBin == 0 || + !script->InvertRecycleBin && dlgData.UseRecycleBin == 1) && + IsDirectoryEmpty(name)) // subdirectory must not contain any files!!! + { + if (!PathContainsValidComponents((char*)name, FALSE)) + { + err = ERROR_INVALID_NAME; + } + else + { + CStrP nameW(ConvertAllocUtf8ToWide(name, -1)); + if (nameW == NULL) + { + err = ERROR_NO_UNICODE_TRANSLATION; + } + else + { + int nameLen = lstrlenW(nameW); + WCHAR* nameListW = (WCHAR*)malloc((nameLen + 2) * sizeof(WCHAR)); + if (nameListW == NULL) + { + err = ERROR_NOT_ENOUGH_MEMORY; + } + else + { + memcpy(nameListW, nameW, (nameLen + 1) * sizeof(WCHAR)); + nameListW[nameLen + 1] = 0; // double null + + CShellExecuteWnd shellExecuteWnd; + SHFILEOPSTRUCTW opCode; + memset(&opCode, 0, sizeof(opCode)); + + opCode.hwnd = shellExecuteWnd.Create(hProgressDlg, "SEW: DoDeleteDir"); + + opCode.wFunc = FO_DELETE; + opCode.pFrom = nameListW; + opCode.fFlags = FOF_ALLOWUNDO | FOF_SILENT | FOF_NOCONFIRMATION; + opCode.lpszProgressTitle = L""; + err = SHFileOperationW(&opCode); + free(nameListW); + } + } + } + } + else + { + if (RemoveDirectoryUtf8(nameRmDir) == 0) + err = GetLastError(); + } + + if (err == ERROR_SUCCESS) + { + script->AddBytesToSpeedMetersAndTFSandPS((DWORD)size.Value, TRUE, 0, NULL, MAX_OP_FILESIZE); + + totalDone += size; + SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); + return TRUE; + } + else + { + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + if (*dlgData.CancelWorker) + return FALSE; + + if (dlgData.SkipAllDeleteErr) + goto SKIP_DELETE; + + if (AutoRetryCounter < 4 && GetTickCount() - startTime + (AutoRetryCounter + 1) * 100 <= 2000 && + (err == ERROR_DIR_NOT_EMPTY || err == ERROR_SHARING_VIOLATION)) + { // add auto-retry to handle this case: I have directories 1\2\3, deleting 1 including subdirectories while 3 is shown in a panel (watching for changes) -> removing 2 reports "directory not empty" because 3 stays in a transitional state due to change notifications (it is deleted, so it cannot be listed, but it still exists on disk briefly; quite a mess) + // TRACE_I("DoDeleteDir(): err: " << GetErrorText(err)); + AutoRetryCounter++; + Sleep(AutoRetryCounter * 100); + // TRACE_I("DoDeleteDir(): " << AutoRetryCounter << ". retry, delay is " << AutoRetryCounter * 100 << "ms"); + } + else + { + int ret; + ret = IDCANCEL; + char* data[4]; + data[0] = (char*)&ret; + data[1] = LoadStr(IDS_ERRORDELETINGDIR); + data[2] = (char*)nameRmDir; + data[3] = GetErrorText(err); + SendMessage(hProgressDlg, WM_USER_DIALOG, 0, (LPARAM)data); + switch (ret) + { + case IDRETRY: + break; + + case IDB_SKIPALL: + dlgData.SkipAllDeleteErr = TRUE; + case IDB_SKIP: + { + SKIP_DELETE: + + totalDone += size; + script->SetProgressSize(totalDone); + SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); + return TRUE; + } + + case IDCANCEL: + return FALSE; + } + } + } + + DWORD attr2 = SalGetFileAttributes(nameRmDir); // get the current attribute state + if (attr2 != INVALID_FILE_ATTRIBUTES) + attr = attr2; + } +} + +#define FSCTL_GET_REPARSE_POINT CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 42, METHOD_BUFFERED, FILE_ANY_ACCESS) // REPARSE_DATA_BUFFER +#define FSCTL_DELETE_REPARSE_POINT CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 43, METHOD_BUFFERED, FILE_SPECIAL_ACCESS) // REPARSE_DATA_BUFFER, + +#define IO_REPARSE_TAG_SYMLINK (0xA000000CL) + +/* This code copies a junction point into an empty directory (the directory must be created in + advance � to keep it simple we always use "D:\\ZUMPA\\link" here). + + People sometimes want to copy the contents of the junction, sometimes they want to copy only the junction as a link, + and sometimes they want to skip it (unclear whether that should create an empty junction directory)... + if we ever implement it properly, the script builder will need a comprehensive dialog asking what to do. + +#define FSCTL_SET_REPARSE_POINT CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 41, METHOD_BUFFERED, FILE_SPECIAL_ACCESS) // REPARSE_DATA_BUFFER, +#define FSCTL_GET_REPARSE_POINT CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 42, METHOD_BUFFERED, FILE_ANY_ACCESS) // REPARSE_DATA_BUFFER + +// Structure for FSCTL_SET_REPARSE_POINT, FSCTL_GET_REPARSE_POINT, and +// FSCTL_DELETE_REPARSE_POINT. +// This version of the reparse data buffer is only for Microsoft tags. +*/ + diff --git a/src/cfgdlg.h b/src/cfgdlg.h index bc679c07..044b04ed 100644 --- a/src/cfgdlg.h +++ b/src/cfgdlg.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 Taskscape Ltd +// SPDX-FileCopyrightText: 2023 Taskscape Ltd // SPDX-License-Identifier: GPL-2.0-or-later // CommentsTranslationProject: TRANSLATED @@ -496,10 +496,10 @@ extern CLoadSaveToRegistryMutex LoadSaveToRegistryMutex; // mutex for synchroniz // // **************************************************************************** -class CCfgPageGeneral : public CCommonPropSheetPage +class CConfigPageGeneral : public CCommonPropSheetPage { public: - CCfgPageGeneral(); + CConfigPageGeneral(); virtual void Validate(CTransferInfo& ti); virtual void Transfer(CTransferInfo& ti); @@ -513,14 +513,14 @@ class CCfgPageGeneral : public CCommonPropSheetPage // // **************************************************************************** -class CCfgPageRegional : public CCommonPropSheetPage +class CConfigPageRegional : public CCommonPropSheetPage { public: char SLGName[MAX_PATH]; char DirName[MAX_PATH]; public: - CCfgPageRegional(); + CConfigPageRegional(); virtual void Transfer(CTransferInfo& ti); @@ -535,7 +535,7 @@ class CCfgPageRegional : public CCommonPropSheetPage class CToolbarHeader; -class CCfgPageView : public CCommonPropSheetPage +class CConfigPageView : public CCommonPropSheetPage { protected: BOOL Dirty; @@ -549,7 +549,7 @@ class CCfgPageView : public CCommonPropSheetPage int SelectIndex; public: - CCfgPageView(int index); + CConfigPageView(int index); virtual void Validate(CTransferInfo& ti); virtual void Transfer(CTransferInfo& ti); @@ -1167,8 +1167,8 @@ class CConfigurationDlg : public CTreePropDialog CConfigurationDlg(HWND parent, CUserMenuItems* userMenuItems, int mode = 0, int param = 0); public: - CCfgPageGeneral PageGeneral; - CCfgPageView PageView; + CConfigPageGeneral PageGeneral; + CConfigPageView PageView; CCfgPageViewer PageViewer; CCfgPageUserMenu PageUserMenu; CCfgPageHotPath PageHotPath; @@ -1183,7 +1183,7 @@ class CConfigurationDlg : public CTreePropDialog CCfgPageIconOvrls PageIconOvrls; CCfgPageAppearance PageAppear; CCfgPageMainWindow PageMainWindow; - CCfgPageRegional PageRegional; + CConfigPageRegional PageRegional; CCfgPageHistory PageHistory; CCfgPageChangeDrive PageChangeDrive; CCfgPagePanels PagePanels; diff --git a/src/dialogs.h b/src/dialogs.h index 514944a1..6298c566 100644 --- a/src/dialogs.h +++ b/src/dialogs.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 Taskscape Ltd +// SPDX-FileCopyrightText: 2023 Taskscape Ltd // SPDX-License-Identifier: GPL-2.0-or-later // CommentsTranslationProject: TRANSLATED @@ -705,13 +705,13 @@ class CSetSpeedLimDialog : public CCommonDialog #define PASSWORD_MAXLEN (256 + 1) #define DOMAIN_MAXLEN (256 + 1) -class CEnterPasswdDialog : public CCommonDialog +class CPasswordDialog : public CCommonDialog { public: char Passwd[PASSWORD_MAXLEN]; char User[USERNAME_MAXLEN]; - CEnterPasswdDialog(HWND parent, const char* path, const char* user, CObjectOrigin origin = ooStandard); + CPasswordDialog(HWND parent, const char* path, const char* user, CObjectOrigin origin = ooStandard); virtual void Validate(CTransferInfo& ti); virtual void Transfer(CTransferInfo& ti); @@ -797,10 +797,10 @@ class CUnpackDialog : public CCommonDialog // // **************************************************************************** -class CZIPSizeResultsDlg : public CCommonDialog +class CZipSizeResultsDialog : public CCommonDialog { public: - CZIPSizeResultsDlg(HWND parent, const CQuadWord& size, int files, int dirs); + CZipSizeResultsDialog(HWND parent, const CQuadWord& size, int files, int dirs); protected: virtual INT_PTR DialogProc(UINT uMsg, WPARAM wParam, LPARAM lParam); diff --git a/src/dialogs3.cpp b/src/dialogs_attributes.cpp similarity index 99% rename from src/dialogs3.cpp rename to src/dialogs_attributes.cpp index 7b74e5d7..3a7b0d44 100644 --- a/src/dialogs3.cpp +++ b/src/dialogs_attributes.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 Taskscape Ltd +// SPDX-FileCopyrightText: 2023 Taskscape Ltd // SPDX-License-Identifier: GPL-2.0-or-later // CommentsTranslationProject: TRANSLATED @@ -1760,10 +1760,10 @@ CDriveInfo::DialogProc(UINT uMsg, WPARAM wParam, LPARAM lParam) // // **************************************************************************** -// CEnterPasswdDialog +// CPasswordDialog // -CEnterPasswdDialog::CEnterPasswdDialog(HWND parent, const char* path, const char* user, +CPasswordDialog::CPasswordDialog(HWND parent, const char* path, const char* user, CObjectOrigin origin) : CCommonDialog(HLanguage, IDD_ENTERPASSWD, IDD_ENTERPASSWD, parent, origin) { @@ -1775,9 +1775,9 @@ CEnterPasswdDialog::CEnterPasswdDialog(HWND parent, const char* path, const char Passwd[0] = 0; } -void CEnterPasswdDialog::Validate(CTransferInfo& ti) +void CPasswordDialog::Validate(CTransferInfo& ti) { - CALL_STACK_MESSAGE1("CEnterPasswdDialog::Validate()"); + CALL_STACK_MESSAGE1("CPasswordDialog::Validate()"); /* // empty user-name = default username HWND edit; if (ti.GetControl(edit, IDE_NETUSER) && ti.Type == ttDataFromWindow) @@ -1792,14 +1792,14 @@ void CEnterPasswdDialog::Validate(CTransferInfo& ti) */ } -void CEnterPasswdDialog::Transfer(CTransferInfo& ti) +void CPasswordDialog::Transfer(CTransferInfo& ti) { ti.EditLine(IDE_NETPASSWD, Passwd, PASSWORD_MAXLEN); ti.EditLine(IDE_NETUSER, User, USERNAME_MAXLEN); } INT_PTR -CEnterPasswdDialog::DialogProc(UINT uMsg, WPARAM wParam, LPARAM lParam) +CPasswordDialog::DialogProc(UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { @@ -2142,10 +2142,10 @@ CUnpackDialog::DialogProc(UINT uMsg, WPARAM wParam, LPARAM lParam) // // **************************************************************************** -// CZIPSizeResultsDlg +// CZipSizeResultsDialog // -CZIPSizeResultsDlg::CZIPSizeResultsDlg(HWND parent, const CQuadWord& size, int files, int dirs) +CZipSizeResultsDialog::CZipSizeResultsDialog(HWND parent, const CQuadWord& size, int files, int dirs) : CCommonDialog(HLanguage, IDD_ZIPSIZERESULTS, parent) { Size = size; @@ -2154,7 +2154,7 @@ CZIPSizeResultsDlg::CZIPSizeResultsDlg(HWND parent, const CQuadWord& size, int f } INT_PTR -CZIPSizeResultsDlg::DialogProc(UINT uMsg, WPARAM wParam, LPARAM lParam) +CZipSizeResultsDialog::DialogProc(UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { diff --git a/src/dialogse.cpp b/src/dialogs_config_environment.cpp similarity index 100% rename from src/dialogse.cpp rename to src/dialogs_config_environment.cpp diff --git a/src/dialogs4.cpp b/src/dialogs_config_general.cpp similarity index 99% rename from src/dialogs4.cpp rename to src/dialogs_config_general.cpp index cdce4ad5..362343ca 100644 --- a/src/dialogs4.cpp +++ b/src/dialogs_config_general.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 Taskscape Ltd +// SPDX-FileCopyrightText: 2023 Taskscape Ltd // SPDX-License-Identifier: GPL-2.0-or-later // CommentsTranslationProject: TRANSLATED @@ -776,15 +776,15 @@ void CConfigurationDlg::DialogProc(UINT uMsg, WPARAM wParam, LPARAM lParam) // // **************************************************************************** -// CCfgPageGeneral +// CConfigPageGeneral // -CCfgPageGeneral::CCfgPageGeneral() +CConfigPageGeneral::CConfigPageGeneral() : CCommonPropSheetPage(NULL, HLanguage, IDD_CFGPAGE_GENERAL, IDD_CFGPAGE_GENERAL, PSP_USETITLE, NULL) { } -void CCfgPageGeneral::Validate(CTransferInfo& ti) +void CConfigPageGeneral::Validate(CTransferInfo& ti) { BOOL useTimeRes; ti.CheckBox(IDC_TIMERESOLUTION, useTimeRes); @@ -801,7 +801,7 @@ void CCfgPageGeneral::Validate(CTransferInfo& ti) } } -void CCfgPageGeneral::Transfer(CTransferInfo& ti) +void CConfigPageGeneral::Transfer(CTransferInfo& ti) { ti.CheckBox(IDC_AUTOSAVE, Configuration.AutoSave); ti.CheckBox(IDC_CLOSESHELL, Configuration.CloseShell); @@ -830,7 +830,7 @@ void CCfgPageGeneral::Transfer(CTransferInfo& ti) EnableControls(); } -void CCfgPageGeneral::EnableControls() +void CConfigPageGeneral::EnableControls() { BOOL useTimeRes = IsDlgButtonChecked(HWindow, IDC_TIMERESOLUTION); EnableWindow(GetDlgItem(HWindow, IDE_TIMERESOLUTION), useTimeRes); @@ -838,7 +838,7 @@ void CCfgPageGeneral::EnableControls() } INT_PTR -CCfgPageGeneral::DialogProc(UINT uMsg, WPARAM wParam, LPARAM lParam) +CConfigPageGeneral::DialogProc(UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { @@ -854,17 +854,17 @@ CCfgPageGeneral::DialogProc(UINT uMsg, WPARAM wParam, LPARAM lParam) // // **************************************************************************** -// CCfgPageRegional +// CConfigPageRegional // -CCfgPageRegional::CCfgPageRegional() +CConfigPageRegional::CConfigPageRegional() : CCommonPropSheetPage(NULL, HLanguage, IDD_CFGPAGE_REGIONAL, IDD_CFGPAGE_REGIONAL, PSP_USETITLE, NULL) { lstrcpy(SLGName, Configuration.SLGName); lstrcpy(DirName, Configuration.ConversionTable); } -void CCfgPageRegional::LoadControls() +void CConfigPageRegional::LoadControls() { CLanguage language; if (language.Init(SLGName, NULL)) @@ -876,7 +876,7 @@ void CCfgPageRegional::LoadControls() } } -void CCfgPageRegional::Transfer(CTransferInfo& ti) +void CConfigPageRegional::Transfer(CTransferInfo& ti) { if (ti.Type == ttDataToWindow) { @@ -905,7 +905,7 @@ void CCfgPageRegional::Transfer(CTransferInfo& ti) } INT_PTR -CCfgPageRegional::DialogProc(UINT uMsg, WPARAM wParam, LPARAM lParam) +CConfigPageRegional::DialogProc(UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { @@ -954,7 +954,7 @@ CCfgPageRegional::DialogProc(UINT uMsg, WPARAM wParam, LPARAM lParam) // // **************************************************************************** -// CCfgPageView +// CConfigPageView // // used only to suppress quick search in the list view @@ -983,7 +983,7 @@ class CMyListView : public CWindow } }; -CCfgPageView::CCfgPageView(int index) +CConfigPageView::CConfigPageView(int index) : CCommonPropSheetPage(NULL, HLanguage, IDD_CFGPAGE_VIEWS, IDD_CFGPAGE_VIEWS, PSP_USETITLE, NULL) { Dirty = FALSE; @@ -998,12 +998,12 @@ CCfgPageView::CCfgPageView(int index) SelectIndex = index; } -BOOL CCfgPageView::IsDirty() +BOOL CConfigPageView::IsDirty() { return Dirty; } -void CCfgPageView::Transfer(CTransferInfo& ti) +void CConfigPageView::Transfer(CTransferInfo& ti) { if (ti.Type == ttDataToWindow) { @@ -1068,7 +1068,7 @@ void CCfgPageView::Transfer(CTransferInfo& ti) } } -void CCfgPageView::Validate(CTransferInfo& ti) +void CConfigPageView::Validate(CTransferInfo& ti) { } @@ -1076,7 +1076,7 @@ const int CFGP2ItemsCount = 8 /*9*/; const int CFGP2Flags[CFGP2ItemsCount] = {0, VIEW_SHOW_EXTENSION, VIEW_SHOW_DOSNAME, VIEW_SHOW_SIZE, VIEW_SHOW_TYPE, VIEW_SHOW_DATE, VIEW_SHOW_TIME, VIEW_SHOW_ATTRIBUTES /*, VIEW_SHOW_DESCRIPTION*/}; const int CFGP2ResID[CFGP2ItemsCount] = {IDS_COLUMN_CFG_NAME, IDS_COLUMN_CFG_EXT, IDS_COLUMN_CFG_DOSNAME, IDS_COLUMN_CFG_SIZE, IDS_COLUMN_CFG_TYPE, IDS_COLUMN_CFG_DATE, IDS_COLUMN_CFG_TIME, IDS_COLUMN_CFG_ATTR /*, IDS_COLUMN_CFG_DESC*/}; -void CCfgPageView::LoadControls() +void CConfigPageView::LoadControls() { DisableNotification = TRUE; int index = ListView_GetNextItem(HListView, -1, LVNI_SELECTED); @@ -1143,7 +1143,7 @@ void CCfgPageView::LoadControls() DisableNotification = FALSE; } -void CCfgPageView::StoreControls() +void CConfigPageView::StoreControls() { int index = ListView_GetNextItem(HListView, -1, LVNI_SELECTED); if (index >= 2) @@ -1161,7 +1161,7 @@ void CCfgPageView::StoreControls() } } -void CCfgPageView::EnableControls() +void CConfigPageView::EnableControls() { int index = ListView_GetNextItem(HListView, -1, LVNI_SELECTED); BOOL enable = TRUE; @@ -1204,7 +1204,7 @@ void CCfgPageView::EnableControls() } DWORD -CCfgPageView::GetEnabledFunctions() +CConfigPageView::GetEnabledFunctions() { DWORD mask = 0; if (!LabelEdit) @@ -1226,12 +1226,12 @@ CCfgPageView::GetEnabledFunctions() return mask; } -void CCfgPageView::EnableHeader() +void CConfigPageView::EnableHeader() { Header->EnableToolbar(GetEnabledFunctions()); } -void CCfgPageView::OnModify() +void CConfigPageView::OnModify() { if ((GetEnabledFunctions() & TLBHDRMASK_MODIFY) == 0) return; @@ -1241,7 +1241,7 @@ void CCfgPageView::OnModify() PostMessage(HListView, LVM_EDITLABEL, index, 0); } -void CCfgPageView::OnDelete() +void CConfigPageView::OnDelete() { if ((GetEnabledFunctions() & TLBHDRMASK_DELETE) == 0) return; @@ -1257,9 +1257,9 @@ void CCfgPageView::OnDelete() EnableHeader(); } -void CCfgPageView::OnMove(BOOL up) +void CConfigPageView::OnMove(BOOL up) { - CALL_STACK_MESSAGE2("CCfgPageView::OnMove(%d)", up); + CALL_STACK_MESSAGE2("CConfigPageView::OnMove(%d)", up); DWORD mask = GetEnabledFunctions(); if (up && (mask & TLBHDRMASK_UP) == 0 || !up && (mask & TLBHDRMASK_DOWN) == 0) @@ -1291,9 +1291,9 @@ void CCfgPageView::OnMove(BOOL up) } INT_PTR -CCfgPageView::DialogProc(UINT uMsg, WPARAM wParam, LPARAM lParam) +CConfigPageView::DialogProc(UINT uMsg, WPARAM wParam, LPARAM lParam) { - CALL_STACK_MESSAGE4("CCfgPageView::DialogProc(0x%X, 0x%IX, 0x%IX)", uMsg, wParam, lParam); + CALL_STACK_MESSAGE4("CConfigPageView::DialogProc(0x%X, 0x%IX, 0x%IX)", uMsg, wParam, lParam); switch (uMsg) { case WM_INITDIALOG: diff --git a/src/dialogsp.cpp b/src/dialogs_config_packing.cpp similarity index 100% rename from src/dialogsp.cpp rename to src/dialogs_config_packing.cpp diff --git a/src/dialogs6.cpp b/src/dialogs_config_panels.cpp similarity index 100% rename from src/dialogs6.cpp rename to src/dialogs_config_panels.cpp diff --git a/src/dialogs5.cpp b/src/dialogs_config_viewers.cpp similarity index 100% rename from src/dialogs5.cpp rename to src/dialogs_config_viewers.cpp diff --git a/src/dialogs.cpp b/src/dialogs_file_ops.cpp similarity index 100% rename from src/dialogs.cpp rename to src/dialogs_file_ops.cpp diff --git a/src/dialogs2.cpp b/src/dialogs_rename.cpp similarity index 100% rename from src/dialogs2.cpp rename to src/dialogs_rename.cpp diff --git a/src/drivelst.cpp b/src/drivelst.cpp index db659940..5c09a798 100644 --- a/src/drivelst.cpp +++ b/src/drivelst.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 Taskscape Ltd +// SPDX-FileCopyrightText: 2023 Taskscape Ltd // SPDX-License-Identifier: GPL-2.0-or-later // CommentsTranslationProject: TRANSLATED @@ -503,7 +503,7 @@ BOOL RestoreNetworkConnection(HWND parent, const char* name, const char* remoteN } } - CEnterPasswdDialog dlg(parent, remoteName, userName); + CPasswordDialog dlg(parent, remoteName, userName); DWORD err; char* passwd = NULL; diff --git a/src/salamdr6.cpp b/src/file_enumeration.cpp similarity index 99% rename from src/salamdr6.cpp rename to src/file_enumeration.cpp index e5491395..df8618d9 100644 --- a/src/salamdr6.cpp +++ b/src/file_enumeration.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 Taskscape Ltd +// SPDX-FileCopyrightText: 2023 Taskscape Ltd // SPDX-License-Identifier: GPL-2.0-or-later // CommentsTranslationProject: TRANSLATED @@ -1104,10 +1104,10 @@ int CDirectorySizes::GetIndex(const char* name) //****************************************************************************** // -// CDirectorySizesHolder +// CDirectorySizeCache // -CDirectorySizesHolder::CDirectorySizesHolder() +CDirectorySizeCache::CDirectorySizeCache() { int i; for (i = 0; i < DIRECOTRY_SIZES_COUNT; i++) @@ -1115,12 +1115,12 @@ CDirectorySizesHolder::CDirectorySizesHolder() ItemsCount = 0; } -CDirectorySizesHolder::~CDirectorySizesHolder() +CDirectorySizeCache::~CDirectorySizeCache() { Clean(); } -void CDirectorySizesHolder::Clean() +void CDirectorySizeCache::Clean() { int i; for (i = 0; i < DIRECOTRY_SIZES_COUNT; i++) @@ -1135,7 +1135,7 @@ void CDirectorySizesHolder::Clean() } CDirectorySizes* -CDirectorySizesHolder::Add(const char* path) +CDirectorySizeCache::Add(const char* path) { // try to locate the requested path int index = GetIndex(path); @@ -1177,7 +1177,7 @@ CDirectorySizesHolder::Add(const char* path) } CDirectorySizes* -CDirectorySizesHolder::Get(const char* path) +CDirectorySizeCache::Get(const char* path) { int index = GetIndex(path); if (index != -1) @@ -1186,7 +1186,7 @@ CDirectorySizesHolder::Get(const char* path) return NULL; } -int CDirectorySizesHolder::GetIndex(const char* path) +int CDirectorySizeCache::GetIndex(const char* path) { int i; for (i = 0; i < ItemsCount; i++) @@ -1197,7 +1197,7 @@ int CDirectorySizesHolder::GetIndex(const char* path) return -1; } -BOOL CDirectorySizesHolder::Store(CFilesWindow* panel) +BOOL CDirectorySizeCache::Store(CFilesWindow* panel) { CDirectorySizes* item = Add(panel->GetPath()); if (item == NULL) @@ -1224,7 +1224,7 @@ BOOL CDirectorySizesHolder::Store(CFilesWindow* panel) return TRUE; } -void CDirectorySizesHolder::Restore(CFilesWindow* panel) +void CDirectorySizeCache::Restore(CFilesWindow* panel) { int index = GetIndex(panel->GetPath()); if (index == -1) diff --git a/src/filesbox.h b/src/filesbox.h index 7e8ff3e1..915515c0 100644 --- a/src/filesbox.h +++ b/src/filesbox.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 Taskscape Ltd +// SPDX-FileCopyrightText: 2023 Taskscape Ltd // SPDX-License-Identifier: GPL-2.0-or-later // CommentsTranslationProject: TRANSLATED @@ -7,7 +7,7 @@ // **************************************************************************** class CFilesWindow; -class CFilesBox; +class CFileListBox; #define UPDATE_VERT_SCROLL 0x00000001 #define UPDATE_HORZ_SCROLL 0x00000002 @@ -32,7 +32,7 @@ class CBottomBar : public CWindow protected: HWND HScrollBar; BOOL VertScrollSpace; // reserve space for the vertical scrollbar - CFilesBox* RelayWindow; // used to relay messages + CFileListBox* RelayWindow; // used to relay messages public: CBottomBar(); @@ -43,12 +43,12 @@ class CBottomBar : public CWindow void LayoutChilds(); // computes rectangles and positions child windows - friend class CFilesBox; + friend class CFileListBox; }; //**************************************************************************** // -// CHeaderLine +// CFileListHeader // enum CHeaderHitTestEnum @@ -58,10 +58,10 @@ enum CHeaderHitTestEnum hhtDivider, // divider of an item with adjustable width }; -class CHeaderLine : public CWindow +class CFileListHeader : public CWindow { protected: - CFilesBox* Parent; + CFileListBox* Parent; TDirectArray* Columns; // pointer to the array held in CFilesWindow int Width; int Height; @@ -74,8 +74,8 @@ class CHeaderLine : public CWindow int OldDragColWidth; // column width before dragging started; used when resizing the column width public: - CHeaderLine(); - ~CHeaderLine(); + CFileListHeader(); + ~CFileListHeader(); // walks through the Columns array and sets minimal widths for columns // based on column names and whether sorting is possible by the column @@ -97,16 +97,16 @@ class CHeaderLine : public CWindow void PaintItem2(int index); void Cancel(); - friend class CFilesBox; + friend class CFileListBox; friend class CFilesWindow; }; //**************************************************************************** // -// CFilesBox +// CFileListBox // -class CFilesBox : public CWindow +class CFileListBox : public CWindow { protected: CFilesWindow* Parent; @@ -114,7 +114,7 @@ class CFilesBox : public CWindow HWND HVScrollBar; HWND HHScrollBar; CBottomBar BottomBar; - CHeaderLine HeaderLine; + CFileListHeader HeaderLine; RECT ClientRect; // dimensions of the entire window RECT HeaderRect; // position of the header control within ClientRect @@ -149,7 +149,7 @@ class CFilesBox : public CWindow int MouseHWheelAccumulator; // horizontal public: - CFilesBox(CFilesWindow* parent); + CFileListBox(CFilesWindow* parent); // sets the number of items in the panel // count - number of items in the panel @@ -206,7 +206,7 @@ class CFilesBox : public CWindow // items - used when prioritizing icon and thumbnail loading) void GetVisibleItems(int* firstIndex, int* count); - CHeaderLine* GetHeaderLine() { return &HeaderLine; } + CFileListHeader* GetHeaderLine() { return &HeaderLine; } // resets the accumulator used for mouse wheel detection; after reset, the next rotation starts from scratch // Microsoft recommends in Best Practices for Supporting Microsoft Mouse and Keyboard Devices (http://msdn.microsoft.com/en-us/library/ms997498.aspx) @@ -233,5 +233,5 @@ class CFilesBox : public CWindow friend class CFilesMap; friend class CScrollPanel; friend class CBottomBar; - friend class CHeaderLine; + friend class CFileListHeader; }; diff --git a/src/filesbx2.cpp b/src/filesbox_input.cpp similarity index 97% rename from src/filesbx2.cpp rename to src/filesbox_input.cpp index 99427610..b79a1571 100644 --- a/src/filesbx2.cpp +++ b/src/filesbox_input.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 Taskscape Ltd +// SPDX-FileCopyrightText: 2023 Taskscape Ltd // SPDX-License-Identifier: GPL-2.0-or-later // CommentsTranslationProject: TRANSLATED @@ -116,10 +116,10 @@ CBottomBar::WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam) //**************************************************************************** // -// CHeaderLine +// CFileListHeader // -CHeaderLine::CHeaderLine() +CFileListHeader::CFileListHeader() : CWindow(ooStatic) { Parent = NULL; @@ -133,13 +133,13 @@ CHeaderLine::CHeaderLine() OldDragColWidth = -1; } -CHeaderLine::~CHeaderLine() +CFileListHeader::~CFileListHeader() { if (HWindow != NULL) DetachWindow(); } -void CHeaderLine::PaintAllItems(HDC hDC, HRGN hUpdateRgn) +void CFileListHeader::PaintAllItems(HDC hDC, HRGN hUpdateRgn) { if (hUpdateRgn != NULL) SelectClipRgn(hDC, hUpdateRgn); @@ -187,11 +187,11 @@ void CHeaderLine::PaintAllItems(HDC hDC, HRGN hUpdateRgn) #define SORT_BITMAP_W 8 // width of the bitmap for sorting #define SORT_BITMAP_H 8 // height of the bitmap for sorting -void CHeaderLine::PaintItem(HDC hDC, int index, int x) +void CFileListHeader::PaintItem(HDC hDC, int index, int x) { if (index > Columns->Count) { - TRACE_E("CHeaderLine::PaintItem() index=" << index << " out of range! count=" << Columns->Count); + TRACE_E("CFileListHeader::PaintItem() index=" << index << " out of range! count=" << Columns->Count); return; } @@ -332,14 +332,14 @@ void CHeaderLine::PaintItem(HDC hDC, int index, int x) ItemBitmap.HMemDC, 0, 0, SRCCOPY); } -void CHeaderLine::PaintItem2(int index) +void CFileListHeader::PaintItem2(int index) { HDC hDC = HANDLES(GetDC(HWindow)); PaintItem(hDC, index); HANDLES(ReleaseDC(HWindow, hDC)); } -void CHeaderLine::SetMinWidths() +void CFileListHeader::SetMinWidths() { int columnsCount = Columns->Count; @@ -374,7 +374,7 @@ void CHeaderLine::SetMinWidths() } CHeaderHitTestEnum -CHeaderLine::HitTest(int xPos, int yPos, int& index, BOOL& extInName) +CFileListHeader::HitTest(int xPos, int yPos, int& index, BOOL& extInName) { extInName = FALSE; if (xPos < 0 || xPos > Width || yPos < 0 || yPos > Height) @@ -420,7 +420,7 @@ CHeaderLine::HitTest(int xPos, int yPos, int& index, BOOL& extInName) return hhtNone; } -void CHeaderLine::Cancel() +void CFileListHeader::Cancel() { SetCurrentToolTip(NULL, 0); MouseIsTracked = FALSE; @@ -447,7 +447,7 @@ void CHeaderLine::Cancel() } LRESULT -CHeaderLine::WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam) +CFileListHeader::WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { diff --git a/src/filesbx1.cpp b/src/filesbox_rendering.cpp similarity index 97% rename from src/filesbx1.cpp rename to src/filesbox_rendering.cpp index 7c0ff0ee..9f4c94cf 100644 --- a/src/filesbx1.cpp +++ b/src/filesbox_rendering.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 Taskscape Ltd +// SPDX-FileCopyrightText: 2023 Taskscape Ltd // SPDX-License-Identifier: GPL-2.0-or-later // CommentsTranslationProject: TRANSLATED @@ -28,10 +28,10 @@ const char* CFILESBOX_CLASSNAME = "SalamanderItemsBox"; //**************************************************************************** // -// CFilesBox +// CFileListBox // -CFilesBox::CFilesBox(CFilesWindow* parent) +CFileListBox::CFileListBox(CFilesWindow* parent) : CWindow(ooStatic) { BottomBar.RelayWindow = this; @@ -67,7 +67,7 @@ CFilesBox::CFilesBox(CFilesWindow* parent) ResetMouseWheelAccumulator(); } -void CFilesBox::SetItemsCount(int count, int xOffset, int topIndex, BOOL disableSBUpdate) +void CFileListBox::SetItemsCount(int count, int xOffset, int topIndex, BOOL disableSBUpdate) { SetItemsCount2(count); XOffset = xOffset; @@ -83,13 +83,13 @@ void CFilesBox::SetItemsCount(int count, int xOffset, int topIndex, BOOL disable Parent->VisibleItemsArraySurround.InvalidateArr(); } -void CFilesBox::SetItemsCount2(int count) +void CFileListBox::SetItemsCount2(int count) { ItemsCount = count; UpdateInternalData(); } -void CFilesBox::SetMode(CViewModeEnum mode, BOOL headerLine) +void CFileListBox::SetMode(CViewModeEnum mode, BOOL headerLine) { ViewMode = mode; HeaderLineVisible = headerLine; @@ -99,7 +99,7 @@ void CFilesBox::SetMode(CViewModeEnum mode, BOOL headerLine) InvalidateRect(HWindow, &FilesRect, FALSE); } -void CFilesBox::SetItemWidthHeight(int width, int height) +void CFileListBox::SetItemWidthHeight(int width, int height) { if (height < 1) { @@ -124,7 +124,7 @@ void CFilesBox::SetItemWidthHeight(int width, int height) } } -void CFilesBox::UpdateInternalData() +void CFileListBox::UpdateInternalData() { EntireItemsInColumn = (FilesRect.bottom - FilesRect.top) / ItemHeight; if (EntireItemsInColumn < 1) @@ -165,17 +165,17 @@ void CFilesBox::UpdateInternalData() } } -int CFilesBox::GetEntireItemsInColumn() +int CFileListBox::GetEntireItemsInColumn() { return EntireItemsInColumn; } -int CFilesBox::GetColumnsCount() +int CFileListBox::GetColumnsCount() { return 1; } -void CFilesBox::PaintAllItems(HRGN hUpdateRgn, DWORD drawFlags) +void CFileListBox::PaintAllItems(HRGN hUpdateRgn, DWORD drawFlags) { int index = TopIndex; @@ -450,7 +450,7 @@ void CFilesBox::PaintAllItems(HRGN hUpdateRgn, DWORD drawFlags) SelectClipRgn(HPrivateDC, NULL); // remove the clipping region if we set it } -void CFilesBox::PaintItem(int index, DWORD drawFlags) +void CFileListBox::PaintItem(int index, DWORD drawFlags) { RECT r; if (GetItemRect(index, &r)) @@ -514,7 +514,7 @@ void CFilesBox::PaintItem(int index, DWORD drawFlags) } } -BOOL CFilesBox::GetItemRect(int index, RECT* rect) +BOOL CFileListBox::GetItemRect(int index, RECT* rect) { switch (ViewMode) { @@ -556,7 +556,7 @@ BOOL CFilesBox::GetItemRect(int index, RECT* rect) return IntersectRect(&dummy, rect, &FilesRect) != 0; } -void CFilesBox::EnsureItemVisible(int index, BOOL forcePaint, BOOL scroll, BOOL selFocChangeOnly) +void CFileListBox::EnsureItemVisible(int index, BOOL forcePaint, BOOL scroll, BOOL selFocChangeOnly) { if (index < 0 || index >= ItemsCount) { @@ -724,7 +724,7 @@ void CFilesBox::EnsureItemVisible(int index, BOOL forcePaint, BOOL scroll, BOOL } } -void CFilesBox::GetVisibleItems(int* firstIndex, int* count) +void CFileListBox::GetVisibleItems(int* firstIndex, int* count) { *firstIndex = *count = 0; switch (ViewMode) @@ -770,7 +770,7 @@ void CFilesBox::GetVisibleItems(int* firstIndex, int* count) } // assumes scroll == TRUE - even a partially visible item counts as visible -BOOL CFilesBox::IsItemVisible(int index, BOOL* isFullyVisible) +BOOL CFileListBox::IsItemVisible(int index, BOOL* isFullyVisible) { if (isFullyVisible != NULL) *isFullyVisible = FALSE; @@ -840,7 +840,7 @@ BOOL CFilesBox::IsItemVisible(int index, BOOL* isFullyVisible) } // assumes scroll == FALSE - the whole item will be visible -int CFilesBox::PredictTopIndex(int index) +int CFileListBox::PredictTopIndex(int index) { int newTopIndex = TopIndex; switch (ViewMode) @@ -907,7 +907,7 @@ int CFilesBox::PredictTopIndex(int index) return newTopIndex; } -void CFilesBox::EnsureItemVisible2(int newTopIndex, int index) +void CFileListBox::EnsureItemVisible2(int newTopIndex, int index) { if (newTopIndex == TopIndex) return; @@ -953,7 +953,7 @@ void CFilesBox::EnsureItemVisible2(int newTopIndex, int index) Parent->VisibleItemsArraySurround.InvalidateArr(); } -void CFilesBox::OnHScroll(int scrollCode, int pos) +void CFileListBox::OnHScroll(int scrollCode, int pos) { if (Parent->DragBox && !Parent->ScrollingWindow) // dragging the cage - block mouse wheel scrolling return; @@ -1100,7 +1100,7 @@ void CFilesBox::OnHScroll(int scrollCode, int pos) } } -void CFilesBox::OnVScroll(int scrollCode, int pos) +void CFileListBox::OnVScroll(int scrollCode, int pos) { if (Parent->DragBox && !Parent->ScrollingWindow) // dragging the cage - block mouse wheel scrolling return; @@ -1256,9 +1256,9 @@ void CFilesBox::OnVScroll(int scrollCode, int pos) } LRESULT -CFilesBox::WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam) +CFileListBox::WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam) { - SLOW_CALL_STACK_MESSAGE4("CFilesBox::WindowProc(0x%X, 0x%IX, 0x%IX)", uMsg, wParam, lParam); + SLOW_CALL_STACK_MESSAGE4("CFileListBox::WindowProc(0x%X, 0x%IX, 0x%IX)", uMsg, wParam, lParam); switch (uMsg) { case WM_CREATE: @@ -1583,7 +1583,7 @@ CFilesBox::WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam) // to make the New submenu work we must also forward the message there if (Parent->ContextSubmenuNew != NULL && Parent->ContextSubmenuNew->MenuIsAssigned()) { - CALL_STACK_MESSAGE1("CFilesBox::WindowProc::SafeHandleMenuNewMsg2"); + CALL_STACK_MESSAGE1("CFileListBox::WindowProc::SafeHandleMenuNewMsg2"); LRESULT lResult; Parent->SafeHandleMenuNewMsg2(uMsg, wParam, lParam, &lResult); return lResult; @@ -1806,12 +1806,12 @@ CFilesBox::WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam) return CWindow::WindowProc(uMsg, wParam, lParam); } -int CFilesBox::GetIndex(int x, int y, BOOL nearest, RECT* labelRect) +int CFileListBox::GetIndex(int x, int y, BOOL nearest, RECT* labelRect) { - CALL_STACK_MESSAGE4("CFilesBox::GetIndex(%d, %d, %d, )", x, y, nearest); + CALL_STACK_MESSAGE4("CFileListBox::GetIndex(%d, %d, %d, )", x, y, nearest); if (labelRect != NULL && nearest) - TRACE_E("CFilesBox::GetIndex nearest && labelRect"); + TRACE_E("CFileListBox::GetIndex nearest && labelRect"); if (ItemsCount == 0) return INT_MAX; @@ -2139,7 +2139,7 @@ int CFilesBox::GetIndex(int x, int y, BOOL nearest, RECT* labelRect) return itemIndex; } -BOOL CFilesBox::ShowHideChilds() +BOOL CFileListBox::ShowHideChilds() { BOOL change = FALSE; if (HeaderLineVisible && ViewMode != vmDetailed) @@ -2227,7 +2227,7 @@ BOOL CFilesBox::ShowHideChilds() return change; } -void CFilesBox::SetupScrollBars(DWORD flags) +void CFileListBox::SetupScrollBars(DWORD flags) { SCROLLINFO si; if (HHScrollBar != NULL && flags & UPDATE_HORZ_SCROLL) @@ -2320,7 +2320,7 @@ void CFilesBox::SetupScrollBars(DWORD flags) } } -void CFilesBox::CheckAndCorrectBoundaries() +void CFileListBox::CheckAndCorrectBoundaries() { switch (ViewMode) { @@ -2427,7 +2427,7 @@ void CFilesBox::CheckAndCorrectBoundaries() } } -void CFilesBox::LayoutChilds(BOOL updateAndCheck) +void CFileListBox::LayoutChilds(BOOL updateAndCheck) { GetClientRect(HWindow, &ClientRect); ItemBitmap.Enlarge(ClientRect.right - ClientRect.left, ItemHeight); @@ -2585,7 +2585,7 @@ void CFilesBox::LayoutChilds(BOOL updateAndCheck) Parent->VisibleItemsArraySurround.InvalidateArr(); } -void CFilesBox::PaintHeaderLine() +void CFileListBox::PaintHeaderLine() { if (HeaderLine.HWindow != NULL) { diff --git a/src/fileswn7.cpp b/src/fileswindow_archiving.cpp similarity index 99% rename from src/fileswn7.cpp rename to src/fileswindow_archiving.cpp index 202f6faf..e15110f6 100644 --- a/src/fileswn7.cpp +++ b/src/fileswindow_archiving.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 Taskscape Ltd +// SPDX-FileCopyrightText: 2023 Taskscape Ltd // SPDX-License-Identifier: GPL-2.0-or-later // CommentsTranslationProject: TRANSLATED @@ -2001,7 +2001,7 @@ void CFilesWindow::CalculateOccupiedZIPSpace(int countSizeMode) CSizeResultsDlg(HWindow, totalSize, CQuadWord(-1, -1), CQuadWord(-1, -1), files, dirs, &sizes) .Execute(); - // CZIPSizeResultsDlg(HWindow, totalSize, files, dirs).Execute(); + // CZipSizeResultsDialog(HWindow, totalSize, files, dirs).Execute(); } if (selIndex != -1 && countSizeMode == 0) { diff --git a/src/fileswn9.cpp b/src/fileswindow_columns.cpp similarity index 99% rename from src/fileswn9.cpp rename to src/fileswindow_columns.cpp index 869edd5d..e78e489e 100644 --- a/src/fileswn9.cpp +++ b/src/fileswindow_columns.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 Taskscape Ltd +// SPDX-FileCopyrightText: 2023 Taskscape Ltd // SPDX-License-Identifier: GPL-2.0-or-later // CommentsTranslationProject: TRANSLATED @@ -2191,7 +2191,7 @@ void CFilesWindow::OnHeaderLineColWidthChanged() BuildColumnsTemplate(); } -CHeaderLine* +CFileListHeader* CFilesWindow::GetHeaderLine() { if (ListBox == NULL) diff --git a/src/fileswn8.cpp b/src/fileswindow_delete.cpp similarity index 100% rename from src/fileswn8.cpp rename to src/fileswindow_delete.cpp diff --git a/src/fileswna.cpp b/src/fileswindow_dir_reading.cpp similarity index 98% rename from src/fileswna.cpp rename to src/fileswindow_dir_reading.cpp index f0bf421b..ecaeca47 100644 --- a/src/fileswna.cpp +++ b/src/fileswindow_dir_reading.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 Taskscape Ltd +// SPDX-FileCopyrightText: 2023 Taskscape Ltd // SPDX-License-Identifier: GPL-2.0-or-later // CommentsTranslationProject: TRANSLATED @@ -809,10 +809,10 @@ void CFilesWindow::DragDropToArcOrFS(CTmpDragDropOperData* data) //**************************************************************************** // -// CVisibleItemsArray +// CVisibleFileItemsArray // -CVisibleItemsArray::CVisibleItemsArray(BOOL surroundArr) +CVisibleFileItemsArray::CVisibleFileItemsArray(BOOL surroundArr) { HANDLES(InitializeCriticalSection(&Monitor)); ArrVersionNum = 0; @@ -825,7 +825,7 @@ CVisibleItemsArray::CVisibleItemsArray(BOOL surroundArr) LastVisibleItem = -1; } -CVisibleItemsArray::~CVisibleItemsArray() +CVisibleFileItemsArray::~CVisibleFileItemsArray() { HANDLES(DeleteCriticalSection(&Monitor)); if (ArrNamesAllocated > 0 && ArrNames != NULL) @@ -834,10 +834,10 @@ CVisibleItemsArray::~CVisibleItemsArray() ArrNamesAllocated = 0; } -BOOL CVisibleItemsArray::IsArrValid(int* versionNum) +BOOL CVisibleFileItemsArray::IsArrValid(int* versionNum) { CALL_STACK_MESSAGE_NONE - // CALL_STACK_MESSAGE1("CVisibleItemsArray::IsArrValid()"); + // CALL_STACK_MESSAGE1("CVisibleFileItemsArray::IsArrValid()"); HANDLES(EnterCriticalSection(&Monitor)); if (versionNum != NULL) *versionNum = ArrVersionNum; @@ -846,9 +846,9 @@ BOOL CVisibleItemsArray::IsArrValid(int* versionNum) return ret; } -void CVisibleItemsArray::InvalidateArr() +void CVisibleFileItemsArray::InvalidateArr() { - CALL_STACK_MESSAGE1("CVisibleItemsArray::InvalidateArr()"); + CALL_STACK_MESSAGE1("CVisibleFileItemsArray::InvalidateArr()"); HANDLES(EnterCriticalSection(&Monitor)); ArrIsValid = FALSE; ArrNamesCount = 0; @@ -885,9 +885,9 @@ void SortNamesCS(char** names, int left, int right) SortNamesCS(names, i, right); } -void CVisibleItemsArray::RefreshArr(CFilesWindow* panel) +void CVisibleFileItemsArray::RefreshArr(CFilesWindow* panel) { - CALL_STACK_MESSAGE1("CVisibleItemsArray::RefreshArr()"); + CALL_STACK_MESSAGE1("CVisibleFileItemsArray::RefreshArr()"); HANDLES(EnterCriticalSection(&Monitor)); int firstIndex, count; panel->ListBox->GetVisibleItems(&firstIndex, &count); @@ -951,9 +951,9 @@ void CVisibleItemsArray::RefreshArr(CFilesWindow* panel) HANDLES(LeaveCriticalSection(&Monitor)); } -BOOL CVisibleItemsArray::ArrContains(const char* name, BOOL* isArrValid, int* versionNum) +BOOL CVisibleFileItemsArray::ArrContains(const char* name, BOOL* isArrValid, int* versionNum) { - DEBUG_SLOW_CALL_STACK_MESSAGE1("CVisibleItemsArray::ArrContains()"); + DEBUG_SLOW_CALL_STACK_MESSAGE1("CVisibleFileItemsArray::ArrContains()"); HANDLES(EnterCriticalSection(&Monitor)); if (versionNum != NULL) *versionNum = ArrVersionNum; @@ -999,9 +999,9 @@ BOOL CVisibleItemsArray::ArrContains(const char* name, BOOL* isArrValid, int* ve } } -BOOL CVisibleItemsArray::ArrContainsIndex(int index, BOOL* isArrValid, int* versionNum) +BOOL CVisibleFileItemsArray::ArrContainsIndex(int index, BOOL* isArrValid, int* versionNum) { - DEBUG_SLOW_CALL_STACK_MESSAGE1("CVisibleItemsArray::ArrContainsIndex()"); + DEBUG_SLOW_CALL_STACK_MESSAGE1("CVisibleFileItemsArray::ArrContainsIndex()"); HANDLES(EnterCriticalSection(&Monitor)); if (versionNum != NULL) *versionNum = ArrVersionNum; diff --git a/src/fileswn4.cpp b/src/fileswindow_display.cpp similarity index 99% rename from src/fileswn4.cpp rename to src/fileswindow_display.cpp index a4b94e91..f1767b97 100644 --- a/src/fileswn4.cpp +++ b/src/fileswindow_display.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 Taskscape Ltd +// SPDX-FileCopyrightText: 2023 Taskscape Ltd // SPDX-License-Identifier: GPL-2.0-or-later // CommentsTranslationProject: TRANSLATED @@ -482,7 +482,7 @@ void DrawFocusRect(HDC hDC, const RECT* r, BOOL selected, BOOL editMode) // changes in item painting (layout) must be reflected in these locations: // // CFilesWindow::DrawItem(WPARAM wParam, LPARAM lParam) -// CFilesBox::GetIndex(int x, int y) +// CFileListBox::GetIndex(int x, int y) // CFilesMap::CreateMap() // diff --git a/src/fileswn2.cpp b/src/fileswindow_execute.cpp similarity index 100% rename from src/fileswn2.cpp rename to src/fileswindow_execute.cpp diff --git a/src/fileswn5.cpp b/src/fileswindow_file_actions.cpp similarity index 99% rename from src/fileswn5.cpp rename to src/fileswindow_file_actions.cpp index beabf6b1..51160a43 100644 --- a/src/fileswn5.cpp +++ b/src/fileswindow_file_actions.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 Taskscape Ltd +// SPDX-FileCopyrightText: 2023 Taskscape Ltd // SPDX-License-Identifier: GPL-2.0-or-later // CommentsTranslationProject: TRANSLATED @@ -2873,10 +2873,10 @@ void CFilesWindow::KillQuickRenameTimer() //**************************************************************************** // -// CQuickRenameWindow +// CInlineRenameEdit // -CQuickRenameWindow::CQuickRenameWindow() +CInlineRenameEdit::CInlineRenameEdit() #ifdef _UNICODE : CWindow(ooStatic) #else @@ -2888,23 +2888,23 @@ CQuickRenameWindow::CQuickRenameWindow() SkipNextCharacter = FALSE; } -void CQuickRenameWindow::SetPanel(CFilesWindow* filesWindow) +void CInlineRenameEdit::SetPanel(CFilesWindow* filesWindow) { FilesWindow = filesWindow; } -void CQuickRenameWindow::SetCloseEnabled(BOOL closeEnabled) +void CInlineRenameEdit::SetCloseEnabled(BOOL closeEnabled) { CloseEnabled = closeEnabled; } -BOOL CQuickRenameWindow::GetCloseEnabled() +BOOL CInlineRenameEdit::GetCloseEnabled() { return CloseEnabled; } LRESULT -CQuickRenameWindow::WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam) +CInlineRenameEdit::WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { diff --git a/src/fileswn1.cpp b/src/fileswindow_init.cpp similarity index 100% rename from src/fileswn1.cpp rename to src/fileswindow_init.cpp diff --git a/src/fileswn3.cpp b/src/fileswindow_navigation.cpp similarity index 100% rename from src/fileswn3.cpp rename to src/fileswindow_navigation.cpp diff --git a/src/fileswn6.cpp b/src/fileswindow_operations.cpp similarity index 100% rename from src/fileswn6.cpp rename to src/fileswindow_operations.cpp diff --git a/src/fileswn0.cpp b/src/fileswindow_quicksearch.cpp similarity index 99% rename from src/fileswn0.cpp rename to src/fileswindow_quicksearch.cpp index ff367986..1cd76f26 100644 --- a/src/fileswn0.cpp +++ b/src/fileswindow_quicksearch.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 Open Salamander Authors +// SPDX-FileCopyrightText: 2023 Open Salamander Authors // SPDX-License-Identifier: GPL-2.0-or-later // CommentsTranslationProject: TRANSLATED @@ -3228,7 +3228,7 @@ void CFilesWindow::SetQuickSearchCaretPos() } iconH += 3 + 2 - 1; - // ATTENTION: keep in sync with CFilesBox::GetIndex + // ATTENTION: keep in sync with CFileListBox::GetIndex char buff[1024]; // target buffer for strings int maxWidth = ListBox->ItemWidth - 4 - 1; // -1, so they don't touch char* out1 = buff; @@ -3251,7 +3251,7 @@ void CFilesWindow::SetQuickSearchCaretPos() case vmTiles: { - // ATTENTION: keep in sync with CFilesBox::GetIndex, see call to GetTileTexts + // ATTENTION: keep in sync with CFileListBox::GetIndex, see call to GetTileTexts // int itemWidth = rect.right - rect.left; // item width int maxTextWidth = ListBox->ItemWidth - TILE_LEFT_MARGIN - IconSizes[ICONSIZE_48] - TILE_LEFT_MARGIN - 4; int widthNeeded = 0; diff --git a/src/fileswnb.cpp b/src/fileswindow_wndproc.cpp similarity index 99% rename from src/fileswnb.cpp rename to src/fileswindow_wndproc.cpp index 1b248270..969fe3ba 100644 --- a/src/fileswnb.cpp +++ b/src/fileswindow_wndproc.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 Taskscape Ltd +// SPDX-FileCopyrightText: 2023 Taskscape Ltd // SPDX-License-Identifier: GPL-2.0-or-later #include "precomp.h" @@ -988,14 +988,14 @@ CFilesWindow::WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam) EnumFileNamesAddSourceUID(HWindow, &EnumFileNamesSourceUID); //--- create listbox with files and directories - ListBox = new CFilesBox(this); + ListBox = new CFileListBox(this); if (ListBox == NULL) { TRACE_E(LOW_MEMORY); return -1; } //--- create status line with information about current file - StatusLine = new CStatusWindow(this, blBottom, ooStatic); + StatusLine = new CPanelStatusBar(this, blBottom, ooStatic); if (StatusLine == NULL) { TRACE_E(LOW_MEMORY); @@ -1003,7 +1003,7 @@ CFilesWindow::WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam) } ToggleStatusLine(); //--- create status line with information about current directory - DirectoryLine = new CStatusWindow(this, blTop, ooStatic); + DirectoryLine = new CPanelStatusBar(this, blTop, ooStatic); if (DirectoryLine == NULL) { TRACE_E(LOW_MEMORY); diff --git a/src/fileswnd.h b/src/fileswnd.h index bb0ce4a0..3d9780ac 100644 --- a/src/fileswnd.h +++ b/src/fileswnd.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 Taskscape Ltd +// SPDX-FileCopyrightText: 2023 Taskscape Ltd // SPDX-License-Identifier: GPL-2.0-or-later // CommentsTranslationProject: TRANSLATED @@ -153,9 +153,9 @@ extern CCopyMoveOptions CopyMoveOptions; // global variable holding the default class CMainWindow; class COperations; -class CFilesBox; -class CHeaderLine; -class CStatusWindow; +class CFileListBox; +class CFileListHeader; +class CPanelStatusBar; class CFilesArray; struct CFileData; class CIconCache; @@ -350,12 +350,12 @@ class CFileTimeStamps //**************************************************************************** // -// CTopIndexMem +// CScrollPositionMemory // #define TOP_INDEX_MEM_SIZE 50 // number of remembered top indexes (levels), at least 1 -class CTopIndexMem +class CScrollPositionMemory { protected: // path for the last remembered top index; the longest is archive + archive-path so 2 * MAX_PATH @@ -364,7 +364,7 @@ class CTopIndexMem int TopIndexesCount; // number of stored top indexes public: - CTopIndexMem() { Clear(); } + CScrollPositionMemory() { Clear(); } void Clear() { Path[0] = 0; @@ -407,25 +407,25 @@ class CDirectorySizes protected: int GetIndex(const char* name); - friend class CDirectorySizesHolder; + friend class CDirectorySizeCache; }; //****************************************************************************** // -// CDirectorySizesHolder +// CDirectorySizeCache // #define DIRECOTRY_SIZES_COUNT 20 -class CDirectorySizesHolder +class CDirectorySizeCache { protected: CDirectorySizes* Items[DIRECOTRY_SIZES_COUNT]; int ItemsCount; // number of valid items in the Items array public: - CDirectorySizesHolder(); - ~CDirectorySizesHolder(); + CDirectorySizeCache(); + ~CDirectorySizeCache(); // destroys all held data except for Path void Clean(); @@ -447,10 +447,10 @@ class CDirectorySizesHolder //**************************************************************************** // -// CQuickRenameWindow +// CInlineRenameEdit // -class CQuickRenameWindow : public CWindow +class CInlineRenameEdit : public CWindow { protected: CFilesWindow* FilesWindow; @@ -458,7 +458,7 @@ class CQuickRenameWindow : public CWindow BOOL SkipNextCharacter; public: - CQuickRenameWindow(); + CInlineRenameEdit(); void SetPanel(CFilesWindow* filesWindow); void SetCloseEnabled(BOOL closeEnabled); @@ -637,7 +637,7 @@ enum CCopyFocusedNameModeEnum // array for prioritizing icon and thumbnail loading by the icon reader according // to the current state of the panel and displayed items -class CVisibleItemsArray +class CVisibleFileItemsArray { protected: CRITICAL_SECTION Monitor; // section used to synchronize this object (monitor behavior) @@ -655,8 +655,8 @@ class CVisibleItemsArray int LastVisibleItem; // index of the last visible item public: - CVisibleItemsArray(BOOL surroundArr); - ~CVisibleItemsArray(); + CVisibleFileItemsArray(BOOL surroundArr); + ~CVisibleFileItemsArray(); // Returns TRUE if the array is filled and valid (matches the current state of thepanel // and visible items), also returns the number of the array version in 'versionNum' @@ -751,8 +751,8 @@ class CFilesWindow : public CFilesWindowAncestor int LastFocus; // to avoid unnecessary overwriting of the status line - CFilesBox* ListBox; - CStatusWindow *StatusLine, + CFileListBox* ListBox; + CPanelStatusBar *StatusLine, *DirectoryLine; BOOL StatusLineVisible; @@ -773,7 +773,7 @@ class CFilesWindow : public CFilesWindowAncestor char NextFocusName[2 * MAX_PATH]; // the name that will receive focus on the next refresh; 2x for UTF-8 BOOL DontClearNextFocusName; // TRUE = do not clear NextFocusName when the main Salamander window is activated BOOL FocusFirstNewItem; // refresh: should the newly added item be selected? (for system New) - CTopIndexMem TopIndexMem; // memory of top index for Execute() + CScrollPositionMemory TopIndexMem; // memory of top index for Execute() int LastRefreshTime; // used to handle the chaos of directory change notifications @@ -879,13 +879,13 @@ class CFilesWindow : public CFilesWindowAncestor CNames OldSelection; // selection before the operation, intended for the Reselect command CNames HiddenNames; // names of files and directories that the user has hidden via the Hide function (unrelated to the Hidden attribute) - CQuickRenameWindow QuickRenameWindow; + CInlineRenameEdit QuickRenameWindow; UINT_PTR QuickRenameTimer; int QuickRenameIndex; RECT QuickRenameRect; - CVisibleItemsArray VisibleItemsArray; // array of items visible in the panel - CVisibleItemsArray VisibleItemsArraySurround; // array of items adjacent to the visible part of the panel + CVisibleFileItemsArray VisibleItemsArray; // array of items visible in the panel + CVisibleFileItemsArray VisibleItemsArraySurround; // array of items adjacent to the visible part of the panel BOOL TemporarilySimpleIcons; // use simple icons until the next ReadDirectory() @@ -1587,7 +1587,7 @@ class CFilesWindow : public CFilesWindowAncestor // if the user changes the column width, this method will be called (after the dragging ends) void OnHeaderLineColWidthChanged(); - CHeaderLine* GetHeaderLine(); + CFileListHeader* GetHeaderLine(); // sends focused/selected files using the default mail client void EmailFiles(); @@ -1666,7 +1666,7 @@ const char* WINAPI PanelEnumDiskSelection(HWND parent, int enumFiles, const char extern CNames GlobalSelection; // stored selection shared by both panels -extern CDirectorySizesHolder DirectorySizesHolder; // holds the list of directory names and sizes with known size +extern CDirectorySizeCache DirectorySizesHolder; // holds the list of directory names and sizes with known size extern CFilesWindow* DropSourcePanel; // prevents drag&drop from/to the same panel extern BOOL OurClipDataObject; // TRUE when pasting our IDataObject diff --git a/src/finddlg1.cpp b/src/find_dialog_ui.cpp similarity index 75% rename from src/finddlg1.cpp rename to src/find_dialog_ui.cpp index 968446e6..1ba3fc9f 100644 --- a/src/finddlg1.cpp +++ b/src/find_dialog_ui.cpp @@ -19,832 +19,7 @@ #include -const char* MINIMIZED_FINDING_CAPTION = "(%d) %s [%s %s]"; -const char* NORMAL_FINDING_CAPTION = "%s [%s %s]"; - -// Helper function to draw UTF-8 text using Unicode API -static int DrawTextUtf8(HDC hDC, const char* text, int textLen, LPRECT rect, UINT format) -{ - if (text == NULL) - return 0; - if (textLen == -1) - textLen = (int)strlen(text); - // Convert UTF-8 to wide characters - int wideLen = MultiByteToWideChar(CP_UTF8, 0, text, textLen, NULL, 0); - if (wideLen <= 0) - return DrawText(hDC, text, textLen, rect, format); // Fallback to ANSI - - wchar_t* wideText = (wchar_t*)_alloca((wideLen + 1) * sizeof(wchar_t)); - MultiByteToWideChar(CP_UTF8, 0, text, textLen, wideText, wideLen + 1); - wideText[wideLen] = 0; - return DrawTextW(hDC, wideText, wideLen, rect, format); -} - -// Helper function for PathCompactPath that handles UTF-8 -static BOOL PathCompactPathUtf8(HDC hDC, char* path, UINT dx) -{ - if (path == NULL) - return FALSE; - // Convert UTF-8 to wide, compact, then back to UTF-8 - wchar_t wpath[2 * MAX_PATH]; - MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, 2 * MAX_PATH); - BOOL result = PathCompactPathW(hDC, wpath, dx); - WideCharToMultiByte(CP_UTF8, 0, wpath, -1, path, 2 * MAX_PATH, NULL, NULL); - return result; -} - -BOOL FindManageInUse = FALSE; -BOOL FindIgnoreInUse = FALSE; - -static char FoundFilesDataTextBuffer[4 * MAX_PATH]; -static WCHAR FoundFilesDataTextBufferW[4 * MAX_PATH]; - -static void Utf8ToAnsiWithLimit(const char* src, char* dst, int dstSize) -{ - if (dst == NULL || dstSize <= 0) - return; - dst[0] = 0; - - WCHAR wide[4 * MAX_PATH]; - int wLen = ConvertUtf8ToWide(src, -1, wide, _countof(wide)); - if (wLen == 0) - return; - - int res = WideCharToMultiByte(CP_ACP, 0, wide, -1, dst, dstSize, NULL, NULL); - if (res == 0 && GetLastError() == ERROR_INSUFFICIENT_BUFFER) - dst[dstSize - 1] = 0; -} -//**************************************************************************** -// -// CFindTBHeader -// - -void CFindOptions::InitMenu(CMenuPopup* popup, BOOL enabled, int originalCount) -{ - int count = popup->GetItemCount(); - if (count > originalCount) - { - // remove any previously inserted items - popup->RemoveItemsRange(originalCount, count - 1); - } - - if (Items.Count > 0) - { - MENU_ITEM_INFO mii; - - // if there are items to append, insert a separator first - mii.Mask = MENU_MASK_TYPE; - mii.Type = MENU_TYPE_SEPARATOR; - popup->InsertItem(-1, TRUE, &mii); - - // append the displayed portion of the items - int maxCount = CM_FIND_OPTIONS_LAST - CM_FIND_OPTIONS_FIRST; - int i; - for (i = 0; i < min(Items.Count, maxCount); i++) - { - mii.Mask = MENU_MASK_TYPE | MENU_MASK_STATE | MENU_MASK_STRING | MENU_MASK_ID; - mii.Type = MENU_TYPE_STRING; - mii.State = enabled ? 0 : MENU_STATE_GRAYED; - if (Items[i]->AutoLoad) - mii.State |= MENU_STATE_DEFAULT; - mii.ID = CM_FIND_OPTIONS_FIRST + i; - mii.String = Items[i]->ItemName; - popup->InsertItem(-1, TRUE, &mii); - } - } -} - -//**************************************************************************** -// -// CFoundFilesData -// - -BOOL CFoundFilesData::Set(const char* path, const char* name, const CQuadWord& size, DWORD attr, - const FILETIME* lastWrite, BOOL isDir) -{ - CALL_STACK_MESSAGE_NONE - // CALL_STACK_MESSAGE5("CFoundFilesData::Set(%s, %s, %g, 0x%X, )", path, name, size.GetDouble(), attr); - int l1 = (int)strlen(path), l2 = (int)strlen(name); - Path = (char*)malloc(l1 + 1); - Name = (char*)malloc(l2 + 1); - if (Path == NULL || Name == NULL) - return FALSE; - memmove(Path, path, l1 + 1); - memmove(Name, name, l2 + 1); - Size = size; - Attr = attr; - LastWrite = *lastWrite; - IsDir = isDir ? 1 : 0; - return TRUE; -} - -char* CFoundFilesData::GetText(int i, char* text, int fileNameFormat) -{ - // several FIND windows may run in parallel, which could overwrite this static buffer - // static char text[50]; - switch (i) - { - case 0: - { - AlterFileName(text, Name, -1, fileNameFormat, 0, IsDir); - return text; - } - - case 1: - return Path; - - case 2: - { - if (IsDir) - CopyMemory(text, DirColumnStr, DirColumnStrLen + 1); - else - NumberToStr(text, Size); - break; - } - - case 3: - { - SYSTEMTIME st; - FILETIME ft; - if (FileTimeToLocalFileTime(&LastWrite, &ft) && - FileTimeToSystemTime(&ft, &st)) - { - if (GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &st, NULL, text, 50) == 0) - sprintf(text, "%u.%u.%u", st.wDay, st.wMonth, st.wYear); - } - else - strcpy(text, LoadStr(IDS_INVALID_DATEORTIME)); - break; - } - - case 4: - { - SYSTEMTIME st; - FILETIME ft; - if (FileTimeToLocalFileTime(&LastWrite, &ft) && - FileTimeToSystemTime(&ft, &st)) - { - if (GetTimeFormat(LOCALE_USER_DEFAULT, 0, &st, NULL, text, 50) == 0) - sprintf(text, "%u:%02u:%02u", st.wHour, st.wMinute, st.wSecond); - } - else - strcpy(text, LoadStr(IDS_INVALID_DATEORTIME)); - break; - } - - default: - { - GetAttrsString(text, Attr); - break; - } - } - return text; -} - -//**************************************************************************** -// -// CFoundFilesListView -// - -CFoundFilesListView::CFoundFilesListView(HWND dlg, int ctrlID, CFindDialog* findDialog) - : Data(1000, 500), DataForRefine(1, 1000), CWindow(dlg, ctrlID) -{ - FindDialog = findDialog; - HANDLES(InitializeCriticalSection(&DataCriticalSection)); - // use Unicode notifications if available - ListView_SetUnicodeFormat(HWindow, TRUE); - - // add this panel to the array of sources for enumerating files in viewers - EnumFileNamesAddSourceUID(HWindow, &EnumFileNamesSourceUID); -} - -CFoundFilesListView::~CFoundFilesListView() -{ - // remove this panel from the array of sources for enumerating files in viewers - EnumFileNamesRemoveSourceUID(HWindow); - - HANDLES(DeleteCriticalSection(&DataCriticalSection)); -} - -CFoundFilesData* -CFoundFilesListView::At(int index) -{ - CFoundFilesData* ptr; - HANDLES(EnterCriticalSection(&DataCriticalSection)); - ptr = Data[index]; - HANDLES(LeaveCriticalSection(&DataCriticalSection)); - return ptr; -} - -void CFoundFilesListView::DestroyMembers() -{ - // HANDLES(EnterCriticalSection(&DataCriticalSection)); - Data.DestroyMembers(); - // HANDLES(LeaveCriticalSection(&DataCriticalSection)); -} - -void CFoundFilesListView::Delete(int index) -{ - HANDLES(EnterCriticalSection(&DataCriticalSection)); - Data.Delete(index); - HANDLES(LeaveCriticalSection(&DataCriticalSection)); -} - -int CFoundFilesListView::GetCount() -{ - int count; - HANDLES(EnterCriticalSection(&DataCriticalSection)); - count = Data.Count; - HANDLES(LeaveCriticalSection(&DataCriticalSection)); - return count; -} - -int CFoundFilesListView::Add(CFoundFilesData* item) -{ - int index; - HANDLES(EnterCriticalSection(&DataCriticalSection)); - index = Data.Add(item); - HANDLES(LeaveCriticalSection(&DataCriticalSection)); - return index; -} - -BOOL CFoundFilesListView::TakeDataForRefine() -{ - DataForRefine.DestroyMembers(); - int i; - for (i = 0; i < Data.Count; i++) - { - CFoundFilesData* refineData = Data[i]; - DataForRefine.Add(refineData); - if (!DataForRefine.IsGood()) - { - DataForRefine.ResetState(); - DataForRefine.DetachMembers(); - return FALSE; - } - } - Data.DetachMembers(); - return TRUE; -} - -void CFoundFilesListView::DestroyDataForRefine() -{ - DataForRefine.DestroyMembers(); -} - -int CFoundFilesListView::GetDataForRefineCount() -{ - return DataForRefine.Count; -} - -CFoundFilesData* -CFoundFilesListView::GetDataForRefine(int index) -{ - CFoundFilesData* ptr; - ptr = DataForRefine[index]; - return ptr; -} - -DWORD -CFoundFilesListView::GetSelectedListSize() -{ - // this method is invoked only from the main thread - DWORD size = 0; - int index = -1; - do - { - index = ListView_GetNextItem(HWindow, index, LVIS_SELECTED); - if (index != -1) - { - CFoundFilesData* ptr = Data[index]; - int pathLen = lstrlen(ptr->Path); - if (ptr->Path[pathLen - 1] != '\\') - pathLen++; // if the path does not contain a backslash, reserve space for it - int nameLen = lstrlen(ptr->Name); - size += pathLen + nameLen + 1; // reserve space for the terminator - } - } while (index != -1); - if (size == 0) - size = 2; - else - size++; - - return size; -} - -BOOL CFoundFilesListView::GetSelectedList(char* list, DWORD maxSize) -{ - DWORD size = 0; - int index = -1; - do - { - index = ListView_GetNextItem(HWindow, index, LVIS_SELECTED); - if (index != -1) - { - CFoundFilesData* ptr = Data[index]; - int pathLen = lstrlen(ptr->Path); - if (ptr->Path[pathLen - 1] != '\\') - size++; // if the path does not contain a backslash, reserve space for it - size += pathLen; - if (size > maxSize) - { - TRACE_E("Buffer is too short"); - return FALSE; - } - memmove(list, ptr->Path, pathLen); - list += pathLen; - if (ptr->Path[pathLen - 1] != '\\') - *list++ = '\\'; - int nameLen = lstrlen(ptr->Name); - size += nameLen + 1; // reserve space for the terminator - if (size > maxSize) - { - TRACE_E("Buffer is too short"); - return FALSE; - } - memmove(list, ptr->Name, nameLen + 1); - list += nameLen + 1; - } - } while (index != -1); - if (size == 0) - { - if (size + 2 > maxSize) - { - TRACE_E("Buffer is too short"); - return FALSE; - } - *list++ = '\0'; - *list++ = '\0'; - } - else - { - if (size + 1 > maxSize) - { - TRACE_E("Buffer is too short"); - return FALSE; - } - *list++ = '\0'; - } - return TRUE; -} - -void CFoundFilesListView::CheckAndRemoveSelectedItems(BOOL forceRemove, int lastFocusedIndex, const CFoundFilesData* lastFocusedItem) -{ - int removedItems = 0; - - int totalCount = ListView_GetItemCount(HWindow); - int i; - for (i = totalCount - 1; i >= 0; i--) - { - if (ListView_GetItemState(HWindow, i, LVIS_SELECTED) & LVIS_SELECTED) - { - CFoundFilesData* ptr = Data[i]; - BOOL remove = forceRemove; - if (!forceRemove) - { - char fullPath[MAX_PATH]; - int pathLen = lstrlen(ptr->Path); - memmove(fullPath, ptr->Path, pathLen + 1); - if (ptr->Path[pathLen - 1] != '\\') - { - fullPath[pathLen] = '\\'; - fullPath[pathLen + 1] = '\0'; - } - lstrcat(fullPath, ptr->Name); - remove = (SalGetFileAttributes(fullPath) == -1); - } - if (remove) - { - Delete(i); - removedItems++; - } - } - } - if (removedItems > 0) - { - // inform the listview about the new item count - totalCount = totalCount - removedItems; - ListView_SetItemCount(HWindow, totalCount); - if (totalCount > 0) - { - // clear selection of all items - ListView_SetItemState(HWindow, -1, 0, LVIS_SELECTED); - - // try to locate the previously selected item and select it again if it still exists - int selectIndex = -1; - if (lastFocusedIndex != -1) - { - for (i = 0; i < totalCount; i++) - { - CFoundFilesData* ptr = Data[i]; - if (lastFocusedItem != NULL && - lastFocusedItem->Name != NULL && strcmp(ptr->Name, lastFocusedItem->Name) == 0 && - lastFocusedItem->Path != NULL && strcmp(ptr->Path, lastFocusedItem->Path) == 0) - { - selectIndex = i; - break; - } - } - if (selectIndex == -1) - selectIndex = min(lastFocusedIndex, totalCount - 1); // if we did not find it, keep the cursor in place but within item count - } - if (selectIndex == -1) // fallback -- first item - selectIndex = 0; - ListView_SetItemState(HWindow, selectIndex, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED); - ListView_EnsureVisible(HWindow, selectIndex, FALSE); - } - else - FindDialog->UpdateStatusBar = TRUE; - FindDialog->UpdateListViewItems(); - } -} - -BOOL CFoundFilesListView::IsGood() -{ - BOOL isGood; - HANDLES(EnterCriticalSection(&DataCriticalSection)); - isGood = Data.IsGood(); - HANDLES(LeaveCriticalSection(&DataCriticalSection)); - return isGood; -} - -void CFoundFilesListView::ResetState() -{ - HANDLES(EnterCriticalSection(&DataCriticalSection)); - Data.ResetState(); - HANDLES(LeaveCriticalSection(&DataCriticalSection)); -} - -void CFoundFilesListView::StoreItemsState() -{ - int count = GetCount(); - int i; - for (i = 0; i < count; i++) - { - DWORD state = ListView_GetItemState(HWindow, i, LVIS_FOCUSED | LVIS_SELECTED); - Data[i]->Selected = (state & LVIS_SELECTED) != 0 ? 1 : 0; - Data[i]->Focused = (state & LVIS_FOCUSED) != 0 ? 1 : 0; - } -} - -void CFoundFilesListView::RestoreItemsState() -{ - int count = GetCount(); - int i; - for (i = 0; i < count; i++) - { - DWORD state = 0; - if (Data[i]->Selected) - state |= LVIS_SELECTED; - if (Data[i]->Focused) - state |= LVIS_FOCUSED; - ListView_SetItemState(HWindow, i, state, LVIS_FOCUSED | LVIS_SELECTED); - } -} - -void CFoundFilesListView::SortItems(int sortBy) -{ - if (sortBy == 5) - return; // sorting by attributes is unsupported - - BOOL enabledNameSize = TRUE; - BOOL enabledPathTime = TRUE; - if (FindDialog->GrepData.FindDuplicates) - { - enabledPathTime = FALSE; // path and time are irrelevant for duplicates - // sorting by name and size works for duplicates only - // when searching for identical name and size - enabledNameSize = (FindDialog->GrepData.FindDupFlags & FIND_DUPLICATES_NAME) && - (FindDialog->GrepData.FindDupFlags & FIND_DUPLICATES_SIZE); - } - - if (!enabledNameSize && (sortBy == 0 || sortBy == 2)) - return; - if (!enabledPathTime && (sortBy == 1 || sortBy == 3 || sortBy == 4)) - return; - - HCURSOR hCursor = SetCursor(LoadCursor(NULL, IDC_WAIT)); - HANDLES(EnterCriticalSection(&DataCriticalSection)); - - // EnumFileNamesChangeSourceUID(HWindow, &EnumFileNamesSourceUID); // commented out, not sure why it is here: Petr - - // if some items are still in data but not in the listview, transfer them - FindDialog->UpdateListViewItems(); - - if (Data.Count > 0) - { - // save the selected and focused item state - StoreItemsState(); - - // sort the array by the requested criterion - QuickSort(0, Data.Count - 1, sortBy); - if (FindDialog->GrepData.FindDuplicates) - { - QuickSortDuplicates(0, Data.Count - 1, sortBy == 0); - SetDifferentByGroup(); - } - else - { - QuickSort(0, Data.Count - 1, sortBy); - } - - // restore the item states - RestoreItemsState(); - - int focusIndex = ListView_GetNextItem(HWindow, -1, LVNI_FOCUSED); - if (focusIndex != -1) - ListView_EnsureVisible(HWindow, focusIndex, FALSE); - ListView_RedrawItems(HWindow, 0, Data.Count - 1); - UpdateWindow(HWindow); - } - - HANDLES(LeaveCriticalSection(&DataCriticalSection)); - SetCursor(hCursor); -} - -void CFoundFilesListView::SetDifferentByGroup() -{ - CFoundFilesData* lastData = NULL; - int different = 0; - if (Data.Count > 0) - { - lastData = Data.At(0); - lastData->Different = different; - } - int i; - for (i = 1; i < Data.Count; i++) - { - CFoundFilesData* data = Data.At(i); - if (data->Group == lastData->Group) - { - data->Different = different; - } - else - { - different++; - if (different > 1) - different = 0; - lastData = data; - lastData->Different = different; - } - } -} - -void CFoundFilesListView::QuickSort(int left, int right, int sortBy) -{ - -LABEL_QuickSort2: - - int i = left, j = right; - CFoundFilesData* pivot = Data[(i + j) / 2]; - - do - { - while (CompareFunc(Data[i], pivot, sortBy) < 0 && i < right) - i++; - while (CompareFunc(pivot, Data[j], sortBy) < 0 && j > left) - j--; - - if (i <= j) - { - CFoundFilesData* swap = Data[i]; - Data[i] = Data[j]; - Data[j] = swap; - i++; - j--; - } - } while (i <= j); - - // the following "nice" code was replaced with a code that is much more stack-efficient (max. log(N) recursion depth) - // if (left < j) QuickSort(left, j, sortBy); - // if (i < right) QuickSort(i, right, sortBy); - - if (left < j) - { - if (i < right) - { - if (j - left < right - i) // both halves need sorting: recurse on the smaller one and process the other via 'goto' - { - QuickSort(left, j, sortBy); - left = i; - goto LABEL_QuickSort2; - } - else - { - QuickSort(i, right, sortBy); - right = j; - goto LABEL_QuickSort2; - } - } - else - { - right = j; - goto LABEL_QuickSort2; - } - } - else - { - if (i < right) - { - left = i; - goto LABEL_QuickSort2; - } - } -} - -int CFoundFilesListView::CompareFunc(CFoundFilesData* f1, CFoundFilesData* f2, int sortBy) -{ - int res; - int next = sortBy; - do - { - if (f1->IsDir == f2->IsDir) // are the items from the same group (directories/files)? - { - switch (next) - { - case 0: - { - res = RegSetStrICmp(f1->Name, f2->Name); - break; - } - - case 1: - { - res = RegSetStrICmp(f1->Path, f2->Path); - break; - break; - } - - case 2: - { - if (f1->Size < f2->Size) - res = -1; - else - { - if (f1->Size == f2->Size) - res = 0; - else - res = 1; - } - break; - } - - default: - { - res = CompareFileTime(&f1->LastWrite, &f2->LastWrite); - break; - } - } - } - else - res = f1->IsDir ? -1 : 1; - - if (next == sortBy) - { - if (sortBy != 0) - next = 0; - else - next = 1; - } - else if (next + 1 != sortBy) - next++; - else - next += 2; - } while (res == 0 && next <= 3); - - return res; -} - -// quick sort routine for duplicate mode; it uses a special comparator -void CFoundFilesListView::QuickSortDuplicates(int left, int right, BOOL byName) -{ - -LABEL_QuickSortDuplicates: - - int i = left, j = right; - CFoundFilesData* pivot = Data[(i + j) / 2]; - - do - { - while (CompareDuplicatesFunc(Data[i], pivot, byName) < 0 && i < right) - i++; - while (CompareDuplicatesFunc(pivot, Data[j], byName) < 0 && j > left) - j--; - - if (i <= j) - { - CFoundFilesData* swap = Data[i]; - Data[i] = Data[j]; - Data[j] = swap; - i++; - j--; - } - } while (i <= j); - - // the following "nice" code was replaced with a code that is much more stack-efficient (max. log(N) recursion depth) - // if (left < j) QuickSortDuplicates(left, j, byName); - // if (i < right) QuickSortDuplicates(i, right, byName); - - if (left < j) - { - if (i < right) - { - if (j - left < right - i) // both halves need sorting: recurse on the smaller one and use 'goto' for the other - { - QuickSortDuplicates(left, j, byName); - left = i; - goto LABEL_QuickSortDuplicates; - } - else - { - QuickSortDuplicates(i, right, byName); - right = j; - goto LABEL_QuickSortDuplicates; - } - } - else - { - right = j; - goto LABEL_QuickSortDuplicates; - } - } - else - { - if (i < right) - { - left = i; - goto LABEL_QuickSortDuplicates; - } - } -} - -// comparator for displayed duplicates; if 'byName', sorting is primarily by name, otherwise by size -int CFoundFilesListView::CompareDuplicatesFunc(CFoundFilesData* f1, CFoundFilesData* f2, BOOL byName) -{ - int res; - if (byName) - { - // by name - res = RegSetStrICmp(f1->Name, f2->Name); - if (res == 0) - { - // by size - if (f1->Size < f2->Size) - res = -1; - else - { - if (f1->Size == f2->Size) - { - // by group - if (f1->Group < f2->Group) - res = -1; - else - { - if (f1->Group == f2->Group) - res = 0; - else - res = 1; - } - } - else - res = 1; - } - } - } - else - { - // by size - if (f1->Size < f2->Size) - res = -1; - else - { - if (f1->Size == f2->Size) - { - // by name - res = RegSetStrICmp(f1->Name, f2->Name); - if (res == 0) - { - // by group - if (f1->Group < f2->Group) - res = -1; - else - { - if (f1->Group == f2->Group) - res = 0; - else - res = 1; - } - } - } - else - res = 1; - } - } - if (res == 0) - res = RegSetStrICmp(f1->Path, f2->Path); - return res; -} - +// Helper: carries selection state for GetNextItemFromFind callback struct CUMDataFromFind { HWND HWindow; @@ -912,438 +87,61 @@ BOOL GetNextItemFromFind(int index, char* path, char* name, void* param) return FALSE; } -LRESULT -CFoundFilesListView::WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - SLOW_CALL_STACK_MESSAGE4("CFoundFilesListView::WindowProc(0x%X, 0x%IX, 0x%IX)", uMsg, wParam, lParam); - switch (uMsg) - { - case WM_GETDLGCODE: - { - if (lParam != NULL) - { - // if it is the Enter key, we want to process it, otherwise the Enter would not be delivered - MSG* msg = (LPMSG)lParam; - if (msg->message == WM_KEYDOWN && msg->wParam == VK_RETURN && - ListView_GetItemCount(HWindow) > 0) - return DLGC_WANTMESSAGE; - } - return DLGC_WANTCHARS | DLGC_WANTARROWS; - } - - case WM_SYSKEYDOWN: - case WM_KEYDOWN: - { - BOOL altPressed = (GetKeyState(VK_MENU) & 0x8000) != 0; - BOOL shiftPressed = (GetKeyState(VK_SHIFT) & 0x8000) != 0; - /* it seems this code is no longer needed; handled in the dialog wndproc - if (wParam == VK_RETURN) - { - if (altPressed) - { - FindDialog->OnProperties(); - FindDialog->SkipCharacter = TRUE; - } - else - FindDialog->OnOpen(); - return TRUE; - } -*/ - if ((wParam == VK_F10 && shiftPressed || wParam == VK_APPS)) - { - POINT p; - GetListViewContextMenuPos(HWindow, &p); - FindDialog->OnContextMenu(p.x, p.y); - return TRUE; - } - break; - } - - case WM_MOUSEACTIVATE: - { - // if Find is inactive and the user tries to drag & drop one of the items, the dialog must not pop up to the foreground - return MA_NOACTIVATE; - } - - case WM_SETFOCUS: - { - SendMessage(GetParent(HWindow), WM_USER_BUTTONS, 0, 0); - break; - } - - case WM_KILLFOCUS: - { - HWND next = (HWND)wParam; - BOOL nextIsButton; - if (next != NULL) - { - char className[30]; - WORD wl = LOWORD(GetWindowLongPtr(next, GWL_STYLE)); // only BS_ styles - nextIsButton = (GetClassName(next, className, 30) != 0 && - StrICmp(className, "BUTTON") == 0 && - (wl == BS_PUSHBUTTON || wl == BS_DEFPUSHBUTTON)); - } - else - nextIsButton = FALSE; - SendMessage(GetParent(HWindow), WM_USER_BUTTONS, nextIsButton ? wParam : 0, 0); - break; - } - - case WM_USER_ENUMFILENAMES: // searching for the next/previous name for the viewer - { - HANDLES(EnterCriticalSection(&FileNamesEnumDataSect)); - if ((int)wParam /* reqUID */ == FileNamesEnumData.RequestUID && // no further request was issued (this one would be pointless) - EnumFileNamesSourceUID == FileNamesEnumData.SrcUID && // the source hasn't changed - !FileNamesEnumData.TimedOut) // someone is still waiting for the result - { - HANDLES(EnterCriticalSection(&DataCriticalSection)); - - BOOL selExists = FALSE; - if (FileNamesEnumData.PreferSelected) // if needed, check whether there is a selection - { - int i = -1; - int selCount = 0; // ignore the state where the only marked item is the focused one (this cannot logically be considered as selected items) - while (1) - { - i = ListView_GetNextItem(HWindow, i, LVNI_SELECTED); - if (i == -1) - break; - else - { - selCount++; - if (!Data[i]->IsDir) - selExists = TRUE; - if (selCount > 1 && selExists) - break; - } - } - if (selExists && selCount <= 1) - selExists = FALSE; - } - - int index = FileNamesEnumData.LastFileIndex; - int count = Data.Count; - BOOL indexNotFound = TRUE; - if (index == -1) // searching from the first or last item - { - if (FileNamesEnumData.RequestType == fnertFindPrevious) - index = count; // looking for the previous item, start at the end - // else // looking for the next item, start at the beginning - } - else - { - if (FileNamesEnumData.LastFileName[0] != 0) // the full name at 'index' is known; check for shifts and search for a new index if needed - { - BOOL ok = FALSE; - CFoundFilesData* f = (index >= 0 && index < count) ? Data[index] : NULL; - char fileName[MAX_PATH]; - if (f != NULL && f->Path != NULL && f->Name != NULL) - { - lstrcpyn(fileName, f->Path, MAX_PATH); - SalPathAppend(fileName, f->Name, MAX_PATH); - if (StrICmp(fileName, FileNamesEnumData.LastFileName) == 0) - { - ok = TRUE; - indexNotFound = FALSE; - } - } - if (!ok) - { // the name at index 'index' isn't FileNamesEnumData.LastFileName, try to find a new index for that name - int i; - for (i = 0; i < count; i++) - { - f = Data[i]; - if (f->Path != NULL && f->Name != NULL) - { - lstrcpyn(fileName, f->Path, MAX_PATH); - SalPathAppend(fileName, f->Name, MAX_PATH); - if (StrICmp(fileName, FileNamesEnumData.LastFileName) == 0) - break; - } - } - if (i != count) // new index found - { - index = i; - indexNotFound = FALSE; - } - } - } - if (index >= count) - { - if (FileNamesEnumData.RequestType == fnertFindNext) - index = count - 1; - else - index = count; - } - if (index < 0) - index = 0; - } - - int wantedViewerType = 0; - BOOL onlyAssociatedExtensions = FALSE; - if (FileNamesEnumData.OnlyAssociatedExtensions) // does the viewer request filtering by associated extensions? - { - if (FileNamesEnumData.Plugin != NULL) // viewer from a plugin - { - int pluginIndex = Plugins.GetIndex(FileNamesEnumData.Plugin); - if (pluginIndex != -1) // "always true" - { - wantedViewerType = -1 - pluginIndex; - onlyAssociatedExtensions = TRUE; - } - } - else // internal viewer - { - wantedViewerType = VIEWER_INTERNAL; - onlyAssociatedExtensions = TRUE; - } - } - - BOOL preferSelected = selExists && FileNamesEnumData.PreferSelected; - switch (FileNamesEnumData.RequestType) - { - case fnertFindNext: // next - { - CDynString strViewerMasks; - if (MainWindow->GetViewersAssoc(wantedViewerType, &strViewerMasks)) - { - CMaskGroup masks; - int errorPos; - if (masks.PrepareMasks(errorPos, strViewerMasks.GetString())) - { - while (index + 1 < count) - { - index++; - if (preferSelected) - { - int i = ListView_GetNextItem(HWindow, index - 1, LVNI_SELECTED); - if (i != -1) - { - index = i; - if (!Data[index]->IsDir) // we only search for files - { - if (!onlyAssociatedExtensions || masks.AgreeMasks(Data[index]->Name, NULL)) - { - FileNamesEnumData.Found = TRUE; - break; - } - } - } - else - index = count - 1; - } - else - { - if (!Data[index]->IsDir) - { - if (!onlyAssociatedExtensions || masks.AgreeMasks(Data[index]->Name, NULL)) - { - FileNamesEnumData.Found = TRUE; - break; - } - } - } - } - } - else - TRACE_E("Unexpected situation in Find::WM_USER_ENUMFILENAMES: grouped viewer's masks can't be prepared for use!"); - } - break; - } - - case fnertFindPrevious: // previous - { - CDynString strViewerMasks; - if (MainWindow->GetViewersAssoc(wantedViewerType, &strViewerMasks)) - { - CMaskGroup masks; - int errorPos; - if (masks.PrepareMasks(errorPos, strViewerMasks.GetString())) - { - while (index - 1 >= 0) - { - index--; - if (!Data[index]->IsDir && - (!preferSelected || - (ListView_GetItemState(HWindow, index, LVIS_SELECTED) & LVIS_SELECTED))) - { - if (!onlyAssociatedExtensions || masks.AgreeMasks(Data[index]->Name, NULL)) - { - FileNamesEnumData.Found = TRUE; - break; - } - } - } - } - else - TRACE_E("Unexpected situation in Find::WM_USER_ENUMFILENAMES: grouped viewer's masks can't be prepared for use!"); - } - break; - } - - case fnertIsSelected: // check selection state - { - if (!indexNotFound && index >= 0 && index < Data.Count) - { - FileNamesEnumData.IsFileSelected = (ListView_GetItemState(HWindow, index, LVIS_SELECTED) & LVIS_SELECTED) != 0; - FileNamesEnumData.Found = TRUE; - } - break; - } - - case fnertSetSelection: // set selection - { - if (!indexNotFound && index >= 0 && index < Data.Count) - { - ListView_SetItemState(HWindow, index, FileNamesEnumData.Select ? LVIS_SELECTED : 0, LVIS_SELECTED); - FileNamesEnumData.Found = TRUE; - } - break; - } - } - - if (FileNamesEnumData.Found) - { - CFoundFilesData* f = Data[index]; - if (f->Path != NULL && f->Name != NULL) - { - lstrcpyn(FileNamesEnumData.FileName, f->Path, MAX_PATH); - SalPathAppend(FileNamesEnumData.FileName, f->Name, MAX_PATH); - FileNamesEnumData.LastFileIndex = index; - } - else // should never happen - { - TRACE_E("Unexpected situation in CFoundFilesListView::WindowProc(): handling of WM_USER_ENUMFILENAMES"); - FileNamesEnumData.Found = FALSE; - FileNamesEnumData.NoMoreFiles = TRUE; - } - } - else - FileNamesEnumData.NoMoreFiles = TRUE; +const char* MINIMIZED_FINDING_CAPTION = "(%d) %s [%s %s]"; +const char* NORMAL_FINDING_CAPTION = "%s [%s %s]"; - HANDLES(LeaveCriticalSection(&DataCriticalSection)); - SetEvent(FileNamesEnumDone); - } - HANDLES(LeaveCriticalSection(&FileNamesEnumDataSect)); +// Helper function to draw UTF-8 text using Unicode API +static int DrawTextUtf8(HDC hDC, const char* text, int textLen, LPRECT rect, UINT format) +{ + if (text == NULL) return 0; - } - } - return CWindow::WindowProc(uMsg, wParam, lParam); + if (textLen == -1) + textLen = (int)strlen(text); + // Convert UTF-8 to wide characters + int wideLen = MultiByteToWideChar(CP_UTF8, 0, text, textLen, NULL, 0); + if (wideLen <= 0) + return DrawText(hDC, text, textLen, rect, format); // Fallback to ANSI + + wchar_t* wideText = (wchar_t*)_alloca((wideLen + 1) * sizeof(wchar_t)); + MultiByteToWideChar(CP_UTF8, 0, text, textLen, wideText, wideLen + 1); + wideText[wideLen] = 0; + return DrawTextW(hDC, wideText, wideLen, rect, format); } -BOOL CFoundFilesListView::InitColumns() +// Helper function for PathCompactPath that handles UTF-8 +static BOOL PathCompactPathUtf8(HDC hDC, char* path, UINT dx) { - CALL_STACK_MESSAGE1("CFoundFilesListView::InitColumns()"); - LV_COLUMN lvc; - int header[] = {IDS_FOUNDFILESCOLUMN1, IDS_FOUNDFILESCOLUMN2, - IDS_FOUNDFILESCOLUMN3, IDS_FOUNDFILESCOLUMN4, - IDS_FOUNDFILESCOLUMN5, IDS_FOUNDFILESCOLUMN6, - -1}; - - lvc.mask = LVCF_FMT | LVCF_TEXT | LVCF_SUBITEM; - lvc.fmt = LVCFMT_LEFT; - int i; - for (i = 0; header[i] != -1; i++) // create columns - { - if (i == 2) - lvc.fmt = LVCFMT_RIGHT; - lvc.pszText = LoadStr(header[i]); - lvc.iSubItem = i; - if (ListView_InsertColumn(HWindow, i, &lvc) == -1) - return FALSE; - } + if (path == NULL) + return FALSE; + // Convert UTF-8 to wide, compact, then back to UTF-8 + wchar_t wpath[2 * MAX_PATH]; + MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, 2 * MAX_PATH); + BOOL result = PathCompactPathW(hDC, wpath, dx); + WideCharToMultiByte(CP_UTF8, 0, wpath, -1, path, 2 * MAX_PATH, NULL, NULL); + return result; +} - RECT r; - GetClientRect(HWindow, &r); - DWORD cx = r.right - r.left - 1; - ListView_SetColumnWidth(HWindow, 5, ListView_GetStringWidth(HWindow, "ARH") + 20); - - char format1[200]; - char format2[200]; - SYSTEMTIME st; - ZeroMemory(&st, sizeof(st)); - st.wYear = 2000; // the longest possible value - st.wMonth = 12; // the longest possible value - st.wDay = 30; // the longest possible value - st.wHour = 10; // morning (not sure whether AM or PM will be shorter, so try both) - st.wMinute = 59; // the longest possible value - st.wSecond = 59; // the longest possible value - if (GetTimeFormat(LOCALE_USER_DEFAULT, 0, &st, NULL, format1, 200) == 0) - sprintf(format1, "%u:%02u:%02u", st.wHour, st.wMinute, st.wSecond); - st.wHour = 20; // afternoon - if (GetTimeFormat(LOCALE_USER_DEFAULT, 0, &st, NULL, format2, 200) == 0) - sprintf(format2, "%u:%02u:%02u", st.wHour, st.wMinute, st.wSecond); - - int maxWidth = ListView_GetStringWidth(HWindow, format1); - int w = ListView_GetStringWidth(HWindow, format2); - if (w > maxWidth) - maxWidth = w; - ListView_SetColumnWidth(HWindow, 4, maxWidth + 20); - - maxWidth = 0; - if (GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &st, NULL, format1, 200) == 0) - sprintf(format1, "%u.%u.%u", st.wDay, st.wMonth, st.wYear); - else - { - // verify that the short date format does not contain alphabetic characters - const char* p = format1; - while (*p != 0 && !IsAlpha[*p]) - p++; - if (IsAlpha[*p]) - { - // contains alphabetic characters -- we must find the longest month and day text - int maxMonth = 0; - int sats[] = {1, 5, 4, 1, 6, 3, 1, 5, 2, 7, 4, 2}; - int mo; - for (mo = 0; mo < 12; mo++) // iterate over all months starting from January; the weekday stays the same so its width doesn't influence the result, wDay is single digit for the same reason - { - st.wDay = sats[mo]; - st.wMonth = 1 + mo; - if (GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &st, NULL, format1, 200) != 0) - { - w = ListView_GetStringWidth(HWindow, format1); - if (w > maxWidth) - { - maxWidth = w; - maxMonth = st.wMonth; - } - } - } - if (maxWidth > 0) - { - st.wMonth = maxMonth; - for (st.wDay = 21; st.wDay < 28; st.wDay++) // all possible weekdays (doesn't have to start on Monday) - { - if (GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &st, NULL, format1, 200) != 0) - { - w = ListView_GetStringWidth(HWindow, format1); - if (w > maxWidth) - { - maxWidth = w; - } - } - } - } - } - } +BOOL FindManageInUse = FALSE; +BOOL FindIgnoreInUse = FALSE; - ListView_SetColumnWidth(HWindow, 3, (maxWidth > 0 ? maxWidth : ListView_GetStringWidth(HWindow, format1)) + 20); - ListView_SetColumnWidth(HWindow, 2, ListView_GetStringWidth(HWindow, "000 000 000 000") + 20); // up to 1TB fits here - int width; - if (Configuration.FindColNameWidth != -1) - width = Configuration.FindColNameWidth; - else - width = 20 + ListView_GetStringWidth(HWindow, "XXXXXXXX.XXX") + 20; - ListView_SetColumnWidth(HWindow, 0, width); - cx -= ListView_GetColumnWidth(HWindow, 0) + ListView_GetColumnWidth(HWindow, 2) + - ListView_GetColumnWidth(HWindow, 3) + ListView_GetColumnWidth(HWindow, 4) + - ListView_GetColumnWidth(HWindow, 5) + GetSystemMetrics(SM_CXHSCROLL) - 1; - ListView_SetColumnWidth(HWindow, 1, cx); - ListView_SetImageList(HWindow, HFindSymbolsImageList, LVSIL_SMALL); +static char FoundFilesDataTextBuffer[4 * MAX_PATH]; +static WCHAR FoundFilesDataTextBufferW[4 * MAX_PATH]; - return TRUE; -} +static void Utf8ToAnsiWithLimit(const char* src, char* dst, int dstSize) +{ + if (dst == NULL || dstSize <= 0) + return; + dst[0] = 0; + + WCHAR wide[4 * MAX_PATH]; + int wLen = ConvertUtf8ToWide(src, -1, wide, _countof(wide)); + if (wLen == 0) + return; + int res = WideCharToMultiByte(CP_ACP, 0, wide, -1, dst, dstSize, NULL, NULL); + if (res == 0 && GetLastError() == ERROR_INSUFFICIENT_BUFFER) + dst[dstSize - 1] = 0; +} //**************************************************************************** // // CFindDialog diff --git a/src/find_results.cpp b/src/find_results.cpp new file mode 100644 index 00000000..f34e13be --- /dev/null +++ b/src/find_results.cpp @@ -0,0 +1,1226 @@ +// SPDX-FileCopyrightText: 2023 Taskscape Ltd +// SPDX-License-Identifier: GPL-2.0-or-later +// CommentsTranslationProject: TRANSLATED + +#include "precomp.h" + +#include "menu.h" +#include "cfgdlg.h" +#include "mainwnd.h" +#include "plugins.h" +#include "fileswnd.h" +#include "viewer.h" +#include "shellib.h" +#include "find.h" +#include "gui.h" +#include "usermenu.h" +#include "execute.h" +#include "tasklist.h" + +#include + +//**************************************************************************** +// +// CFindTBHeader +// + +void CFindOptions::InitMenu(CMenuPopup* popup, BOOL enabled, int originalCount) +{ + int count = popup->GetItemCount(); + if (count > originalCount) + { + // remove any previously inserted items + popup->RemoveItemsRange(originalCount, count - 1); + } + + if (Items.Count > 0) + { + MENU_ITEM_INFO mii; + + // if there are items to append, insert a separator first + mii.Mask = MENU_MASK_TYPE; + mii.Type = MENU_TYPE_SEPARATOR; + popup->InsertItem(-1, TRUE, &mii); + + // append the displayed portion of the items + int maxCount = CM_FIND_OPTIONS_LAST - CM_FIND_OPTIONS_FIRST; + int i; + for (i = 0; i < min(Items.Count, maxCount); i++) + { + mii.Mask = MENU_MASK_TYPE | MENU_MASK_STATE | MENU_MASK_STRING | MENU_MASK_ID; + mii.Type = MENU_TYPE_STRING; + mii.State = enabled ? 0 : MENU_STATE_GRAYED; + if (Items[i]->AutoLoad) + mii.State |= MENU_STATE_DEFAULT; + mii.ID = CM_FIND_OPTIONS_FIRST + i; + mii.String = Items[i]->ItemName; + popup->InsertItem(-1, TRUE, &mii); + } + } +} + +//**************************************************************************** +// +// CFoundFilesData +// + +BOOL CFoundFilesData::Set(const char* path, const char* name, const CQuadWord& size, DWORD attr, + const FILETIME* lastWrite, BOOL isDir) +{ + CALL_STACK_MESSAGE_NONE + // CALL_STACK_MESSAGE5("CFoundFilesData::Set(%s, %s, %g, 0x%X, )", path, name, size.GetDouble(), attr); + int l1 = (int)strlen(path), l2 = (int)strlen(name); + Path = (char*)malloc(l1 + 1); + Name = (char*)malloc(l2 + 1); + if (Path == NULL || Name == NULL) + return FALSE; + memmove(Path, path, l1 + 1); + memmove(Name, name, l2 + 1); + Size = size; + Attr = attr; + LastWrite = *lastWrite; + IsDir = isDir ? 1 : 0; + return TRUE; +} + +char* CFoundFilesData::GetText(int i, char* text, int fileNameFormat) +{ + // several FIND windows may run in parallel, which could overwrite this static buffer + // static char text[50]; + switch (i) + { + case 0: + { + AlterFileName(text, Name, -1, fileNameFormat, 0, IsDir); + return text; + } + + case 1: + return Path; + + case 2: + { + if (IsDir) + CopyMemory(text, DirColumnStr, DirColumnStrLen + 1); + else + NumberToStr(text, Size); + break; + } + + case 3: + { + SYSTEMTIME st; + FILETIME ft; + if (FileTimeToLocalFileTime(&LastWrite, &ft) && + FileTimeToSystemTime(&ft, &st)) + { + if (GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &st, NULL, text, 50) == 0) + sprintf(text, "%u.%u.%u", st.wDay, st.wMonth, st.wYear); + } + else + strcpy(text, LoadStr(IDS_INVALID_DATEORTIME)); + break; + } + + case 4: + { + SYSTEMTIME st; + FILETIME ft; + if (FileTimeToLocalFileTime(&LastWrite, &ft) && + FileTimeToSystemTime(&ft, &st)) + { + if (GetTimeFormat(LOCALE_USER_DEFAULT, 0, &st, NULL, text, 50) == 0) + sprintf(text, "%u:%02u:%02u", st.wHour, st.wMinute, st.wSecond); + } + else + strcpy(text, LoadStr(IDS_INVALID_DATEORTIME)); + break; + } + + default: + { + GetAttrsString(text, Attr); + break; + } + } + return text; +} + +//**************************************************************************** +// +// CFoundFilesListView +// + +CFoundFilesListView::CFoundFilesListView(HWND dlg, int ctrlID, CFindDialog* findDialog) + : Data(1000, 500), DataForRefine(1, 1000), CWindow(dlg, ctrlID) +{ + FindDialog = findDialog; + HANDLES(InitializeCriticalSection(&DataCriticalSection)); + // use Unicode notifications if available + ListView_SetUnicodeFormat(HWindow, TRUE); + + // add this panel to the array of sources for enumerating files in viewers + EnumFileNamesAddSourceUID(HWindow, &EnumFileNamesSourceUID); +} + +CFoundFilesListView::~CFoundFilesListView() +{ + // remove this panel from the array of sources for enumerating files in viewers + EnumFileNamesRemoveSourceUID(HWindow); + + HANDLES(DeleteCriticalSection(&DataCriticalSection)); +} + +CFoundFilesData* +CFoundFilesListView::At(int index) +{ + CFoundFilesData* ptr; + HANDLES(EnterCriticalSection(&DataCriticalSection)); + ptr = Data[index]; + HANDLES(LeaveCriticalSection(&DataCriticalSection)); + return ptr; +} + +void CFoundFilesListView::DestroyMembers() +{ + // HANDLES(EnterCriticalSection(&DataCriticalSection)); + Data.DestroyMembers(); + // HANDLES(LeaveCriticalSection(&DataCriticalSection)); +} + +void CFoundFilesListView::Delete(int index) +{ + HANDLES(EnterCriticalSection(&DataCriticalSection)); + Data.Delete(index); + HANDLES(LeaveCriticalSection(&DataCriticalSection)); +} + +int CFoundFilesListView::GetCount() +{ + int count; + HANDLES(EnterCriticalSection(&DataCriticalSection)); + count = Data.Count; + HANDLES(LeaveCriticalSection(&DataCriticalSection)); + return count; +} + +int CFoundFilesListView::Add(CFoundFilesData* item) +{ + int index; + HANDLES(EnterCriticalSection(&DataCriticalSection)); + index = Data.Add(item); + HANDLES(LeaveCriticalSection(&DataCriticalSection)); + return index; +} + +BOOL CFoundFilesListView::TakeDataForRefine() +{ + DataForRefine.DestroyMembers(); + int i; + for (i = 0; i < Data.Count; i++) + { + CFoundFilesData* refineData = Data[i]; + DataForRefine.Add(refineData); + if (!DataForRefine.IsGood()) + { + DataForRefine.ResetState(); + DataForRefine.DetachMembers(); + return FALSE; + } + } + Data.DetachMembers(); + return TRUE; +} + +void CFoundFilesListView::DestroyDataForRefine() +{ + DataForRefine.DestroyMembers(); +} + +int CFoundFilesListView::GetDataForRefineCount() +{ + return DataForRefine.Count; +} + +CFoundFilesData* +CFoundFilesListView::GetDataForRefine(int index) +{ + CFoundFilesData* ptr; + ptr = DataForRefine[index]; + return ptr; +} + +DWORD +CFoundFilesListView::GetSelectedListSize() +{ + // this method is invoked only from the main thread + DWORD size = 0; + int index = -1; + do + { + index = ListView_GetNextItem(HWindow, index, LVIS_SELECTED); + if (index != -1) + { + CFoundFilesData* ptr = Data[index]; + int pathLen = lstrlen(ptr->Path); + if (ptr->Path[pathLen - 1] != '\\') + pathLen++; // if the path does not contain a backslash, reserve space for it + int nameLen = lstrlen(ptr->Name); + size += pathLen + nameLen + 1; // reserve space for the terminator + } + } while (index != -1); + if (size == 0) + size = 2; + else + size++; + + return size; +} + +BOOL CFoundFilesListView::GetSelectedList(char* list, DWORD maxSize) +{ + DWORD size = 0; + int index = -1; + do + { + index = ListView_GetNextItem(HWindow, index, LVIS_SELECTED); + if (index != -1) + { + CFoundFilesData* ptr = Data[index]; + int pathLen = lstrlen(ptr->Path); + if (ptr->Path[pathLen - 1] != '\\') + size++; // if the path does not contain a backslash, reserve space for it + size += pathLen; + if (size > maxSize) + { + TRACE_E("Buffer is too short"); + return FALSE; + } + memmove(list, ptr->Path, pathLen); + list += pathLen; + if (ptr->Path[pathLen - 1] != '\\') + *list++ = '\\'; + int nameLen = lstrlen(ptr->Name); + size += nameLen + 1; // reserve space for the terminator + if (size > maxSize) + { + TRACE_E("Buffer is too short"); + return FALSE; + } + memmove(list, ptr->Name, nameLen + 1); + list += nameLen + 1; + } + } while (index != -1); + if (size == 0) + { + if (size + 2 > maxSize) + { + TRACE_E("Buffer is too short"); + return FALSE; + } + *list++ = '\0'; + *list++ = '\0'; + } + else + { + if (size + 1 > maxSize) + { + TRACE_E("Buffer is too short"); + return FALSE; + } + *list++ = '\0'; + } + return TRUE; +} + +void CFoundFilesListView::CheckAndRemoveSelectedItems(BOOL forceRemove, int lastFocusedIndex, const CFoundFilesData* lastFocusedItem) +{ + int removedItems = 0; + + int totalCount = ListView_GetItemCount(HWindow); + int i; + for (i = totalCount - 1; i >= 0; i--) + { + if (ListView_GetItemState(HWindow, i, LVIS_SELECTED) & LVIS_SELECTED) + { + CFoundFilesData* ptr = Data[i]; + BOOL remove = forceRemove; + if (!forceRemove) + { + char fullPath[MAX_PATH]; + int pathLen = lstrlen(ptr->Path); + memmove(fullPath, ptr->Path, pathLen + 1); + if (ptr->Path[pathLen - 1] != '\\') + { + fullPath[pathLen] = '\\'; + fullPath[pathLen + 1] = '\0'; + } + lstrcat(fullPath, ptr->Name); + remove = (SalGetFileAttributes(fullPath) == -1); + } + if (remove) + { + Delete(i); + removedItems++; + } + } + } + if (removedItems > 0) + { + // inform the listview about the new item count + totalCount = totalCount - removedItems; + ListView_SetItemCount(HWindow, totalCount); + if (totalCount > 0) + { + // clear selection of all items + ListView_SetItemState(HWindow, -1, 0, LVIS_SELECTED); + + // try to locate the previously selected item and select it again if it still exists + int selectIndex = -1; + if (lastFocusedIndex != -1) + { + for (i = 0; i < totalCount; i++) + { + CFoundFilesData* ptr = Data[i]; + if (lastFocusedItem != NULL && + lastFocusedItem->Name != NULL && strcmp(ptr->Name, lastFocusedItem->Name) == 0 && + lastFocusedItem->Path != NULL && strcmp(ptr->Path, lastFocusedItem->Path) == 0) + { + selectIndex = i; + break; + } + } + if (selectIndex == -1) + selectIndex = min(lastFocusedIndex, totalCount - 1); // if we did not find it, keep the cursor in place but within item count + } + if (selectIndex == -1) // fallback -- first item + selectIndex = 0; + ListView_SetItemState(HWindow, selectIndex, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED); + ListView_EnsureVisible(HWindow, selectIndex, FALSE); + } + else + FindDialog->UpdateStatusBar = TRUE; + FindDialog->UpdateListViewItems(); + } +} + +BOOL CFoundFilesListView::IsGood() +{ + BOOL isGood; + HANDLES(EnterCriticalSection(&DataCriticalSection)); + isGood = Data.IsGood(); + HANDLES(LeaveCriticalSection(&DataCriticalSection)); + return isGood; +} + +void CFoundFilesListView::ResetState() +{ + HANDLES(EnterCriticalSection(&DataCriticalSection)); + Data.ResetState(); + HANDLES(LeaveCriticalSection(&DataCriticalSection)); +} + +void CFoundFilesListView::StoreItemsState() +{ + int count = GetCount(); + int i; + for (i = 0; i < count; i++) + { + DWORD state = ListView_GetItemState(HWindow, i, LVIS_FOCUSED | LVIS_SELECTED); + Data[i]->Selected = (state & LVIS_SELECTED) != 0 ? 1 : 0; + Data[i]->Focused = (state & LVIS_FOCUSED) != 0 ? 1 : 0; + } +} + +void CFoundFilesListView::RestoreItemsState() +{ + int count = GetCount(); + int i; + for (i = 0; i < count; i++) + { + DWORD state = 0; + if (Data[i]->Selected) + state |= LVIS_SELECTED; + if (Data[i]->Focused) + state |= LVIS_FOCUSED; + ListView_SetItemState(HWindow, i, state, LVIS_FOCUSED | LVIS_SELECTED); + } +} + +void CFoundFilesListView::SortItems(int sortBy) +{ + if (sortBy == 5) + return; // sorting by attributes is unsupported + + BOOL enabledNameSize = TRUE; + BOOL enabledPathTime = TRUE; + if (FindDialog->GrepData.FindDuplicates) + { + enabledPathTime = FALSE; // path and time are irrelevant for duplicates + // sorting by name and size works for duplicates only + // when searching for identical name and size + enabledNameSize = (FindDialog->GrepData.FindDupFlags & FIND_DUPLICATES_NAME) && + (FindDialog->GrepData.FindDupFlags & FIND_DUPLICATES_SIZE); + } + + if (!enabledNameSize && (sortBy == 0 || sortBy == 2)) + return; + if (!enabledPathTime && (sortBy == 1 || sortBy == 3 || sortBy == 4)) + return; + + HCURSOR hCursor = SetCursor(LoadCursor(NULL, IDC_WAIT)); + HANDLES(EnterCriticalSection(&DataCriticalSection)); + + // EnumFileNamesChangeSourceUID(HWindow, &EnumFileNamesSourceUID); // commented out, not sure why it is here: Petr + + // if some items are still in data but not in the listview, transfer them + FindDialog->UpdateListViewItems(); + + if (Data.Count > 0) + { + // save the selected and focused item state + StoreItemsState(); + + // sort the array by the requested criterion + QuickSort(0, Data.Count - 1, sortBy); + if (FindDialog->GrepData.FindDuplicates) + { + QuickSortDuplicates(0, Data.Count - 1, sortBy == 0); + SetDifferentByGroup(); + } + else + { + QuickSort(0, Data.Count - 1, sortBy); + } + + // restore the item states + RestoreItemsState(); + + int focusIndex = ListView_GetNextItem(HWindow, -1, LVNI_FOCUSED); + if (focusIndex != -1) + ListView_EnsureVisible(HWindow, focusIndex, FALSE); + ListView_RedrawItems(HWindow, 0, Data.Count - 1); + UpdateWindow(HWindow); + } + + HANDLES(LeaveCriticalSection(&DataCriticalSection)); + SetCursor(hCursor); +} + +void CFoundFilesListView::SetDifferentByGroup() +{ + CFoundFilesData* lastData = NULL; + int different = 0; + if (Data.Count > 0) + { + lastData = Data.At(0); + lastData->Different = different; + } + int i; + for (i = 1; i < Data.Count; i++) + { + CFoundFilesData* data = Data.At(i); + if (data->Group == lastData->Group) + { + data->Different = different; + } + else + { + different++; + if (different > 1) + different = 0; + lastData = data; + lastData->Different = different; + } + } +} + +void CFoundFilesListView::QuickSort(int left, int right, int sortBy) +{ + +LABEL_QuickSort2: + + int i = left, j = right; + CFoundFilesData* pivot = Data[(i + j) / 2]; + + do + { + while (CompareFunc(Data[i], pivot, sortBy) < 0 && i < right) + i++; + while (CompareFunc(pivot, Data[j], sortBy) < 0 && j > left) + j--; + + if (i <= j) + { + CFoundFilesData* swap = Data[i]; + Data[i] = Data[j]; + Data[j] = swap; + i++; + j--; + } + } while (i <= j); + + // the following "nice" code was replaced with a code that is much more stack-efficient (max. log(N) recursion depth) + // if (left < j) QuickSort(left, j, sortBy); + // if (i < right) QuickSort(i, right, sortBy); + + if (left < j) + { + if (i < right) + { + if (j - left < right - i) // both halves need sorting: recurse on the smaller one and process the other via 'goto' + { + QuickSort(left, j, sortBy); + left = i; + goto LABEL_QuickSort2; + } + else + { + QuickSort(i, right, sortBy); + right = j; + goto LABEL_QuickSort2; + } + } + else + { + right = j; + goto LABEL_QuickSort2; + } + } + else + { + if (i < right) + { + left = i; + goto LABEL_QuickSort2; + } + } +} + +int CFoundFilesListView::CompareFunc(CFoundFilesData* f1, CFoundFilesData* f2, int sortBy) +{ + int res; + int next = sortBy; + do + { + if (f1->IsDir == f2->IsDir) // are the items from the same group (directories/files)? + { + switch (next) + { + case 0: + { + res = RegSetStrICmp(f1->Name, f2->Name); + break; + } + + case 1: + { + res = RegSetStrICmp(f1->Path, f2->Path); + break; + break; + } + + case 2: + { + if (f1->Size < f2->Size) + res = -1; + else + { + if (f1->Size == f2->Size) + res = 0; + else + res = 1; + } + break; + } + + default: + { + res = CompareFileTime(&f1->LastWrite, &f2->LastWrite); + break; + } + } + } + else + res = f1->IsDir ? -1 : 1; + + if (next == sortBy) + { + if (sortBy != 0) + next = 0; + else + next = 1; + } + else if (next + 1 != sortBy) + next++; + else + next += 2; + } while (res == 0 && next <= 3); + + return res; +} + +// quick sort routine for duplicate mode; it uses a special comparator +void CFoundFilesListView::QuickSortDuplicates(int left, int right, BOOL byName) +{ + +LABEL_QuickSortDuplicates: + + int i = left, j = right; + CFoundFilesData* pivot = Data[(i + j) / 2]; + + do + { + while (CompareDuplicatesFunc(Data[i], pivot, byName) < 0 && i < right) + i++; + while (CompareDuplicatesFunc(pivot, Data[j], byName) < 0 && j > left) + j--; + + if (i <= j) + { + CFoundFilesData* swap = Data[i]; + Data[i] = Data[j]; + Data[j] = swap; + i++; + j--; + } + } while (i <= j); + + // the following "nice" code was replaced with a code that is much more stack-efficient (max. log(N) recursion depth) + // if (left < j) QuickSortDuplicates(left, j, byName); + // if (i < right) QuickSortDuplicates(i, right, byName); + + if (left < j) + { + if (i < right) + { + if (j - left < right - i) // both halves need sorting: recurse on the smaller one and use 'goto' for the other + { + QuickSortDuplicates(left, j, byName); + left = i; + goto LABEL_QuickSortDuplicates; + } + else + { + QuickSortDuplicates(i, right, byName); + right = j; + goto LABEL_QuickSortDuplicates; + } + } + else + { + right = j; + goto LABEL_QuickSortDuplicates; + } + } + else + { + if (i < right) + { + left = i; + goto LABEL_QuickSortDuplicates; + } + } +} + +// comparator for displayed duplicates; if 'byName', sorting is primarily by name, otherwise by size +int CFoundFilesListView::CompareDuplicatesFunc(CFoundFilesData* f1, CFoundFilesData* f2, BOOL byName) +{ + int res; + if (byName) + { + // by name + res = RegSetStrICmp(f1->Name, f2->Name); + if (res == 0) + { + // by size + if (f1->Size < f2->Size) + res = -1; + else + { + if (f1->Size == f2->Size) + { + // by group + if (f1->Group < f2->Group) + res = -1; + else + { + if (f1->Group == f2->Group) + res = 0; + else + res = 1; + } + } + else + res = 1; + } + } + } + else + { + // by size + if (f1->Size < f2->Size) + res = -1; + else + { + if (f1->Size == f2->Size) + { + // by name + res = RegSetStrICmp(f1->Name, f2->Name); + if (res == 0) + { + // by group + if (f1->Group < f2->Group) + res = -1; + else + { + if (f1->Group == f2->Group) + res = 0; + else + res = 1; + } + } + } + else + res = 1; + } + } + if (res == 0) + res = RegSetStrICmp(f1->Path, f2->Path); + return res; +} + +// CUMDataFromFind and GetNextItemFromFind moved to find_dialog_ui.cpp +// (they are only called from CFindDialog methods) + +LRESULT +CFoundFilesListView::WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + SLOW_CALL_STACK_MESSAGE4("CFoundFilesListView::WindowProc(0x%X, 0x%IX, 0x%IX)", uMsg, wParam, lParam); + switch (uMsg) + { + case WM_GETDLGCODE: + { + if (lParam != NULL) + { + // if it is the Enter key, we want to process it, otherwise the Enter would not be delivered + MSG* msg = (LPMSG)lParam; + if (msg->message == WM_KEYDOWN && msg->wParam == VK_RETURN && + ListView_GetItemCount(HWindow) > 0) + return DLGC_WANTMESSAGE; + } + return DLGC_WANTCHARS | DLGC_WANTARROWS; + } + + case WM_SYSKEYDOWN: + case WM_KEYDOWN: + { + BOOL altPressed = (GetKeyState(VK_MENU) & 0x8000) != 0; + BOOL shiftPressed = (GetKeyState(VK_SHIFT) & 0x8000) != 0; + /* it seems this code is no longer needed; handled in the dialog wndproc + if (wParam == VK_RETURN) + { + if (altPressed) + { + FindDialog->OnProperties(); + FindDialog->SkipCharacter = TRUE; + } + else + FindDialog->OnOpen(); + return TRUE; + } +*/ + if ((wParam == VK_F10 && shiftPressed || wParam == VK_APPS)) + { + POINT p; + GetListViewContextMenuPos(HWindow, &p); + FindDialog->OnContextMenu(p.x, p.y); + return TRUE; + } + break; + } + + case WM_MOUSEACTIVATE: + { + // if Find is inactive and the user tries to drag & drop one of the items, the dialog must not pop up to the foreground + return MA_NOACTIVATE; + } + + case WM_SETFOCUS: + { + SendMessage(GetParent(HWindow), WM_USER_BUTTONS, 0, 0); + break; + } + + case WM_KILLFOCUS: + { + HWND next = (HWND)wParam; + BOOL nextIsButton; + if (next != NULL) + { + char className[30]; + WORD wl = LOWORD(GetWindowLongPtr(next, GWL_STYLE)); // only BS_ styles + nextIsButton = (GetClassName(next, className, 30) != 0 && + StrICmp(className, "BUTTON") == 0 && + (wl == BS_PUSHBUTTON || wl == BS_DEFPUSHBUTTON)); + } + else + nextIsButton = FALSE; + SendMessage(GetParent(HWindow), WM_USER_BUTTONS, nextIsButton ? wParam : 0, 0); + break; + } + + case WM_USER_ENUMFILENAMES: // searching for the next/previous name for the viewer + { + HANDLES(EnterCriticalSection(&FileNamesEnumDataSect)); + if ((int)wParam /* reqUID */ == FileNamesEnumData.RequestUID && // no further request was issued (this one would be pointless) + EnumFileNamesSourceUID == FileNamesEnumData.SrcUID && // the source hasn't changed + !FileNamesEnumData.TimedOut) // someone is still waiting for the result + { + HANDLES(EnterCriticalSection(&DataCriticalSection)); + + BOOL selExists = FALSE; + if (FileNamesEnumData.PreferSelected) // if needed, check whether there is a selection + { + int i = -1; + int selCount = 0; // ignore the state where the only marked item is the focused one (this cannot logically be considered as selected items) + while (1) + { + i = ListView_GetNextItem(HWindow, i, LVNI_SELECTED); + if (i == -1) + break; + else + { + selCount++; + if (!Data[i]->IsDir) + selExists = TRUE; + if (selCount > 1 && selExists) + break; + } + } + if (selExists && selCount <= 1) + selExists = FALSE; + } + + int index = FileNamesEnumData.LastFileIndex; + int count = Data.Count; + BOOL indexNotFound = TRUE; + if (index == -1) // searching from the first or last item + { + if (FileNamesEnumData.RequestType == fnertFindPrevious) + index = count; // looking for the previous item, start at the end + // else // looking for the next item, start at the beginning + } + else + { + if (FileNamesEnumData.LastFileName[0] != 0) // the full name at 'index' is known; check for shifts and search for a new index if needed + { + BOOL ok = FALSE; + CFoundFilesData* f = (index >= 0 && index < count) ? Data[index] : NULL; + char fileName[MAX_PATH]; + if (f != NULL && f->Path != NULL && f->Name != NULL) + { + lstrcpyn(fileName, f->Path, MAX_PATH); + SalPathAppend(fileName, f->Name, MAX_PATH); + if (StrICmp(fileName, FileNamesEnumData.LastFileName) == 0) + { + ok = TRUE; + indexNotFound = FALSE; + } + } + if (!ok) + { // the name at index 'index' isn't FileNamesEnumData.LastFileName, try to find a new index for that name + int i; + for (i = 0; i < count; i++) + { + f = Data[i]; + if (f->Path != NULL && f->Name != NULL) + { + lstrcpyn(fileName, f->Path, MAX_PATH); + SalPathAppend(fileName, f->Name, MAX_PATH); + if (StrICmp(fileName, FileNamesEnumData.LastFileName) == 0) + break; + } + } + if (i != count) // new index found + { + index = i; + indexNotFound = FALSE; + } + } + } + if (index >= count) + { + if (FileNamesEnumData.RequestType == fnertFindNext) + index = count - 1; + else + index = count; + } + if (index < 0) + index = 0; + } + + int wantedViewerType = 0; + BOOL onlyAssociatedExtensions = FALSE; + if (FileNamesEnumData.OnlyAssociatedExtensions) // does the viewer request filtering by associated extensions? + { + if (FileNamesEnumData.Plugin != NULL) // viewer from a plugin + { + int pluginIndex = Plugins.GetIndex(FileNamesEnumData.Plugin); + if (pluginIndex != -1) // "always true" + { + wantedViewerType = -1 - pluginIndex; + onlyAssociatedExtensions = TRUE; + } + } + else // internal viewer + { + wantedViewerType = VIEWER_INTERNAL; + onlyAssociatedExtensions = TRUE; + } + } + + BOOL preferSelected = selExists && FileNamesEnumData.PreferSelected; + switch (FileNamesEnumData.RequestType) + { + case fnertFindNext: // next + { + CDynString strViewerMasks; + if (MainWindow->GetViewersAssoc(wantedViewerType, &strViewerMasks)) + { + CMaskGroup masks; + int errorPos; + if (masks.PrepareMasks(errorPos, strViewerMasks.GetString())) + { + while (index + 1 < count) + { + index++; + if (preferSelected) + { + int i = ListView_GetNextItem(HWindow, index - 1, LVNI_SELECTED); + if (i != -1) + { + index = i; + if (!Data[index]->IsDir) // we only search for files + { + if (!onlyAssociatedExtensions || masks.AgreeMasks(Data[index]->Name, NULL)) + { + FileNamesEnumData.Found = TRUE; + break; + } + } + } + else + index = count - 1; + } + else + { + if (!Data[index]->IsDir) + { + if (!onlyAssociatedExtensions || masks.AgreeMasks(Data[index]->Name, NULL)) + { + FileNamesEnumData.Found = TRUE; + break; + } + } + } + } + } + else + TRACE_E("Unexpected situation in Find::WM_USER_ENUMFILENAMES: grouped viewer's masks can't be prepared for use!"); + } + break; + } + + case fnertFindPrevious: // previous + { + CDynString strViewerMasks; + if (MainWindow->GetViewersAssoc(wantedViewerType, &strViewerMasks)) + { + CMaskGroup masks; + int errorPos; + if (masks.PrepareMasks(errorPos, strViewerMasks.GetString())) + { + while (index - 1 >= 0) + { + index--; + if (!Data[index]->IsDir && + (!preferSelected || + (ListView_GetItemState(HWindow, index, LVIS_SELECTED) & LVIS_SELECTED))) + { + if (!onlyAssociatedExtensions || masks.AgreeMasks(Data[index]->Name, NULL)) + { + FileNamesEnumData.Found = TRUE; + break; + } + } + } + } + else + TRACE_E("Unexpected situation in Find::WM_USER_ENUMFILENAMES: grouped viewer's masks can't be prepared for use!"); + } + break; + } + + case fnertIsSelected: // check selection state + { + if (!indexNotFound && index >= 0 && index < Data.Count) + { + FileNamesEnumData.IsFileSelected = (ListView_GetItemState(HWindow, index, LVIS_SELECTED) & LVIS_SELECTED) != 0; + FileNamesEnumData.Found = TRUE; + } + break; + } + + case fnertSetSelection: // set selection + { + if (!indexNotFound && index >= 0 && index < Data.Count) + { + ListView_SetItemState(HWindow, index, FileNamesEnumData.Select ? LVIS_SELECTED : 0, LVIS_SELECTED); + FileNamesEnumData.Found = TRUE; + } + break; + } + } + + if (FileNamesEnumData.Found) + { + CFoundFilesData* f = Data[index]; + if (f->Path != NULL && f->Name != NULL) + { + lstrcpyn(FileNamesEnumData.FileName, f->Path, MAX_PATH); + SalPathAppend(FileNamesEnumData.FileName, f->Name, MAX_PATH); + FileNamesEnumData.LastFileIndex = index; + } + else // should never happen + { + TRACE_E("Unexpected situation in CFoundFilesListView::WindowProc(): handling of WM_USER_ENUMFILENAMES"); + FileNamesEnumData.Found = FALSE; + FileNamesEnumData.NoMoreFiles = TRUE; + } + } + else + FileNamesEnumData.NoMoreFiles = TRUE; + + HANDLES(LeaveCriticalSection(&DataCriticalSection)); + SetEvent(FileNamesEnumDone); + } + HANDLES(LeaveCriticalSection(&FileNamesEnumDataSect)); + return 0; + } + } + return CWindow::WindowProc(uMsg, wParam, lParam); +} + +BOOL CFoundFilesListView::InitColumns() +{ + CALL_STACK_MESSAGE1("CFoundFilesListView::InitColumns()"); + LV_COLUMN lvc; + int header[] = {IDS_FOUNDFILESCOLUMN1, IDS_FOUNDFILESCOLUMN2, + IDS_FOUNDFILESCOLUMN3, IDS_FOUNDFILESCOLUMN4, + IDS_FOUNDFILESCOLUMN5, IDS_FOUNDFILESCOLUMN6, + -1}; + + lvc.mask = LVCF_FMT | LVCF_TEXT | LVCF_SUBITEM; + lvc.fmt = LVCFMT_LEFT; + int i; + for (i = 0; header[i] != -1; i++) // create columns + { + if (i == 2) + lvc.fmt = LVCFMT_RIGHT; + lvc.pszText = LoadStr(header[i]); + lvc.iSubItem = i; + if (ListView_InsertColumn(HWindow, i, &lvc) == -1) + return FALSE; + } + + RECT r; + GetClientRect(HWindow, &r); + DWORD cx = r.right - r.left - 1; + ListView_SetColumnWidth(HWindow, 5, ListView_GetStringWidth(HWindow, "ARH") + 20); + + char format1[200]; + char format2[200]; + SYSTEMTIME st; + ZeroMemory(&st, sizeof(st)); + st.wYear = 2000; // the longest possible value + st.wMonth = 12; // the longest possible value + st.wDay = 30; // the longest possible value + st.wHour = 10; // morning (not sure whether AM or PM will be shorter, so try both) + st.wMinute = 59; // the longest possible value + st.wSecond = 59; // the longest possible value + if (GetTimeFormat(LOCALE_USER_DEFAULT, 0, &st, NULL, format1, 200) == 0) + sprintf(format1, "%u:%02u:%02u", st.wHour, st.wMinute, st.wSecond); + st.wHour = 20; // afternoon + if (GetTimeFormat(LOCALE_USER_DEFAULT, 0, &st, NULL, format2, 200) == 0) + sprintf(format2, "%u:%02u:%02u", st.wHour, st.wMinute, st.wSecond); + + int maxWidth = ListView_GetStringWidth(HWindow, format1); + int w = ListView_GetStringWidth(HWindow, format2); + if (w > maxWidth) + maxWidth = w; + ListView_SetColumnWidth(HWindow, 4, maxWidth + 20); + + maxWidth = 0; + if (GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &st, NULL, format1, 200) == 0) + sprintf(format1, "%u.%u.%u", st.wDay, st.wMonth, st.wYear); + else + { + // verify that the short date format does not contain alphabetic characters + const char* p = format1; + while (*p != 0 && !IsAlpha[*p]) + p++; + if (IsAlpha[*p]) + { + // contains alphabetic characters -- we must find the longest month and day text + int maxMonth = 0; + int sats[] = {1, 5, 4, 1, 6, 3, 1, 5, 2, 7, 4, 2}; + int mo; + for (mo = 0; mo < 12; mo++) // iterate over all months starting from January; the weekday stays the same so its width doesn't influence the result, wDay is single digit for the same reason + { + st.wDay = sats[mo]; + st.wMonth = 1 + mo; + if (GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &st, NULL, format1, 200) != 0) + { + w = ListView_GetStringWidth(HWindow, format1); + if (w > maxWidth) + { + maxWidth = w; + maxMonth = st.wMonth; + } + } + } + if (maxWidth > 0) + { + st.wMonth = maxMonth; + for (st.wDay = 21; st.wDay < 28; st.wDay++) // all possible weekdays (doesn't have to start on Monday) + { + if (GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &st, NULL, format1, 200) != 0) + { + w = ListView_GetStringWidth(HWindow, format1); + if (w > maxWidth) + { + maxWidth = w; + } + } + } + } + } + } + + ListView_SetColumnWidth(HWindow, 3, (maxWidth > 0 ? maxWidth : ListView_GetStringWidth(HWindow, format1)) + 20); + ListView_SetColumnWidth(HWindow, 2, ListView_GetStringWidth(HWindow, "000 000 000 000") + 20); // up to 1TB fits here + int width; + if (Configuration.FindColNameWidth != -1) + width = Configuration.FindColNameWidth; + else + width = 20 + ListView_GetStringWidth(HWindow, "XXXXXXXX.XXX") + 20; + ListView_SetColumnWidth(HWindow, 0, width); + cx -= ListView_GetColumnWidth(HWindow, 0) + ListView_GetColumnWidth(HWindow, 2) + + ListView_GetColumnWidth(HWindow, 3) + ListView_GetColumnWidth(HWindow, 4) + + ListView_GetColumnWidth(HWindow, 5) + GetSystemMetrics(SM_CXHSCROLL) - 1; + ListView_SetColumnWidth(HWindow, 1, cx); + ListView_SetImageList(HWindow, HFindSymbolsImageList, LVSIL_SMALL); + + return TRUE; +} diff --git a/src/gui.cpp b/src/gui.cpp index 67ddc93b..9e69731e 100644 --- a/src/gui.cpp +++ b/src/gui.cpp @@ -1,3993 +1,9 @@ -// SPDX-FileCopyrightText: 2023 Taskscape Ltd +// SPDX-FileCopyrightText: 2023 Taskscape Ltd // SPDX-License-Identifier: GPL-2.0-or-later // CommentsTranslationProject: TRANSLATED -#include "precomp.h" - -#include "svg.h" -#include "gui.h" -#include "toolbar.h" -#include "menu.h" -#include "tooltip.h" -#include -#include - -#include "nanosvg\nanosvg.h" -#include "nanosvg\nanosvgrast.h" - -#include "mainwnd.h" - -//**************************************************************************** -// -// CGuiBitmap -// - -// support for the silly drawing of the disabled button variant -class CGuiBitmap : public CBitmap -{ -public: - HBITMAP CreateCopyBitmap() - { - CALL_STACK_MESSAGE1("CSharedBitmapAndDC::CreateCopyBitmap()"); - HDC hdc = HANDLES(GetDC(NULL)); - - HBITMAP HCopyBitmap = HANDLES(CreateCompatibleBitmap(hdc, Width, Height)); - - HDC hMemDC = HANDLES(CreateCompatibleDC(hdc)); - HBITMAP hOldBitmap = (HBITMAP)SelectObject(hMemDC, HCopyBitmap); - - HBITMAP hOld = (HBITMAP)SelectObject(HMemDC, HOldBmp); - - HIMAGELIST hImageList = ImageList_Create(Width, Height, ILC_MASK | GetImageListColorFlags(), 1, 0); - ImageList_AddMasked(hImageList, HBmp, GetSysColor(COLOR_BTNFACE)); // j.r. the color was hardcoded to 192,192,192 here, which caused issues with the XP look - RECT r; - r.left = 0; - r.top = 0; - r.right = Width; - r.bottom = Height; - FillRect(hMemDC, &r, (HBRUSH)HANDLES(GetStockObject(WHITE_BRUSH))); - ImageList_Draw(hImageList, 0, hMemDC, 0, 0, ILD_TRANSPARENT); - ImageList_Destroy(hImageList); - - SelectObject(HMemDC, hOld); - - SelectObject(hMemDC, hOldBitmap); - HANDLES(DeleteDC(hMemDC)); - - HANDLES(ReleaseDC(NULL, hdc)); - return HCopyBitmap; - } -}; - -//**************************************************************************** -// -// CProgressBar -// - -CProgressBar::CProgressBar(HWND hDlg, int ctrlID) - : CWindow(hDlg, ctrlID, ooAllocated) -{ - if (HWindow != NULL) - { - RECT r; - GetClientRect(HWindow, &r); - Width = r.right - r.left; - Height = r.bottom - r.top; - } - else - { - Width = 10; - Height = 10; - } - - Progress = 0; - SelfMoveTime = 0xFFFFFFFF; // after calling SetProgress(-1) the rectangle will move indefinitely - SelfMoveTicks = 0; - SelfMoveSpeed = 50; // 20 moves per second - TimerIsRunning = FALSE; - Bitmap = new CBitmap(); - if (Bitmap != NULL) - { - HDC hDC = HANDLES(GetDC(NULL)); - if (!Bitmap->CreateBmp(hDC, Width, Height)) - { - delete Bitmap; - Bitmap = NULL; - } - HANDLES(ReleaseDC(NULL, hDC)); - } - Text = NULL; - - // get the default font from the dialog - HFont = (HFONT)SendMessage(hDlg, WM_GETFONT, 0, 0); - if (HFont == NULL) - HFont = (HFONT)GetStockObject(DEFAULT_GUI_FONT); // it uses the system font, so obtain it from the system -} - -CProgressBar::~CProgressBar() -{ - Stop(); - if (Bitmap != NULL) - delete (Bitmap); - if (Text != NULL) - free(Text); -} - -void CProgressBar::SetProgress(DWORD progress, const char* text) -{ - // use SendMessage instead of a direct call to cross the thread boundary - SendMessage(HWindow, WM_USER_SETPROGRESS, progress, (LPARAM)text); -} - -void CProgressBar::SetProgress2(const CQuadWord& progressCurrent, const CQuadWord& progressTotal, const char* text) -{ - // it can happen that progressTotal is 1 and progressCurrent is a large number, - // making the calculation meaningless (RTC also fails), so we must explicitly set 0% or 100% (value 1000) - SetProgress(progressCurrent >= progressTotal ? (progressTotal.Value == 0 ? 0 : 1000) : (DWORD)((progressCurrent * CQuadWord(1000, 0)) / progressTotal).Value, - text); -} - -void CProgressBar::SetSelfMoveTime(DWORD time) -{ - SelfMoveTime = time; -} - -void CProgressBar::SetSelfMoveSpeed(DWORD moveTime) -{ - SelfMoveSpeed = moveTime; - if (TimerIsRunning) - { - KillTimer(HWindow, IDT_PROGRESSSELFMOVE); - SetTimer(HWindow, IDT_PROGRESSSELFMOVE, SelfMoveSpeed, NULL); - } -} - -void CProgressBar::Stop() -{ - if (TimerIsRunning) - { - KillTimer(HWindow, IDT_PROGRESSSELFMOVE); - TimerIsRunning = FALSE; - } -} - -void CProgressBar::Paint(HDC hDC) -{ - BOOL releaseDC = FALSE; - if (hDC == NULL) - { - hDC = HANDLES(GetDC(HWindow)); - releaseDC = TRUE; - } - - // if we have a bitmap, use the cache - HDC hMemDC = NULL; - if (Bitmap != NULL && Progress != -1) // caching Progress==-1 is pointless, there's nothing to flicker - hMemDC = Bitmap->HMemDC; - else - hMemDC = hDC; - - if (Progress == -1) - { - // indeterminate mode: white, blue rectangle, white - RECT r; - r.top = 1; - r.bottom = Height - 1; - - int mid = BarX + 1; - int midW = Height * 2; - - SelectObject(hMemDC, HFont); - - COLORREF oldBkColor = SetBkColor(hMemDC, GetCOLORREF(CurrentColors[PROGRESS_BK_NORMAL])); - - r.left = 1; - r.right = mid - midW; - if (r.left < 1) - r.left = 1; - if (r.left > Width - 1) - r.left = Width - 1; - if (r.right < 1) - r.right = 1; - if (r.right > Width - 1) - r.right = Width - 1; - ExtTextOut(hMemDC, 0, 0, ETO_OPAQUE, &r, "", 0, NULL); - - SetBkColor(hMemDC, GetCOLORREF(CurrentColors[PROGRESS_BK_SELECTED])); - r.left = 1 + mid - midW; - r.right = mid + midW; - if (r.left < 1) - r.left = 1; - if (r.left > Width - 1) - r.left = Width - 1; - if (r.right < 1) - r.right = 1; - if (r.right > Width - 1) - r.right = Width - 1; - ExtTextOut(hMemDC, 0, 0, ETO_OPAQUE, &r, "", 0, NULL); - - SetBkColor(hMemDC, GetCOLORREF(CurrentColors[PROGRESS_BK_NORMAL])); - r.left = 1 + mid + midW; - r.right = Width - 1; - if (r.left < 1) - r.left = 1; - if (r.left > Width - 1) - r.left = Width - 1; - if (r.right < 1) - r.right = 1; - if (r.right > Width - 1) - r.right = Width - 1; - ExtTextOut(hMemDC, 0, 0, ETO_OPAQUE, &r, "", 0, NULL); - - SetBkColor(hMemDC, oldBkColor); - } - else - { - // prepare and measure the string - char buff[50]; - - char* progress; - int progressLen; - - if (Text != NULL) - { - progress = Text; - progressLen = (int)strlen(progress); - } - else - { - progress = buff; - progressLen = sprintf(progress, "%d %%", (int)((Progress /*+ 5*/) / 10)); // we do not round the progress, beacause otherwise 100% is visible from 99.5%-100%, which annoys some users (notable with FTP, where it can last half a minute) - } - - SIZE sz; - GetTextExtentPoint32(hMemDC, progress, progressLen, &sz); - - // text position -- centered in both axes - int x = (Width - sz.cx) / 2; - int y = (Height - sz.cy) / 2; - - // left part of the progress (SELECTED) - RECT r; - r.left = 1; - r.right = 1 + (Width - 2) * Progress / 1000; - r.top = 1; - r.bottom = Height - 1; - - SelectObject(hMemDC, HFont); - - COLORREF oldTextColor = SetTextColor(hMemDC, GetCOLORREF(CurrentColors[PROGRESS_FG_SELECTED])); - COLORREF oldBkColor = SetBkColor(hMemDC, GetCOLORREF(CurrentColors[PROGRESS_BK_SELECTED])); - ExtTextOut(hMemDC, x, y, ETO_OPAQUE | ETO_CLIPPED, &r, progress, progressLen, NULL); - - // right part of the progress (NORMAL) - r.left = r.right; - r.right = Width - 1; - - SetTextColor(hMemDC, GetCOLORREF(CurrentColors[PROGRESS_FG_NORMAL])); - SetBkColor(hMemDC, GetCOLORREF(CurrentColors[PROGRESS_BK_NORMAL])); - ExtTextOut(hMemDC, x, y, ETO_OPAQUE | ETO_CLIPPED, &r, progress, progressLen, NULL); - SetTextColor(hMemDC, oldTextColor); - SetBkColor(hMemDC, oldBkColor); - } - - HPEN hOldPen = (HPEN)SelectObject(hMemDC, BtnShadowPen); - MoveToEx(hMemDC, 0, 0, NULL); - LineTo(hMemDC, Width - 1, 0); - LineTo(hMemDC, Width - 1, Height - 1); - LineTo(hMemDC, 0, Height - 1); - LineTo(hMemDC, 0, 0); - SelectObject(hMemDC, hOldPen); - - // if drawing through the cache, copy it to the screen - if (Bitmap != NULL && hMemDC != hDC) - BitBlt(hDC, 0, 0, Width, Height, hMemDC, 0, 0, SRCCOPY); - - if (releaseDC) - HANDLES(ReleaseDC(HWindow, hDC)); -} - -void CProgressBar::MoveBar() -{ - if (Progress != -1) - { - // start the movement - Progress = -1; - BarX = 0; - MoveBarRight = TRUE; - } - else - { - if (MoveBarRight) - { - BarX += 4; - if (BarX > Width - 2) - { - BarX = Width - 2; - MoveBarRight = !MoveBarRight; - } - } - else - { - BarX -= 4; - if (BarX < 0) - { - BarX = 0; - MoveBarRight = !MoveBarRight; - } - } - } -} - -LRESULT -CProgressBar::WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - CALL_STACK_MESSAGE4("CProgressBar::WindowProc(0x%X, 0x%IX, 0x%IX)", uMsg, wParam, lParam); - switch (uMsg) - { - case WM_TIMER: - { - if (wParam == IDT_PROGRESSSELFMOVE) - { - if ((SelfMoveTime != 0xFFFFFFFF) && (GetTickCount() - SelfMoveTicks > SelfMoveTime)) - { - KillTimer(HWindow, IDT_PROGRESSSELFMOVE); - TimerIsRunning = FALSE; - } - else - { - MoveBar(); - Paint(NULL); - } - return 0; - } - break; - } - - case WM_USER_SETPROGRESS: - { - DWORD progress = (DWORD)wParam; - const char* text = (const char*)lParam; - - BOOL paint = TRUE; - BOOL textChanged = FALSE; - - if ((text != NULL || Text != NULL) && - (text == NULL || Text == NULL || strcmp(text, Text) != 0)) - { - textChanged = TRUE; - if (Text != NULL) - { - free(Text); - Text = NULL; - } - if (text != NULL) - { - Text = DupStr(text); - if (Text == NULL) - TRACE_E(LOW_MEMORY); - } - } - - if (progress == (DWORD)-1) - { - if (SelfMoveTime > 0) - { - SelfMoveTicks = GetTickCount(); - if (!TimerIsRunning) - { - SetTimer(HWindow, IDT_PROGRESSSELFMOVE, SelfMoveSpeed, NULL); - TimerIsRunning = TRUE; - MoveBar(); - } - else - paint = FALSE; // the change will occur on the timer, no reason to redraw now - } - else - MoveBar(); - } - else - { - if (TimerIsRunning) - Stop(); - if (progress > 1000) - progress = 1000; // max. 100% (a copy of the "active" file may report progress >100%) - if (progress != Progress) - Progress = progress; - else - paint = textChanged; // progress didn't change; if the text didn't change either, redrawing is skipped - /* - BOOL redraw = Progress == 0 || // always show 0% - Progress == 1000 || // always show 100% - Progress - DisplayedProgress >= 100; // always show a change greater than 10% - if (redraw && Progress != DisplayedProgress) - Paint(NULL); - */ - } - if (paint) - Paint(NULL); - return 0; - } - - case WM_SIZE: - { - Width = LOWORD(lParam); - Height = HIWORD(lParam); - Bitmap->Enlarge(Width, Height); - return 0; - } - - case WM_ERASEBKGND: - { - return TRUE; - } - - case WM_PAINT: - { - PAINTSTRUCT ps; - HDC hDC = HANDLES(BeginPaint(HWindow, &ps)); - Paint(hDC); - HANDLES(EndPaint(HWindow, &ps)); - return 0; - } - } - - return CWindow::WindowProc(uMsg, wParam, lParam); -} - -//**************************************************************************** -// -// CStaticText -// - -CStaticText::CStaticText(HWND hDlg, int ctrlID, DWORD flags) - : CWindow(hDlg, ctrlID, ooAllocated) -{ - if ((flags & STF_HANDLEPREFIX) && ((flags & STF_END_ELLIPSIS) || (flags & STF_PATH_ELLIPSIS))) - { - TRACE_E("Flag STF_HANDLEPREFIX cannot be used with STF_END_ELLIPSIS or STF_PATH_ELLIPSIS."); - flags &= ~STF_HANDLEPREFIX; - } - - Flags = flags; - Text = NULL; - TextLen = 0; - TextW = NULL; - TextLenW = 0; - Text2 = NULL; - Text2Len = 0; - Text2W = NULL; - Text2LenW = 0; - AlpDX = NULL; - Allocated = 0; - AllocatedW = 0; - Bitmap = NULL; - HFont = NULL; - DestroyFont = FALSE; - ClipDraw = FALSE; - Text2Draw = FALSE; - Alignment = 0; // left - PathSeparator = '\\'; - MouseIsTracked = FALSE; - ToolTipText = NULL; - HToolTipNW = NULL; - ToolTipID = 0; - HintMode = FALSE; - - if (HWindow == NULL) - return; // avoid flickering the screen - - UIState = (WORD)SendMessage(HWindow, WM_QUERYUISTATE, 0, 0); - - // get the alignment - DWORD style = (DWORD)GetWindowLongPtr(HWindow, GWL_STYLE); - if (style & SS_RIGHT) - Alignment = 2; - else if (style & SS_CENTER) - Alignment = 1; - - // measure the maximum size of the static - RECT r; - GetClientRect(HWindow, &r); - Width = r.right - r.left; - Height = r.bottom - r.top; - - // if we should draw via cache, we create a bitmap - if (Flags & STF_CACHED_PAINT) - { - Bitmap = new CBitmap(); // if allocation fails, paint won't be cached - if (Bitmap != NULL) - { - HDC hDC = HANDLES(GetDC(HWindow)); - if (!Bitmap->CreateBmp(hDC, Width, Height)) - { - delete Bitmap; - Bitmap = NULL; - } - HANDLES(ReleaseDC(HWindow, hDC)); - } - } - - // obtain the default font from the static - HFont = (HFONT)SendMessage(HWindow, WM_GETFONT, 0, 0); - if ((Flags & STF_BOLD) || (Flags & STF_UNDERLINE)) - { - // if the text is BOLD or UNDERLINE, prepare our own font - LOGFONT lf; - GetObject(HFont, sizeof(lf), &lf); - if (Flags & STF_BOLD) - lf.lfWeight = FW_BOLD; - if (Flags & STF_UNDERLINE) - lf.lfUnderline = TRUE; - HFont = HANDLES(CreateFontIndirect(&lf)); - DestroyFont = TRUE; - } - - // obtain the initial text of the static - char buff[4096]; - CWindow::WindowProc(WM_GETTEXT, 4096, (LPARAM)buff); - buff[4095] = 0; // just to be sure... - if (buff[0] != 0) - SetText(buff); -} - -CStaticText::~CStaticText() -{ - if (ToolTipText != NULL) - free(ToolTipText); - if (Text != NULL) - free(Text); - if (TextW != NULL) - free(TextW); - if (Text2 != NULL) - free(Text2); - if (Text2W != NULL) - free(Text2W); - if (AlpDX != NULL) - free(AlpDX); - if (Bitmap != NULL) - delete Bitmap; - if (HFont != NULL && DestroyFont) - HANDLES(DeleteObject(HFont)); -} - -// prevents numerous reallocations when gradually allocating larger and larger strings -#define ST_ALLOC_GRANULARITY 20 - -BOOL CStaticText::SetText(const char* text) -{ - CALL_STACK_MESSAGE2("CStaticText::SetText(%s)", text); - - if (text == NULL) - text = ""; - if (Text != NULL && strcmp(Text, text) == 0) - return TRUE; - - int l = (int)strlen(text) + 1; - if (Allocated < l) - { - char* newText = (char*)realloc(Text, l + ST_ALLOC_GRANULARITY); - if (newText == NULL) - { - TRACE_E(LOW_MEMORY); - return FALSE; - } - if (Flags & (STF_PATH_ELLIPSIS | STF_END_ELLIPSIS)) - { - int* newAlpDX = (int*)realloc(AlpDX, (l + ST_ALLOC_GRANULARITY) * sizeof(int)); - if (newAlpDX == NULL) - { - TRACE_E(LOW_MEMORY); - free(newText); - return FALSE; - } - char* newText2 = (char*)realloc(Text2, l + ST_ALLOC_GRANULARITY + 3); // 3: space for "..." (I can remove W and add "...") - if (newText2 == NULL) - { - TRACE_E(LOW_MEMORY); - free(newText); - free(newAlpDX); - return FALSE; - } - AlpDX = newAlpDX; - Text2 = newText2; - } - Text = newText; - Allocated = l + ST_ALLOC_GRANULARITY; - } - memmove(Text, text, l); - TextLen = l - 1; - - // Convert UTF-8 to wide characters for proper Unicode display - int wideLen = MultiByteToWideChar(CP_UTF8, 0, text, -1, NULL, 0); - if (wideLen > 0) - { - if (AllocatedW < wideLen) - { - wchar_t* newTextW = (wchar_t*)realloc(TextW, (wideLen + ST_ALLOC_GRANULARITY) * sizeof(wchar_t)); - if (newTextW == NULL) - { - TRACE_E(LOW_MEMORY); - // Continue without wide text - will fall back to ANSI display - } - else - { - TextW = newTextW; - AllocatedW = wideLen + ST_ALLOC_GRANULARITY; - } - if (Flags & (STF_PATH_ELLIPSIS | STF_END_ELLIPSIS)) - { - wchar_t* newText2W = (wchar_t*)realloc(Text2W, (wideLen + ST_ALLOC_GRANULARITY + 3) * sizeof(wchar_t)); - if (newText2W != NULL) - Text2W = newText2W; - } - } - if (TextW != NULL) - { - MultiByteToWideChar(CP_UTF8, 0, text, -1, TextW, AllocatedW); - TextLenW = wideLen - 1; - } - } - - PrepareForPaint(); - - InvalidateRect(HWindow, NULL, FALSE); - UpdateWindow(HWindow); - return TRUE; -} - -BOOL CStaticText::SetTextToDblQuotesIfNeeded(const char* text) -{ - CALL_STACK_MESSAGE2("CStaticText::SetTextToDblQuotesIfNeeded(%s)", text); - - if (text != NULL) - { - int len = (int)strlen(text); - if (len > 0 && (text[0] <= ' ' || text[len - 1] <= ' ') && len < 2 * MAX_PATH) - { - char buf[2 * MAX_PATH + 2]; - sprintf(buf, "\"%s\"", text); // spaces at the beginning and end will be visible in quotes (otherwise they are invisible) - return SetText(buf); - } - } - return SetText(text); -} - -void CStaticText::PrepareForPaint() -{ - ClipDraw = FALSE; - Text2Draw = FALSE; - - if (Text == NULL || TextLen == 0) // the algorithm is designed only for a non-zero number of characters - { - TextWidth = 0; - TextHeight = 0; - return; - } - - HDC hDC = HANDLES(GetDC(HWindow)); - HFONT hOldFont = (HFONT)SelectObject(hDC, HFont); - SIZE sz; - - // Use wide character APIs for proper Unicode support - BOOL useWide = (TextW != NULL && TextLenW > 0); - - if (Flags & (STF_PATH_ELLIPSIS | STF_END_ELLIPSIS)) - { - if (Flags & STF_END_ELLIPSIS) - { - // STF_END_ELLIPSIS: the string will end with an ellipsis - // we need lengths only for the characters that fit - int fitChars; - if (useWide) - GetTextExtentExPointW(hDC, TextW, TextLenW, Width, &fitChars, AlpDX, &sz); - else - GetTextExtentExPoint(hDC, Text, TextLen, Width, &fitChars, AlpDX, &sz); - - int textLen = useWide ? TextLenW : TextLen; - - if (fitChars < textLen) - { - //we it did not fit -- we must insert an ellipsis - - // we get the width of "..." for the ellipsis - SIZE ellipsisSZ; - GetTextExtentPoint32W(hDC, L"...", 3, &ellipsisSZ); - int ellipsisWidth = ellipsisSZ.cx; - - // we search from the right end to find how much to trim so we can append the ellipsis - while (fitChars > 0 && AlpDX[fitChars - 1] + ellipsisWidth > Width) - fitChars--; - if (fitChars > 0) - { - if (useWide && Text2W != NULL) - { - memmove(Text2W, TextW, fitChars * sizeof(wchar_t)); - Text2LenW = fitChars; - } - memmove(Text2, Text, fitChars); - TextWidth = AlpDX[fitChars - 1]; - Text2Len = fitChars; - } - else - { - TextWidth = 0; - Text2Len = 0; - Text2LenW = 0; - } - strcpy(Text2 + fitChars, "..."); - if (useWide && Text2W != NULL) - wcscpy(Text2W + fitChars, L"..."); - TextWidth += ellipsisWidth; - Text2Len += 3; - Text2LenW = Text2Len; - - Text2Draw = TRUE; - } - else - { - TextWidth = sz.cx; - } - } - else - { - // STF_PATH_ELLIPSIS: the ellipsis will be inside the text - // we need lengths of all substrings - if (useWide) - GetTextExtentExPointW(hDC, TextW, TextLenW, 0, NULL, AlpDX, &sz); - else - GetTextExtentExPoint(hDC, Text, TextLen, 0, NULL, AlpDX, &sz); - - int textLen = useWide ? TextLenW : TextLen; - - if (sz.cx > Width) - { - // we did not fit -- we must insert an ellipsis - - // get the width of "..." for the ellipsis - SIZE ellipsisSZ; - GetTextExtentPoint32W(hDC, L"...", 3, &ellipsisSZ); - int ellipsisWidth = ellipsisSZ.cx; - - // search from the right end for the path separator - int pIndex; - if (useWide) - { - const wchar_t* p = TextW + TextLenW - 1; - wchar_t pathSepW = (wchar_t)PathSeparator; - while (*p != pathSepW && p > TextW) - p--; - const wchar_t* p2 = p; - if (p > TextW) - p--; - pIndex = (int)(p - TextW); - - // the text from 'p' and further should fit entirely including the ellipsis - if (ellipsisWidth + sz.cx - AlpDX[pIndex] > Width) - { - // it did not fit =>we search from the left end for a place to insert the ellipsis - while (pIndex < TextLenW && (ellipsisWidth + sz.cx - AlpDX[pIndex] > Width)) - pIndex++; - - // we insert the ellipsis and then the rest of the text behind it - pIndex++; - if (Text2W != NULL) - wcscpy(Text2W, L"..."); - strcpy(Text2, "..."); - Text2Len = 3; - Text2LenW = 3; - TextWidth = ellipsisWidth; - if (pIndex < TextLenW) - { - if (Text2W != NULL) - { - memmove(Text2W + 3, TextW + pIndex, (TextLenW - pIndex + 1) * sizeof(wchar_t)); - Text2LenW += TextLenW - pIndex; - } - memmove(Text2 + 3, Text + pIndex, TextLen - pIndex + 1); - Text2Len += TextLen - pIndex; - TextWidth += sz.cx - AlpDX[pIndex - 1]; - } - } - else - { - int rightPartWidth = sz.cx - AlpDX[pIndex]; - // we determine how many characters to keep on the left side of the ellipsis - while (pIndex >= 0 && (AlpDX[pIndex] + ellipsisWidth + rightPartWidth) > Width) - pIndex--; - // left part - Text2Len = 0; - Text2LenW = 0; - TextWidth = 0; - if (pIndex >= 0) - { - if (Text2W != NULL) - { - memmove(Text2W, TextW, (pIndex + 1) * sizeof(wchar_t)); - Text2LenW += pIndex + 1; - } - memmove(Text2, Text, pIndex + 1); - Text2Len += pIndex + 1; - TextWidth += AlpDX[pIndex]; - } - // ellipsis - if (Text2W != NULL) - { - memmove(Text2W + Text2LenW, L"...", 3 * sizeof(wchar_t)); - Text2LenW += 3; - } - memmove(Text2 + Text2Len, "...", 3); - Text2Len += 3; - TextWidth += ellipsisWidth; - // right part - int rightPartLen = TextLenW - (int)(p2 - TextW); - if (Text2W != NULL) - { - memmove(Text2W + Text2LenW, p2, (rightPartLen + 1) * sizeof(wchar_t)); - Text2LenW += rightPartLen; - } - int rightPartLenA = TextLen - (int)((Text + TextLen) - (Text + pIndex + 1 + (p2 - (TextW + pIndex + 1)))); - // Approximate - use same ratio - rightPartLenA = TextLen - pIndex - 1; - const char* p2A = Text + TextLen - rightPartLen; - memmove(Text2 + Text2Len, p2A, rightPartLen + 1); - Text2Len += rightPartLen; - TextWidth += rightPartWidth; - } - } - else - { - // ANSI fallback - const char* p = Text + TextLen - 1; - while (*p != PathSeparator && p > Text) - p--; - const char* p2 = p; - if (p > Text) - p--; - pIndex = (int)(p - Text); - - // the text from 'p' and further should fit entirely including the ellipsis - if (ellipsisWidth + sz.cx - AlpDX[pIndex] > Width) - { - // it did not fit =>we search from the left end for a place to insert the ellipsis - while (pIndex < TextLen && (ellipsisWidth + sz.cx - AlpDX[pIndex] > Width)) - pIndex++; - - // we insert the ellipsis and then the rest of the text behind it - pIndex++; - strcpy(Text2, "..."); - Text2Len = 3; - TextWidth = ellipsisWidth; - if (pIndex < TextLen) - { - memmove(Text2 + 3, Text + pIndex, TextLen - pIndex + 1); // including the terminator - Text2Len += TextLen - pIndex; - TextWidth += sz.cx - AlpDX[pIndex - 1]; - } - } - else - { - int rightPartWidth = sz.cx - AlpDX[pIndex]; - // we determine how many characters to keep on the left side of the ellipsis - while (pIndex >= 0 && (AlpDX[pIndex] + ellipsisWidth + rightPartWidth) > Width) - pIndex--; - // left part - Text2Len = 0; - TextWidth = 0; - if (pIndex >= 0) - { - memmove(Text2, Text, pIndex + 1); - Text2Len += pIndex + 1; - TextWidth += AlpDX[pIndex]; - } - // ellipsis - memmove(Text2 + Text2Len, "...", 3); - Text2Len += 3; - TextWidth += ellipsisWidth; - // right part - int rightPartLen = TextLen - (int)(p2 - Text); - memmove(Text2 + Text2Len, p2, rightPartLen + 1); - Text2Len += rightPartLen; - TextWidth += rightPartWidth; - } - } - - Text2Draw = TRUE; - } - else - { - TextWidth = sz.cx; - } - } - TextHeight = sz.cy; - } - else - { - // the overall dimensions are sufficient - if (Flags & STF_HANDLEPREFIX) - { - RECT r; - GetClientRect(HWindow, &r); - if (useWide) - DrawTextW(hDC, TextW, TextLenW, &r, DT_CALCRECT | DT_SINGLELINE | DT_LEFT); - else - DrawText(hDC, Text, TextLen, &r, DT_CALCRECT | DT_SINGLELINE | DT_LEFT); - TextWidth = r.right; - TextHeight = r.bottom; - } - else - { - if (useWide) - GetTextExtentPoint32W(hDC, TextW, TextLenW, &sz); - else - GetTextExtentPoint32(hDC, Text, TextLen, &sz); - TextWidth = sz.cx + 1; - TextHeight = sz.cy; - } - } - // if the text would cross the window boundary, we must clip during drawing - if (TextWidth > Width) - { - TextWidth = Width; - ClipDraw = TRUE; - } - if (TextHeight > Height) - { - TextHeight = Height; - ClipDraw = TRUE; - } - SelectObject(hDC, hOldFont); - HANDLES(ReleaseDC(HWindow, hDC)); -} - -void CStaticText::SetPathSeparator(char separator) -{ - if (separator == 0) - TRACE_E("CStaticText::SetPathSeparator == 0"); - else - { - if (separator != PathSeparator) - { - PathSeparator = separator; - InvalidateRect(HWindow, NULL, FALSE); - PrepareForPaint(); - } - } -} - -int CStaticText::GetTextXOffset() -{ - int xOffset = 0; // SS_LEFT - if (Alignment == 1) - xOffset = (Width - TextWidth) / 2; // SS_CENTER - else if (Alignment == 2) - xOffset = Width - TextWidth; // SS_RIGHT - return xOffset; -} - -BOOL CStaticText::TextHitTest(POINT* screenCursorPos) -{ - POINT p = *screenCursorPos; - ScreenToClient(HWindow, &p); - - int xOffset = GetTextXOffset(); - - RECT r; - r.left = xOffset; - r.top = 0; - r.right = xOffset + TextWidth; - r.bottom = TextHeight; - - return PtInRect(&r, p); -} - -BOOL CStaticText::SetToolTipText(const char* text) -{ - if (text != NULL && ToolTipText != NULL && strcmp(ToolTipText, text) == 0) - return TRUE; - - if (text == NULL) - { - if (ToolTipText != NULL) - free(ToolTipText); - ToolTipText = NULL; - HToolTipNW = NULL; - ToolTipID = 0; - return TRUE; - } - - char* newText = DupStr(text); - if (newText == NULL) - return FALSE; - - if (ToolTipText != NULL) - free(ToolTipText); - - ToolTipText = newText; - HToolTipNW = NULL; - ToolTipID = 0; - - PostMessage(MainWindow->ToolTip->HWindow, WM_USER_REFRESHTOOLTIP, 0, 0); // ask the window to load the new text and redraw - - return TRUE; -} - -void CStaticText::SetToolTip(HWND hNotifyWindow, DWORD id) -{ - if (ToolTipText != NULL) - free(ToolTipText); - ToolTipText = NULL; - - HToolTipNW = hNotifyWindow; - ToolTipID = id; -} - -void CStaticText::EnableHintToolTip(BOOL enable) -{ - HintMode = enable; -} - -BOOL CStaticText::ToolTipAssigned() -{ - return ToolTipText != NULL || HToolTipNW != NULL; -} - -void CStaticText::DrawFocus(HDC hDC) -{ - BOOL releaseDC = FALSE; - if (hDC == NULL) - { - hDC = HANDLES(GetDC(HWindow)); - releaseDC = TRUE; - } - - int xOffset = GetTextXOffset(); - - RECT r; - r.left = xOffset; - r.top = 0; - r.right = xOffset + TextWidth; - r.bottom = TextHeight; - - int oldColor = SetTextColor(hDC, GetSysColor(COLOR_BTNFACE)); - int oldBkColor = SetBkColor(hDC, GetSysColor(COLOR_BTNTEXT)); - POINT oldBrushPoint; - SetBrushOrgEx(hDC, 0, 0, &oldBrushPoint); // under XP with the Normal skin the paint misbehaved if the static was placed on a gradient background (FTP configuration) - DrawFocusRect(hDC, &r); - SetBrushOrgEx(hDC, oldBrushPoint.x, oldBrushPoint.y, NULL); - SetTextColor(hDC, oldColor); - SetBkColor(hDC, oldBkColor); - - if (releaseDC) - HANDLES(ReleaseDC(HWindow, hDC)); -} - -BOOL CStaticText::ShowHint() -{ - SetCurrentToolTip(NULL, 0); - - RECT r; - GetWindowRect(HWindow, &r); - int xOffset = GetTextXOffset(); - - MainWindow->ToolTip->SetCurrentToolTip(HWindow, 1, -1); - MainWindow->ToolTip->Show(r.left + xOffset, r.bottom, FALSE, TRUE, HWindow); - // note: Show has the parameter 'modal'==TRUE, so control returns here only after the tooltip is closed - return TRUE; -} - -LRESULT -CStaticText::WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - SLOW_CALL_STACK_MESSAGE4("CStaticText::WindowProc(0x%X, 0x%IX, 0x%IX)", uMsg, wParam, lParam); - switch (uMsg) - { - case WM_SIZE: - { - Width = LOWORD(lParam); - Height = HIWORD(lParam); - if (Bitmap != NULL) - { - if (!Bitmap->Enlarge(Width, Height)) - { - delete Bitmap; - Bitmap = NULL; - } - } - InvalidateRect(HWindow, NULL, FALSE); - PrepareForPaint(); - return 0; - } - - case WM_ERASEBKGND: - { - // background will erased in paint - return TRUE; - } - - case WM_ENABLE: - { - InvalidateRect(HWindow, NULL, FALSE); - PrepareForPaint(); - return 0; - } - - case WM_MOUSEMOVE: - { - if (ToolTipAssigned()) - { - POINT p; - DWORD messagePos = GetMessagePos(); - p.x = GET_X_LPARAM(messagePos); - p.y = GET_Y_LPARAM(messagePos); - if (TextHitTest(&p)) - { - if (ToolTipText != NULL) - SetCurrentToolTip(HWindow, 1); - else if (HToolTipNW != NULL) - SetCurrentToolTip(HWindow, ToolTipID); - } - else - SetCurrentToolTip(NULL, 0); - - if (!MouseIsTracked) - { - TRACKMOUSEEVENT tme; - tme.cbSize = sizeof(tme); - tme.dwFlags = TME_LEAVE; - tme.hwndTrack = HWindow; - MouseIsTracked = TrackMouseEvent(&tme); - } - } - break; - } - - case WM_SHOWWINDOW: - { - if (wParam == TRUE) - break; - // if someone hides us, we must dismiss the tooltip - if (MainWindow != NULL && MainWindow->ToolTip != NULL && MainWindow->ToolTip->HWindow != NULL) - MainWindow->ToolTip->Hide(); - //PostMessage(MainWindow->ToolTip->HWindow, WM_CANCELMODE, 0, 0); - } // fall through to WM_MOUSELEAVE - case WM_MOUSELEAVE: - { - if (ToolTipAssigned()) - { - SetCurrentToolTip(NULL, 0); - MouseIsTracked = FALSE; - } - break; - } - - case WM_USER_TTGETTEXT: - { - if (ToolTipText != NULL) - lstrcpyn((char*)lParam, ToolTipText, TOOLTIP_TEXT_MAX); - return 0; - } - - case WM_SETTEXT: - { - return SetText((char*)lParam); - } - - case WM_GETTEXT: - { - if (Text == NULL || wParam < 2) - return 0; - - int len = (int)strlen(Text); - if (len > (int)wParam - 1) - len = (int)wParam - 1; - memcpy((char*)lParam, Text, len); - ((char*)lParam)[len + 1] = 0; - return len; - } - - case WM_GETDLGCODE: - { - LRESULT ret = DLGC_STATIC; - if (HintMode) - ret |= DLGC_WANTARROWS; - return ret; - } - - case WM_SETFOCUS: - case WM_KILLFOCUS: - { - if (GetWindowLongPtr(HWindow, GWL_STYLE) & WS_TABSTOP) - { - DrawFocus(NULL); - } - break; - } - - case WM_LBUTTONDOWN: - { - if (HintMode) - ShowHint(); - break; - } - - case WM_KEYDOWN: - { - if (HintMode && (wParam == VK_SPACE || wParam == VK_UP || wParam == VK_DOWN)) - ShowHint(); - break; - } - - case WM_PAINT: - { - PAINTSTRUCT ps; - HANDLES(BeginPaint(HWindow, &ps)); - - // if we have a bitmap,we will draw into it, otherwise directly to the screen - HDC hDC; - if (Bitmap != NULL) - hDC = Bitmap->HMemDC; - else - hDC = ps.hdc; - - RECT r; - r.left = 0; - r.top = 0; - r.right = Width; - r.bottom = Height; - - // display our own text - if (Text != NULL) - { - // under XPTheme we have to let Windows erase the background - BOOL bkErased = FALSE; - if (IsAppThemed()) - { - DrawThemeParentBackground(HWindow, hDC, &r); - bkErased = TRUE; - } - - // set the DC parameters and store their original values - int oldBkMode = SetBkMode(hDC, TRANSPARENT); - - HWND hParent = GetParent(HWindow); - if (hParent != NULL) - SendMessage(hParent, WM_CTLCOLORSTATIC, (WPARAM)hDC, (LPARAM)HWindow); - if (Flags & STF_HYPERLINK_COLOR) - SetTextColor(hDC, RGB(0, 0, 255)); - BOOL enabled = IsWindowEnabled(HWindow); - if (!enabled) - SetTextColor(hDC, GetSysColor(COLOR_GRAYTEXT)); - - // COLORREF textClr; - // if (Flags & STF_HYPERLINK_COLOR) - // textClr = RGB(0, 0, 255); - // else - // textClr = GetSysColor(COLOR_BTNTEXT); - // COLORREF oldTextColor = SetTextColor(hDC, textClr); - // COLORREF oldBkColor = SetBkColor(hDC, GetSysColor(COLOR_BTNFACE)); - HFONT hOldFont = (HFONT)SelectObject(hDC, HFont); - - // we draw the text - // Use wide character APIs for proper Unicode support - BOOL useWide = (TextW != NULL && TextLenW > 0); - - if (Flags & STF_HANDLEPREFIX) - { - DWORD drawFlags = DT_SINGLELINE | DT_TOP; - if (Alignment == 1) - drawFlags |= DT_CENTER; - else if (Alignment == 2) - drawFlags |= DT_RIGHT; - else - drawFlags |= DT_LEFT; - // because ClearType spills beyond the control and leaves stray colored dots, we must - // clip everything; the issue is visible in the Plugins Manager Salamander 2.51 when scrolling - // the plugin list, leaving a red dot before the URL - // if (!ClipDraw) - drawFlags |= DT_NOCLIP; - - if (UIState & UISF_HIDEACCEL) - drawFlags |= DT_HIDEPREFIX; - - if (useWide) - DrawTextW(hDC, TextW, TextLenW, &r, drawFlags); - else - DrawText(hDC, Text, TextLen, &r, drawFlags); - } - else - { - DWORD drawFlags = (bkErased) ? 0 : ETO_OPAQUE; - // if (ClipDraw) // same problem as above - drawFlags |= ETO_CLIPPED; - - int xOffset = GetTextXOffset(); - - if (useWide) - { - const wchar_t* textW; - int textLenW; - if (Text2Draw && Text2W != NULL) - { - textW = Text2W; - textLenW = Text2LenW; - } - else - { - textW = TextW; - textLenW = TextLenW; - } - ExtTextOutW(hDC, r.left + xOffset, r.top, drawFlags, &r, textW, textLenW, NULL); - } - else - { - const char* text; - int textLen; - if (Text2Draw) - { - text = Text2; - textLen = Text2Len; - } - else - { - text = Text; - textLen = TextLen; - } - ExtTextOut(hDC, r.left + xOffset, r.top, drawFlags, &r, text, textLen, NULL); - } - } - - if (Flags & STF_DOTUNDERLINE) - { - // dotted underline - int xOffset = GetTextXOffset(); - - HPEN hDottedPen = HANDLES(CreatePen(PS_DOT, 0, GetTextColor(hDC))); - HPEN hOldPen = (HPEN)SelectObject(hDC, hDottedPen); - MoveToEx(hDC, r.left + xOffset, r.bottom - 1, NULL); - LineTo(hDC, r.left + xOffset + TextWidth, r.bottom - 1); - SelectObject(hDC, hOldPen); - HANDLES(DeleteObject(hDottedPen)); - } - - // restore the original DC values - SelectObject(hDC, hOldFont); - // SetBkColor(hDC, oldBkColor); - // SetTextColor(hDC, oldTextColor); - SetBkMode(hDC, oldBkMode); - } - else - { - // no text stored; we must at least erase the background - if (IsAppThemed()) - { - DrawThemeParentBackground(HWindow, hDC, &r); - } - else - FillRect(hDC, &r, (HBRUSH)(COLOR_BTNFACE + 1)); - } - - if ((GetWindowLongPtr(HWindow, GWL_STYLE) & WS_TABSTOP) && GetFocus() == HWindow) - DrawFocus(hDC); - - if (Bitmap != NULL) - { - // if using the cache, we must copy it to the window - BitBlt(ps.hdc, 0, 0, Width, Height, Bitmap->HMemDC, 0, 0, SRCCOPY); - } - - HANDLES(EndPaint(HWindow, &ps)); - return 0; - } - - case WM_UPDATEUISTATE: - { - // unfortunately we cannot rely on the standard static handling because - // under Vista (and maybe earlier) it draws the Alt underline at a nonsensical - // position; one solution would be to capture the text into our buffer and draw from it, - // but I chose a different approach and maintain the state ourselves - if (LOWORD(wParam) == UIS_CLEAR) - UIState &= ~HIWORD(wParam); - else if (LOWORD(wParam) == UIS_SET) - UIState |= HIWORD(wParam); - - BOOL showAccel = (LOWORD(wParam) == UIS_CLEAR) && ((HIWORD(wParam) & UISF_HIDEACCEL) != 0); - if (showAccel) - { - InvalidateRect(HWindow, NULL, TRUE); // if not cached we flicker a little, but never mind - UpdateWindow(HWindow); - } - return 0; - } - } - - return CWindow::WindowProc(uMsg, wParam, lParam); -} - -//**************************************************************************** -// -// CHyperLink -// - -CHyperLink::CHyperLink(HWND hDlg, int ctrlID, DWORD flags) - : CStaticText(hDlg, ctrlID, flags) -{ - File[0] = 0; - Command = 0; - HDialog = hDlg; - - // adjust the style so we receive messages - DWORD style = (DWORD)GetWindowLongPtr(HWindow, GWL_STYLE); - style |= SS_NOTIFY; - SetWindowLongPtr(HWindow, GWL_STYLE, style); -} - -void CHyperLink::SetActionOpen(const char* file) -{ - EnableHintToolTip(FALSE); - lstrcpyn(File, file, MAX_PATH); -} - -void CHyperLink::SetActionPostCommand(WORD command) -{ - EnableHintToolTip(FALSE); - Command = command; -} - -BOOL CHyperLink::SetActionShowHint(const char* text) -{ - EnableHintToolTip(TRUE); - if (text == NULL) - return TRUE; - else - return SetToolTipText(text); -} - -BOOL CHyperLink::ExecuteIt() -{ - BOOL ret = TRUE; - if (File[0] != 0) - { - // do not switch to shellExecuteWnd, we use BugReport - int err = (int)(INT_PTR)ShellExecute(HWindow, "open", File, NULL, NULL, SW_SHOWNORMAL); - if (err <= 32) - { - ret = FALSE; - SalMessageBox(HDialog, GetErrorText(err), LoadStr(IDS_ERRORTITLE), MB_OK | MB_ICONEXCLAMATION); - } - } - if (Command != 0) - { - PostMessage(HDialog, WM_COMMAND, Command, 0); - } - return ret; -} - -void CHyperLink::OnContextMenu(int x, int y) -{ - /* used by the export_mnu.py script that generates salmenu.mnu for the Translator - keep synchronized with the InsertMenu() call below... -MENU_TEMPLATE_ITEM HyperLinkMenu[] = -{ - {MNTT_PB, 0 - {MNTT_IT, IDS_COPYTOCLIPBOARD - {MNTT_PE, 0 -}; -*/ - HMENU hMenu = CreatePopupMenu(); - InsertMenu(hMenu, 0, MF_BYPOSITION, 1, LoadStr(IDS_COPYTOCLIPBOARD)); - DWORD cmd = TrackPopupMenuEx(hMenu, TPM_RETURNCMD | TPM_LEFTALIGN | TPM_RIGHTBUTTON, - x, y, HWindow, NULL); - DestroyMenu(hMenu); - if (cmd == 1) - { - CopyTextToClipboard(Text, -1, TRUE, HWindow); - } -} - -LRESULT -CHyperLink::WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - CALL_STACK_MESSAGE4("CHyperLink::WindowProc(0x%X, 0x%IX, 0x%IX)", uMsg, wParam, lParam); - switch (uMsg) - { - case WM_SETCURSOR: - { - POINT p; - DWORD messagePos = GetMessagePos(); - p.x = GET_X_LPARAM(messagePos); - p.y = GET_Y_LPARAM(messagePos); - if (TextHitTest(&p)) - SetHandCursor(); - else - SetCursor(LoadCursor(NULL, IDC_ARROW)); - return TRUE; - } - - case WM_LBUTTONDOWN: - case WM_RBUTTONDOWN: - { - POINT p; - DWORD messagePos = GetMessagePos(); - p.x = GET_X_LPARAM(messagePos); - p.y = GET_Y_LPARAM(messagePos); - if (TextHitTest(&p)) - { - SetCapture(HWindow); - if (GetWindowLongPtr(HWindow, GWL_STYLE) & WS_TABSTOP) - SetFocus(HWindow); - } - break; - } - - case WM_LBUTTONUP: - { - if (GetCapture() != HWindow) - break; - ReleaseCapture(); - POINT p; - DWORD messagePos = GetMessagePos(); - p.x = GET_X_LPARAM(messagePos); - p.y = GET_Y_LPARAM(messagePos); - if (TextHitTest(&p)) - ExecuteIt(); - break; - } - - case WM_RBUTTONUP: - { - if (GetCapture() != HWindow) - break; - ReleaseCapture(); - POINT p; - DWORD messagePos = GetMessagePos(); - p.x = GET_X_LPARAM(messagePos); - p.y = GET_Y_LPARAM(messagePos); - if (TextHitTest(&p)) - OnContextMenu(p.x, p.y); - break; - } - - case WM_SYSKEYDOWN: - case WM_KEYDOWN: - { - if (wParam == VK_SPACE || wParam == VK_RETURN) - ExecuteIt(); - - BOOL controlPressed = (GetKeyState(VK_CONTROL) & 0x8000) != 0; - BOOL altPressed = (GetKeyState(VK_MENU) & 0x8000) != 0; - BOOL shiftPressed = (GetKeyState(VK_SHIFT) & 0x8000) != 0; - if ((wParam == VK_F10 && shiftPressed || wParam == VK_APPS)) - { - RECT r; - GetWindowRect(HWindow, &r); - OnContextMenu(r.left, r.bottom); - } - - // support for our message boxes, forward Ctrl+C - // sending WM_COPY in a standard dialog should not be an issue - if (!shiftPressed && controlPressed && !altPressed) - { - if (wParam == 'C') - { - HWND hParent = GetParent(HWindow); - if (hParent != NULL) - PostMessage(hParent, WM_COPY, 0, 0); - } - } - - break; - } - } - - return CStaticText::WindowProc(uMsg, wParam, lParam); -} - -//**************************************************************************** -// -// CButton -// - -CColorRectangle::CColorRectangle(HWND hDlg, int ctrlID, CObjectOrigin origin) - : CWindow(hDlg, ctrlID, origin) -{ - Color = RGB(255, 255, 128); -} - -void CColorRectangle::SetColor(COLORREF color) -{ - Color = color; - InvalidateRect(HWindow, NULL, FALSE); - UpdateWindow(HWindow); -} - -void CColorRectangle::PaintFace(HDC hdc) -{ - RECT r; - GetClientRect(HWindow, &r); - - COLORREF oldBkColor = SetBkColor(hdc, Color); - ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &r, NULL, 0, NULL); - SetBkColor(hdc, oldBkColor); -} - -LRESULT -CColorRectangle::WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - switch (uMsg) - { - case WM_PAINT: - { - PAINTSTRUCT ps; - HDC hdc = HANDLES(BeginPaint(HWindow, &ps)); - if (hdc != NULL) - { - PaintFace(hdc); - HANDLES(EndPaint(HWindow, &ps)); - } - return 0; - } - } - return CWindow::WindowProc(uMsg, wParam, lParam); -} - -//**************************************************************************** -// -// CColorGraph -// - -CColorGraph::CColorGraph(HWND hDlg, int ctrlID, CObjectOrigin origin) - : CWindow(hDlg, ctrlID, origin) -{ - Color1Light = NULL; - Color1Dark = NULL; - Color2Light = NULL; - Color2Dark = NULL; - UsedProc = 0; - - GetClientRect(HWindow, &ClientRect); -} - -CColorGraph::~CColorGraph() -{ - if (Color1Light != NULL) - HANDLES(DeleteObject(Color1Light)); - if (Color1Dark != NULL) - HANDLES(DeleteObject(Color1Dark)); - if (Color2Light != NULL) - HANDLES(DeleteObject(Color2Light)); - if (Color2Dark != NULL) - HANDLES(DeleteObject(Color2Dark)); -} - -void CColorGraph::SetColor(COLORREF color1Light, COLORREF color1Dark, - COLORREF color2Light, COLORREF color2Dark) -{ - Color1Light = HANDLES(CreateSolidBrush(color1Light)); - Color1Dark = HANDLES(CreateSolidBrush(color1Dark)); - Color2Light = HANDLES(CreateSolidBrush(color2Light)); - Color2Dark = HANDLES(CreateSolidBrush(color2Dark)); - - InvalidateRect(HWindow, NULL, FALSE); - UpdateWindow(HWindow); -} - -void CColorGraph::SetUsed(double used) -{ - UsedProc = used; - InvalidateRect(HWindow, NULL, FALSE); - UpdateWindow(HWindow); -} - -#define GRAPH_HEIGHT 4 -#define PI 3.141592653589793 - -void CColorGraph::PaintFace(HDC hdc) -{ - CALL_STACK_MESSAGE1("CColorGraph::PaintFace()"); - RECT r; - GetClientRect(HWindow, &r); - - double beta = UsedProc * 360 * PI / 180; - - double elX0 = r.right / 2; - double elY0 = (r.bottom - GRAPH_HEIGHT) / 2; - double elA = r.right / 2; - double elB = (r.bottom - GRAPH_HEIGHT) / 2; - double elX = elX0 + elA * cos(beta); - double elY = elB * sin(beta); - - // draw the bottom part - HBRUSH hOldBrush = (HBRUSH)GetCurrentObject(hdc, OBJ_BRUSH); - HPEN hBlackPen = HANDLES(CreatePen(PS_SOLID, 0, RGB(0, 0, 0))); - HPEN hOldPen = (HPEN)SelectObject(hdc, hBlackPen); - - if (UsedProc < 0.5) - SelectObject(hdc, Color1Dark); // free color - else - SelectObject(hdc, Color2Dark); // used color - - Ellipse(hdc, r.left, r.top + GRAPH_HEIGHT, r.right, r.bottom); - - // handle variant (b) - if (UsedProc > 0 && UsedProc < 0.5) - { - SelectObject(hdc, Color2Dark); // used color - int x = (int)elX; - int y1 = (int)(elY0 + GRAPH_HEIGHT + elY); - int y2 = (int)(elY0 + GRAPH_HEIGHT - elY); - Chord(hdc, r.left, r.top + GRAPH_HEIGHT, r.right, r.bottom, - x, y1, x, y2); - } - - // draw the top part - if (UsedProc >= 0 && UsedProc < 1) - SelectObject(hdc, Color1Light); // free color - else - SelectObject(hdc, Color2Light); // used color - - Ellipse(hdc, r.left, r.top, r.right, r.bottom - GRAPH_HEIGHT); - - if (UsedProc > 0 && UsedProc < 1) - { - SelectObject(hdc, Color2Light); // used color - int y1 = (int)(elY0 + elY); - int y2 = (int)elY0; - if (y1 == y2 && UsedProc < 0.1) // a messy solution - SelectObject(hdc, Color1Light); // used color - Pie(hdc, r.left, r.top, r.right, r.bottom - GRAPH_HEIGHT, - (int)elX, y1, r.right, y2); - } - - SelectObject(hdc, hOldBrush); - SelectObject(hdc, hOldPen); - HANDLES(DeleteObject(hBlackPen)); -} - -/* -LRESULT -CColorGraph::WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - CALL_STACK_MESSAGE4("CColorGraph::WindowProc(0x%X, 0x%IX, 0x%IX)", uMsg, wParam, lParam); - switch (uMsg) - { - case WM_PAINT: - { - PAINTSTRUCT ps; - HDC hdc = HANDLES(BeginPaint(HWindow, &ps)); - FillRect(hdc, &ClientRect, (HBRUSH)(COLOR_BTNFACE + 1)); - PaintFace(hdc); - HANDLES(EndPaint(HWindow, &ps)); - return 0; - } - } - return CWindow::WindowProc(uMsg, wParam, lParam); -} -*/ - -// this version with memDC works even on XP (the one above has jagged curves on Windows XP) - -LRESULT -CColorGraph::WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - CALL_STACK_MESSAGE4("CColorGraph::WindowProc(0x%X, 0x%IX, 0x%IX)", uMsg, wParam, lParam); - switch (uMsg) - { - case WM_PAINT: - { - PAINTSTRUCT ps; - HDC hdc = HANDLES(BeginPaint(HWindow, &ps)); - if (hdc != NULL) - { - CBitmap bitmap; - bitmap.CreateBmp(hdc, ClientRect.right, ClientRect.bottom); - - HBRUSH hBrush = (HBRUSH)(COLOR_BTNFACE + 1); - FillRect(bitmap.HMemDC, &ClientRect, hBrush); - - PaintFace(bitmap.HMemDC); - - BitBlt(hdc, - 0, 0, - ClientRect.right, ClientRect.bottom, - bitmap.HMemDC, - 0, 0, - SRCCOPY); - - HANDLES(EndPaint(HWindow, &ps)); - } - return 0; - } - } - return CWindow::WindowProc(uMsg, wParam, lParam); -} - -//**************************************************************************** -// -// CButton -// - -CButton::CButton(HWND hDlg, int ctrlID, DWORD flags, CObjectOrigin origin) - : CWindow(hDlg, ctrlID, origin) -{ - Flags = flags; - DropDownPressed = FALSE; - Checked = FALSE; - ButtonPressed = FALSE; - Pressed = FALSE; - DefPushButton = FALSE; - Captured = FALSE; - Space = FALSE; - MouseIsTracked = FALSE; - ToolTipText = NULL; - HToolTipNW = NULL; - ToolTipID = 0; - Hot = FALSE; - GetClientRect(HWindow, &ClientRect); - DropDownUpTime = GetTickCount(); - UIState = (WORD)SendMessage(HWindow, WM_QUERYUISTATE, 0, 0); -} - -CButton::~CButton() -{ - if (ToolTipText != NULL) - free(ToolTipText); -} - -DWORD -CButton::GetFlags() -{ - return Flags; -} - -void CButton::SetFlags(DWORD flags, BOOL updateWindow) -{ - Flags = flags; - if (HWindow != NULL) - { - InvalidateRect(HWindow, NULL, FALSE); - if (updateWindow) - UpdateWindow(HWindow); - } -} - -void CButton::RePaint() -{ - InvalidateRect(HWindow, NULL, FALSE); - UpdateWindow(HWindow); -} - -void CButton::NotifyParent(WORD notify) -{ - int id = GetWindowLong(HWindow, GWL_ID); - PostMessage(GetParent(HWindow), WM_COMMAND, - (WPARAM)(id | ((WPARAM)notify << 16)), (LPARAM)HWindow); -} - -void CButton::PaintFrame(HDC hDC, const RECT* r, BOOL down) -{ - if (/*!(ButtonPressed && Pressed) && */ (Flags & BTF_CHECKBOX) && Checked) - { - // darkest on the left and top - HPEN hOldPen = (HPEN)SelectObject(hDC, WndFramePen); - MoveToEx(hDC, r->left, r->bottom - 2, NULL); - LineTo(hDC, r->left, r->top); - LineTo(hDC, r->right - 1, r->top); - // dark on the left and top inside - SelectObject(hDC, BtnShadowPen); - MoveToEx(hDC, r->left + 1, r->bottom - 3, NULL); - LineTo(hDC, r->left + 1, r->top + 1); - LineTo(hDC, r->right - 2, r->top + 1); - // a bit darker on the right and bottom inside - SelectObject(hDC, Btn3DLightPen); - MoveToEx(hDC, r->right - 2, r->top + 1, NULL); - LineTo(hDC, r->right - 2, r->bottom - 2); - LineTo(hDC, r->left, r->bottom - 2); - // light on the right and bottom - SelectObject(hDC, BtnHilightPen); - MoveToEx(hDC, r->left, r->bottom - 1, NULL); - LineTo(hDC, r->right - 1, r->bottom - 1); - LineTo(hDC, r->right - 1, -1); - SelectObject(hDC, hOldPen); - return; - } - - if (down) - { - HPEN hOldPen = (HPEN)SelectObject(hDC, BtnShadowPen); - HBRUSH hOldBrush = (HBRUSH)SelectObject(hDC, HANDLES(GetStockObject(NULL_BRUSH))); - Rectangle(hDC, r->left, r->top, r->right, r->bottom); - SelectObject(hDC, hOldBrush); - SelectObject(hDC, hOldPen); - } - else - { - // light on the left and top - HPEN hOldPen = (HPEN)SelectObject(hDC, BtnHilightPen); - MoveToEx(hDC, r->left, r->bottom - 2, NULL); - LineTo(hDC, r->left, r->top); - LineTo(hDC, r->right - 1, r->top); - // a bit darker inside - SelectObject(hDC, Btn3DLightPen); - MoveToEx(hDC, r->left + 1, r->bottom - 3, NULL); - LineTo(hDC, r->left + 1, r->top + 1); - LineTo(hDC, r->right - 2, r->top + 1); - // dark on the right and bottom - SelectObject(hDC, BtnShadowPen); - MoveToEx(hDC, r->right - 2, r->top + 1, NULL); - LineTo(hDC, r->right - 2, r->bottom - 2); - LineTo(hDC, r->left, r->bottom - 2); - // darkest on the outside - SelectObject(hDC, WndFramePen); - MoveToEx(hDC, r->left, r->bottom - 1, NULL); - LineTo(hDC, r->right - 1, r->bottom - 1); - LineTo(hDC, r->right - 1, -1); - SelectObject(hDC, hOldPen); - } -} - -void CButton::PaintDrop(HDC hDC, const RECT* r, BOOL enabled) -{ - SIZE sz; - SVGArrowDropDown.GetSize(&sz); - SVGArrowDropDown.AlphaBlend(hDC, - r->left + (r->right - r->left - sz.cx) / 2, - r->top + (r->bottom - r->top - sz.cy) / 2, - -1, -1, - enabled ? SVGSTATE_ENABLED : SVGSTATE_DISABLED); -} - -int CButton::GetDropPartWidth() -{ - return (int)((double)SVGArrowDropDown.GetWidth() * 1.6); -} - -int CButton::HitTest(LPARAM lParam) -{ - POINT p; - p.x = LOWORD(lParam); - p.y = HIWORD(lParam); - if (!PtInRect(&ClientRect, p)) - return 0; // nowhere - - if (Flags & BTF_DROPDOWN) - { - RECT r = ClientRect; - r.left = r.right - GetDropPartWidth() - 1; - if (PtInRect(&r, p)) - return 2; // drop down - } - - return 1; // button -} - -void CButton::PaintFace(HDC hdc, const RECT* rect, BOOL enabled) -{ - RECT r = *rect; - if (Flags & BTF_RIGHTARROW) - r.right -= (int)((double)SVGArrowRight.GetWidth() * 1.5); - if (Flags & BTF_DROPDOWN) - r.right -= GetDropPartWidth(); - if (Flags & BTF_MORE) - r.right -= (int)((double)SVGArrowMore.GetWidth() * 1.3); - - DWORD wndStyle = (DWORD)GetWindowLongPtr(HWindow, GWL_STYLE); - if (wndStyle & BS_ICON) - { - // icon - - HICON hIcon = (HICON)SendMessage(HWindow, BM_GETIMAGE, IMAGE_ICON, 0); - if (hIcon != NULL) - { - ICONINFO iconInfo; - if (GetIconInfo(hIcon, &iconInfo)) - { - BITMAP bm; - GetObject(iconInfo.hbmColor, sizeof(bm), &bm); - if (enabled) - { - DrawIcon(hdc, r.left + (r.right - r.left - bm.bmWidth) / 2, - r.top + (r.bottom - r.top - bm.bmHeight) / 2, hIcon); - } - else - { - // disabled - CGuiBitmap tmpFaceBitmap; - tmpFaceBitmap.CreateBmp(hdc, bm.bmWidth, bm.bmHeight); - RECT fillR = {0}; - fillR.right = bm.bmWidth; - fillR.bottom = bm.bmHeight; - FillRect(tmpFaceBitmap.HMemDC, &fillR, (HBRUSH)(COLOR_BTNFACE + 1)); - DrawIcon(tmpFaceBitmap.HMemDC, 0, 0, hIcon); - HBITMAP hBmp = tmpFaceBitmap.CreateCopyBitmap(); - DrawState(hdc, NULL, NULL, (LPARAM)hBmp, 0, - r.left + (r.right - r.left - bm.bmWidth) / 2, - r.top + (r.bottom - r.top - bm.bmHeight) / 2, - bm.bmWidth, bm.bmHeight, - DST_BITMAP | DSS_DISABLED); - HANDLES(DeleteObject(hBmp)); - } - DeleteObject(iconInfo.hbmMask); - DeleteObject(iconInfo.hbmColor); - } - } - } - else - { - // text - - // get the button text - char buff[500]; - GetWindowText(HWindow, buff, 500); - - // get the current font - HFONT hFont = (HFONT)SendMessage(HWindow, WM_GETFONT, 0, 0); - - HFONT hOldFont = (HFONT)SelectObject(hdc, hFont); - int oldBkMode = SetBkMode(hdc, TRANSPARENT); - int oldTextColor = SetTextColor(hdc, GetSysColor(enabled ? COLOR_BTNTEXT : COLOR_GRAYTEXT)); - RECT r2 = r; - r2.top--; - DWORD dtFlags = DT_CENTER | DT_VCENTER | DT_SINGLELINE; - if (UIState & UISF_HIDEACCEL) - dtFlags |= DT_HIDEPREFIX; - DrawText(hdc, buff, -1, &r2, dtFlags); - SetTextColor(hdc, oldTextColor); - SetBkMode(hdc, oldBkMode); - SelectObject(hdc, hOldFont); - } - - if (Flags & BTF_RIGHTARROW) - { - BOOL empty = FALSE; - if ((wndStyle & BS_ICON) == 0) - { - char buff[500]; - GetWindowText(HWindow, buff, 500); - empty = (buff[0] == 0); - } - - SIZE sz; - SVGArrowRight.GetSize(&sz); - - if (empty) - { - // if the button only contains an arrow, center it in both axes - r = *rect; - r.left += (int)((double)sz.cx * 0.3); - } - else - { - r.right += sz.cx; - r.left = r.right - sz.cx; - } - SVGArrowRight.AlphaBlend(hdc, - r.left + (r.right - r.left - sz.cx) / 2, - r.top + (r.bottom - r.top - sz.cy) / 2, - -1, -1, - enabled ? SVGSTATE_ENABLED : SVGSTATE_DISABLED); - } - - if (Flags & BTF_MORE) - { - CSVGSprite* sprite = Checked ? &SVGArrowLess : &SVGArrowMore; - - SIZE sz; - sprite->GetSize(&sz); - - r.right += sz.cx; - r.left = r.right - sz.cx; - sprite->AlphaBlend(hdc, - r.left + (r.right - r.left - sz.cx) / 2, - r.top + (r.bottom - r.top - sz.cy) / 2, - -1, -1, - enabled ? SVGSTATE_ENABLED : SVGSTATE_DISABLED); - } -} - -BOOL CButton::SetToolTipText(const char* text) -{ - if (text == NULL) - { - if (ToolTipText != NULL) - free(ToolTipText); - ToolTipText = NULL; - HToolTipNW = NULL; - ToolTipID = 0; - return TRUE; - } - - char* newText = DupStr(text); - if (newText == NULL) - return FALSE; - - if (ToolTipText != NULL) - free(ToolTipText); - - ToolTipText = newText; - HToolTipNW = NULL; - ToolTipID = 0; - return TRUE; -} - -void CButton::SetToolTip(HWND hNotifyWindow, DWORD id) -{ - if (ToolTipText != NULL) - free(ToolTipText); - ToolTipText = NULL; - - HToolTipNW = hNotifyWindow; - ToolTipID = id; -} - -BOOL CButton::ToolTipAssigned() -{ - return ToolTipText != NULL || HToolTipNW != NULL; -} - -LRESULT -CButton::WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - SLOW_CALL_STACK_MESSAGE4("CButton::WindowProc(0x%X, 0x%IX, 0x%IX)", uMsg, wParam, lParam); - switch (uMsg) - { - case WM_GETDLGCODE: - { - DWORD ret = DLGC_BUTTON; - if (DefPushButton) - ret |= DLGC_DEFPUSHBUTTON; - else - ret |= DLGC_UNDEFPUSHBUTTON; - if (Flags & BTF_DROPDOWN) - ret |= DLGC_WANTARROWS; - return ret; - } - - case WM_SETFOCUS: - { - RePaint(); - if (GetWindowLongPtr(HWindow, GWL_STYLE) & BS_NOTIFY) - NotifyParent(BN_SETFOCUS); - return 0; - } - - case WM_KILLFOCUS: - { - if (Captured) - { - ReleaseCapture(); - Captured = FALSE; - ButtonPressed = FALSE; - Pressed = FALSE; - } - if (Space) - { - Space = FALSE; - ButtonPressed = FALSE; - Pressed = FALSE; - } - RePaint(); - if (GetWindowLongPtr(HWindow, GWL_STYLE) & BS_NOTIFY) - NotifyParent(BN_KILLFOCUS); - return 0; - } - - case WM_ENABLE: - { - RePaint(); - return 0; - } - - case WM_SIZE: - { - GetClientRect(HWindow, &ClientRect); - break; - } - - case WM_SETTEXT: - { - // WM_SETTEXT would explicitly redraw the control -- we avoid that - SendMessage(HWindow, WM_SETREDRAW, FALSE, 0); - LRESULT ret = CWindow::WindowProc(uMsg, wParam, lParam); - SendMessage(HWindow, WM_SETREDRAW, TRUE, 0); - RePaint(); - return ret; - } - - case WM_PAINT: - { - PAINTSTRUCT ps; - HDC hdc = HANDLES(BeginPaint(HWindow, &ps)); - - if (hdc != NULL) - { - BOOL enabled = IsWindowEnabled(HWindow); - //BOOL down = enabled && (ButtonPressed && Pressed || (Flags & BTF_CHECKBOX) && Checked); - BOOL checked = enabled && (Flags & BTF_CHECKBOX) && Checked; - BOOL down = enabled && ButtonPressed && Pressed; - BOOL focused = GetFocus() == HWindow; - - // we will draw through a memory DC - CGuiBitmap tmpBitmap; - tmpBitmap.CreateBmp(hdc, ClientRect.right, ClientRect.bottom); - HDC hMemDC = tmpBitmap.HMemDC; - - if (IsAppThemed()) - { - // if running under the XP theme, use it - HTHEME hTheme = OpenThemeData(HWindow, L"Button"); - int state = PBS_DISABLED; - if (enabled) - { - state = PBS_NORMAL; - if (Hot) - state = PBS_HOT; - if (down || checked) - { - if (checked && Hot && !down) - state = PBS_HOT; - else - state = PBS_PRESSED; - } - else if (focused) - { - if (Hot) - state = PBS_HOT; - else - state = PBS_DEFAULTED; - } - } - // erase the background, the button has transparent areas - HBRUSH hBrush = (HBRUSH)(COLOR_BTNFACE + 1); - // if (!(ButtonPressed && Pressed) && (Flags & BTF_CHECKBOX) && Checked) hBrush = HDitherBrush; - FillRect(hMemDC, &ClientRect, hBrush); - - // draw the button background - DrawThemeBackground(hTheme, hMemDC, BP_PUSHBUTTON, state, &ClientRect, NULL); - - if (Flags & BTF_DROPDOWN) - { - RECT ddR; - ddR = ClientRect; - ddR.left = ddR.right - GetDropPartWidth() - 3; - - RECT r = ddR; - r.left += 1; - r.top += 4; - r.right = r.left + 1; - r.bottom -= 4; - FillRect(hMemDC, &r, (HBRUSH)(COLOR_GRAYTEXT + 1)); - r.left = r.right; - r.right = r.left + 1; - FillRect(hMemDC, &r, (HBRUSH)(COLOR_3DHILIGHT + 1)); - - if (DropDownPressed && Pressed) - { - // draw the button background in the drop-down part - r = ddR; - r.left += 2; - DrawThemeBackground(hTheme, hMemDC, BP_PUSHBUTTON, PBS_PRESSED, &ClientRect, &r); - - ddR.top++; - ddR.bottom++; - } - PaintDrop(hMemDC, &ddR, enabled); - } - - // draw the face - RECT fr = ClientRect; - fr.left += 4; - fr.top += 4; - fr.right -= 4; - fr.bottom -= 4; - PaintFace(hMemDC, &fr, enabled); - - // draw the focus - if (focused) - { - RECT r = ClientRect; - r.left += 3; - r.top += 3; - r.right -= 3; - r.bottom -= 3; - DrawFocusRect(hMemDC, &r); - } - CloseThemeData(hTheme); - } - else - { - // otherwise we draw it ourselves - HBRUSH hBrush = (HBRUSH)(COLOR_BTNFACE + 1); - if (/*!(ButtonPressed && Pressed) && */ (Flags & BTF_CHECKBOX) && Checked) - { - hBrush = HDitherBrush; - SetTextColor(hMemDC, GetSysColor(COLOR_BTNFACE)); - SetBkColor(hMemDC, GetSysColor(COLOR_3DHILIGHT)); - } - FillRect(hMemDC, &ClientRect, hBrush); - - RECT fr = ClientRect; - fr.left += 4; - fr.top += 4; - fr.right -= 4; - fr.bottom -= 4; - if (down) - { - fr.left++; - fr.top++; - fr.right++; - fr.bottom++; - } - PaintFace(hMemDC, &fr, enabled); - - RECT clR = ClientRect; - if (DefPushButton && !Checked) - { - HPEN hOldPen = (HPEN)SelectObject(hMemDC, WndFramePen); - HBRUSH hOldBrush = (HBRUSH)SelectObject(hMemDC, HANDLES(GetStockObject(NULL_BRUSH))); - Rectangle(hMemDC, ClientRect.left, ClientRect.top, ClientRect.right, ClientRect.bottom); - SelectObject(hMemDC, hOldBrush); - SelectObject(hMemDC, hOldPen); - InflateRect(&clR, -1, -1); - } - - RECT ddR; - if (Flags & BTF_DROPDOWN) - { - ddR = clR; - ddR.left = ddR.right - GetDropPartWidth() - 1; - clR.right -= GetDropPartWidth(); - } - PaintFrame(hMemDC, &clR, down); - if (Flags & BTF_DROPDOWN) - { - PaintFrame(hMemDC, &ddR, DropDownPressed && Pressed); - if (DropDownPressed && Pressed) - { - ddR.left++; - ddR.top++; - ddR.right++; - ddR.bottom++; - } - PaintDrop(hMemDC, &ddR, enabled); - } - - if (focused) - { - RECT r = ClientRect; - InflateRect(&r, -4, -4); - if (Flags & BTF_DROPDOWN) - r.right -= GetDropPartWidth(); - if (down) - { - r.left++; - r.top++; - r.right++; - r.bottom++; - } - int oldColor = SetTextColor(hMemDC, GetSysColor(COLOR_BTNFACE)); - int oldBkColor = SetBkColor(hMemDC, GetSysColor(COLOR_BTNTEXT)); - DrawFocusRect(hMemDC, &r); - SetTextColor(hMemDC, oldColor); - SetBkColor(hMemDC, oldBkColor); - } - } - - BitBlt(hdc, - 0, 0, - ClientRect.right, ClientRect.bottom, - hMemDC, - 0, 0, - SRCCOPY); - - HANDLES(EndPaint(HWindow, &ps)); - } - return 0; - } - - case WM_UPDATEUISTATE: - { - // unfortunately we cannot rely on the standard static handling because - // under Vista (and maybe earlier) it draws the Alt underline at a nonsensical - // position; one solution would be to capture the text into our buffer and draw from it, - // but I chose a different approach and we maintain the state ourselves - if (LOWORD(wParam) == UIS_CLEAR) - UIState &= ~HIWORD(wParam); - else if (LOWORD(wParam) == UIS_SET) - UIState |= HIWORD(wParam); - - BOOL showAccel = (LOWORD(wParam) == UIS_CLEAR) && ((HIWORD(wParam) & UISF_HIDEACCEL) != 0); - if (showAccel) - { - InvalidateRect(HWindow, NULL, TRUE); // we use a cached bitmap, so it doesn't flicker - UpdateWindow(HWindow); - } - return 0; - } - - case WM_SYSKEYDOWN: - case WM_KEYDOWN: - { - BOOL controlPressed = (GetKeyState(VK_CONTROL) & 0x8000) != 0; - BOOL altPressed = (GetKeyState(VK_MENU) & 0x8000) != 0; - BOOL shiftPressed = (GetKeyState(VK_SHIFT) & 0x8000) != 0; - - if ((Flags & BTF_DROPDOWN) && (wParam == VK_RIGHT || wParam == VK_LEFT)) - { - // the left and right arrow keys might work - HWND hParent = GetParent(HWindow); - if (hParent != NULL) - { - HWND hNext = GetNextDlgGroupItem(hParent, HWindow, wParam == VK_LEFT); - if (hNext != NULL) - { - SendMessage(hParent, WM_NEXTDLGCTL, (WPARAM)hNext, TRUE); - } - } - return 0; - } - if ((Flags & BTF_DROPDOWN) && (wParam == VK_DOWN || wParam == VK_UP)) - { - // the Up/Down keys can open the drop-down - ButtonPressed = FALSE; - DropDownPressed = TRUE; - Pressed = TRUE; - RePaint(); - SendMessage(GetParent(HWindow), WM_USER_BUTTONDROPDOWN, - (WPARAM)GetMenu(HWindow), MAKELPARAM(TRUE, 0)); - DropDownPressed = FALSE; - Pressed = FALSE; - Captured = FALSE; - RePaint(); - return 0; - } - if ((int)wParam == VK_SPACE) - { - // the space key presses the button - ButtonPressed = TRUE; - Pressed = TRUE; - RePaint(); - if (Flags & BTF_LBUTTONDOWN) - { - SendMessage(GetParent(HWindow), WM_USER_BUTTON, - MAKELPARAM(GetMenu(HWindow), 0), MAKELPARAM(TRUE, 0)); - ButtonPressed = FALSE; - Pressed = FALSE; - RePaint(); - } - else - Space = TRUE; - return 0; - } - else if (Space) - { - Space = FALSE; - ButtonPressed = FALSE; - Pressed = FALSE; - RePaint(); - return 0; - } - break; - } - - case WM_KEYUP: - { - if (Space && wParam == VK_SPACE) - { - Space = FALSE; - ButtonPressed = FALSE; - Pressed = FALSE; - if (Flags & BTF_CHECKBOX) - Checked = !Checked; - RePaint(); - NotifyParent(BN_CLICKED); - return 0; - } - break; - } - - case WM_LBUTTONDBLCLK: - case WM_LBUTTONDOWN: - { - // if the click arrived within 25ms of releasing the drop-down, ignore it - // to prevent an unnecessary new press - if (GetTickCount() - DropDownUpTime <= 25) - return 0; - - if (ToolTipAssigned()) - { - SetCurrentToolTip(NULL, 0); - } - - int hitTest = HitTest(lParam); - if (!Captured && hitTest != 0) - { - if (hitTest == 1) - { - ButtonPressed = TRUE; - DropDownPressed = FALSE; - } - else - { - DropDownPressed = TRUE; - ButtonPressed = FALSE; - } - - Pressed = TRUE; - Captured = TRUE; - SetCapture(HWindow); - if (GetFocus() != HWindow) - SetFocus(HWindow); - RePaint(); - - if (DropDownPressed) - { - SendMessage(GetParent(HWindow), WM_USER_BUTTONDROPDOWN, - (WPARAM)GetMenu(HWindow), MAKELPARAM(FALSE, 0)); - DropDownPressed = FALSE; - Pressed = FALSE; - Captured = FALSE; - ReleaseCapture(); - RePaint(); - DropDownUpTime = GetTickCount(); - } - else - { - if (Flags & BTF_LBUTTONDOWN) - { - SendMessage(GetParent(HWindow), WM_USER_BUTTON, - MAKELPARAM(GetMenu(HWindow), 0), MAKELPARAM(FALSE, 0)); - Pressed = FALSE; - Captured = FALSE; - ReleaseCapture(); - RePaint(); - } - } - } - return 0; - } - - case WM_LBUTTONUP: - { - if (Captured) - { - ReleaseCapture(); - Captured = FALSE; - ButtonPressed = FALSE; - DropDownPressed = FALSE; - Pressed = FALSE; - - int hitTest = HitTest(lParam); - if (hitTest == 1 && (Flags & BTF_CHECKBOX)) - Checked = !Checked; - RePaint(); - if (hitTest == 1) - NotifyParent(BN_CLICKED); - } - return 0; - } - - case WM_MOUSEMOVE: - { - if (Captured) - { - int hitTest = HitTest(lParam); - BOOL pressed = FALSE; - if (ButtonPressed && hitTest == 1) - pressed = TRUE; - if (DropDownPressed && hitTest == 2) - pressed = TRUE; - if (pressed != Pressed) - { - Pressed = pressed; - RePaint(); - } - } - else - { - if (!Hot && IsAppThemed()) - { - Hot = TRUE; - RePaint(); - if (!MouseIsTracked) - { - TRACKMOUSEEVENT tme; - tme.cbSize = sizeof(tme); - tme.dwFlags = TME_LEAVE; - tme.hwndTrack = HWindow; - MouseIsTracked = TrackMouseEvent(&tme); - } - } - - if (ToolTipAssigned()) - { - if (HitTest(lParam) != 0) - { - if (ToolTipText != NULL) - SetCurrentToolTip(HWindow, 1); - else if (HToolTipNW != NULL) - SetCurrentToolTip(HWindow, ToolTipID); - } - else - SetCurrentToolTip(NULL, 0); - - if (!MouseIsTracked) - { - TRACKMOUSEEVENT tme; - tme.cbSize = sizeof(tme); - tme.dwFlags = TME_LEAVE; - tme.hwndTrack = HWindow; - MouseIsTracked = TrackMouseEvent(&tme); - } - } - } - return 0; - } - - case WM_MOUSELEAVE: - { - if (Hot) - { - Hot = FALSE; - RePaint(); - } - if (ToolTipAssigned()) - SetCurrentToolTip(NULL, 0); - MouseIsTracked = FALSE; - break; - } - - case WM_USER_TTGETTEXT: - { - if (ToolTipText != NULL) - lstrcpyn((char*)lParam, ToolTipText, TOOLTIP_TEXT_MAX); - return 0; - } - - case BM_SETSTATE: - { - BOOL highlight = (wParam != 0); - if (highlight != ButtonPressed) - { - ButtonPressed = highlight; - Pressed = ButtonPressed; - RePaint(); - } - return 0; - } - - case BM_GETSTATE: - { - int state = 0; - if (GetFocus() == HWindow) - state |= BST_FOCUS; - if (ButtonPressed && Pressed) - state |= BST_PUSHED; - return state; - } - - case BM_SETCHECK: - { - BOOL checked = (wParam == BST_CHECKED); - if (checked != Checked) - { - Checked = checked; - RePaint(); - } - } - - case BM_GETCHECK: - { - if (Checked) - return BST_CHECKED; - return BST_UNCHECKED; - } - - case BM_SETSTYLE: - { - WORD dwStyle = LOWORD(wParam); - BOOL fRedraw = LOWORD(lParam); - DefPushButton = FALSE; - if (dwStyle & BS_DEFPUSHBUTTON) - DefPushButton = TRUE; - if (fRedraw) - RePaint(); - return 0; - } - } - return CWindow::WindowProc(uMsg, wParam, lParam); -} - -//**************************************************************************** -// -// CColorButton -// -/* -CColorButton::CColorButton(HWND hDlg, int ctrlID, CObjectOrigin origin) - : CButton(hDlg, ctrlID, origin, FALSE, FALSE) -{ - Color = RGB(255, 255, 128); -} - - -void -CColorButton::SetColor(COLORREF color) -{ - Color = color; - RePaint(); -} - -void -CColorButton::PaintFace(HDC hdc, const RECT *rect) -{ - RECT r = *rect; -// InflateRect(&r, -4, -4); - - COLORREF oldBkColor = SetBkColor(hdc, Color); - ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &r, NULL, 0, NULL); - SetBkColor(hdc, oldBkColor); - - HPEN hOldPen = (HPEN)SelectObject(hdc, WndFramePen); - HBRUSH hOldBrush = (HBRUSH)SelectObject(hdc, HANDLES(GetStockObject(NULL_BRUSH))); - Rectangle(hdc, r.left, r.top, r.right, r.bottom); - SelectObject(hdc, hOldBrush); - SelectObject(hdc, hOldPen); -} -*/ - -//**************************************************************************** -// -// CColorArrowButton -// -// background with text followed by an arrow - used to open a menu -// - -CColorArrowButton::CColorArrowButton(HWND hDlg, int ctrlID, BOOL showArrow, CObjectOrigin origin) - : CButton(hDlg, ctrlID, origin) -{ - TextColor = RGB(0, 0, 0); - BkgndColor = RGB(255, 255, 255); - ShowArrow = showArrow; -} - -void CColorArrowButton::SetColor(COLORREF textColor, COLORREF bkgndColor) -{ - TextColor = textColor; - BkgndColor = bkgndColor; - RePaint(); -} - -void CColorArrowButton::SetTextColor(COLORREF textColor) -{ - SetColor(textColor, BkgndColor); -} - -void CColorArrowButton::SetBkgndColor(COLORREF bkgndColor) -{ - SetColor(TextColor, bkgndColor); -} - -void CColorArrowButton::PaintFace(HDC hdc, const RECT* rect, BOOL enabled) -{ - RECT r = *rect; - SIZE arrowSize; - - if (ShowArrow) - { - SVGArrowRightSmall.GetSize(&arrowSize); - r.right -= (int)((double)arrowSize.cx * 2.5); - } - - COLORREF bkColor = GetNearestColor(hdc, BkgndColor); - HBRUSH hBrush = HANDLES(CreateSolidBrush(bkColor)); - HPEN hOldPen = (HPEN)SelectObject(hdc, WndFramePen); - HBRUSH hOldBrush = (HBRUSH)SelectObject(hdc, hBrush); - Rectangle(hdc, r.left, r.top, r.right, r.bottom); - SelectObject(hdc, hOldBrush); - SelectObject(hdc, hOldPen); - HANDLES(DeleteObject(hBrush)); - - HFONT hFont = (HFONT)SendMessage(HWindow, WM_GETFONT, 0, 0); - LOGFONT lf; - GetObject(hFont, sizeof(lf), &lf); - lf.lfHeight += 1; // fix for 100% DPI when an overly large text touches the rectangle - hFont = HANDLES(CreateFontIndirect(&lf)); - HFONT hOldFont = (HFONT)SelectObject(hdc, hFont); - int oldTextColor = ::SetTextColor(hdc, TextColor); - int oldBkMode = SetBkMode(hdc, TRANSPARENT); - DrawText(hdc, "ABC", -1, &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE); - SetBkMode(hdc, oldBkMode); - ::SetTextColor(hdc, oldTextColor); - SelectObject(hdc, hOldFont); - HANDLES(DeleteObject(hFont)); - - if (ShowArrow) - { - SVGArrowRightSmall.AlphaBlend(hdc, - r.right + (rect->right - r.right - (int)((double)arrowSize.cx * 0.6)) / 2, - r.top + (r.bottom - r.top - arrowSize.cy) / 2, - -1, -1, - enabled ? SVGSTATE_ENABLED : SVGSTATE_DISABLED); - } -} - -//**************************************************************************** -// -// CToolbarHeader -// - -int TlbHdrTooltips[TLBHDR_COUNT] = - { - IDS_EDTLB_MODIFY, - IDS_EDTLB_NEW, - IDS_EDTLB_DELETE, - IDS_EDTLB_SORT, - IDS_EDTLB_UP, - IDS_EDTLB_DOWN, -}; - -CToolbarHeader::CToolbarHeader(HWND hDlg, int ctrlID, HWND hAlignWindow, DWORD buttonMask) - : CWindow(hDlg, ctrlID, ooAllocated) -{ - CALL_STACK_MESSAGE3("CToolbarHeader::CToolbarHeader(, %d, , %u)", ctrlID, buttonMask); - HNotifyWindow = hDlg; - ButtonMask = buttonMask; - ToolBar = new CToolBar(HWindow); - ToolBar->CreateWnd(HWindow); - -#ifdef TOOLBARHDR_USE_SVG - CreateImageLists(&HEnabledImageList, &HDisabledImageList); - ToolBar->SetImageList(HDisabledImageList); - ToolBar->SetHotImageList(HEnabledImageList); -#else - - CSVGIcon svgIcons[TLBHDR_COUNT] = { - {0, "Modify"}, - {1, "New_Insert"}, - {2, "Delete"}, - {3, "SortByName"}, - {4, "MoveItemUp"}, - {5, "MoveItemDown"}, - }; - - int iconSize = GetIconSizeForSystemDPI(ICONSIZE_16); - HBITMAP hTmpMaskBitmap; - HBITMAP hTmpGrayBitmap; - HBITMAP hTmpColorBitmap; - CreateToolbarBitmaps(HInstance, - IDB_EDTLBTB, - RGB(255, 0, 255), GetSysColor(COLOR_BTNFACE), - hTmpMaskBitmap, hTmpGrayBitmap, hTmpColorBitmap, - FALSE, svgIcons, TLBHDR_COUNT); - HHotImageList = ImageList_Create(iconSize, iconSize, ILC_MASK | ILC_COLORDDB, TLBHDR_COUNT, 1); - HGrayImageList = ImageList_Create(iconSize, iconSize, ILC_MASK | ILC_COLORDDB, TLBHDR_COUNT, 1); - ImageList_Add(HHotImageList, hTmpColorBitmap, hTmpMaskBitmap); - ImageList_Add(HGrayImageList, hTmpGrayBitmap, hTmpMaskBitmap); - HANDLES(DeleteObject(hTmpMaskBitmap)); - HANDLES(DeleteObject(hTmpGrayBitmap)); - HANDLES(DeleteObject(hTmpColorBitmap)); - ToolBar->SetImageList(HGrayImageList); - ToolBar->SetHotImageList(HHotImageList); - //HImageList = ImageList_Create(TOOLBARHDR_WIDTH, TOOLBARHDR_HEIGHT, - // ILC_MASK | ILC_COLORDDB, 5, 1); - //HBITMAP hbmp = HANDLES(LoadBitmap(HInstance, MAKEINTRESOURCE(IDB_EDTLBTB))); - //ImageList_AddMasked(HImageList, hbmp, RGB(255, 0, 255)); - //HANDLES(DeleteObject(hbmp)); - //ToolBar->SetImageList(HImageList); -#endif - - UIState = (WORD)SendMessage(HWindow, WM_QUERYUISTATE, 0, 0); - - TLBI_ITEM_INFO2 tii; - tii.Mask = TLBI_MASK_ID | TLBI_MASK_IMAGEINDEX; - int buttonsCount = 0; - int i; - for (i = 0; i < TLBHDR_COUNT; i++) - { - if ((1 << i) & ButtonMask) - { - tii.ImageIndex = i; - tii.ID = i + 1; - ToolBar->InsertItem2(buttonsCount, TRUE, &tii); - buttonsCount++; - } - } - - SIZE sz; - sz.cx = ToolBar->GetNeededWidth(); - sz.cy = ToolBar->GetNeededHeight(); - - RECT r; - GetWindowRect(hAlignWindow, &r); - int width = r.right - r.left; - int height = sz.cy + 2; - POINT p; - p.x = r.left; - p.y = r.top - height; - ScreenToClient(hDlg, &p); - SetWindowPos(HWindow, 0, p.x, p.y, width, height, SWP_NOZORDER); - SetWindowPos(ToolBar->HWindow, HWND_TOP, width - sz.cx - 1, 1, sz.cx, sz.cy, SWP_SHOWWINDOW); -} - -#ifdef TOOLBARHDR_USE_SVG -void CToolbarHeader::CreateImageLists(HIMAGELIST* enabled, HIMAGELIST* disabled) -{ - HIMAGELIST hEnabled; - HIMAGELIST hDisabled; - int iconSize = GetIconSizeForSystemDPI(ICONSIZE_16); // small icon size - - // http://stackoverflow.com/questions/2640823/is-it-possible-to-create-a-cimagelist-with-alpha-blending-transparency - hEnabled = ImageList_Create(iconSize, iconSize, - ILC_COLOR32, TOOLBARHDR_BUTTONS, 1); - hDisabled = ImageList_Create(iconSize, iconSize, - ILC_COLOR32 /*ILC_COLORDDB */, TOOLBARHDR_BUTTONS, 1); - - HDC hDC = HANDLES(CreateCompatibleDC(NULL)); - - int width = iconSize * TOOLBARHDR_BUTTONS; - int height = iconSize; - - BITMAPINFOHEADER bmhdr; - memset(&bmhdr, 0, sizeof(bmhdr)); - bmhdr.biSize = sizeof(bmhdr); - bmhdr.biWidth = width; - bmhdr.biHeight = -height; // top-down - bmhdr.biPlanes = 1; - bmhdr.biBitCount = 32; - bmhdr.biCompression = BI_RGB; - void* lpBits = NULL; - HBITMAP hBmp = HANDLES(CreateDIBSection(NULL, (CONST BITMAPINFO*)&bmhdr, - DIB_RGB_COLORS, &lpBits, NULL, 0)); - - NSVGrasterizer* rast = nsvgCreateRasterizer(); - // JRYFIXME: temporarily reading from a file, switch to a shared storage with toolbars - const char* svgNames[] = {"Modify", "New_Insert", "Delete", "SortByName", "MoveItemUp", "MoveItemDown"}; - for (int j = 0; j < 2; j++) - { - DWORD* p = (DWORD*)lpBits; - for (int i = 0; i < width * height; i++) - *p++ = 0x00000000; - - HBITMAP hOldBmp = (HBITMAP)SelectObject(hDC, hBmp); - for (int i = 0; i < TOOLBARHDR_BUTTONS; i++) - RenderSVGImage(rast, hDC, i * iconSize, 0, svgNames[i], iconSize, RGB(0xff, 0xff, 0xff), j == 0 ? TRUE : FALSE); - SelectObject(hDC, hOldBmp); - ImageList_Add(j == 0 ? hEnabled : hDisabled, hBmp, hBmp); - } - nsvgDeleteRasterizer(rast); - HANDLES(DeleteDC(hDC)); - HANDLES(DeleteObject(hBmp)); - *enabled = hEnabled; - *disabled = hDisabled; -} -#endif // TOOLBARHDR_USE_SVG - -void CToolbarHeader::EnableToolbar(DWORD enableMask) -{ - int i; - for (i = 0; i < TLBHDR_COUNT; i++) - { - if ((1 << i) & ButtonMask) - ToolBar->EnableItem(i + 1, FALSE, ((1 << i) & enableMask) != 0); - } -} - -void CToolbarHeader::CheckToolbar(DWORD checkMask) -{ - int i; - for (i = 0; i < TLBHDR_COUNT; i++) - { - if ((1 << i) & ButtonMask) - ToolBar->CheckItem(i + 1, FALSE, ((1 << i) & checkMask) != 0); - } -} - -void CToolbarHeader::OnPaint(HDC hDC, BOOL hideAccel, BOOL prefixOnly) -{ - RECT r; - GetClientRect(HWindow, &r); - DrawEdge(hDC, &r, BDR_SUNKENOUTER, BF_RECT); - r.left += 5; - char buff[100]; - GetWindowText(HWindow, buff, 100); - SetBkMode(hDC, TRANSPARENT); - HFONT hOldFont = (HFONT)SelectObject(hDC, (HFONT)SendMessage(HWindow, WM_GETFONT, 0, 0)); - DWORD dtFlags = DT_SINGLELINE | DT_LEFT | DT_VCENTER; - if (hideAccel) - dtFlags |= DT_HIDEPREFIX; - if (prefixOnly) - dtFlags |= DT_PREFIXONLY; - DrawText(hDC, buff, -1, &r, dtFlags); - SelectObject(hDC, hOldFont); -} - -LRESULT -CToolbarHeader::WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - CALL_STACK_MESSAGE4("CToolbarHeader::WindowProc(0x%X, 0x%IX, 0x%IX)", uMsg, wParam, lParam); - switch (uMsg) - { - case WM_DESTROY: - { - if (ToolBar != NULL) - DestroyWindow(ToolBar->HWindow); -#ifdef TOOLBARHDR_USE_SVG - if (HEnabledImageList != NULL) - ImageList_Destroy(HEnabledImageList); - if (HDisabledImageList != NULL) - ImageList_Destroy(HDisabledImageList); -#else - if (HHotImageList != NULL) - ImageList_Destroy(HHotImageList); - if (HGrayImageList != NULL) - ImageList_Destroy(HGrayImageList); -#endif - break; - } - - case WM_PAINT: - { - PAINTSTRUCT ps; - HDC hDC = HANDLES(BeginPaint(HWindow, &ps)); - BOOL hideAccel = (UIState & UISF_HIDEACCEL) != 0; - OnPaint(hDC, hideAccel, FALSE); - HANDLES(EndPaint(HWindow, &ps)); - return 0; - } - - case WM_UPDATEUISTATE: - { - // unfortunately we cannot rely on the standard static handling because - // under Vista (and maybe earlier) it draws the Alt underline at a nonsensical - // position; one solution would be to capture the text into our buffer and draw from it, - // but I chose a different approach and we maintain the state ourselves - if (LOWORD(wParam) == UIS_CLEAR) - UIState &= ~HIWORD(wParam); - else if (LOWORD(wParam) == UIS_SET) - UIState |= HIWORD(wParam); - - BOOL showAccel = (LOWORD(wParam) == UIS_CLEAR) && ((HIWORD(wParam) & UISF_HIDEACCEL) != 0); - if (showAccel) - { - HDC hDC = HANDLES(GetDC(HWindow)); - OnPaint(hDC, FALSE, TRUE); - HANDLES(ReleaseDC(HWindow, hDC)); - } - return 0; - } - - case WM_COMMAND: - { - if ((HWND)lParam == ToolBar->HWindow) - PostMessage(HNotifyWindow, WM_COMMAND, - MAKEWPARAM((WORD)(UINT_PTR)GetMenu(HWindow), LOWORD(wParam)), (LPARAM)HWindow); - break; - } - - case WM_USER_TBGETTOOLTIP: - { - TOOLBAR_TOOLTIP* tt = (TOOLBAR_TOOLTIP*)lParam; - lstrcpy(tt->Buffer, LoadStr(TlbHdrTooltips[tt->ID - 1])); - return TRUE; - } - - case WM_ERASEBKGND: - { - HDC hdc = (HDC)wParam; - RECT r; - GetClientRect(HWindow, &r); - FillRect(hdc, &r, (HBRUSH)(COLOR_3DFACE + 1)); - return 1; - } - } - return CWindow::WindowProc(uMsg, wParam, lParam); -} - -//**************************************************************************** -// -// CAnimate -// - -/* -CAnimate::CAnimate(HBITMAP hBitmap, int framesCount, int firstLoopFrame, COLORREF bkColor, CObjectOrigin origin) - : CWindow(origin) -{ - HBitmap = hBitmap; - FramesCount = framesCount; - FirstLoopFrame = firstLoopFrame; - HThread = NULL; - CurrentFrame = 0; - SleepThread = FALSE; - NestedCount = 0; - BkColor = bkColor; - MouseIsTracked = FALSE; - - HANDLES(InitializeCriticalSection(&GDICriticalSection)); - HANDLES(InitializeCriticalSection(&DataCriticalSection)); - - HRunEvent = HANDLES(CreateEvent(NULL, TRUE, FALSE, NULL)); - if (HRunEvent == NULL) - TRACE_E("Unable to create HRunEvent event."); - - HTerminateEvent = HANDLES(CreateEvent(NULL, TRUE, FALSE, NULL)); // "nonsignaled" state, manual - if (HTerminateEvent == NULL) - TRACE_E("Unable to create HTerminateEvent event."); - - if (HRunEvent != NULL && HTerminateEvent != NULL) - { - DWORD threadID; - HThread = HANDLES(CreateThread(NULL, 0, AuxThreadF, this, 0, &threadID)); - if (HThread == NULL) - TRACE_E("Unable to start thread for animation control."); - } - else HThread = NULL; - - BITMAP bitmap; - GetObject(HWorkerBitmap, sizeof(bitmap), &bitmap); - FrameSize.cx = bitmap.bmWidth; - FrameSize.cy = bitmap.bmHeight / framesCount; -} - -BOOL -CAnimate::IsGood() -{ - return HThread != NULL && HRunEvent != NULL && HTerminateEvent != NULL; -} - -void -CAnimate::Paint(HDC hdc) -{ - // just to be safe, we synchronize access to the bitmap - HANDLES(EnterCriticalSection(&GDICriticalSection)); - - // if no DC is provided, we obtain our own - HDC hDC; - if (hdc == NULL) - hDC = HANDLES(GetDC(HWindow)); - else - hDC = hdc; - - // memory DC for BitBlt - HDC hMemDC = HANDLES(CreateCompatibleDC(NULL)); - HBITMAP hOldBitmap = (HBITMAP)SelectObject(hMemDC, HBitmap); - - RECT r; - GetClientRect(HWindow, &r); - BitBlt(hDC, - (r.right - r.left - FrameSize.cx) / 2, - (r.bottom - r.top - FrameSize.cy) / 2, - FrameSize.cx, - FrameSize.cy, - hMemDC, 0, - CurrentFrame * FrameSize.cy, - SRCCOPY); - - // cleanup - SelectObject(hMemDC, hOldBitmap); - HANDLES(DeleteDC(hMemDC)); - if (hdc == NULL) - HANDLES(ReleaseDC(HWindow, hDC)); - HANDLES(LeaveCriticalSection(&GDICriticalSection)); -} - -void -CAnimate::GetFrameSize(SIZE *sz) -{ - *sz = FrameSize; -} - -void -CAnimate::FirstFrame() -{ - CurrentFrame = 0; -} - -void -CAnimate::NextFrame() -{ - CurrentFrame++; - if (CurrentFrame >= FramesCount) - CurrentFrame = FirstLoopFrame; -} - -void -CAnimate::Start() -{ - HANDLES(EnterCriticalSection(&DataCriticalSection)); - NestedCount++; - SleepThread = FALSE; - SetEvent(HRunEvent); - HANDLES(LeaveCriticalSection(&DataCriticalSection)); -} - -void -CAnimate::Stop() -{ - HANDLES(EnterCriticalSection(&DataCriticalSection)); - NestedCount--; - if (NestedCount < 1) - { - if (NestedCount < 0) - { - TRACE_E("CAnimate::Stop() NestedCount = "<HTerminateEvent; // must be at index zero because it has priority - handles[1] = animate->HRunEvent; - - // raise the thread priority so the animation doesn't buffer - // we can afford it because it consumes almost no time - SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST); - - while (TRUE) - { - DWORD wait = WaitForMultipleObjects(2, handles, FALSE, INFINITE); - - if (wait == WAIT_OBJECT_0) - break; // we need to terminate the thread - - if (animate->SleepThread) // we should stop the animation - { - animate->FirstFrame(); // move to the first frame - ResetEvent(animate->HRunEvent); // and disable running - } - - // draw the current frame - animate->Paint(); - // move to the next one - animate->NextFrame(); - // 1000ms / 40ms = 25 frames per second, just like in a movie - WaitForSingleObject(animate->HTerminateEvent, 40); // if we should exit, we wll not wait unnecessarily - } - TRACE_I("End"); - return 0; -} - -unsigned -CAnimate::AuxThreadEH(void *param) -{ - CALL_STACK_MESSAGE_NONE -#ifndef CALLSTK_DISABLE - __try - { -#endif // CALLSTK_DISABLE - return ThreadF(param); -#ifndef CALLSTK_DISABLE - } - __except (CCallStack::HandleException(GetExceptionInformation())) - { - TRACE_I("Thread in CAnimate: calling ExitProcess(1)."); -// ExitProcess(1); - TerminateProcess(GetCurrentProcess(), 1); // harder exit (this call still performs some operations) - return 1; - } -#endif // CALLSTK_DISABLE -} - - -DWORD WINAPI -CAnimate::AuxThreadF(void *param) -{ - CALL_STACK_MESSAGE_NONE -#ifndef CALLSTK_DISABLE - CCallStack stack; -#endif // CALLSTK_DISABLE - return AuxThreadEH(param); -} - -LRESULT -CAnimate::WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - CALL_STACK_MESSAGE4("CAnimate::WindowProc(0x%X, 0x%IX, 0x%IX)", uMsg, wParam, lParam); - - switch (uMsg) - { - case WM_CREATE: - { - break; - } - - case WM_DESTROY: - { - SetEvent(HTerminateEvent); - WaitForSingleObject(HThread, INFINITE); // wait for the thread to finish - HANDLES(CloseHandle(HThread)); - HANDLES(DeleteCriticalSection(&GDICriticalSection)); - HANDLES(DeleteCriticalSection(&DataCriticalSection)); - HANDLES(CloseHandle(HRunEvent)); - HANDLES(CloseHandle(HTerminateEvent)); - break; - } - - case WM_MOUSEMOVE: - { - SetCurrentToolTip(HWindow, 1); - - if (!MouseIsTracked) - { - TRACKMOUSEEVENT tme; - tme.cbSize = sizeof(tme); - tme.dwFlags = TME_LEAVE; - tme.hwndTrack = HWindow; - TrackMouseEvent(&tme); - MouseIsTracked = TRUE; - } - - break; - } - - case WM_MOUSELEAVE: - { - SetCurrentToolTip(NULL, 0); - MouseIsTracked = FALSE; - break; - } - - case WM_USER_TTGETTEXT: - { - char *text = (char *)lParam; - lstrcpy(text, "(CAnimate class)\nClick to Start animate, click again to Stop animate.\n\t1\nTab\t2"); - return TRUE; - } - - case WM_RBUTTONDOWN: - { - SetCurrentToolTip(NULL, 0); - break; - } - - case WM_RBUTTONUP: - { - SetCurrentToolTip(NULL, 0); - CMenuPopup popup; - BOOL runnig = WaitForSingleObject(HRunEvent, 0) == WAIT_OBJECT_0; - MENU_ITEM_INFO mii; - mii.Mask = MENU_MASK_TYPE | MENU_MASK_ID | MENU_MASK_STRING | MENU_MASK_STATE; - mii.Type = MENU_TYPE_STRING; - mii.ID = 1; - mii.String = "&Start"; - mii.State = runnig ? MENU_STATE_GRAYED : 0; - popup.InsertItem(0xFFFFFFFF, TRUE, &mii); - mii.ID = 2; - mii.String = "S&top"; - mii.State = runnig ? 0 : MENU_STATE_GRAYED; - popup.InsertItem(0xFFFFFFFF, TRUE, &mii); - - DWORD pos = GetMessagePos(); - DWORD cmd = popup.Track(MENU_TRACK_RETURNCMD | MENU_TRACK_RIGHTBUTTON, - GET_X_LPARAM(pos), GET_Y_LPARAM(pos), HWindow, NULL); - if (cmd == 1) - Start(); - if (cmd == 2) - Stop(); - return 0; - } - - case WM_LBUTTONDBLCLK: - case WM_LBUTTONDOWN: - { - SetCurrentToolTip(NULL, 0); - if (WaitForSingleObject(HRunEvent, 0) == WAIT_OBJECT_0) - Stop(); - else - Start(); - break; - } - - case WM_PAINT: - { - PAINTSTRUCT ps; - HDC hDC = HANDLES(BeginPaint(HWindow, &ps)); - Paint(hDC); - HANDLES(EndPaint(HWindow, &ps)); - return 0; - } - - case WM_ERASEBKGND: - { - HDC hDC = (HDC)wParam; - RECT r; - GetClientRect(HWindow, &r); - COLORREF oldColor = (COLORREF)SetBkColor(hDC, BkColor); - ExtTextOut((HDC)wParam, 0, 0, ETO_OPAQUE, &r, "", 0, NULL); - SetBkColor(hDC, oldColor); - return TRUE; - } - } - - return CWindow::WindowProc(uMsg, wParam, lParam); -} -*/ - -// -// **************************************************************************** -// ChangeToArrowButton -// - -BOOL ChangeToArrowButton(HWND hParent, int ctrlID) -{ - CALL_STACK_MESSAGE_NONE - // the old approach did not work with high-contrast colors where the arrow should be drawn inverted - // switching to our own drawing - CButton* button = new CButton(hParent, ctrlID, BTF_RIGHTARROW); - /* - // under XP BS_ICON is not drawn using themes, it has the old look - // so we draw the button ourselves - CButton *button = new CButton(hParent, ctrlID, 0); - HWND hButton = GetDlgItem(hParent, ctrlID); - if (hButton == NULL) - { - TRACE_E("Cannot find button ctrlID=" << ctrlID << " in the window hParent=0x" << hParent); - return FALSE; - } - LONG_PTR l = GetWindowLongPtr(hButton, GWL_STYLE); - l |= BS_ICON; - SetWindowLongPtr(hButton, GWL_STYLE, l); - SendMessage(hButton, BM_SETIMAGE, IMAGE_ICON, (WPARAM)HANDLES(LoadIcon(HInstance, MAKEINTRESOURCE(IDI_BROWSE)))); -*/ - return TRUE; -} - -BOOL ChangeToIconButton(HWND hParent, int ctrlID, int iconID) -{ - CALL_STACK_MESSAGE_NONE - HWND hButton = GetDlgItem(hParent, ctrlID); - if (hButton == NULL) - { - TRACE_E("Cannot find button ctrlID=" << ctrlID << " in the window hParent=0x" << hParent); - return FALSE; - } - DWORD stl = (DWORD)GetWindowLongPtr(hButton, GWL_STYLE); - stl |= BS_ICON; - SetWindowLongPtr(hButton, GWL_STYLE, stl); - SendMessage(hButton, BM_SETIMAGE, IMAGE_ICON, (WPARAM)HANDLES(LoadIcon(HInstance, MAKEINTRESOURCE(iconID)))); - - // adjust the button size according to the preceding edit line or combo box - HWND hPrevWnd = GetWindow(hButton, GW_HWNDPREV); - if (hPrevWnd != NULL) - { - char className[30]; - GetClassName(hPrevWnd, className, 29); - className[29] = 0; - if (stricmp(className, "edit") == 0 || stricmp(className, "combobox") == 0) - { - RECT r; - GetWindowRect(hPrevWnd, &r); - RECT r2; - GetWindowRect(hButton, &r2); - POINT p; - p.x = r2.left; - p.y = r.top; - ScreenToClient(hParent, &p); - SetWindowPos(hButton, NULL, p.x, p.y, r2.right - r2.left, r.bottom - r.top, SWP_NOZORDER); - } - } - - CButton* button = new CButton(hParent, ctrlID, BTF_LBUTTONDOWN | BTF_RIGHTARROW); - if (button != NULL) - button->SetToolTipText(LoadStr(IDS_BROWSE_BTN_TIP)); - - return TRUE; -} - -// -// **************************************************************************** -// VerticalAlignChildToChild -// - -void VerticalAlignChildToChild(HWND hParent, int alignID, int toID) -{ - HWND hAlign = GetDlgItem(hParent, alignID); - HWND hTo = GetDlgItem(hParent, toID); - if (hParent == NULL || hAlign == NULL || hTo == NULL) - { - TRACE_E("VerticalAlignChildToChild() Invalid parameters! hParent=" << hParent << " alignID=" << alignID << " toID=" << toID); - return; - } - - RECT alignR; - RECT toR; - GetWindowRect(hAlign, &alignR); - GetWindowRect(hTo, &toR); - - int width = alignR.right - alignR.left; - int height = toR.bottom - toR.top; - - ScreenToClient(hParent, (LPPOINT)&alignR); - ScreenToClient(hParent, (LPPOINT)&toR); - - SetWindowPos(hAlign, NULL, alignR.left, toR.top, width, height, SWP_NOZORDER); -} - -// -// **************************************************************************** -// CondenseStaticTexts -// - -void CondenseStaticTexts(HWND hWindow, int* staticsArr) -{ - int count = 0; - while (staticsArr[count] != 0) - count++; - if (count < 2) - return; // there isnothing to do - - HFONT hFont = (HFONT)SendDlgItemMessage(hWindow, staticsArr[0], WM_GETFONT, 0, 0); - HDC hDC = HANDLES(GetDC(hWindow)); - HFONT hOldFont = (HFONT)SelectObject(hDC, hFont); - SIZE sz; - GetTextExtentPoint32(hDC, " ", 1, &sz); - int spaceWidth = sz.cx; - int pos = -1; - for (int i = 0; i < count; i++) - { - HWND control = GetDlgItem(hWindow, staticsArr[i]); - if (control != NULL) - { - RECT r; - GetWindowRect(control, &r); - POINT p; - p.x = r.left; - p.y = r.top; - ScreenToClient(hWindow, &p); - WCHAR text[1000]; - GetWindowTextW(control, text, _countof(text)); - int textLen = (int)wcslen(text); - if (textLen > 0 && text[textLen - 1] == ' ') - text[--textLen] = 0; - GetTextExtentPoint32W(hDC, text, textLen, &sz); - if (pos == -1) - pos = p.x; - MoveWindow(control, pos, p.y, sz.cx, r.bottom - r.top, TRUE); - pos += sz.cx + spaceWidth; - } - else - TRACE_E("CondenseStaticTexts(): invalid control ID found in array with statics IDs: " << staticsArr[i]); - } - SelectObject(hDC, hOldFont); - HANDLES(ReleaseDC(hWindow, hDC)); -} - -// -// **************************************************************************** -// ArrangeHorizontalLines -// - -BOOL CALLBACK FindHorizLines(HWND hwnd, LPARAM lParam) -{ - RECT r; - GetClientRect(hwnd, &r); - if (r.bottom == 0) // horizontal line 0 points high - { - LONG style = GetWindowLong(hwnd, GWL_STYLE); - if ((style & SS_TYPEMASK) == SS_ETCHEDHORZ) - { - char className[300]; - if (GetClassName(hwnd, className, _countof(className)) && stricmp(className, "Static") == 0) - ((TDirectArray*)lParam)->Add(hwnd); - } - } - return TRUE; -} - -BOOL CALLBACK FindGroupBoxes(HWND hwnd, LPARAM lParam) -{ - LONG style = GetWindowLong(hwnd, GWL_STYLE); - if ((style & BS_TYPEMASK) == BS_GROUPBOX) - { - char className[300]; - if (GetClassName(hwnd, className, _countof(className)) && stricmp(className, "Button") == 0) - ((TDirectArray*)lParam)->Add(hwnd); - } - return TRUE; -} - -struct CDataForFindHorizLineLabel -{ - HWND Line; - RECT LineRect; - HWND Label; - BOOL IsCheckOrRadioBox; - BOOL NoPrefix; - BOOL MoreLabels; -}; - -BOOL CALLBACK FindHorizLineLabel(HWND hwnd, LPARAM lParam) -{ - CDataForFindHorizLineLabel* data = (CDataForFindHorizLineLabel*)lParam; - if (data->Line != hwnd) // skip the line for which we search a label - { - RECT r; - GetWindowRect(hwnd, &r); - if (r.top <= data->LineRect.top && r.bottom >= data->LineRect.bottom) // label vertically overlaps the line - { - if (r.left < data->LineRect.left && r.right < data->LineRect.right) // label starts before the line + line ends after the label - { - char className[300]; - if (GetClassName(hwnd, className, _countof(className))) - { - LONG style = GetWindowLong(hwnd, GWL_STYLE); - if (stricmp(className, "Static") == 0) // it's a left-aligned static text - { - if ((style & SS_TYPEMASK) == SS_LEFT || - (style & SS_TYPEMASK) == SS_SIMPLE || - (style & SS_TYPEMASK) == SS_LEFTNOWORDWRAP) - { - data->IsCheckOrRadioBox = FALSE; - data->NoPrefix = (style & SS_NOPREFIX) != 0; - if (data->Label != NULL) - data->MoreLabels = TRUE; - else - data->Label = hwnd; - } - } - else - { - if (stricmp(className, "Button") == 0) // it's a button (check box, radio button, or push button) - { - if (((style & BS_TYPEMASK) == BS_CHECKBOX || - (style & BS_TYPEMASK) == BS_AUTOCHECKBOX || - (style & BS_TYPEMASK) == BS_AUTO3STATE || - (style & BS_TYPEMASK) == BS_3STATE || - (style & BS_TYPEMASK) == BS_RADIOBUTTON || - (style & BS_TYPEMASK) == BS_AUTORADIOBUTTON) && - (style & BS_PUSHLIKE) == 0) - { - data->IsCheckOrRadioBox = TRUE; - data->NoPrefix = FALSE; - if (data->Label != NULL) - data->MoreLabels = TRUE; - else - data->Label = hwnd; - } - } - } - } - } - } - } - return TRUE; -} - -struct CDataForFindGroupBoxLabel -{ - HWND GroupBox; - RECT GroupBoxRect; - HWND Label; - BOOL MoreLabels; -}; - -BOOL CALLBACK FindGroupBoxLabel(HWND hwnd, LPARAM lParam) -{ - CDataForFindGroupBoxLabel* data = (CDataForFindGroupBoxLabel*)lParam; - if (data->GroupBox != hwnd) // skip the group box for which we search a label - { - RECT r; - GetWindowRect(hwnd, &r); - if (r.top <= data->GroupBoxRect.top && r.bottom >= data->GroupBoxRect.top && // label vertically overlaps the top line of the group box - r.left >= data->GroupBoxRect.left && r.right <= data->GroupBoxRect.right) // label horizontally lies on the group box - { - char className[300]; - if (GetClassName(hwnd, className, _countof(className)) && - stricmp(className, "Button") == 0) // it's a button (check box, radio button, or push button) - { - LONG style = GetWindowLong(hwnd, GWL_STYLE); - if (((style & BS_TYPEMASK) == BS_CHECKBOX || - (style & BS_TYPEMASK) == BS_AUTOCHECKBOX || - (style & BS_TYPEMASK) == BS_AUTO3STATE || - (style & BS_TYPEMASK) == BS_3STATE || - (style & BS_TYPEMASK) == BS_RADIOBUTTON || - (style & BS_TYPEMASK) == BS_AUTORADIOBUTTON) && - (style & BS_PUSHLIKE) == 0) - { - if (data->Label != NULL) - data->MoreLabels = TRUE; - else - data->Label = hwnd; - } - } - } - } - return TRUE; -} - -void ArrangeHorizontalLines(HWND hWindow) -{ - TDirectArray horizLines(10, 5); - EnumChildWindows(hWindow, FindHorizLines, (LPARAM)&horizLines); - - HDC hDC = HANDLES(GetDC(hWindow)); - int spaceWidth = -1; - RECT windowRect; - GetWindowRect(hWindow, &windowRect); - for (int i = 0; i < horizLines.Count; i++) - { - CDataForFindHorizLineLabel data; - data.Line = horizLines[i]; - data.Label = NULL; - data.MoreLabels = FALSE; - data.IsCheckOrRadioBox = FALSE; - data.NoPrefix = FALSE; - GetWindowRect(data.Line, &data.LineRect); - EnumChildWindows(hWindow, FindHorizLineLabel, (LPARAM)&data); - if (data.Label != NULL) - { - if (data.MoreLabels) - TRACE_E("ArrangeHorizontalLines(): unexpected situation: more labels for one horizontal line!"); - else - { - HFONT hFont = (HFONT)SendMessage(data.Label, WM_GETFONT, 0, 0); - HFONT hOldFont = (HFONT)SelectObject(hDC, hFont); - if (spaceWidth == -1) - { - SIZE sz; - GetTextExtentPoint32(hDC, " ", 1, &sz); - spaceWidth = sz.cx; - } - RECT labelRect; - GetWindowRect(data.Label, &labelRect); - WCHAR text[1000]; - GetWindowTextW(data.Label, text, _countof(text)); - int textLen = (int)wcslen(text); - if (textLen > 0 && text[textLen - 1] == ' ') - text[--textLen] = 0; - DWORD dtFlags = DT_CALCRECT | DT_LEFT | DT_SINGLELINE | (data.NoPrefix ? DT_NOPREFIX : 0); - RECT txtR = labelRect; - txtR.right -= txtR.left; - txtR.bottom -= txtR.top; - txtR.left = txtR.top = 0; - DrawTextW(hDC, text, textLen, &txtR, dtFlags); - SelectObject(hDC, hOldFont); - - POINT p; - p.x = labelRect.left; - p.y = labelRect.top; - ScreenToClient(hWindow, &p); - POINT p2; - p2.x = data.LineRect.left; - p2.y = data.LineRect.top; - ScreenToClient(hWindow, &p2); - - if (labelRect.right + 5 * spaceWidth < data.LineRect.left) - TRACE_E("ArrangeHorizontalLines(): unexpected situation: horizontal line begins more than five spaces behind label, ignoring label..."); - else // we shorten the label so it does not cover the line and we extend the line to the label - { - if (data.IsCheckOrRadioBox) - { - LOGFONT lf; - GetObject(hFont, sizeof(LOGFONT), &lf); - // on Win7 I found that symbol sizes (e.g. radio/check boxes) change - // only for 100%, 125%, 150% and 200% DPI, intermediate DPIs always use symbols - // from the lower "whole" DPI; we therefore measure all intermediate font sizes - // to cover all possible DPIs - int boxSize = -lf.lfHeight < 13 ? 16 : // < 125% DPI - -lf.lfHeight < 16 ? 20 - : // < 150% DPI - -lf.lfHeight < 21 ? 25 - : 27; // < 200% DPI : == 200% DPI - if (p2.x + (data.LineRect.right - data.LineRect.left) > p.x + boxSize + txtR.right + 2 * spaceWidth) - { - MoveWindow(data.Label, p.x, p.y, boxSize + txtR.right + spaceWidth, labelRect.bottom - labelRect.top, TRUE); - MoveWindow(data.Line, p.x + boxSize + txtR.right + 2 * spaceWidth, p2.y, - p2.x + (data.LineRect.right - data.LineRect.left) - (p.x + boxSize + txtR.right + 2 * spaceWidth), - data.LineRect.bottom - data.LineRect.top, TRUE); - } - } - else - { - if (p2.x + (data.LineRect.right - data.LineRect.left) > p.x + txtR.right + 2 * spaceWidth) - { - MoveWindow(data.Label, p.x, p.y, txtR.right + spaceWidth, labelRect.bottom - labelRect.top, TRUE); - MoveWindow(data.Line, p.x + txtR.right + 2 * spaceWidth, p2.y, - p2.x + (data.LineRect.right - data.LineRect.left) - (p.x + txtR.right + 2 * spaceWidth), - data.LineRect.bottom - data.LineRect.top, TRUE); - } - } - } - } - } - else - { - POINT p; - p.x = data.LineRect.left; - p.y = data.LineRect.top; - ScreenToClient(hWindow, &p); - RECT rect = {0, 0, 20, 1}; - MapDialogRect(hWindow, &rect); - if (p.x > rect.right) - TRACE_E("ArrangeHorizontalLines(): label not found, but line begins more than 20 dlg-units from left side of dialog!"); - } - } - - // alignment of check boxes and radio buttons used as labels on group boxes - horizLines.DestroyMembers(); - EnumChildWindows(hWindow, FindGroupBoxes, (LPARAM)&horizLines); - for (int i = 0; i < horizLines.Count; i++) - { - CDataForFindGroupBoxLabel data; - data.GroupBox = horizLines[i]; - data.Label = NULL; - data.MoreLabels = FALSE; - GetWindowRect(data.GroupBox, &data.GroupBoxRect); - EnumChildWindows(hWindow, FindGroupBoxLabel, (LPARAM)&data); - if (data.Label != NULL) - { - if (data.MoreLabels) - TRACE_E("ArrangeHorizontalLines(): unexpected situation: more labels for one group box!"); - else - { - HFONT hFont = (HFONT)SendMessage(data.Label, WM_GETFONT, 0, 0); - HFONT hOldFont = (HFONT)SelectObject(hDC, hFont); - if (spaceWidth == -1) - { - SIZE sz; - GetTextExtentPoint32(hDC, " ", 1, &sz); - spaceWidth = sz.cx; - } - RECT labelRect; - GetWindowRect(data.Label, &labelRect); - WCHAR text[1000]; - GetWindowTextW(data.Label, text, _countof(text)); - int textLen = (int)wcslen(text); - if (textLen > 0 && text[textLen - 1] == ' ') - text[--textLen] = 0; - DWORD dtFlags = DT_CALCRECT | DT_LEFT | DT_SINGLELINE; - RECT txtR = labelRect; - txtR.right -= txtR.left; - txtR.bottom -= txtR.top; - txtR.left = txtR.top = 0; - DrawTextW(hDC, text, textLen, &txtR, dtFlags); - SelectObject(hDC, hOldFont); - - POINT p; - p.x = labelRect.left; - p.y = labelRect.top; - ScreenToClient(hWindow, &p); - // shorten the check box or radio button according to the content and current font - LOGFONT lf; - GetObject(hFont, sizeof(LOGFONT), &lf); - // on Win7 I found that symbol sizes (e.g. radio/check boxes) change - // only for 100%, 125%, 150% and 200% DPI; intermediate DPIs always use symbols - // from the lower "whole" DPI, so we measure all intermediate font sizes - // this should cover all possible DPIs - int boxSize = -lf.lfHeight < 13 ? 16 : // < 125% DPI - -lf.lfHeight < 16 ? 20 - : // < 150% DPI - -lf.lfHeight < 21 ? 25 - : 27; // < 200% DPI : == 200% DPI - MoveWindow(data.Label, p.x, p.y, boxSize + txtR.right + 2 * spaceWidth, labelRect.bottom - labelRect.top, TRUE); - } - } - } - HANDLES(ReleaseDC(hWindow, hDC)); -} - -// -// **************************************************************************** -// GetWindowFontHeight -// - -int GetWindowFontHeight(HWND hWindow) -{ - HFONT hFont = (HFONT)SendMessage(hWindow, WM_GETFONT, 0, 0); - LOGFONT lf; - if (GetObject(hFont, sizeof(lf), &lf) == 0) - { - DWORD err = GetLastError(); - TRACE_E("GetObject() failed! err=" << err); - return -12; - } - return lf.lfHeight; - // retrieving the size via GetTextMetrics() returns -19 for 150% DPI under Windows 7, - // while GetObject() returns -16, which is correct; creating a font with -19 - // results in text that is too large - // TEXTMETRIC tm; - // HDC hDC = HANDLES(GetDC(hWindow)); - // HFONT hOldFont = (HFONT)SelectObject(hDC, hFont); - // GetTextMetrics(hDC, &tm); - // SelectObject(hDC, hOldFont); - // HANDLES(ReleaseDC(hWindow, hDC)); - // return tm.tmHeight; -} - -// -// **************************************************************************** -// CreateCheckboxImagelist() -// - -HIMAGELIST CreateCheckboxImagelist(int itemSize) -{ - HIMAGELIST hIL = ImageList_Create(itemSize, itemSize, GetImageListColorFlags() | ILC_MASK, 0, 1); - - HDC hDC = HANDLES(GetDC(NULL)); - HBITMAP hBitmap = HANDLES(CreateCompatibleBitmap(hDC, itemSize, itemSize)); - HDC hMemDC = HANDLES(CreateCompatibleDC(hDC)); - HBITMAP hOldBitmap = (HBITMAP)SelectObject(hMemDC, hBitmap); - RECT r = {0, 0, itemSize, itemSize}; - - BOOL fallBack = TRUE; - if (IsAppThemed()) - { - HTHEME hTheme = OpenThemeData(NULL, L"BUTTON"); - if (hTheme != NULL) - { - FillRect(hMemDC, &r, (HBRUSH)(COLOR_WINDOW + 1)); - SIZE sz; - GetThemePartSize(hTheme, hMemDC, BP_CHECKBOX, CBS_CHECKEDNORMAL, NULL, TS_TRUE, &sz); - if (sz.cx < r.right && sz.cy < r.bottom) - { - r.left = (r.right - sz.cx) / 2; - r.top = (r.bottom - sz.cy) / 2; - r.right = r.left + sz.cx; - r.bottom = r.top + sz.cy; - } - DrawThemeBackground(hTheme, hMemDC, BP_CHECKBOX, CBS_UNCHECKEDNORMAL, &r, NULL); - SelectObject(hMemDC, hOldBitmap); - ImageList_Add(hIL, hBitmap, NULL); - - HBITMAP hOldBitmap = (HBITMAP)SelectObject(hMemDC, hBitmap); - DrawThemeBackground(hTheme, hMemDC, BP_CHECKBOX, CBS_CHECKEDNORMAL, &r, NULL); - SelectObject(hMemDC, hOldBitmap); - ImageList_Add(hIL, hBitmap, NULL); - - CloseThemeData(hTheme); - fallBack = FALSE; - } - } - if (fallBack) - { - FillRect(hMemDC, &r, (HBRUSH)(COLOR_WINDOW + 1)); - DrawFrameControl(hMemDC, &r, DFC_BUTTON, DFCS_BUTTONCHECK); - SelectObject(hMemDC, hOldBitmap); - ImageList_Add(hIL, hBitmap, NULL); - - hOldBitmap = (HBITMAP)SelectObject(hMemDC, hBitmap); - FillRect(hMemDC, &r, (HBRUSH)(COLOR_WINDOW + 1)); - DrawFrameControl(hMemDC, &r, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_CHECKED); - SelectObject(hMemDC, hOldBitmap); - ImageList_Add(hIL, hBitmap, NULL); - } - - HANDLES(DeleteObject(hBitmap)); - HANDLES(DeleteDC(hMemDC)); - HANDLES(ReleaseDC(NULL, hDC)); - - return hIL; -} - -// -// **************************************************************************** -// SalLoadIcon() -// - -HICON SalLoadIcon(HINSTANCE hInst, LPCTSTR iconName, CIconSizeEnum iconSize) -{ - int width = IconSizes[iconSize]; - HICON hIcon = NULL; - HRESULT hres = LoadIconWithScaleDown(hInst, (PCWSTR)iconName, width, width, &hIcon); - if (hres != S_OK) - { - DWORD err = GetLastError(); - TRACE_E("LoadIconWithScaleDown() failed. hInst=" << hInst << " iconName=" << iconName << " err=" << err); - } - else - { - HANDLES_ADD(__htIcon, __hoLoadIcon, hIcon); - } - return hIcon; -} +// This file has been split into: +// gui_progressbar.cpp -- CGuiBitmap helper + CProgressBar +// gui_statictext.cpp -- CStaticText +// gui_controls.cpp -- CHyperLink, CColorRectangle, CColorGraph, CButton, +// CColorArrowButton, CToolbarHeader, and all remaining helpers \ No newline at end of file diff --git a/src/gui_bitmap.h b/src/gui_bitmap.h new file mode 100644 index 00000000..91f08bfa --- /dev/null +++ b/src/gui_bitmap.h @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: 2023 Taskscape Ltd +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "bitmap.h" + +// Internal helper: memory-DC bitmap used for flicker-free drawing of disabled buttons. +class CGuiBitmap : public CBitmap +{ +public: + HBITMAP CreateCopyBitmap() + { + CALL_STACK_MESSAGE1("CSharedBitmapAndDC::CreateCopyBitmap()"); + HDC hdc = HANDLES(GetDC(NULL)); + + HBITMAP HCopyBitmap = HANDLES(CreateCompatibleBitmap(hdc, Width, Height)); + + HDC hMemDC = HANDLES(CreateCompatibleDC(hdc)); + HBITMAP hOldBitmap = (HBITMAP)SelectObject(hMemDC, HCopyBitmap); + + HBITMAP hOld = (HBITMAP)SelectObject(HMemDC, HOldBmp); + + HIMAGELIST hImageList = ImageList_Create(Width, Height, ILC_MASK | GetImageListColorFlags(), 1, 0); + ImageList_AddMasked(hImageList, HBmp, GetSysColor(COLOR_BTNFACE)); // j.r. the color was hardcoded to 192,192,192 here, which caused issues with the XP look + RECT r; + r.left = 0; + r.top = 0; + r.right = Width; + r.bottom = Height; + FillRect(hMemDC, &r, (HBRUSH)HANDLES(GetStockObject(WHITE_BRUSH))); + ImageList_Draw(hImageList, 0, hMemDC, 0, 0, ILD_TRANSPARENT); + ImageList_Destroy(hImageList); + + SelectObject(HMemDC, hOld); + + SelectObject(hMemDC, hOldBitmap); + HANDLES(DeleteDC(hMemDC)); + + HANDLES(ReleaseDC(NULL, hdc)); + return HCopyBitmap; + } +}; diff --git a/src/gui_controls.cpp b/src/gui_controls.cpp new file mode 100644 index 00000000..944ac377 --- /dev/null +++ b/src/gui_controls.cpp @@ -0,0 +1,2608 @@ +// SPDX-FileCopyrightText: 2023 Taskscape Ltd +// SPDX-License-Identifier: GPL-2.0-or-later +// CommentsTranslationProject: TRANSLATED + +#include "precomp.h" + +#include "svg.h" +#include "gui.h" +#include "gui_bitmap.h" +#include "toolbar.h" +#include "menu.h" +#include "tooltip.h" +#include +#include + +#include "nanosvg\nanosvg.h" +#include "nanosvg\nanosvgrast.h" + +#include "mainwnd.h" + + +//**************************************************************************** +// +// CHyperLink +// + +CHyperLink::CHyperLink(HWND hDlg, int ctrlID, DWORD flags) + : CStaticText(hDlg, ctrlID, flags) +{ + File[0] = 0; + Command = 0; + HDialog = hDlg; + + // adjust the style so we receive messages + DWORD style = (DWORD)GetWindowLongPtr(HWindow, GWL_STYLE); + style |= SS_NOTIFY; + SetWindowLongPtr(HWindow, GWL_STYLE, style); +} + +void CHyperLink::SetActionOpen(const char* file) +{ + EnableHintToolTip(FALSE); + lstrcpyn(File, file, MAX_PATH); +} + +void CHyperLink::SetActionPostCommand(WORD command) +{ + EnableHintToolTip(FALSE); + Command = command; +} + +BOOL CHyperLink::SetActionShowHint(const char* text) +{ + EnableHintToolTip(TRUE); + if (text == NULL) + return TRUE; + else + return SetToolTipText(text); +} + +BOOL CHyperLink::ExecuteIt() +{ + BOOL ret = TRUE; + if (File[0] != 0) + { + // do not switch to shellExecuteWnd, we use BugReport + int err = (int)(INT_PTR)ShellExecute(HWindow, "open", File, NULL, NULL, SW_SHOWNORMAL); + if (err <= 32) + { + ret = FALSE; + SalMessageBox(HDialog, GetErrorText(err), LoadStr(IDS_ERRORTITLE), MB_OK | MB_ICONEXCLAMATION); + } + } + if (Command != 0) + { + PostMessage(HDialog, WM_COMMAND, Command, 0); + } + return ret; +} + +void CHyperLink::OnContextMenu(int x, int y) +{ + /* used by the export_mnu.py script that generates salmenu.mnu for the Translator + keep synchronized with the InsertMenu() call below... +MENU_TEMPLATE_ITEM HyperLinkMenu[] = +{ + {MNTT_PB, 0 + {MNTT_IT, IDS_COPYTOCLIPBOARD + {MNTT_PE, 0 +}; +*/ + HMENU hMenu = CreatePopupMenu(); + InsertMenu(hMenu, 0, MF_BYPOSITION, 1, LoadStr(IDS_COPYTOCLIPBOARD)); + DWORD cmd = TrackPopupMenuEx(hMenu, TPM_RETURNCMD | TPM_LEFTALIGN | TPM_RIGHTBUTTON, + x, y, HWindow, NULL); + DestroyMenu(hMenu); + if (cmd == 1) + { + CopyTextToClipboard(Text, -1, TRUE, HWindow); + } +} + +LRESULT +CHyperLink::WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + CALL_STACK_MESSAGE4("CHyperLink::WindowProc(0x%X, 0x%IX, 0x%IX)", uMsg, wParam, lParam); + switch (uMsg) + { + case WM_SETCURSOR: + { + POINT p; + DWORD messagePos = GetMessagePos(); + p.x = GET_X_LPARAM(messagePos); + p.y = GET_Y_LPARAM(messagePos); + if (TextHitTest(&p)) + SetHandCursor(); + else + SetCursor(LoadCursor(NULL, IDC_ARROW)); + return TRUE; + } + + case WM_LBUTTONDOWN: + case WM_RBUTTONDOWN: + { + POINT p; + DWORD messagePos = GetMessagePos(); + p.x = GET_X_LPARAM(messagePos); + p.y = GET_Y_LPARAM(messagePos); + if (TextHitTest(&p)) + { + SetCapture(HWindow); + if (GetWindowLongPtr(HWindow, GWL_STYLE) & WS_TABSTOP) + SetFocus(HWindow); + } + break; + } + + case WM_LBUTTONUP: + { + if (GetCapture() != HWindow) + break; + ReleaseCapture(); + POINT p; + DWORD messagePos = GetMessagePos(); + p.x = GET_X_LPARAM(messagePos); + p.y = GET_Y_LPARAM(messagePos); + if (TextHitTest(&p)) + ExecuteIt(); + break; + } + + case WM_RBUTTONUP: + { + if (GetCapture() != HWindow) + break; + ReleaseCapture(); + POINT p; + DWORD messagePos = GetMessagePos(); + p.x = GET_X_LPARAM(messagePos); + p.y = GET_Y_LPARAM(messagePos); + if (TextHitTest(&p)) + OnContextMenu(p.x, p.y); + break; + } + + case WM_SYSKEYDOWN: + case WM_KEYDOWN: + { + if (wParam == VK_SPACE || wParam == VK_RETURN) + ExecuteIt(); + + BOOL controlPressed = (GetKeyState(VK_CONTROL) & 0x8000) != 0; + BOOL altPressed = (GetKeyState(VK_MENU) & 0x8000) != 0; + BOOL shiftPressed = (GetKeyState(VK_SHIFT) & 0x8000) != 0; + if ((wParam == VK_F10 && shiftPressed || wParam == VK_APPS)) + { + RECT r; + GetWindowRect(HWindow, &r); + OnContextMenu(r.left, r.bottom); + } + + // support for our message boxes, forward Ctrl+C + // sending WM_COPY in a standard dialog should not be an issue + if (!shiftPressed && controlPressed && !altPressed) + { + if (wParam == 'C') + { + HWND hParent = GetParent(HWindow); + if (hParent != NULL) + PostMessage(hParent, WM_COPY, 0, 0); + } + } + + break; + } + } + + return CStaticText::WindowProc(uMsg, wParam, lParam); +} + +//**************************************************************************** +// +// CButton +// + +CColorRectangle::CColorRectangle(HWND hDlg, int ctrlID, CObjectOrigin origin) + : CWindow(hDlg, ctrlID, origin) +{ + Color = RGB(255, 255, 128); +} + +void CColorRectangle::SetColor(COLORREF color) +{ + Color = color; + InvalidateRect(HWindow, NULL, FALSE); + UpdateWindow(HWindow); +} + +void CColorRectangle::PaintFace(HDC hdc) +{ + RECT r; + GetClientRect(HWindow, &r); + + COLORREF oldBkColor = SetBkColor(hdc, Color); + ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &r, NULL, 0, NULL); + SetBkColor(hdc, oldBkColor); +} + +LRESULT +CColorRectangle::WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) + { + case WM_PAINT: + { + PAINTSTRUCT ps; + HDC hdc = HANDLES(BeginPaint(HWindow, &ps)); + if (hdc != NULL) + { + PaintFace(hdc); + HANDLES(EndPaint(HWindow, &ps)); + } + return 0; + } + } + return CWindow::WindowProc(uMsg, wParam, lParam); +} + +//**************************************************************************** +// +// CColorGraph +// + +CColorGraph::CColorGraph(HWND hDlg, int ctrlID, CObjectOrigin origin) + : CWindow(hDlg, ctrlID, origin) +{ + Color1Light = NULL; + Color1Dark = NULL; + Color2Light = NULL; + Color2Dark = NULL; + UsedProc = 0; + + GetClientRect(HWindow, &ClientRect); +} + +CColorGraph::~CColorGraph() +{ + if (Color1Light != NULL) + HANDLES(DeleteObject(Color1Light)); + if (Color1Dark != NULL) + HANDLES(DeleteObject(Color1Dark)); + if (Color2Light != NULL) + HANDLES(DeleteObject(Color2Light)); + if (Color2Dark != NULL) + HANDLES(DeleteObject(Color2Dark)); +} + +void CColorGraph::SetColor(COLORREF color1Light, COLORREF color1Dark, + COLORREF color2Light, COLORREF color2Dark) +{ + Color1Light = HANDLES(CreateSolidBrush(color1Light)); + Color1Dark = HANDLES(CreateSolidBrush(color1Dark)); + Color2Light = HANDLES(CreateSolidBrush(color2Light)); + Color2Dark = HANDLES(CreateSolidBrush(color2Dark)); + + InvalidateRect(HWindow, NULL, FALSE); + UpdateWindow(HWindow); +} + +void CColorGraph::SetUsed(double used) +{ + UsedProc = used; + InvalidateRect(HWindow, NULL, FALSE); + UpdateWindow(HWindow); +} + +#define GRAPH_HEIGHT 4 +#define PI 3.141592653589793 + +void CColorGraph::PaintFace(HDC hdc) +{ + CALL_STACK_MESSAGE1("CColorGraph::PaintFace()"); + RECT r; + GetClientRect(HWindow, &r); + + double beta = UsedProc * 360 * PI / 180; + + double elX0 = r.right / 2; + double elY0 = (r.bottom - GRAPH_HEIGHT) / 2; + double elA = r.right / 2; + double elB = (r.bottom - GRAPH_HEIGHT) / 2; + double elX = elX0 + elA * cos(beta); + double elY = elB * sin(beta); + + // draw the bottom part + HBRUSH hOldBrush = (HBRUSH)GetCurrentObject(hdc, OBJ_BRUSH); + HPEN hBlackPen = HANDLES(CreatePen(PS_SOLID, 0, RGB(0, 0, 0))); + HPEN hOldPen = (HPEN)SelectObject(hdc, hBlackPen); + + if (UsedProc < 0.5) + SelectObject(hdc, Color1Dark); // free color + else + SelectObject(hdc, Color2Dark); // used color + + Ellipse(hdc, r.left, r.top + GRAPH_HEIGHT, r.right, r.bottom); + + // handle variant (b) + if (UsedProc > 0 && UsedProc < 0.5) + { + SelectObject(hdc, Color2Dark); // used color + int x = (int)elX; + int y1 = (int)(elY0 + GRAPH_HEIGHT + elY); + int y2 = (int)(elY0 + GRAPH_HEIGHT - elY); + Chord(hdc, r.left, r.top + GRAPH_HEIGHT, r.right, r.bottom, + x, y1, x, y2); + } + + // draw the top part + if (UsedProc >= 0 && UsedProc < 1) + SelectObject(hdc, Color1Light); // free color + else + SelectObject(hdc, Color2Light); // used color + + Ellipse(hdc, r.left, r.top, r.right, r.bottom - GRAPH_HEIGHT); + + if (UsedProc > 0 && UsedProc < 1) + { + SelectObject(hdc, Color2Light); // used color + int y1 = (int)(elY0 + elY); + int y2 = (int)elY0; + if (y1 == y2 && UsedProc < 0.1) // a messy solution + SelectObject(hdc, Color1Light); // used color + Pie(hdc, r.left, r.top, r.right, r.bottom - GRAPH_HEIGHT, + (int)elX, y1, r.right, y2); + } + + SelectObject(hdc, hOldBrush); + SelectObject(hdc, hOldPen); + HANDLES(DeleteObject(hBlackPen)); +} + +/* +LRESULT +CColorGraph::WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + CALL_STACK_MESSAGE4("CColorGraph::WindowProc(0x%X, 0x%IX, 0x%IX)", uMsg, wParam, lParam); + switch (uMsg) + { + case WM_PAINT: + { + PAINTSTRUCT ps; + HDC hdc = HANDLES(BeginPaint(HWindow, &ps)); + FillRect(hdc, &ClientRect, (HBRUSH)(COLOR_BTNFACE + 1)); + PaintFace(hdc); + HANDLES(EndPaint(HWindow, &ps)); + return 0; + } + } + return CWindow::WindowProc(uMsg, wParam, lParam); +} +*/ + +// this version with memDC works even on XP (the one above has jagged curves on Windows XP) + +LRESULT +CColorGraph::WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + CALL_STACK_MESSAGE4("CColorGraph::WindowProc(0x%X, 0x%IX, 0x%IX)", uMsg, wParam, lParam); + switch (uMsg) + { + case WM_PAINT: + { + PAINTSTRUCT ps; + HDC hdc = HANDLES(BeginPaint(HWindow, &ps)); + if (hdc != NULL) + { + CBitmap bitmap; + bitmap.CreateBmp(hdc, ClientRect.right, ClientRect.bottom); + + HBRUSH hBrush = (HBRUSH)(COLOR_BTNFACE + 1); + FillRect(bitmap.HMemDC, &ClientRect, hBrush); + + PaintFace(bitmap.HMemDC); + + BitBlt(hdc, + 0, 0, + ClientRect.right, ClientRect.bottom, + bitmap.HMemDC, + 0, 0, + SRCCOPY); + + HANDLES(EndPaint(HWindow, &ps)); + } + return 0; + } + } + return CWindow::WindowProc(uMsg, wParam, lParam); +} + +//**************************************************************************** +// +// CButton +// + +CButton::CButton(HWND hDlg, int ctrlID, DWORD flags, CObjectOrigin origin) + : CWindow(hDlg, ctrlID, origin) +{ + Flags = flags; + DropDownPressed = FALSE; + Checked = FALSE; + ButtonPressed = FALSE; + Pressed = FALSE; + DefPushButton = FALSE; + Captured = FALSE; + Space = FALSE; + MouseIsTracked = FALSE; + ToolTipText = NULL; + HToolTipNW = NULL; + ToolTipID = 0; + Hot = FALSE; + GetClientRect(HWindow, &ClientRect); + DropDownUpTime = GetTickCount(); + UIState = (WORD)SendMessage(HWindow, WM_QUERYUISTATE, 0, 0); +} + +CButton::~CButton() +{ + if (ToolTipText != NULL) + free(ToolTipText); +} + +DWORD +CButton::GetFlags() +{ + return Flags; +} + +void CButton::SetFlags(DWORD flags, BOOL updateWindow) +{ + Flags = flags; + if (HWindow != NULL) + { + InvalidateRect(HWindow, NULL, FALSE); + if (updateWindow) + UpdateWindow(HWindow); + } +} + +void CButton::RePaint() +{ + InvalidateRect(HWindow, NULL, FALSE); + UpdateWindow(HWindow); +} + +void CButton::NotifyParent(WORD notify) +{ + int id = GetWindowLong(HWindow, GWL_ID); + PostMessage(GetParent(HWindow), WM_COMMAND, + (WPARAM)(id | ((WPARAM)notify << 16)), (LPARAM)HWindow); +} + +void CButton::PaintFrame(HDC hDC, const RECT* r, BOOL down) +{ + if (/*!(ButtonPressed && Pressed) && */ (Flags & BTF_CHECKBOX) && Checked) + { + // darkest on the left and top + HPEN hOldPen = (HPEN)SelectObject(hDC, WndFramePen); + MoveToEx(hDC, r->left, r->bottom - 2, NULL); + LineTo(hDC, r->left, r->top); + LineTo(hDC, r->right - 1, r->top); + // dark on the left and top inside + SelectObject(hDC, BtnShadowPen); + MoveToEx(hDC, r->left + 1, r->bottom - 3, NULL); + LineTo(hDC, r->left + 1, r->top + 1); + LineTo(hDC, r->right - 2, r->top + 1); + // a bit darker on the right and bottom inside + SelectObject(hDC, Btn3DLightPen); + MoveToEx(hDC, r->right - 2, r->top + 1, NULL); + LineTo(hDC, r->right - 2, r->bottom - 2); + LineTo(hDC, r->left, r->bottom - 2); + // light on the right and bottom + SelectObject(hDC, BtnHilightPen); + MoveToEx(hDC, r->left, r->bottom - 1, NULL); + LineTo(hDC, r->right - 1, r->bottom - 1); + LineTo(hDC, r->right - 1, -1); + SelectObject(hDC, hOldPen); + return; + } + + if (down) + { + HPEN hOldPen = (HPEN)SelectObject(hDC, BtnShadowPen); + HBRUSH hOldBrush = (HBRUSH)SelectObject(hDC, HANDLES(GetStockObject(NULL_BRUSH))); + Rectangle(hDC, r->left, r->top, r->right, r->bottom); + SelectObject(hDC, hOldBrush); + SelectObject(hDC, hOldPen); + } + else + { + // light on the left and top + HPEN hOldPen = (HPEN)SelectObject(hDC, BtnHilightPen); + MoveToEx(hDC, r->left, r->bottom - 2, NULL); + LineTo(hDC, r->left, r->top); + LineTo(hDC, r->right - 1, r->top); + // a bit darker inside + SelectObject(hDC, Btn3DLightPen); + MoveToEx(hDC, r->left + 1, r->bottom - 3, NULL); + LineTo(hDC, r->left + 1, r->top + 1); + LineTo(hDC, r->right - 2, r->top + 1); + // dark on the right and bottom + SelectObject(hDC, BtnShadowPen); + MoveToEx(hDC, r->right - 2, r->top + 1, NULL); + LineTo(hDC, r->right - 2, r->bottom - 2); + LineTo(hDC, r->left, r->bottom - 2); + // darkest on the outside + SelectObject(hDC, WndFramePen); + MoveToEx(hDC, r->left, r->bottom - 1, NULL); + LineTo(hDC, r->right - 1, r->bottom - 1); + LineTo(hDC, r->right - 1, -1); + SelectObject(hDC, hOldPen); + } +} + +void CButton::PaintDrop(HDC hDC, const RECT* r, BOOL enabled) +{ + SIZE sz; + SVGArrowDropDown.GetSize(&sz); + SVGArrowDropDown.AlphaBlend(hDC, + r->left + (r->right - r->left - sz.cx) / 2, + r->top + (r->bottom - r->top - sz.cy) / 2, + -1, -1, + enabled ? SVGSTATE_ENABLED : SVGSTATE_DISABLED); +} + +int CButton::GetDropPartWidth() +{ + return (int)((double)SVGArrowDropDown.GetWidth() * 1.6); +} + +int CButton::HitTest(LPARAM lParam) +{ + POINT p; + p.x = LOWORD(lParam); + p.y = HIWORD(lParam); + if (!PtInRect(&ClientRect, p)) + return 0; // nowhere + + if (Flags & BTF_DROPDOWN) + { + RECT r = ClientRect; + r.left = r.right - GetDropPartWidth() - 1; + if (PtInRect(&r, p)) + return 2; // drop down + } + + return 1; // button +} + +void CButton::PaintFace(HDC hdc, const RECT* rect, BOOL enabled) +{ + RECT r = *rect; + if (Flags & BTF_RIGHTARROW) + r.right -= (int)((double)SVGArrowRight.GetWidth() * 1.5); + if (Flags & BTF_DROPDOWN) + r.right -= GetDropPartWidth(); + if (Flags & BTF_MORE) + r.right -= (int)((double)SVGArrowMore.GetWidth() * 1.3); + + DWORD wndStyle = (DWORD)GetWindowLongPtr(HWindow, GWL_STYLE); + if (wndStyle & BS_ICON) + { + // icon + + HICON hIcon = (HICON)SendMessage(HWindow, BM_GETIMAGE, IMAGE_ICON, 0); + if (hIcon != NULL) + { + ICONINFO iconInfo; + if (GetIconInfo(hIcon, &iconInfo)) + { + BITMAP bm; + GetObject(iconInfo.hbmColor, sizeof(bm), &bm); + if (enabled) + { + DrawIcon(hdc, r.left + (r.right - r.left - bm.bmWidth) / 2, + r.top + (r.bottom - r.top - bm.bmHeight) / 2, hIcon); + } + else + { + // disabled + CGuiBitmap tmpFaceBitmap; + tmpFaceBitmap.CreateBmp(hdc, bm.bmWidth, bm.bmHeight); + RECT fillR = {0}; + fillR.right = bm.bmWidth; + fillR.bottom = bm.bmHeight; + FillRect(tmpFaceBitmap.HMemDC, &fillR, (HBRUSH)(COLOR_BTNFACE + 1)); + DrawIcon(tmpFaceBitmap.HMemDC, 0, 0, hIcon); + HBITMAP hBmp = tmpFaceBitmap.CreateCopyBitmap(); + DrawState(hdc, NULL, NULL, (LPARAM)hBmp, 0, + r.left + (r.right - r.left - bm.bmWidth) / 2, + r.top + (r.bottom - r.top - bm.bmHeight) / 2, + bm.bmWidth, bm.bmHeight, + DST_BITMAP | DSS_DISABLED); + HANDLES(DeleteObject(hBmp)); + } + DeleteObject(iconInfo.hbmMask); + DeleteObject(iconInfo.hbmColor); + } + } + } + else + { + // text + + // get the button text + char buff[500]; + GetWindowText(HWindow, buff, 500); + + // get the current font + HFONT hFont = (HFONT)SendMessage(HWindow, WM_GETFONT, 0, 0); + + HFONT hOldFont = (HFONT)SelectObject(hdc, hFont); + int oldBkMode = SetBkMode(hdc, TRANSPARENT); + int oldTextColor = SetTextColor(hdc, GetSysColor(enabled ? COLOR_BTNTEXT : COLOR_GRAYTEXT)); + RECT r2 = r; + r2.top--; + DWORD dtFlags = DT_CENTER | DT_VCENTER | DT_SINGLELINE; + if (UIState & UISF_HIDEACCEL) + dtFlags |= DT_HIDEPREFIX; + DrawText(hdc, buff, -1, &r2, dtFlags); + SetTextColor(hdc, oldTextColor); + SetBkMode(hdc, oldBkMode); + SelectObject(hdc, hOldFont); + } + + if (Flags & BTF_RIGHTARROW) + { + BOOL empty = FALSE; + if ((wndStyle & BS_ICON) == 0) + { + char buff[500]; + GetWindowText(HWindow, buff, 500); + empty = (buff[0] == 0); + } + + SIZE sz; + SVGArrowRight.GetSize(&sz); + + if (empty) + { + // if the button only contains an arrow, center it in both axes + r = *rect; + r.left += (int)((double)sz.cx * 0.3); + } + else + { + r.right += sz.cx; + r.left = r.right - sz.cx; + } + SVGArrowRight.AlphaBlend(hdc, + r.left + (r.right - r.left - sz.cx) / 2, + r.top + (r.bottom - r.top - sz.cy) / 2, + -1, -1, + enabled ? SVGSTATE_ENABLED : SVGSTATE_DISABLED); + } + + if (Flags & BTF_MORE) + { + CSVGSprite* sprite = Checked ? &SVGArrowLess : &SVGArrowMore; + + SIZE sz; + sprite->GetSize(&sz); + + r.right += sz.cx; + r.left = r.right - sz.cx; + sprite->AlphaBlend(hdc, + r.left + (r.right - r.left - sz.cx) / 2, + r.top + (r.bottom - r.top - sz.cy) / 2, + -1, -1, + enabled ? SVGSTATE_ENABLED : SVGSTATE_DISABLED); + } +} + +BOOL CButton::SetToolTipText(const char* text) +{ + if (text == NULL) + { + if (ToolTipText != NULL) + free(ToolTipText); + ToolTipText = NULL; + HToolTipNW = NULL; + ToolTipID = 0; + return TRUE; + } + + char* newText = DupStr(text); + if (newText == NULL) + return FALSE; + + if (ToolTipText != NULL) + free(ToolTipText); + + ToolTipText = newText; + HToolTipNW = NULL; + ToolTipID = 0; + return TRUE; +} + +void CButton::SetToolTip(HWND hNotifyWindow, DWORD id) +{ + if (ToolTipText != NULL) + free(ToolTipText); + ToolTipText = NULL; + + HToolTipNW = hNotifyWindow; + ToolTipID = id; +} + +BOOL CButton::ToolTipAssigned() +{ + return ToolTipText != NULL || HToolTipNW != NULL; +} + +LRESULT +CButton::WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + SLOW_CALL_STACK_MESSAGE4("CButton::WindowProc(0x%X, 0x%IX, 0x%IX)", uMsg, wParam, lParam); + switch (uMsg) + { + case WM_GETDLGCODE: + { + DWORD ret = DLGC_BUTTON; + if (DefPushButton) + ret |= DLGC_DEFPUSHBUTTON; + else + ret |= DLGC_UNDEFPUSHBUTTON; + if (Flags & BTF_DROPDOWN) + ret |= DLGC_WANTARROWS; + return ret; + } + + case WM_SETFOCUS: + { + RePaint(); + if (GetWindowLongPtr(HWindow, GWL_STYLE) & BS_NOTIFY) + NotifyParent(BN_SETFOCUS); + return 0; + } + + case WM_KILLFOCUS: + { + if (Captured) + { + ReleaseCapture(); + Captured = FALSE; + ButtonPressed = FALSE; + Pressed = FALSE; + } + if (Space) + { + Space = FALSE; + ButtonPressed = FALSE; + Pressed = FALSE; + } + RePaint(); + if (GetWindowLongPtr(HWindow, GWL_STYLE) & BS_NOTIFY) + NotifyParent(BN_KILLFOCUS); + return 0; + } + + case WM_ENABLE: + { + RePaint(); + return 0; + } + + case WM_SIZE: + { + GetClientRect(HWindow, &ClientRect); + break; + } + + case WM_SETTEXT: + { + // WM_SETTEXT would explicitly redraw the control -- we avoid that + SendMessage(HWindow, WM_SETREDRAW, FALSE, 0); + LRESULT ret = CWindow::WindowProc(uMsg, wParam, lParam); + SendMessage(HWindow, WM_SETREDRAW, TRUE, 0); + RePaint(); + return ret; + } + + case WM_PAINT: + { + PAINTSTRUCT ps; + HDC hdc = HANDLES(BeginPaint(HWindow, &ps)); + + if (hdc != NULL) + { + BOOL enabled = IsWindowEnabled(HWindow); + //BOOL down = enabled && (ButtonPressed && Pressed || (Flags & BTF_CHECKBOX) && Checked); + BOOL checked = enabled && (Flags & BTF_CHECKBOX) && Checked; + BOOL down = enabled && ButtonPressed && Pressed; + BOOL focused = GetFocus() == HWindow; + + // we will draw through a memory DC + CGuiBitmap tmpBitmap; + tmpBitmap.CreateBmp(hdc, ClientRect.right, ClientRect.bottom); + HDC hMemDC = tmpBitmap.HMemDC; + + if (IsAppThemed()) + { + // if running under the XP theme, use it + HTHEME hTheme = OpenThemeData(HWindow, L"Button"); + int state = PBS_DISABLED; + if (enabled) + { + state = PBS_NORMAL; + if (Hot) + state = PBS_HOT; + if (down || checked) + { + if (checked && Hot && !down) + state = PBS_HOT; + else + state = PBS_PRESSED; + } + else if (focused) + { + if (Hot) + state = PBS_HOT; + else + state = PBS_DEFAULTED; + } + } + // erase the background, the button has transparent areas + HBRUSH hBrush = (HBRUSH)(COLOR_BTNFACE + 1); + // if (!(ButtonPressed && Pressed) && (Flags & BTF_CHECKBOX) && Checked) hBrush = HDitherBrush; + FillRect(hMemDC, &ClientRect, hBrush); + + // draw the button background + DrawThemeBackground(hTheme, hMemDC, BP_PUSHBUTTON, state, &ClientRect, NULL); + + if (Flags & BTF_DROPDOWN) + { + RECT ddR; + ddR = ClientRect; + ddR.left = ddR.right - GetDropPartWidth() - 3; + + RECT r = ddR; + r.left += 1; + r.top += 4; + r.right = r.left + 1; + r.bottom -= 4; + FillRect(hMemDC, &r, (HBRUSH)(COLOR_GRAYTEXT + 1)); + r.left = r.right; + r.right = r.left + 1; + FillRect(hMemDC, &r, (HBRUSH)(COLOR_3DHILIGHT + 1)); + + if (DropDownPressed && Pressed) + { + // draw the button background in the drop-down part + r = ddR; + r.left += 2; + DrawThemeBackground(hTheme, hMemDC, BP_PUSHBUTTON, PBS_PRESSED, &ClientRect, &r); + + ddR.top++; + ddR.bottom++; + } + PaintDrop(hMemDC, &ddR, enabled); + } + + // draw the face + RECT fr = ClientRect; + fr.left += 4; + fr.top += 4; + fr.right -= 4; + fr.bottom -= 4; + PaintFace(hMemDC, &fr, enabled); + + // draw the focus + if (focused) + { + RECT r = ClientRect; + r.left += 3; + r.top += 3; + r.right -= 3; + r.bottom -= 3; + DrawFocusRect(hMemDC, &r); + } + CloseThemeData(hTheme); + } + else + { + // otherwise we draw it ourselves + HBRUSH hBrush = (HBRUSH)(COLOR_BTNFACE + 1); + if (/*!(ButtonPressed && Pressed) && */ (Flags & BTF_CHECKBOX) && Checked) + { + hBrush = HDitherBrush; + SetTextColor(hMemDC, GetSysColor(COLOR_BTNFACE)); + SetBkColor(hMemDC, GetSysColor(COLOR_3DHILIGHT)); + } + FillRect(hMemDC, &ClientRect, hBrush); + + RECT fr = ClientRect; + fr.left += 4; + fr.top += 4; + fr.right -= 4; + fr.bottom -= 4; + if (down) + { + fr.left++; + fr.top++; + fr.right++; + fr.bottom++; + } + PaintFace(hMemDC, &fr, enabled); + + RECT clR = ClientRect; + if (DefPushButton && !Checked) + { + HPEN hOldPen = (HPEN)SelectObject(hMemDC, WndFramePen); + HBRUSH hOldBrush = (HBRUSH)SelectObject(hMemDC, HANDLES(GetStockObject(NULL_BRUSH))); + Rectangle(hMemDC, ClientRect.left, ClientRect.top, ClientRect.right, ClientRect.bottom); + SelectObject(hMemDC, hOldBrush); + SelectObject(hMemDC, hOldPen); + InflateRect(&clR, -1, -1); + } + + RECT ddR; + if (Flags & BTF_DROPDOWN) + { + ddR = clR; + ddR.left = ddR.right - GetDropPartWidth() - 1; + clR.right -= GetDropPartWidth(); + } + PaintFrame(hMemDC, &clR, down); + if (Flags & BTF_DROPDOWN) + { + PaintFrame(hMemDC, &ddR, DropDownPressed && Pressed); + if (DropDownPressed && Pressed) + { + ddR.left++; + ddR.top++; + ddR.right++; + ddR.bottom++; + } + PaintDrop(hMemDC, &ddR, enabled); + } + + if (focused) + { + RECT r = ClientRect; + InflateRect(&r, -4, -4); + if (Flags & BTF_DROPDOWN) + r.right -= GetDropPartWidth(); + if (down) + { + r.left++; + r.top++; + r.right++; + r.bottom++; + } + int oldColor = SetTextColor(hMemDC, GetSysColor(COLOR_BTNFACE)); + int oldBkColor = SetBkColor(hMemDC, GetSysColor(COLOR_BTNTEXT)); + DrawFocusRect(hMemDC, &r); + SetTextColor(hMemDC, oldColor); + SetBkColor(hMemDC, oldBkColor); + } + } + + BitBlt(hdc, + 0, 0, + ClientRect.right, ClientRect.bottom, + hMemDC, + 0, 0, + SRCCOPY); + + HANDLES(EndPaint(HWindow, &ps)); + } + return 0; + } + + case WM_UPDATEUISTATE: + { + // unfortunately we cannot rely on the standard static handling because + // under Vista (and maybe earlier) it draws the Alt underline at a nonsensical + // position; one solution would be to capture the text into our buffer and draw from it, + // but I chose a different approach and we maintain the state ourselves + if (LOWORD(wParam) == UIS_CLEAR) + UIState &= ~HIWORD(wParam); + else if (LOWORD(wParam) == UIS_SET) + UIState |= HIWORD(wParam); + + BOOL showAccel = (LOWORD(wParam) == UIS_CLEAR) && ((HIWORD(wParam) & UISF_HIDEACCEL) != 0); + if (showAccel) + { + InvalidateRect(HWindow, NULL, TRUE); // we use a cached bitmap, so it doesn't flicker + UpdateWindow(HWindow); + } + return 0; + } + + case WM_SYSKEYDOWN: + case WM_KEYDOWN: + { + BOOL controlPressed = (GetKeyState(VK_CONTROL) & 0x8000) != 0; + BOOL altPressed = (GetKeyState(VK_MENU) & 0x8000) != 0; + BOOL shiftPressed = (GetKeyState(VK_SHIFT) & 0x8000) != 0; + + if ((Flags & BTF_DROPDOWN) && (wParam == VK_RIGHT || wParam == VK_LEFT)) + { + // the left and right arrow keys might work + HWND hParent = GetParent(HWindow); + if (hParent != NULL) + { + HWND hNext = GetNextDlgGroupItem(hParent, HWindow, wParam == VK_LEFT); + if (hNext != NULL) + { + SendMessage(hParent, WM_NEXTDLGCTL, (WPARAM)hNext, TRUE); + } + } + return 0; + } + if ((Flags & BTF_DROPDOWN) && (wParam == VK_DOWN || wParam == VK_UP)) + { + // the Up/Down keys can open the drop-down + ButtonPressed = FALSE; + DropDownPressed = TRUE; + Pressed = TRUE; + RePaint(); + SendMessage(GetParent(HWindow), WM_USER_BUTTONDROPDOWN, + (WPARAM)GetMenu(HWindow), MAKELPARAM(TRUE, 0)); + DropDownPressed = FALSE; + Pressed = FALSE; + Captured = FALSE; + RePaint(); + return 0; + } + if ((int)wParam == VK_SPACE) + { + // the space key presses the button + ButtonPressed = TRUE; + Pressed = TRUE; + RePaint(); + if (Flags & BTF_LBUTTONDOWN) + { + SendMessage(GetParent(HWindow), WM_USER_BUTTON, + MAKELPARAM(GetMenu(HWindow), 0), MAKELPARAM(TRUE, 0)); + ButtonPressed = FALSE; + Pressed = FALSE; + RePaint(); + } + else + Space = TRUE; + return 0; + } + else if (Space) + { + Space = FALSE; + ButtonPressed = FALSE; + Pressed = FALSE; + RePaint(); + return 0; + } + break; + } + + case WM_KEYUP: + { + if (Space && wParam == VK_SPACE) + { + Space = FALSE; + ButtonPressed = FALSE; + Pressed = FALSE; + if (Flags & BTF_CHECKBOX) + Checked = !Checked; + RePaint(); + NotifyParent(BN_CLICKED); + return 0; + } + break; + } + + case WM_LBUTTONDBLCLK: + case WM_LBUTTONDOWN: + { + // if the click arrived within 25ms of releasing the drop-down, ignore it + // to prevent an unnecessary new press + if (GetTickCount() - DropDownUpTime <= 25) + return 0; + + if (ToolTipAssigned()) + { + SetCurrentToolTip(NULL, 0); + } + + int hitTest = HitTest(lParam); + if (!Captured && hitTest != 0) + { + if (hitTest == 1) + { + ButtonPressed = TRUE; + DropDownPressed = FALSE; + } + else + { + DropDownPressed = TRUE; + ButtonPressed = FALSE; + } + + Pressed = TRUE; + Captured = TRUE; + SetCapture(HWindow); + if (GetFocus() != HWindow) + SetFocus(HWindow); + RePaint(); + + if (DropDownPressed) + { + SendMessage(GetParent(HWindow), WM_USER_BUTTONDROPDOWN, + (WPARAM)GetMenu(HWindow), MAKELPARAM(FALSE, 0)); + DropDownPressed = FALSE; + Pressed = FALSE; + Captured = FALSE; + ReleaseCapture(); + RePaint(); + DropDownUpTime = GetTickCount(); + } + else + { + if (Flags & BTF_LBUTTONDOWN) + { + SendMessage(GetParent(HWindow), WM_USER_BUTTON, + MAKELPARAM(GetMenu(HWindow), 0), MAKELPARAM(FALSE, 0)); + Pressed = FALSE; + Captured = FALSE; + ReleaseCapture(); + RePaint(); + } + } + } + return 0; + } + + case WM_LBUTTONUP: + { + if (Captured) + { + ReleaseCapture(); + Captured = FALSE; + ButtonPressed = FALSE; + DropDownPressed = FALSE; + Pressed = FALSE; + + int hitTest = HitTest(lParam); + if (hitTest == 1 && (Flags & BTF_CHECKBOX)) + Checked = !Checked; + RePaint(); + if (hitTest == 1) + NotifyParent(BN_CLICKED); + } + return 0; + } + + case WM_MOUSEMOVE: + { + if (Captured) + { + int hitTest = HitTest(lParam); + BOOL pressed = FALSE; + if (ButtonPressed && hitTest == 1) + pressed = TRUE; + if (DropDownPressed && hitTest == 2) + pressed = TRUE; + if (pressed != Pressed) + { + Pressed = pressed; + RePaint(); + } + } + else + { + if (!Hot && IsAppThemed()) + { + Hot = TRUE; + RePaint(); + if (!MouseIsTracked) + { + TRACKMOUSEEVENT tme; + tme.cbSize = sizeof(tme); + tme.dwFlags = TME_LEAVE; + tme.hwndTrack = HWindow; + MouseIsTracked = TrackMouseEvent(&tme); + } + } + + if (ToolTipAssigned()) + { + if (HitTest(lParam) != 0) + { + if (ToolTipText != NULL) + SetCurrentToolTip(HWindow, 1); + else if (HToolTipNW != NULL) + SetCurrentToolTip(HWindow, ToolTipID); + } + else + SetCurrentToolTip(NULL, 0); + + if (!MouseIsTracked) + { + TRACKMOUSEEVENT tme; + tme.cbSize = sizeof(tme); + tme.dwFlags = TME_LEAVE; + tme.hwndTrack = HWindow; + MouseIsTracked = TrackMouseEvent(&tme); + } + } + } + return 0; + } + + case WM_MOUSELEAVE: + { + if (Hot) + { + Hot = FALSE; + RePaint(); + } + if (ToolTipAssigned()) + SetCurrentToolTip(NULL, 0); + MouseIsTracked = FALSE; + break; + } + + case WM_USER_TTGETTEXT: + { + if (ToolTipText != NULL) + lstrcpyn((char*)lParam, ToolTipText, TOOLTIP_TEXT_MAX); + return 0; + } + + case BM_SETSTATE: + { + BOOL highlight = (wParam != 0); + if (highlight != ButtonPressed) + { + ButtonPressed = highlight; + Pressed = ButtonPressed; + RePaint(); + } + return 0; + } + + case BM_GETSTATE: + { + int state = 0; + if (GetFocus() == HWindow) + state |= BST_FOCUS; + if (ButtonPressed && Pressed) + state |= BST_PUSHED; + return state; + } + + case BM_SETCHECK: + { + BOOL checked = (wParam == BST_CHECKED); + if (checked != Checked) + { + Checked = checked; + RePaint(); + } + } + + case BM_GETCHECK: + { + if (Checked) + return BST_CHECKED; + return BST_UNCHECKED; + } + + case BM_SETSTYLE: + { + WORD dwStyle = LOWORD(wParam); + BOOL fRedraw = LOWORD(lParam); + DefPushButton = FALSE; + if (dwStyle & BS_DEFPUSHBUTTON) + DefPushButton = TRUE; + if (fRedraw) + RePaint(); + return 0; + } + } + return CWindow::WindowProc(uMsg, wParam, lParam); +} + +//**************************************************************************** +// +// CColorButton +// +/* +CColorButton::CColorButton(HWND hDlg, int ctrlID, CObjectOrigin origin) + : CButton(hDlg, ctrlID, origin, FALSE, FALSE) +{ + Color = RGB(255, 255, 128); +} + + +void +CColorButton::SetColor(COLORREF color) +{ + Color = color; + RePaint(); +} + +void +CColorButton::PaintFace(HDC hdc, const RECT *rect) +{ + RECT r = *rect; +// InflateRect(&r, -4, -4); + + COLORREF oldBkColor = SetBkColor(hdc, Color); + ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &r, NULL, 0, NULL); + SetBkColor(hdc, oldBkColor); + + HPEN hOldPen = (HPEN)SelectObject(hdc, WndFramePen); + HBRUSH hOldBrush = (HBRUSH)SelectObject(hdc, HANDLES(GetStockObject(NULL_BRUSH))); + Rectangle(hdc, r.left, r.top, r.right, r.bottom); + SelectObject(hdc, hOldBrush); + SelectObject(hdc, hOldPen); +} +*/ + +//**************************************************************************** +// +// CColorArrowButton +// +// background with text followed by an arrow - used to open a menu +// + +CColorArrowButton::CColorArrowButton(HWND hDlg, int ctrlID, BOOL showArrow, CObjectOrigin origin) + : CButton(hDlg, ctrlID, origin) +{ + TextColor = RGB(0, 0, 0); + BkgndColor = RGB(255, 255, 255); + ShowArrow = showArrow; +} + +void CColorArrowButton::SetColor(COLORREF textColor, COLORREF bkgndColor) +{ + TextColor = textColor; + BkgndColor = bkgndColor; + RePaint(); +} + +void CColorArrowButton::SetTextColor(COLORREF textColor) +{ + SetColor(textColor, BkgndColor); +} + +void CColorArrowButton::SetBkgndColor(COLORREF bkgndColor) +{ + SetColor(TextColor, bkgndColor); +} + +void CColorArrowButton::PaintFace(HDC hdc, const RECT* rect, BOOL enabled) +{ + RECT r = *rect; + SIZE arrowSize; + + if (ShowArrow) + { + SVGArrowRightSmall.GetSize(&arrowSize); + r.right -= (int)((double)arrowSize.cx * 2.5); + } + + COLORREF bkColor = GetNearestColor(hdc, BkgndColor); + HBRUSH hBrush = HANDLES(CreateSolidBrush(bkColor)); + HPEN hOldPen = (HPEN)SelectObject(hdc, WndFramePen); + HBRUSH hOldBrush = (HBRUSH)SelectObject(hdc, hBrush); + Rectangle(hdc, r.left, r.top, r.right, r.bottom); + SelectObject(hdc, hOldBrush); + SelectObject(hdc, hOldPen); + HANDLES(DeleteObject(hBrush)); + + HFONT hFont = (HFONT)SendMessage(HWindow, WM_GETFONT, 0, 0); + LOGFONT lf; + GetObject(hFont, sizeof(lf), &lf); + lf.lfHeight += 1; // fix for 100% DPI when an overly large text touches the rectangle + hFont = HANDLES(CreateFontIndirect(&lf)); + HFONT hOldFont = (HFONT)SelectObject(hdc, hFont); + int oldTextColor = ::SetTextColor(hdc, TextColor); + int oldBkMode = SetBkMode(hdc, TRANSPARENT); + DrawText(hdc, "ABC", -1, &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE); + SetBkMode(hdc, oldBkMode); + ::SetTextColor(hdc, oldTextColor); + SelectObject(hdc, hOldFont); + HANDLES(DeleteObject(hFont)); + + if (ShowArrow) + { + SVGArrowRightSmall.AlphaBlend(hdc, + r.right + (rect->right - r.right - (int)((double)arrowSize.cx * 0.6)) / 2, + r.top + (r.bottom - r.top - arrowSize.cy) / 2, + -1, -1, + enabled ? SVGSTATE_ENABLED : SVGSTATE_DISABLED); + } +} + +//**************************************************************************** +// +// CToolbarHeader +// + +int TlbHdrTooltips[TLBHDR_COUNT] = + { + IDS_EDTLB_MODIFY, + IDS_EDTLB_NEW, + IDS_EDTLB_DELETE, + IDS_EDTLB_SORT, + IDS_EDTLB_UP, + IDS_EDTLB_DOWN, +}; + +CToolbarHeader::CToolbarHeader(HWND hDlg, int ctrlID, HWND hAlignWindow, DWORD buttonMask) + : CWindow(hDlg, ctrlID, ooAllocated) +{ + CALL_STACK_MESSAGE3("CToolbarHeader::CToolbarHeader(, %d, , %u)", ctrlID, buttonMask); + HNotifyWindow = hDlg; + ButtonMask = buttonMask; + ToolBar = new CToolBar(HWindow); + ToolBar->CreateWnd(HWindow); + +#ifdef TOOLBARHDR_USE_SVG + CreateImageLists(&HEnabledImageList, &HDisabledImageList); + ToolBar->SetImageList(HDisabledImageList); + ToolBar->SetHotImageList(HEnabledImageList); +#else + + CSVGIcon svgIcons[TLBHDR_COUNT] = { + {0, "Modify"}, + {1, "New_Insert"}, + {2, "Delete"}, + {3, "SortByName"}, + {4, "MoveItemUp"}, + {5, "MoveItemDown"}, + }; + + int iconSize = GetIconSizeForSystemDPI(ICONSIZE_16); + HBITMAP hTmpMaskBitmap; + HBITMAP hTmpGrayBitmap; + HBITMAP hTmpColorBitmap; + CreateToolbarBitmaps(HInstance, + IDB_EDTLBTB, + RGB(255, 0, 255), GetSysColor(COLOR_BTNFACE), + hTmpMaskBitmap, hTmpGrayBitmap, hTmpColorBitmap, + FALSE, svgIcons, TLBHDR_COUNT); + HHotImageList = ImageList_Create(iconSize, iconSize, ILC_MASK | ILC_COLORDDB, TLBHDR_COUNT, 1); + HGrayImageList = ImageList_Create(iconSize, iconSize, ILC_MASK | ILC_COLORDDB, TLBHDR_COUNT, 1); + ImageList_Add(HHotImageList, hTmpColorBitmap, hTmpMaskBitmap); + ImageList_Add(HGrayImageList, hTmpGrayBitmap, hTmpMaskBitmap); + HANDLES(DeleteObject(hTmpMaskBitmap)); + HANDLES(DeleteObject(hTmpGrayBitmap)); + HANDLES(DeleteObject(hTmpColorBitmap)); + ToolBar->SetImageList(HGrayImageList); + ToolBar->SetHotImageList(HHotImageList); + //HImageList = ImageList_Create(TOOLBARHDR_WIDTH, TOOLBARHDR_HEIGHT, + // ILC_MASK | ILC_COLORDDB, 5, 1); + //HBITMAP hbmp = HANDLES(LoadBitmap(HInstance, MAKEINTRESOURCE(IDB_EDTLBTB))); + //ImageList_AddMasked(HImageList, hbmp, RGB(255, 0, 255)); + //HANDLES(DeleteObject(hbmp)); + //ToolBar->SetImageList(HImageList); +#endif + + UIState = (WORD)SendMessage(HWindow, WM_QUERYUISTATE, 0, 0); + + TLBI_ITEM_INFO2 tii; + tii.Mask = TLBI_MASK_ID | TLBI_MASK_IMAGEINDEX; + int buttonsCount = 0; + int i; + for (i = 0; i < TLBHDR_COUNT; i++) + { + if ((1 << i) & ButtonMask) + { + tii.ImageIndex = i; + tii.ID = i + 1; + ToolBar->InsertItem2(buttonsCount, TRUE, &tii); + buttonsCount++; + } + } + + SIZE sz; + sz.cx = ToolBar->GetNeededWidth(); + sz.cy = ToolBar->GetNeededHeight(); + + RECT r; + GetWindowRect(hAlignWindow, &r); + int width = r.right - r.left; + int height = sz.cy + 2; + POINT p; + p.x = r.left; + p.y = r.top - height; + ScreenToClient(hDlg, &p); + SetWindowPos(HWindow, 0, p.x, p.y, width, height, SWP_NOZORDER); + SetWindowPos(ToolBar->HWindow, HWND_TOP, width - sz.cx - 1, 1, sz.cx, sz.cy, SWP_SHOWWINDOW); +} + +#ifdef TOOLBARHDR_USE_SVG +void CToolbarHeader::CreateImageLists(HIMAGELIST* enabled, HIMAGELIST* disabled) +{ + HIMAGELIST hEnabled; + HIMAGELIST hDisabled; + int iconSize = GetIconSizeForSystemDPI(ICONSIZE_16); // small icon size + + // http://stackoverflow.com/questions/2640823/is-it-possible-to-create-a-cimagelist-with-alpha-blending-transparency + hEnabled = ImageList_Create(iconSize, iconSize, + ILC_COLOR32, TOOLBARHDR_BUTTONS, 1); + hDisabled = ImageList_Create(iconSize, iconSize, + ILC_COLOR32 /*ILC_COLORDDB */, TOOLBARHDR_BUTTONS, 1); + + HDC hDC = HANDLES(CreateCompatibleDC(NULL)); + + int width = iconSize * TOOLBARHDR_BUTTONS; + int height = iconSize; + + BITMAPINFOHEADER bmhdr; + memset(&bmhdr, 0, sizeof(bmhdr)); + bmhdr.biSize = sizeof(bmhdr); + bmhdr.biWidth = width; + bmhdr.biHeight = -height; // top-down + bmhdr.biPlanes = 1; + bmhdr.biBitCount = 32; + bmhdr.biCompression = BI_RGB; + void* lpBits = NULL; + HBITMAP hBmp = HANDLES(CreateDIBSection(NULL, (CONST BITMAPINFO*)&bmhdr, + DIB_RGB_COLORS, &lpBits, NULL, 0)); + + NSVGrasterizer* rast = nsvgCreateRasterizer(); + // JRYFIXME: temporarily reading from a file, switch to a shared storage with toolbars + const char* svgNames[] = {"Modify", "New_Insert", "Delete", "SortByName", "MoveItemUp", "MoveItemDown"}; + for (int j = 0; j < 2; j++) + { + DWORD* p = (DWORD*)lpBits; + for (int i = 0; i < width * height; i++) + *p++ = 0x00000000; + + HBITMAP hOldBmp = (HBITMAP)SelectObject(hDC, hBmp); + for (int i = 0; i < TOOLBARHDR_BUTTONS; i++) + RenderSVGImage(rast, hDC, i * iconSize, 0, svgNames[i], iconSize, RGB(0xff, 0xff, 0xff), j == 0 ? TRUE : FALSE); + SelectObject(hDC, hOldBmp); + ImageList_Add(j == 0 ? hEnabled : hDisabled, hBmp, hBmp); + } + nsvgDeleteRasterizer(rast); + HANDLES(DeleteDC(hDC)); + HANDLES(DeleteObject(hBmp)); + *enabled = hEnabled; + *disabled = hDisabled; +} +#endif // TOOLBARHDR_USE_SVG + +void CToolbarHeader::EnableToolbar(DWORD enableMask) +{ + int i; + for (i = 0; i < TLBHDR_COUNT; i++) + { + if ((1 << i) & ButtonMask) + ToolBar->EnableItem(i + 1, FALSE, ((1 << i) & enableMask) != 0); + } +} + +void CToolbarHeader::CheckToolbar(DWORD checkMask) +{ + int i; + for (i = 0; i < TLBHDR_COUNT; i++) + { + if ((1 << i) & ButtonMask) + ToolBar->CheckItem(i + 1, FALSE, ((1 << i) & checkMask) != 0); + } +} + +void CToolbarHeader::OnPaint(HDC hDC, BOOL hideAccel, BOOL prefixOnly) +{ + RECT r; + GetClientRect(HWindow, &r); + DrawEdge(hDC, &r, BDR_SUNKENOUTER, BF_RECT); + r.left += 5; + char buff[100]; + GetWindowText(HWindow, buff, 100); + SetBkMode(hDC, TRANSPARENT); + HFONT hOldFont = (HFONT)SelectObject(hDC, (HFONT)SendMessage(HWindow, WM_GETFONT, 0, 0)); + DWORD dtFlags = DT_SINGLELINE | DT_LEFT | DT_VCENTER; + if (hideAccel) + dtFlags |= DT_HIDEPREFIX; + if (prefixOnly) + dtFlags |= DT_PREFIXONLY; + DrawText(hDC, buff, -1, &r, dtFlags); + SelectObject(hDC, hOldFont); +} + +LRESULT +CToolbarHeader::WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + CALL_STACK_MESSAGE4("CToolbarHeader::WindowProc(0x%X, 0x%IX, 0x%IX)", uMsg, wParam, lParam); + switch (uMsg) + { + case WM_DESTROY: + { + if (ToolBar != NULL) + DestroyWindow(ToolBar->HWindow); +#ifdef TOOLBARHDR_USE_SVG + if (HEnabledImageList != NULL) + ImageList_Destroy(HEnabledImageList); + if (HDisabledImageList != NULL) + ImageList_Destroy(HDisabledImageList); +#else + if (HHotImageList != NULL) + ImageList_Destroy(HHotImageList); + if (HGrayImageList != NULL) + ImageList_Destroy(HGrayImageList); +#endif + break; + } + + case WM_PAINT: + { + PAINTSTRUCT ps; + HDC hDC = HANDLES(BeginPaint(HWindow, &ps)); + BOOL hideAccel = (UIState & UISF_HIDEACCEL) != 0; + OnPaint(hDC, hideAccel, FALSE); + HANDLES(EndPaint(HWindow, &ps)); + return 0; + } + + case WM_UPDATEUISTATE: + { + // unfortunately we cannot rely on the standard static handling because + // under Vista (and maybe earlier) it draws the Alt underline at a nonsensical + // position; one solution would be to capture the text into our buffer and draw from it, + // but I chose a different approach and we maintain the state ourselves + if (LOWORD(wParam) == UIS_CLEAR) + UIState &= ~HIWORD(wParam); + else if (LOWORD(wParam) == UIS_SET) + UIState |= HIWORD(wParam); + + BOOL showAccel = (LOWORD(wParam) == UIS_CLEAR) && ((HIWORD(wParam) & UISF_HIDEACCEL) != 0); + if (showAccel) + { + HDC hDC = HANDLES(GetDC(HWindow)); + OnPaint(hDC, FALSE, TRUE); + HANDLES(ReleaseDC(HWindow, hDC)); + } + return 0; + } + + case WM_COMMAND: + { + if ((HWND)lParam == ToolBar->HWindow) + PostMessage(HNotifyWindow, WM_COMMAND, + MAKEWPARAM((WORD)(UINT_PTR)GetMenu(HWindow), LOWORD(wParam)), (LPARAM)HWindow); + break; + } + + case WM_USER_TBGETTOOLTIP: + { + TOOLBAR_TOOLTIP* tt = (TOOLBAR_TOOLTIP*)lParam; + lstrcpy(tt->Buffer, LoadStr(TlbHdrTooltips[tt->ID - 1])); + return TRUE; + } + + case WM_ERASEBKGND: + { + HDC hdc = (HDC)wParam; + RECT r; + GetClientRect(HWindow, &r); + FillRect(hdc, &r, (HBRUSH)(COLOR_3DFACE + 1)); + return 1; + } + } + return CWindow::WindowProc(uMsg, wParam, lParam); +} + +//**************************************************************************** +// +// CAnimate +// + +/* +CAnimate::CAnimate(HBITMAP hBitmap, int framesCount, int firstLoopFrame, COLORREF bkColor, CObjectOrigin origin) + : CWindow(origin) +{ + HBitmap = hBitmap; + FramesCount = framesCount; + FirstLoopFrame = firstLoopFrame; + HThread = NULL; + CurrentFrame = 0; + SleepThread = FALSE; + NestedCount = 0; + BkColor = bkColor; + MouseIsTracked = FALSE; + + HANDLES(InitializeCriticalSection(&GDICriticalSection)); + HANDLES(InitializeCriticalSection(&DataCriticalSection)); + + HRunEvent = HANDLES(CreateEvent(NULL, TRUE, FALSE, NULL)); + if (HRunEvent == NULL) + TRACE_E("Unable to create HRunEvent event."); + + HTerminateEvent = HANDLES(CreateEvent(NULL, TRUE, FALSE, NULL)); // "nonsignaled" state, manual + if (HTerminateEvent == NULL) + TRACE_E("Unable to create HTerminateEvent event."); + + if (HRunEvent != NULL && HTerminateEvent != NULL) + { + DWORD threadID; + HThread = HANDLES(CreateThread(NULL, 0, AuxThreadF, this, 0, &threadID)); + if (HThread == NULL) + TRACE_E("Unable to start thread for animation control."); + } + else HThread = NULL; + + BITMAP bitmap; + GetObject(HWorkerBitmap, sizeof(bitmap), &bitmap); + FrameSize.cx = bitmap.bmWidth; + FrameSize.cy = bitmap.bmHeight / framesCount; +} + +BOOL +CAnimate::IsGood() +{ + return HThread != NULL && HRunEvent != NULL && HTerminateEvent != NULL; +} + +void +CAnimate::Paint(HDC hdc) +{ + // just to be safe, we synchronize access to the bitmap + HANDLES(EnterCriticalSection(&GDICriticalSection)); + + // if no DC is provided, we obtain our own + HDC hDC; + if (hdc == NULL) + hDC = HANDLES(GetDC(HWindow)); + else + hDC = hdc; + + // memory DC for BitBlt + HDC hMemDC = HANDLES(CreateCompatibleDC(NULL)); + HBITMAP hOldBitmap = (HBITMAP)SelectObject(hMemDC, HBitmap); + + RECT r; + GetClientRect(HWindow, &r); + BitBlt(hDC, + (r.right - r.left - FrameSize.cx) / 2, + (r.bottom - r.top - FrameSize.cy) / 2, + FrameSize.cx, + FrameSize.cy, + hMemDC, 0, + CurrentFrame * FrameSize.cy, + SRCCOPY); + + // cleanup + SelectObject(hMemDC, hOldBitmap); + HANDLES(DeleteDC(hMemDC)); + if (hdc == NULL) + HANDLES(ReleaseDC(HWindow, hDC)); + HANDLES(LeaveCriticalSection(&GDICriticalSection)); +} + +void +CAnimate::GetFrameSize(SIZE *sz) +{ + *sz = FrameSize; +} + +void +CAnimate::FirstFrame() +{ + CurrentFrame = 0; +} + +void +CAnimate::NextFrame() +{ + CurrentFrame++; + if (CurrentFrame >= FramesCount) + CurrentFrame = FirstLoopFrame; +} + +void +CAnimate::Start() +{ + HANDLES(EnterCriticalSection(&DataCriticalSection)); + NestedCount++; + SleepThread = FALSE; + SetEvent(HRunEvent); + HANDLES(LeaveCriticalSection(&DataCriticalSection)); +} + +void +CAnimate::Stop() +{ + HANDLES(EnterCriticalSection(&DataCriticalSection)); + NestedCount--; + if (NestedCount < 1) + { + if (NestedCount < 0) + { + TRACE_E("CAnimate::Stop() NestedCount = "<HTerminateEvent; // must be at index zero because it has priority + handles[1] = animate->HRunEvent; + + // raise the thread priority so the animation doesn't buffer + // we can afford it because it consumes almost no time + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST); + + while (TRUE) + { + DWORD wait = WaitForMultipleObjects(2, handles, FALSE, INFINITE); + + if (wait == WAIT_OBJECT_0) + break; // we need to terminate the thread + + if (animate->SleepThread) // we should stop the animation + { + animate->FirstFrame(); // move to the first frame + ResetEvent(animate->HRunEvent); // and disable running + } + + // draw the current frame + animate->Paint(); + // move to the next one + animate->NextFrame(); + // 1000ms / 40ms = 25 frames per second, just like in a movie + WaitForSingleObject(animate->HTerminateEvent, 40); // if we should exit, we wll not wait unnecessarily + } + TRACE_I("End"); + return 0; +} + +unsigned +CAnimate::AuxThreadEH(void *param) +{ + CALL_STACK_MESSAGE_NONE +#ifndef CALLSTK_DISABLE + __try + { +#endif // CALLSTK_DISABLE + return ThreadF(param); +#ifndef CALLSTK_DISABLE + } + __except (CCallStack::HandleException(GetExceptionInformation())) + { + TRACE_I("Thread in CAnimate: calling ExitProcess(1)."); +// ExitProcess(1); + TerminateProcess(GetCurrentProcess(), 1); // harder exit (this call still performs some operations) + return 1; + } +#endif // CALLSTK_DISABLE +} + + +DWORD WINAPI +CAnimate::AuxThreadF(void *param) +{ + CALL_STACK_MESSAGE_NONE +#ifndef CALLSTK_DISABLE + CCallStack stack; +#endif // CALLSTK_DISABLE + return AuxThreadEH(param); +} + +LRESULT +CAnimate::WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + CALL_STACK_MESSAGE4("CAnimate::WindowProc(0x%X, 0x%IX, 0x%IX)", uMsg, wParam, lParam); + + switch (uMsg) + { + case WM_CREATE: + { + break; + } + + case WM_DESTROY: + { + SetEvent(HTerminateEvent); + WaitForSingleObject(HThread, INFINITE); // wait for the thread to finish + HANDLES(CloseHandle(HThread)); + HANDLES(DeleteCriticalSection(&GDICriticalSection)); + HANDLES(DeleteCriticalSection(&DataCriticalSection)); + HANDLES(CloseHandle(HRunEvent)); + HANDLES(CloseHandle(HTerminateEvent)); + break; + } + + case WM_MOUSEMOVE: + { + SetCurrentToolTip(HWindow, 1); + + if (!MouseIsTracked) + { + TRACKMOUSEEVENT tme; + tme.cbSize = sizeof(tme); + tme.dwFlags = TME_LEAVE; + tme.hwndTrack = HWindow; + TrackMouseEvent(&tme); + MouseIsTracked = TRUE; + } + + break; + } + + case WM_MOUSELEAVE: + { + SetCurrentToolTip(NULL, 0); + MouseIsTracked = FALSE; + break; + } + + case WM_USER_TTGETTEXT: + { + char *text = (char *)lParam; + lstrcpy(text, "(CAnimate class)\nClick to Start animate, click again to Stop animate.\n\t1\nTab\t2"); + return TRUE; + } + + case WM_RBUTTONDOWN: + { + SetCurrentToolTip(NULL, 0); + break; + } + + case WM_RBUTTONUP: + { + SetCurrentToolTip(NULL, 0); + CMenuPopup popup; + BOOL runnig = WaitForSingleObject(HRunEvent, 0) == WAIT_OBJECT_0; + MENU_ITEM_INFO mii; + mii.Mask = MENU_MASK_TYPE | MENU_MASK_ID | MENU_MASK_STRING | MENU_MASK_STATE; + mii.Type = MENU_TYPE_STRING; + mii.ID = 1; + mii.String = "&Start"; + mii.State = runnig ? MENU_STATE_GRAYED : 0; + popup.InsertItem(0xFFFFFFFF, TRUE, &mii); + mii.ID = 2; + mii.String = "S&top"; + mii.State = runnig ? 0 : MENU_STATE_GRAYED; + popup.InsertItem(0xFFFFFFFF, TRUE, &mii); + + DWORD pos = GetMessagePos(); + DWORD cmd = popup.Track(MENU_TRACK_RETURNCMD | MENU_TRACK_RIGHTBUTTON, + GET_X_LPARAM(pos), GET_Y_LPARAM(pos), HWindow, NULL); + if (cmd == 1) + Start(); + if (cmd == 2) + Stop(); + return 0; + } + + case WM_LBUTTONDBLCLK: + case WM_LBUTTONDOWN: + { + SetCurrentToolTip(NULL, 0); + if (WaitForSingleObject(HRunEvent, 0) == WAIT_OBJECT_0) + Stop(); + else + Start(); + break; + } + + case WM_PAINT: + { + PAINTSTRUCT ps; + HDC hDC = HANDLES(BeginPaint(HWindow, &ps)); + Paint(hDC); + HANDLES(EndPaint(HWindow, &ps)); + return 0; + } + + case WM_ERASEBKGND: + { + HDC hDC = (HDC)wParam; + RECT r; + GetClientRect(HWindow, &r); + COLORREF oldColor = (COLORREF)SetBkColor(hDC, BkColor); + ExtTextOut((HDC)wParam, 0, 0, ETO_OPAQUE, &r, "", 0, NULL); + SetBkColor(hDC, oldColor); + return TRUE; + } + } + + return CWindow::WindowProc(uMsg, wParam, lParam); +} +*/ + +// +// **************************************************************************** +// ChangeToArrowButton +// + +BOOL ChangeToArrowButton(HWND hParent, int ctrlID) +{ + CALL_STACK_MESSAGE_NONE + // the old approach did not work with high-contrast colors where the arrow should be drawn inverted + // switching to our own drawing + CButton* button = new CButton(hParent, ctrlID, BTF_RIGHTARROW); + /* + // under XP BS_ICON is not drawn using themes, it has the old look + // so we draw the button ourselves + CButton *button = new CButton(hParent, ctrlID, 0); + HWND hButton = GetDlgItem(hParent, ctrlID); + if (hButton == NULL) + { + TRACE_E("Cannot find button ctrlID=" << ctrlID << " in the window hParent=0x" << hParent); + return FALSE; + } + LONG_PTR l = GetWindowLongPtr(hButton, GWL_STYLE); + l |= BS_ICON; + SetWindowLongPtr(hButton, GWL_STYLE, l); + SendMessage(hButton, BM_SETIMAGE, IMAGE_ICON, (WPARAM)HANDLES(LoadIcon(HInstance, MAKEINTRESOURCE(IDI_BROWSE)))); +*/ + return TRUE; +} + +BOOL ChangeToIconButton(HWND hParent, int ctrlID, int iconID) +{ + CALL_STACK_MESSAGE_NONE + HWND hButton = GetDlgItem(hParent, ctrlID); + if (hButton == NULL) + { + TRACE_E("Cannot find button ctrlID=" << ctrlID << " in the window hParent=0x" << hParent); + return FALSE; + } + DWORD stl = (DWORD)GetWindowLongPtr(hButton, GWL_STYLE); + stl |= BS_ICON; + SetWindowLongPtr(hButton, GWL_STYLE, stl); + SendMessage(hButton, BM_SETIMAGE, IMAGE_ICON, (WPARAM)HANDLES(LoadIcon(HInstance, MAKEINTRESOURCE(iconID)))); + + // adjust the button size according to the preceding edit line or combo box + HWND hPrevWnd = GetWindow(hButton, GW_HWNDPREV); + if (hPrevWnd != NULL) + { + char className[30]; + GetClassName(hPrevWnd, className, 29); + className[29] = 0; + if (stricmp(className, "edit") == 0 || stricmp(className, "combobox") == 0) + { + RECT r; + GetWindowRect(hPrevWnd, &r); + RECT r2; + GetWindowRect(hButton, &r2); + POINT p; + p.x = r2.left; + p.y = r.top; + ScreenToClient(hParent, &p); + SetWindowPos(hButton, NULL, p.x, p.y, r2.right - r2.left, r.bottom - r.top, SWP_NOZORDER); + } + } + + CButton* button = new CButton(hParent, ctrlID, BTF_LBUTTONDOWN | BTF_RIGHTARROW); + if (button != NULL) + button->SetToolTipText(LoadStr(IDS_BROWSE_BTN_TIP)); + + return TRUE; +} + +// +// **************************************************************************** +// VerticalAlignChildToChild +// + +void VerticalAlignChildToChild(HWND hParent, int alignID, int toID) +{ + HWND hAlign = GetDlgItem(hParent, alignID); + HWND hTo = GetDlgItem(hParent, toID); + if (hParent == NULL || hAlign == NULL || hTo == NULL) + { + TRACE_E("VerticalAlignChildToChild() Invalid parameters! hParent=" << hParent << " alignID=" << alignID << " toID=" << toID); + return; + } + + RECT alignR; + RECT toR; + GetWindowRect(hAlign, &alignR); + GetWindowRect(hTo, &toR); + + int width = alignR.right - alignR.left; + int height = toR.bottom - toR.top; + + ScreenToClient(hParent, (LPPOINT)&alignR); + ScreenToClient(hParent, (LPPOINT)&toR); + + SetWindowPos(hAlign, NULL, alignR.left, toR.top, width, height, SWP_NOZORDER); +} + +// +// **************************************************************************** +// CondenseStaticTexts +// + +void CondenseStaticTexts(HWND hWindow, int* staticsArr) +{ + int count = 0; + while (staticsArr[count] != 0) + count++; + if (count < 2) + return; // there isnothing to do + + HFONT hFont = (HFONT)SendDlgItemMessage(hWindow, staticsArr[0], WM_GETFONT, 0, 0); + HDC hDC = HANDLES(GetDC(hWindow)); + HFONT hOldFont = (HFONT)SelectObject(hDC, hFont); + SIZE sz; + GetTextExtentPoint32(hDC, " ", 1, &sz); + int spaceWidth = sz.cx; + int pos = -1; + for (int i = 0; i < count; i++) + { + HWND control = GetDlgItem(hWindow, staticsArr[i]); + if (control != NULL) + { + RECT r; + GetWindowRect(control, &r); + POINT p; + p.x = r.left; + p.y = r.top; + ScreenToClient(hWindow, &p); + WCHAR text[1000]; + GetWindowTextW(control, text, _countof(text)); + int textLen = (int)wcslen(text); + if (textLen > 0 && text[textLen - 1] == ' ') + text[--textLen] = 0; + GetTextExtentPoint32W(hDC, text, textLen, &sz); + if (pos == -1) + pos = p.x; + MoveWindow(control, pos, p.y, sz.cx, r.bottom - r.top, TRUE); + pos += sz.cx + spaceWidth; + } + else + TRACE_E("CondenseStaticTexts(): invalid control ID found in array with statics IDs: " << staticsArr[i]); + } + SelectObject(hDC, hOldFont); + HANDLES(ReleaseDC(hWindow, hDC)); +} + +// +// **************************************************************************** +// ArrangeHorizontalLines +// + +BOOL CALLBACK FindHorizLines(HWND hwnd, LPARAM lParam) +{ + RECT r; + GetClientRect(hwnd, &r); + if (r.bottom == 0) // horizontal line 0 points high + { + LONG style = GetWindowLong(hwnd, GWL_STYLE); + if ((style & SS_TYPEMASK) == SS_ETCHEDHORZ) + { + char className[300]; + if (GetClassName(hwnd, className, _countof(className)) && stricmp(className, "Static") == 0) + ((TDirectArray*)lParam)->Add(hwnd); + } + } + return TRUE; +} + +BOOL CALLBACK FindGroupBoxes(HWND hwnd, LPARAM lParam) +{ + LONG style = GetWindowLong(hwnd, GWL_STYLE); + if ((style & BS_TYPEMASK) == BS_GROUPBOX) + { + char className[300]; + if (GetClassName(hwnd, className, _countof(className)) && stricmp(className, "Button") == 0) + ((TDirectArray*)lParam)->Add(hwnd); + } + return TRUE; +} + +struct CDataForFindHorizLineLabel +{ + HWND Line; + RECT LineRect; + HWND Label; + BOOL IsCheckOrRadioBox; + BOOL NoPrefix; + BOOL MoreLabels; +}; + +BOOL CALLBACK FindHorizLineLabel(HWND hwnd, LPARAM lParam) +{ + CDataForFindHorizLineLabel* data = (CDataForFindHorizLineLabel*)lParam; + if (data->Line != hwnd) // skip the line for which we search a label + { + RECT r; + GetWindowRect(hwnd, &r); + if (r.top <= data->LineRect.top && r.bottom >= data->LineRect.bottom) // label vertically overlaps the line + { + if (r.left < data->LineRect.left && r.right < data->LineRect.right) // label starts before the line + line ends after the label + { + char className[300]; + if (GetClassName(hwnd, className, _countof(className))) + { + LONG style = GetWindowLong(hwnd, GWL_STYLE); + if (stricmp(className, "Static") == 0) // it's a left-aligned static text + { + if ((style & SS_TYPEMASK) == SS_LEFT || + (style & SS_TYPEMASK) == SS_SIMPLE || + (style & SS_TYPEMASK) == SS_LEFTNOWORDWRAP) + { + data->IsCheckOrRadioBox = FALSE; + data->NoPrefix = (style & SS_NOPREFIX) != 0; + if (data->Label != NULL) + data->MoreLabels = TRUE; + else + data->Label = hwnd; + } + } + else + { + if (stricmp(className, "Button") == 0) // it's a button (check box, radio button, or push button) + { + if (((style & BS_TYPEMASK) == BS_CHECKBOX || + (style & BS_TYPEMASK) == BS_AUTOCHECKBOX || + (style & BS_TYPEMASK) == BS_AUTO3STATE || + (style & BS_TYPEMASK) == BS_3STATE || + (style & BS_TYPEMASK) == BS_RADIOBUTTON || + (style & BS_TYPEMASK) == BS_AUTORADIOBUTTON) && + (style & BS_PUSHLIKE) == 0) + { + data->IsCheckOrRadioBox = TRUE; + data->NoPrefix = FALSE; + if (data->Label != NULL) + data->MoreLabels = TRUE; + else + data->Label = hwnd; + } + } + } + } + } + } + } + return TRUE; +} + +struct CDataForFindGroupBoxLabel +{ + HWND GroupBox; + RECT GroupBoxRect; + HWND Label; + BOOL MoreLabels; +}; + +BOOL CALLBACK FindGroupBoxLabel(HWND hwnd, LPARAM lParam) +{ + CDataForFindGroupBoxLabel* data = (CDataForFindGroupBoxLabel*)lParam; + if (data->GroupBox != hwnd) // skip the group box for which we search a label + { + RECT r; + GetWindowRect(hwnd, &r); + if (r.top <= data->GroupBoxRect.top && r.bottom >= data->GroupBoxRect.top && // label vertically overlaps the top line of the group box + r.left >= data->GroupBoxRect.left && r.right <= data->GroupBoxRect.right) // label horizontally lies on the group box + { + char className[300]; + if (GetClassName(hwnd, className, _countof(className)) && + stricmp(className, "Button") == 0) // it's a button (check box, radio button, or push button) + { + LONG style = GetWindowLong(hwnd, GWL_STYLE); + if (((style & BS_TYPEMASK) == BS_CHECKBOX || + (style & BS_TYPEMASK) == BS_AUTOCHECKBOX || + (style & BS_TYPEMASK) == BS_AUTO3STATE || + (style & BS_TYPEMASK) == BS_3STATE || + (style & BS_TYPEMASK) == BS_RADIOBUTTON || + (style & BS_TYPEMASK) == BS_AUTORADIOBUTTON) && + (style & BS_PUSHLIKE) == 0) + { + if (data->Label != NULL) + data->MoreLabels = TRUE; + else + data->Label = hwnd; + } + } + } + } + return TRUE; +} + +void ArrangeHorizontalLines(HWND hWindow) +{ + TDirectArray horizLines(10, 5); + EnumChildWindows(hWindow, FindHorizLines, (LPARAM)&horizLines); + + HDC hDC = HANDLES(GetDC(hWindow)); + int spaceWidth = -1; + RECT windowRect; + GetWindowRect(hWindow, &windowRect); + for (int i = 0; i < horizLines.Count; i++) + { + CDataForFindHorizLineLabel data; + data.Line = horizLines[i]; + data.Label = NULL; + data.MoreLabels = FALSE; + data.IsCheckOrRadioBox = FALSE; + data.NoPrefix = FALSE; + GetWindowRect(data.Line, &data.LineRect); + EnumChildWindows(hWindow, FindHorizLineLabel, (LPARAM)&data); + if (data.Label != NULL) + { + if (data.MoreLabels) + TRACE_E("ArrangeHorizontalLines(): unexpected situation: more labels for one horizontal line!"); + else + { + HFONT hFont = (HFONT)SendMessage(data.Label, WM_GETFONT, 0, 0); + HFONT hOldFont = (HFONT)SelectObject(hDC, hFont); + if (spaceWidth == -1) + { + SIZE sz; + GetTextExtentPoint32(hDC, " ", 1, &sz); + spaceWidth = sz.cx; + } + RECT labelRect; + GetWindowRect(data.Label, &labelRect); + WCHAR text[1000]; + GetWindowTextW(data.Label, text, _countof(text)); + int textLen = (int)wcslen(text); + if (textLen > 0 && text[textLen - 1] == ' ') + text[--textLen] = 0; + DWORD dtFlags = DT_CALCRECT | DT_LEFT | DT_SINGLELINE | (data.NoPrefix ? DT_NOPREFIX : 0); + RECT txtR = labelRect; + txtR.right -= txtR.left; + txtR.bottom -= txtR.top; + txtR.left = txtR.top = 0; + DrawTextW(hDC, text, textLen, &txtR, dtFlags); + SelectObject(hDC, hOldFont); + + POINT p; + p.x = labelRect.left; + p.y = labelRect.top; + ScreenToClient(hWindow, &p); + POINT p2; + p2.x = data.LineRect.left; + p2.y = data.LineRect.top; + ScreenToClient(hWindow, &p2); + + if (labelRect.right + 5 * spaceWidth < data.LineRect.left) + TRACE_E("ArrangeHorizontalLines(): unexpected situation: horizontal line begins more than five spaces behind label, ignoring label..."); + else // we shorten the label so it does not cover the line and we extend the line to the label + { + if (data.IsCheckOrRadioBox) + { + LOGFONT lf; + GetObject(hFont, sizeof(LOGFONT), &lf); + // on Win7 I found that symbol sizes (e.g. radio/check boxes) change + // only for 100%, 125%, 150% and 200% DPI, intermediate DPIs always use symbols + // from the lower "whole" DPI; we therefore measure all intermediate font sizes + // to cover all possible DPIs + int boxSize = -lf.lfHeight < 13 ? 16 : // < 125% DPI + -lf.lfHeight < 16 ? 20 + : // < 150% DPI + -lf.lfHeight < 21 ? 25 + : 27; // < 200% DPI : == 200% DPI + if (p2.x + (data.LineRect.right - data.LineRect.left) > p.x + boxSize + txtR.right + 2 * spaceWidth) + { + MoveWindow(data.Label, p.x, p.y, boxSize + txtR.right + spaceWidth, labelRect.bottom - labelRect.top, TRUE); + MoveWindow(data.Line, p.x + boxSize + txtR.right + 2 * spaceWidth, p2.y, + p2.x + (data.LineRect.right - data.LineRect.left) - (p.x + boxSize + txtR.right + 2 * spaceWidth), + data.LineRect.bottom - data.LineRect.top, TRUE); + } + } + else + { + if (p2.x + (data.LineRect.right - data.LineRect.left) > p.x + txtR.right + 2 * spaceWidth) + { + MoveWindow(data.Label, p.x, p.y, txtR.right + spaceWidth, labelRect.bottom - labelRect.top, TRUE); + MoveWindow(data.Line, p.x + txtR.right + 2 * spaceWidth, p2.y, + p2.x + (data.LineRect.right - data.LineRect.left) - (p.x + txtR.right + 2 * spaceWidth), + data.LineRect.bottom - data.LineRect.top, TRUE); + } + } + } + } + } + else + { + POINT p; + p.x = data.LineRect.left; + p.y = data.LineRect.top; + ScreenToClient(hWindow, &p); + RECT rect = {0, 0, 20, 1}; + MapDialogRect(hWindow, &rect); + if (p.x > rect.right) + TRACE_E("ArrangeHorizontalLines(): label not found, but line begins more than 20 dlg-units from left side of dialog!"); + } + } + + // alignment of check boxes and radio buttons used as labels on group boxes + horizLines.DestroyMembers(); + EnumChildWindows(hWindow, FindGroupBoxes, (LPARAM)&horizLines); + for (int i = 0; i < horizLines.Count; i++) + { + CDataForFindGroupBoxLabel data; + data.GroupBox = horizLines[i]; + data.Label = NULL; + data.MoreLabels = FALSE; + GetWindowRect(data.GroupBox, &data.GroupBoxRect); + EnumChildWindows(hWindow, FindGroupBoxLabel, (LPARAM)&data); + if (data.Label != NULL) + { + if (data.MoreLabels) + TRACE_E("ArrangeHorizontalLines(): unexpected situation: more labels for one group box!"); + else + { + HFONT hFont = (HFONT)SendMessage(data.Label, WM_GETFONT, 0, 0); + HFONT hOldFont = (HFONT)SelectObject(hDC, hFont); + if (spaceWidth == -1) + { + SIZE sz; + GetTextExtentPoint32(hDC, " ", 1, &sz); + spaceWidth = sz.cx; + } + RECT labelRect; + GetWindowRect(data.Label, &labelRect); + WCHAR text[1000]; + GetWindowTextW(data.Label, text, _countof(text)); + int textLen = (int)wcslen(text); + if (textLen > 0 && text[textLen - 1] == ' ') + text[--textLen] = 0; + DWORD dtFlags = DT_CALCRECT | DT_LEFT | DT_SINGLELINE; + RECT txtR = labelRect; + txtR.right -= txtR.left; + txtR.bottom -= txtR.top; + txtR.left = txtR.top = 0; + DrawTextW(hDC, text, textLen, &txtR, dtFlags); + SelectObject(hDC, hOldFont); + + POINT p; + p.x = labelRect.left; + p.y = labelRect.top; + ScreenToClient(hWindow, &p); + // shorten the check box or radio button according to the content and current font + LOGFONT lf; + GetObject(hFont, sizeof(LOGFONT), &lf); + // on Win7 I found that symbol sizes (e.g. radio/check boxes) change + // only for 100%, 125%, 150% and 200% DPI; intermediate DPIs always use symbols + // from the lower "whole" DPI, so we measure all intermediate font sizes + // this should cover all possible DPIs + int boxSize = -lf.lfHeight < 13 ? 16 : // < 125% DPI + -lf.lfHeight < 16 ? 20 + : // < 150% DPI + -lf.lfHeight < 21 ? 25 + : 27; // < 200% DPI : == 200% DPI + MoveWindow(data.Label, p.x, p.y, boxSize + txtR.right + 2 * spaceWidth, labelRect.bottom - labelRect.top, TRUE); + } + } + } + HANDLES(ReleaseDC(hWindow, hDC)); +} + +// +// **************************************************************************** +// GetWindowFontHeight +// + +int GetWindowFontHeight(HWND hWindow) +{ + HFONT hFont = (HFONT)SendMessage(hWindow, WM_GETFONT, 0, 0); + LOGFONT lf; + if (GetObject(hFont, sizeof(lf), &lf) == 0) + { + DWORD err = GetLastError(); + TRACE_E("GetObject() failed! err=" << err); + return -12; + } + return lf.lfHeight; + // retrieving the size via GetTextMetrics() returns -19 for 150% DPI under Windows 7, + // while GetObject() returns -16, which is correct; creating a font with -19 + // results in text that is too large + // TEXTMETRIC tm; + // HDC hDC = HANDLES(GetDC(hWindow)); + // HFONT hOldFont = (HFONT)SelectObject(hDC, hFont); + // GetTextMetrics(hDC, &tm); + // SelectObject(hDC, hOldFont); + // HANDLES(ReleaseDC(hWindow, hDC)); + // return tm.tmHeight; +} + +// +// **************************************************************************** +// CreateCheckboxImagelist() +// + +HIMAGELIST CreateCheckboxImagelist(int itemSize) +{ + HIMAGELIST hIL = ImageList_Create(itemSize, itemSize, GetImageListColorFlags() | ILC_MASK, 0, 1); + + HDC hDC = HANDLES(GetDC(NULL)); + HBITMAP hBitmap = HANDLES(CreateCompatibleBitmap(hDC, itemSize, itemSize)); + HDC hMemDC = HANDLES(CreateCompatibleDC(hDC)); + HBITMAP hOldBitmap = (HBITMAP)SelectObject(hMemDC, hBitmap); + RECT r = {0, 0, itemSize, itemSize}; + + BOOL fallBack = TRUE; + if (IsAppThemed()) + { + HTHEME hTheme = OpenThemeData(NULL, L"BUTTON"); + if (hTheme != NULL) + { + FillRect(hMemDC, &r, (HBRUSH)(COLOR_WINDOW + 1)); + SIZE sz; + GetThemePartSize(hTheme, hMemDC, BP_CHECKBOX, CBS_CHECKEDNORMAL, NULL, TS_TRUE, &sz); + if (sz.cx < r.right && sz.cy < r.bottom) + { + r.left = (r.right - sz.cx) / 2; + r.top = (r.bottom - sz.cy) / 2; + r.right = r.left + sz.cx; + r.bottom = r.top + sz.cy; + } + DrawThemeBackground(hTheme, hMemDC, BP_CHECKBOX, CBS_UNCHECKEDNORMAL, &r, NULL); + SelectObject(hMemDC, hOldBitmap); + ImageList_Add(hIL, hBitmap, NULL); + + HBITMAP hOldBitmap = (HBITMAP)SelectObject(hMemDC, hBitmap); + DrawThemeBackground(hTheme, hMemDC, BP_CHECKBOX, CBS_CHECKEDNORMAL, &r, NULL); + SelectObject(hMemDC, hOldBitmap); + ImageList_Add(hIL, hBitmap, NULL); + + CloseThemeData(hTheme); + fallBack = FALSE; + } + } + if (fallBack) + { + FillRect(hMemDC, &r, (HBRUSH)(COLOR_WINDOW + 1)); + DrawFrameControl(hMemDC, &r, DFC_BUTTON, DFCS_BUTTONCHECK); + SelectObject(hMemDC, hOldBitmap); + ImageList_Add(hIL, hBitmap, NULL); + + hOldBitmap = (HBITMAP)SelectObject(hMemDC, hBitmap); + FillRect(hMemDC, &r, (HBRUSH)(COLOR_WINDOW + 1)); + DrawFrameControl(hMemDC, &r, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_CHECKED); + SelectObject(hMemDC, hOldBitmap); + ImageList_Add(hIL, hBitmap, NULL); + } + + HANDLES(DeleteObject(hBitmap)); + HANDLES(DeleteDC(hMemDC)); + HANDLES(ReleaseDC(NULL, hDC)); + + return hIL; +} + +// +// **************************************************************************** +// SalLoadIcon() +// + +HICON SalLoadIcon(HINSTANCE hInst, LPCTSTR iconName, CIconSizeEnum iconSize) +{ + int width = IconSizes[iconSize]; + HICON hIcon = NULL; + HRESULT hres = LoadIconWithScaleDown(hInst, (PCWSTR)iconName, width, width, &hIcon); + if (hres != S_OK) + { + DWORD err = GetLastError(); + TRACE_E("LoadIconWithScaleDown() failed. hInst=" << hInst << " iconName=" << iconName << " err=" << err); + } + else + { + HANDLES_ADD(__htIcon, __hoLoadIcon, hIcon); + } + return hIcon; +} \ No newline at end of file diff --git a/src/gui_progressbar.cpp b/src/gui_progressbar.cpp new file mode 100644 index 00000000..495c7cea --- /dev/null +++ b/src/gui_progressbar.cpp @@ -0,0 +1,396 @@ +// SPDX-FileCopyrightText: 2023 Taskscape Ltd +// SPDX-License-Identifier: GPL-2.0-or-later +// CommentsTranslationProject: TRANSLATED + +#include "precomp.h" + +#include "svg.h" +#include "gui.h" +#include "toolbar.h" +#include "menu.h" +#include "tooltip.h" +#include +#include + +#include "nanosvg\nanosvg.h" +#include "nanosvg\nanosvgrast.h" + +#include "mainwnd.h" + +#include "gui_bitmap.h" + +//**************************************************************************** +// +// CProgressBar +// + +CProgressBar::CProgressBar(HWND hDlg, int ctrlID) + : CWindow(hDlg, ctrlID, ooAllocated) +{ + if (HWindow != NULL) + { + RECT r; + GetClientRect(HWindow, &r); + Width = r.right - r.left; + Height = r.bottom - r.top; + } + else + { + Width = 10; + Height = 10; + } + + Progress = 0; + SelfMoveTime = 0xFFFFFFFF; // after calling SetProgress(-1) the rectangle will move indefinitely + SelfMoveTicks = 0; + SelfMoveSpeed = 50; // 20 moves per second + TimerIsRunning = FALSE; + Bitmap = new CBitmap(); + if (Bitmap != NULL) + { + HDC hDC = HANDLES(GetDC(NULL)); + if (!Bitmap->CreateBmp(hDC, Width, Height)) + { + delete Bitmap; + Bitmap = NULL; + } + HANDLES(ReleaseDC(NULL, hDC)); + } + Text = NULL; + + // get the default font from the dialog + HFont = (HFONT)SendMessage(hDlg, WM_GETFONT, 0, 0); + if (HFont == NULL) + HFont = (HFONT)GetStockObject(DEFAULT_GUI_FONT); // it uses the system font, so obtain it from the system +} + +CProgressBar::~CProgressBar() +{ + Stop(); + if (Bitmap != NULL) + delete (Bitmap); + if (Text != NULL) + free(Text); +} + +void CProgressBar::SetProgress(DWORD progress, const char* text) +{ + // use SendMessage instead of a direct call to cross the thread boundary + SendMessage(HWindow, WM_USER_SETPROGRESS, progress, (LPARAM)text); +} + +void CProgressBar::SetProgress2(const CQuadWord& progressCurrent, const CQuadWord& progressTotal, const char* text) +{ + // it can happen that progressTotal is 1 and progressCurrent is a large number, + // making the calculation meaningless (RTC also fails), so we must explicitly set 0% or 100% (value 1000) + SetProgress(progressCurrent >= progressTotal ? (progressTotal.Value == 0 ? 0 : 1000) : (DWORD)((progressCurrent * CQuadWord(1000, 0)) / progressTotal).Value, + text); +} + +void CProgressBar::SetSelfMoveTime(DWORD time) +{ + SelfMoveTime = time; +} + +void CProgressBar::SetSelfMoveSpeed(DWORD moveTime) +{ + SelfMoveSpeed = moveTime; + if (TimerIsRunning) + { + KillTimer(HWindow, IDT_PROGRESSSELFMOVE); + SetTimer(HWindow, IDT_PROGRESSSELFMOVE, SelfMoveSpeed, NULL); + } +} + +void CProgressBar::Stop() +{ + if (TimerIsRunning) + { + KillTimer(HWindow, IDT_PROGRESSSELFMOVE); + TimerIsRunning = FALSE; + } +} + +void CProgressBar::Paint(HDC hDC) +{ + BOOL releaseDC = FALSE; + if (hDC == NULL) + { + hDC = HANDLES(GetDC(HWindow)); + releaseDC = TRUE; + } + + // if we have a bitmap, use the cache + HDC hMemDC = NULL; + if (Bitmap != NULL && Progress != -1) // caching Progress==-1 is pointless, there's nothing to flicker + hMemDC = Bitmap->HMemDC; + else + hMemDC = hDC; + + if (Progress == -1) + { + // indeterminate mode: white, blue rectangle, white + RECT r; + r.top = 1; + r.bottom = Height - 1; + + int mid = BarX + 1; + int midW = Height * 2; + + SelectObject(hMemDC, HFont); + + COLORREF oldBkColor = SetBkColor(hMemDC, GetCOLORREF(CurrentColors[PROGRESS_BK_NORMAL])); + + r.left = 1; + r.right = mid - midW; + if (r.left < 1) + r.left = 1; + if (r.left > Width - 1) + r.left = Width - 1; + if (r.right < 1) + r.right = 1; + if (r.right > Width - 1) + r.right = Width - 1; + ExtTextOut(hMemDC, 0, 0, ETO_OPAQUE, &r, "", 0, NULL); + + SetBkColor(hMemDC, GetCOLORREF(CurrentColors[PROGRESS_BK_SELECTED])); + r.left = 1 + mid - midW; + r.right = mid + midW; + if (r.left < 1) + r.left = 1; + if (r.left > Width - 1) + r.left = Width - 1; + if (r.right < 1) + r.right = 1; + if (r.right > Width - 1) + r.right = Width - 1; + ExtTextOut(hMemDC, 0, 0, ETO_OPAQUE, &r, "", 0, NULL); + + SetBkColor(hMemDC, GetCOLORREF(CurrentColors[PROGRESS_BK_NORMAL])); + r.left = 1 + mid + midW; + r.right = Width - 1; + if (r.left < 1) + r.left = 1; + if (r.left > Width - 1) + r.left = Width - 1; + if (r.right < 1) + r.right = 1; + if (r.right > Width - 1) + r.right = Width - 1; + ExtTextOut(hMemDC, 0, 0, ETO_OPAQUE, &r, "", 0, NULL); + + SetBkColor(hMemDC, oldBkColor); + } + else + { + // prepare and measure the string + char buff[50]; + + char* progress; + int progressLen; + + if (Text != NULL) + { + progress = Text; + progressLen = (int)strlen(progress); + } + else + { + progress = buff; + progressLen = sprintf(progress, "%d %%", (int)((Progress /*+ 5*/) / 10)); // we do not round the progress, beacause otherwise 100% is visible from 99.5%-100%, which annoys some users (notable with FTP, where it can last half a minute) + } + + SIZE sz; + GetTextExtentPoint32(hMemDC, progress, progressLen, &sz); + + // text position -- centered in both axes + int x = (Width - sz.cx) / 2; + int y = (Height - sz.cy) / 2; + + // left part of the progress (SELECTED) + RECT r; + r.left = 1; + r.right = 1 + (Width - 2) * Progress / 1000; + r.top = 1; + r.bottom = Height - 1; + + SelectObject(hMemDC, HFont); + + COLORREF oldTextColor = SetTextColor(hMemDC, GetCOLORREF(CurrentColors[PROGRESS_FG_SELECTED])); + COLORREF oldBkColor = SetBkColor(hMemDC, GetCOLORREF(CurrentColors[PROGRESS_BK_SELECTED])); + ExtTextOut(hMemDC, x, y, ETO_OPAQUE | ETO_CLIPPED, &r, progress, progressLen, NULL); + + // right part of the progress (NORMAL) + r.left = r.right; + r.right = Width - 1; + + SetTextColor(hMemDC, GetCOLORREF(CurrentColors[PROGRESS_FG_NORMAL])); + SetBkColor(hMemDC, GetCOLORREF(CurrentColors[PROGRESS_BK_NORMAL])); + ExtTextOut(hMemDC, x, y, ETO_OPAQUE | ETO_CLIPPED, &r, progress, progressLen, NULL); + SetTextColor(hMemDC, oldTextColor); + SetBkColor(hMemDC, oldBkColor); + } + + HPEN hOldPen = (HPEN)SelectObject(hMemDC, BtnShadowPen); + MoveToEx(hMemDC, 0, 0, NULL); + LineTo(hMemDC, Width - 1, 0); + LineTo(hMemDC, Width - 1, Height - 1); + LineTo(hMemDC, 0, Height - 1); + LineTo(hMemDC, 0, 0); + SelectObject(hMemDC, hOldPen); + + // if drawing through the cache, copy it to the screen + if (Bitmap != NULL && hMemDC != hDC) + BitBlt(hDC, 0, 0, Width, Height, hMemDC, 0, 0, SRCCOPY); + + if (releaseDC) + HANDLES(ReleaseDC(HWindow, hDC)); +} + +void CProgressBar::MoveBar() +{ + if (Progress != -1) + { + // start the movement + Progress = -1; + BarX = 0; + MoveBarRight = TRUE; + } + else + { + if (MoveBarRight) + { + BarX += 4; + if (BarX > Width - 2) + { + BarX = Width - 2; + MoveBarRight = !MoveBarRight; + } + } + else + { + BarX -= 4; + if (BarX < 0) + { + BarX = 0; + MoveBarRight = !MoveBarRight; + } + } + } +} + +LRESULT +CProgressBar::WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + CALL_STACK_MESSAGE4("CProgressBar::WindowProc(0x%X, 0x%IX, 0x%IX)", uMsg, wParam, lParam); + switch (uMsg) + { + case WM_TIMER: + { + if (wParam == IDT_PROGRESSSELFMOVE) + { + if ((SelfMoveTime != 0xFFFFFFFF) && (GetTickCount() - SelfMoveTicks > SelfMoveTime)) + { + KillTimer(HWindow, IDT_PROGRESSSELFMOVE); + TimerIsRunning = FALSE; + } + else + { + MoveBar(); + Paint(NULL); + } + return 0; + } + break; + } + + case WM_USER_SETPROGRESS: + { + DWORD progress = (DWORD)wParam; + const char* text = (const char*)lParam; + + BOOL paint = TRUE; + BOOL textChanged = FALSE; + + if ((text != NULL || Text != NULL) && + (text == NULL || Text == NULL || strcmp(text, Text) != 0)) + { + textChanged = TRUE; + if (Text != NULL) + { + free(Text); + Text = NULL; + } + if (text != NULL) + { + Text = DupStr(text); + if (Text == NULL) + TRACE_E(LOW_MEMORY); + } + } + + if (progress == (DWORD)-1) + { + if (SelfMoveTime > 0) + { + SelfMoveTicks = GetTickCount(); + if (!TimerIsRunning) + { + SetTimer(HWindow, IDT_PROGRESSSELFMOVE, SelfMoveSpeed, NULL); + TimerIsRunning = TRUE; + MoveBar(); + } + else + paint = FALSE; // the change will occur on the timer, no reason to redraw now + } + else + MoveBar(); + } + else + { + if (TimerIsRunning) + Stop(); + if (progress > 1000) + progress = 1000; // max. 100% (a copy of the "active" file may report progress >100%) + if (progress != Progress) + Progress = progress; + else + paint = textChanged; // progress didn't change; if the text didn't change either, redrawing is skipped + /* + BOOL redraw = Progress == 0 || // always show 0% + Progress == 1000 || // always show 100% + Progress - DisplayedProgress >= 100; // always show a change greater than 10% + if (redraw && Progress != DisplayedProgress) + Paint(NULL); + */ + } + if (paint) + Paint(NULL); + return 0; + } + + case WM_SIZE: + { + Width = LOWORD(lParam); + Height = HIWORD(lParam); + Bitmap->Enlarge(Width, Height); + return 0; + } + + case WM_ERASEBKGND: + { + return TRUE; + } + + case WM_PAINT: + { + PAINTSTRUCT ps; + HDC hDC = HANDLES(BeginPaint(HWindow, &ps)); + Paint(hDC); + HANDLES(EndPaint(HWindow, &ps)); + return 0; + } + } + + return CWindow::WindowProc(uMsg, wParam, lParam); +} diff --git a/src/gui_statictext.cpp b/src/gui_statictext.cpp new file mode 100644 index 00000000..e4a560ae --- /dev/null +++ b/src/gui_statictext.cpp @@ -0,0 +1,987 @@ +// SPDX-FileCopyrightText: 2023 Taskscape Ltd +// SPDX-License-Identifier: GPL-2.0-or-later +// CommentsTranslationProject: TRANSLATED + +#include "precomp.h" + +#include "svg.h" +#include "gui.h" +#include "toolbar.h" +#include "menu.h" +#include "tooltip.h" +#include +#include + +#include "nanosvg\nanosvg.h" +#include "nanosvg\nanosvgrast.h" + +#include "mainwnd.h" + +//**************************************************************************** +// +// CStaticText +// + +CStaticText::CStaticText(HWND hDlg, int ctrlID, DWORD flags) + : CWindow(hDlg, ctrlID, ooAllocated) +{ + if ((flags & STF_HANDLEPREFIX) && ((flags & STF_END_ELLIPSIS) || (flags & STF_PATH_ELLIPSIS))) + { + TRACE_E("Flag STF_HANDLEPREFIX cannot be used with STF_END_ELLIPSIS or STF_PATH_ELLIPSIS."); + flags &= ~STF_HANDLEPREFIX; + } + + Flags = flags; + Text = NULL; + TextLen = 0; + TextW = NULL; + TextLenW = 0; + Text2 = NULL; + Text2Len = 0; + Text2W = NULL; + Text2LenW = 0; + AlpDX = NULL; + Allocated = 0; + AllocatedW = 0; + Bitmap = NULL; + HFont = NULL; + DestroyFont = FALSE; + ClipDraw = FALSE; + Text2Draw = FALSE; + Alignment = 0; // left + PathSeparator = '\\'; + MouseIsTracked = FALSE; + ToolTipText = NULL; + HToolTipNW = NULL; + ToolTipID = 0; + HintMode = FALSE; + + if (HWindow == NULL) + return; // avoid flickering the screen + + UIState = (WORD)SendMessage(HWindow, WM_QUERYUISTATE, 0, 0); + + // get the alignment + DWORD style = (DWORD)GetWindowLongPtr(HWindow, GWL_STYLE); + if (style & SS_RIGHT) + Alignment = 2; + else if (style & SS_CENTER) + Alignment = 1; + + // measure the maximum size of the static + RECT r; + GetClientRect(HWindow, &r); + Width = r.right - r.left; + Height = r.bottom - r.top; + + // if we should draw via cache, we create a bitmap + if (Flags & STF_CACHED_PAINT) + { + Bitmap = new CBitmap(); // if allocation fails, paint won't be cached + if (Bitmap != NULL) + { + HDC hDC = HANDLES(GetDC(HWindow)); + if (!Bitmap->CreateBmp(hDC, Width, Height)) + { + delete Bitmap; + Bitmap = NULL; + } + HANDLES(ReleaseDC(HWindow, hDC)); + } + } + + // obtain the default font from the static + HFont = (HFONT)SendMessage(HWindow, WM_GETFONT, 0, 0); + if ((Flags & STF_BOLD) || (Flags & STF_UNDERLINE)) + { + // if the text is BOLD or UNDERLINE, prepare our own font + LOGFONT lf; + GetObject(HFont, sizeof(lf), &lf); + if (Flags & STF_BOLD) + lf.lfWeight = FW_BOLD; + if (Flags & STF_UNDERLINE) + lf.lfUnderline = TRUE; + HFont = HANDLES(CreateFontIndirect(&lf)); + DestroyFont = TRUE; + } + + // obtain the initial text of the static + char buff[4096]; + CWindow::WindowProc(WM_GETTEXT, 4096, (LPARAM)buff); + buff[4095] = 0; // just to be sure... + if (buff[0] != 0) + SetText(buff); +} + +CStaticText::~CStaticText() +{ + if (ToolTipText != NULL) + free(ToolTipText); + if (Text != NULL) + free(Text); + if (TextW != NULL) + free(TextW); + if (Text2 != NULL) + free(Text2); + if (Text2W != NULL) + free(Text2W); + if (AlpDX != NULL) + free(AlpDX); + if (Bitmap != NULL) + delete Bitmap; + if (HFont != NULL && DestroyFont) + HANDLES(DeleteObject(HFont)); +} + +// prevents numerous reallocations when gradually allocating larger and larger strings +#define ST_ALLOC_GRANULARITY 20 + +BOOL CStaticText::SetText(const char* text) +{ + CALL_STACK_MESSAGE2("CStaticText::SetText(%s)", text); + + if (text == NULL) + text = ""; + if (Text != NULL && strcmp(Text, text) == 0) + return TRUE; + + int l = (int)strlen(text) + 1; + if (Allocated < l) + { + char* newText = (char*)realloc(Text, l + ST_ALLOC_GRANULARITY); + if (newText == NULL) + { + TRACE_E(LOW_MEMORY); + return FALSE; + } + if (Flags & (STF_PATH_ELLIPSIS | STF_END_ELLIPSIS)) + { + int* newAlpDX = (int*)realloc(AlpDX, (l + ST_ALLOC_GRANULARITY) * sizeof(int)); + if (newAlpDX == NULL) + { + TRACE_E(LOW_MEMORY); + free(newText); + return FALSE; + } + char* newText2 = (char*)realloc(Text2, l + ST_ALLOC_GRANULARITY + 3); // 3: space for "..." (I can remove W and add "...") + if (newText2 == NULL) + { + TRACE_E(LOW_MEMORY); + free(newText); + free(newAlpDX); + return FALSE; + } + AlpDX = newAlpDX; + Text2 = newText2; + } + Text = newText; + Allocated = l + ST_ALLOC_GRANULARITY; + } + memmove(Text, text, l); + TextLen = l - 1; + + // Convert UTF-8 to wide characters for proper Unicode display + int wideLen = MultiByteToWideChar(CP_UTF8, 0, text, -1, NULL, 0); + if (wideLen > 0) + { + if (AllocatedW < wideLen) + { + wchar_t* newTextW = (wchar_t*)realloc(TextW, (wideLen + ST_ALLOC_GRANULARITY) * sizeof(wchar_t)); + if (newTextW == NULL) + { + TRACE_E(LOW_MEMORY); + // Continue without wide text - will fall back to ANSI display + } + else + { + TextW = newTextW; + AllocatedW = wideLen + ST_ALLOC_GRANULARITY; + } + if (Flags & (STF_PATH_ELLIPSIS | STF_END_ELLIPSIS)) + { + wchar_t* newText2W = (wchar_t*)realloc(Text2W, (wideLen + ST_ALLOC_GRANULARITY + 3) * sizeof(wchar_t)); + if (newText2W != NULL) + Text2W = newText2W; + } + } + if (TextW != NULL) + { + MultiByteToWideChar(CP_UTF8, 0, text, -1, TextW, AllocatedW); + TextLenW = wideLen - 1; + } + } + + PrepareForPaint(); + + InvalidateRect(HWindow, NULL, FALSE); + UpdateWindow(HWindow); + return TRUE; +} + +BOOL CStaticText::SetTextToDblQuotesIfNeeded(const char* text) +{ + CALL_STACK_MESSAGE2("CStaticText::SetTextToDblQuotesIfNeeded(%s)", text); + + if (text != NULL) + { + int len = (int)strlen(text); + if (len > 0 && (text[0] <= ' ' || text[len - 1] <= ' ') && len < 2 * MAX_PATH) + { + char buf[2 * MAX_PATH + 2]; + sprintf(buf, "\"%s\"", text); // spaces at the beginning and end will be visible in quotes (otherwise they are invisible) + return SetText(buf); + } + } + return SetText(text); +} + +void CStaticText::PrepareForPaint() +{ + ClipDraw = FALSE; + Text2Draw = FALSE; + + if (Text == NULL || TextLen == 0) // the algorithm is designed only for a non-zero number of characters + { + TextWidth = 0; + TextHeight = 0; + return; + } + + HDC hDC = HANDLES(GetDC(HWindow)); + HFONT hOldFont = (HFONT)SelectObject(hDC, HFont); + SIZE sz; + + // Use wide character APIs for proper Unicode support + BOOL useWide = (TextW != NULL && TextLenW > 0); + + if (Flags & (STF_PATH_ELLIPSIS | STF_END_ELLIPSIS)) + { + if (Flags & STF_END_ELLIPSIS) + { + // STF_END_ELLIPSIS: the string will end with an ellipsis + // we need lengths only for the characters that fit + int fitChars; + if (useWide) + GetTextExtentExPointW(hDC, TextW, TextLenW, Width, &fitChars, AlpDX, &sz); + else + GetTextExtentExPoint(hDC, Text, TextLen, Width, &fitChars, AlpDX, &sz); + + int textLen = useWide ? TextLenW : TextLen; + + if (fitChars < textLen) + { + //we it did not fit -- we must insert an ellipsis + + // we get the width of "..." for the ellipsis + SIZE ellipsisSZ; + GetTextExtentPoint32W(hDC, L"...", 3, &ellipsisSZ); + int ellipsisWidth = ellipsisSZ.cx; + + // we search from the right end to find how much to trim so we can append the ellipsis + while (fitChars > 0 && AlpDX[fitChars - 1] + ellipsisWidth > Width) + fitChars--; + if (fitChars > 0) + { + if (useWide && Text2W != NULL) + { + memmove(Text2W, TextW, fitChars * sizeof(wchar_t)); + Text2LenW = fitChars; + } + memmove(Text2, Text, fitChars); + TextWidth = AlpDX[fitChars - 1]; + Text2Len = fitChars; + } + else + { + TextWidth = 0; + Text2Len = 0; + Text2LenW = 0; + } + strcpy(Text2 + fitChars, "..."); + if (useWide && Text2W != NULL) + wcscpy(Text2W + fitChars, L"..."); + TextWidth += ellipsisWidth; + Text2Len += 3; + Text2LenW = Text2Len; + + Text2Draw = TRUE; + } + else + { + TextWidth = sz.cx; + } + } + else + { + // STF_PATH_ELLIPSIS: the ellipsis will be inside the text + // we need lengths of all substrings + if (useWide) + GetTextExtentExPointW(hDC, TextW, TextLenW, 0, NULL, AlpDX, &sz); + else + GetTextExtentExPoint(hDC, Text, TextLen, 0, NULL, AlpDX, &sz); + + int textLen = useWide ? TextLenW : TextLen; + + if (sz.cx > Width) + { + // we did not fit -- we must insert an ellipsis + + // get the width of "..." for the ellipsis + SIZE ellipsisSZ; + GetTextExtentPoint32W(hDC, L"...", 3, &ellipsisSZ); + int ellipsisWidth = ellipsisSZ.cx; + + // search from the right end for the path separator + int pIndex; + if (useWide) + { + const wchar_t* p = TextW + TextLenW - 1; + wchar_t pathSepW = (wchar_t)PathSeparator; + while (*p != pathSepW && p > TextW) + p--; + const wchar_t* p2 = p; + if (p > TextW) + p--; + pIndex = (int)(p - TextW); + + // the text from 'p' and further should fit entirely including the ellipsis + if (ellipsisWidth + sz.cx - AlpDX[pIndex] > Width) + { + // it did not fit =>we search from the left end for a place to insert the ellipsis + while (pIndex < TextLenW && (ellipsisWidth + sz.cx - AlpDX[pIndex] > Width)) + pIndex++; + + // we insert the ellipsis and then the rest of the text behind it + pIndex++; + if (Text2W != NULL) + wcscpy(Text2W, L"..."); + strcpy(Text2, "..."); + Text2Len = 3; + Text2LenW = 3; + TextWidth = ellipsisWidth; + if (pIndex < TextLenW) + { + if (Text2W != NULL) + { + memmove(Text2W + 3, TextW + pIndex, (TextLenW - pIndex + 1) * sizeof(wchar_t)); + Text2LenW += TextLenW - pIndex; + } + memmove(Text2 + 3, Text + pIndex, TextLen - pIndex + 1); + Text2Len += TextLen - pIndex; + TextWidth += sz.cx - AlpDX[pIndex - 1]; + } + } + else + { + int rightPartWidth = sz.cx - AlpDX[pIndex]; + // we determine how many characters to keep on the left side of the ellipsis + while (pIndex >= 0 && (AlpDX[pIndex] + ellipsisWidth + rightPartWidth) > Width) + pIndex--; + // left part + Text2Len = 0; + Text2LenW = 0; + TextWidth = 0; + if (pIndex >= 0) + { + if (Text2W != NULL) + { + memmove(Text2W, TextW, (pIndex + 1) * sizeof(wchar_t)); + Text2LenW += pIndex + 1; + } + memmove(Text2, Text, pIndex + 1); + Text2Len += pIndex + 1; + TextWidth += AlpDX[pIndex]; + } + // ellipsis + if (Text2W != NULL) + { + memmove(Text2W + Text2LenW, L"...", 3 * sizeof(wchar_t)); + Text2LenW += 3; + } + memmove(Text2 + Text2Len, "...", 3); + Text2Len += 3; + TextWidth += ellipsisWidth; + // right part + int rightPartLen = TextLenW - (int)(p2 - TextW); + if (Text2W != NULL) + { + memmove(Text2W + Text2LenW, p2, (rightPartLen + 1) * sizeof(wchar_t)); + Text2LenW += rightPartLen; + } + int rightPartLenA = TextLen - (int)((Text + TextLen) - (Text + pIndex + 1 + (p2 - (TextW + pIndex + 1)))); + // Approximate - use same ratio + rightPartLenA = TextLen - pIndex - 1; + const char* p2A = Text + TextLen - rightPartLen; + memmove(Text2 + Text2Len, p2A, rightPartLen + 1); + Text2Len += rightPartLen; + TextWidth += rightPartWidth; + } + } + else + { + // ANSI fallback + const char* p = Text + TextLen - 1; + while (*p != PathSeparator && p > Text) + p--; + const char* p2 = p; + if (p > Text) + p--; + pIndex = (int)(p - Text); + + // the text from 'p' and further should fit entirely including the ellipsis + if (ellipsisWidth + sz.cx - AlpDX[pIndex] > Width) + { + // it did not fit =>we search from the left end for a place to insert the ellipsis + while (pIndex < TextLen && (ellipsisWidth + sz.cx - AlpDX[pIndex] > Width)) + pIndex++; + + // we insert the ellipsis and then the rest of the text behind it + pIndex++; + strcpy(Text2, "..."); + Text2Len = 3; + TextWidth = ellipsisWidth; + if (pIndex < TextLen) + { + memmove(Text2 + 3, Text + pIndex, TextLen - pIndex + 1); // including the terminator + Text2Len += TextLen - pIndex; + TextWidth += sz.cx - AlpDX[pIndex - 1]; + } + } + else + { + int rightPartWidth = sz.cx - AlpDX[pIndex]; + // we determine how many characters to keep on the left side of the ellipsis + while (pIndex >= 0 && (AlpDX[pIndex] + ellipsisWidth + rightPartWidth) > Width) + pIndex--; + // left part + Text2Len = 0; + TextWidth = 0; + if (pIndex >= 0) + { + memmove(Text2, Text, pIndex + 1); + Text2Len += pIndex + 1; + TextWidth += AlpDX[pIndex]; + } + // ellipsis + memmove(Text2 + Text2Len, "...", 3); + Text2Len += 3; + TextWidth += ellipsisWidth; + // right part + int rightPartLen = TextLen - (int)(p2 - Text); + memmove(Text2 + Text2Len, p2, rightPartLen + 1); + Text2Len += rightPartLen; + TextWidth += rightPartWidth; + } + } + + Text2Draw = TRUE; + } + else + { + TextWidth = sz.cx; + } + } + TextHeight = sz.cy; + } + else + { + // the overall dimensions are sufficient + if (Flags & STF_HANDLEPREFIX) + { + RECT r; + GetClientRect(HWindow, &r); + if (useWide) + DrawTextW(hDC, TextW, TextLenW, &r, DT_CALCRECT | DT_SINGLELINE | DT_LEFT); + else + DrawText(hDC, Text, TextLen, &r, DT_CALCRECT | DT_SINGLELINE | DT_LEFT); + TextWidth = r.right; + TextHeight = r.bottom; + } + else + { + if (useWide) + GetTextExtentPoint32W(hDC, TextW, TextLenW, &sz); + else + GetTextExtentPoint32(hDC, Text, TextLen, &sz); + TextWidth = sz.cx + 1; + TextHeight = sz.cy; + } + } + // if the text would cross the window boundary, we must clip during drawing + if (TextWidth > Width) + { + TextWidth = Width; + ClipDraw = TRUE; + } + if (TextHeight > Height) + { + TextHeight = Height; + ClipDraw = TRUE; + } + SelectObject(hDC, hOldFont); + HANDLES(ReleaseDC(HWindow, hDC)); +} + +void CStaticText::SetPathSeparator(char separator) +{ + if (separator == 0) + TRACE_E("CStaticText::SetPathSeparator == 0"); + else + { + if (separator != PathSeparator) + { + PathSeparator = separator; + InvalidateRect(HWindow, NULL, FALSE); + PrepareForPaint(); + } + } +} + +int CStaticText::GetTextXOffset() +{ + int xOffset = 0; // SS_LEFT + if (Alignment == 1) + xOffset = (Width - TextWidth) / 2; // SS_CENTER + else if (Alignment == 2) + xOffset = Width - TextWidth; // SS_RIGHT + return xOffset; +} + +BOOL CStaticText::TextHitTest(POINT* screenCursorPos) +{ + POINT p = *screenCursorPos; + ScreenToClient(HWindow, &p); + + int xOffset = GetTextXOffset(); + + RECT r; + r.left = xOffset; + r.top = 0; + r.right = xOffset + TextWidth; + r.bottom = TextHeight; + + return PtInRect(&r, p); +} + +BOOL CStaticText::SetToolTipText(const char* text) +{ + if (text != NULL && ToolTipText != NULL && strcmp(ToolTipText, text) == 0) + return TRUE; + + if (text == NULL) + { + if (ToolTipText != NULL) + free(ToolTipText); + ToolTipText = NULL; + HToolTipNW = NULL; + ToolTipID = 0; + return TRUE; + } + + char* newText = DupStr(text); + if (newText == NULL) + return FALSE; + + if (ToolTipText != NULL) + free(ToolTipText); + + ToolTipText = newText; + HToolTipNW = NULL; + ToolTipID = 0; + + PostMessage(MainWindow->ToolTip->HWindow, WM_USER_REFRESHTOOLTIP, 0, 0); // ask the window to load the new text and redraw + + return TRUE; +} + +void CStaticText::SetToolTip(HWND hNotifyWindow, DWORD id) +{ + if (ToolTipText != NULL) + free(ToolTipText); + ToolTipText = NULL; + + HToolTipNW = hNotifyWindow; + ToolTipID = id; +} + +void CStaticText::EnableHintToolTip(BOOL enable) +{ + HintMode = enable; +} + +BOOL CStaticText::ToolTipAssigned() +{ + return ToolTipText != NULL || HToolTipNW != NULL; +} + +void CStaticText::DrawFocus(HDC hDC) +{ + BOOL releaseDC = FALSE; + if (hDC == NULL) + { + hDC = HANDLES(GetDC(HWindow)); + releaseDC = TRUE; + } + + int xOffset = GetTextXOffset(); + + RECT r; + r.left = xOffset; + r.top = 0; + r.right = xOffset + TextWidth; + r.bottom = TextHeight; + + int oldColor = SetTextColor(hDC, GetSysColor(COLOR_BTNFACE)); + int oldBkColor = SetBkColor(hDC, GetSysColor(COLOR_BTNTEXT)); + POINT oldBrushPoint; + SetBrushOrgEx(hDC, 0, 0, &oldBrushPoint); // under XP with the Normal skin the paint misbehaved if the static was placed on a gradient background (FTP configuration) + DrawFocusRect(hDC, &r); + SetBrushOrgEx(hDC, oldBrushPoint.x, oldBrushPoint.y, NULL); + SetTextColor(hDC, oldColor); + SetBkColor(hDC, oldBkColor); + + if (releaseDC) + HANDLES(ReleaseDC(HWindow, hDC)); +} + +BOOL CStaticText::ShowHint() +{ + SetCurrentToolTip(NULL, 0); + + RECT r; + GetWindowRect(HWindow, &r); + int xOffset = GetTextXOffset(); + + MainWindow->ToolTip->SetCurrentToolTip(HWindow, 1, -1); + MainWindow->ToolTip->Show(r.left + xOffset, r.bottom, FALSE, TRUE, HWindow); + // note: Show has the parameter 'modal'==TRUE, so control returns here only after the tooltip is closed + return TRUE; +} + +LRESULT +CStaticText::WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + SLOW_CALL_STACK_MESSAGE4("CStaticText::WindowProc(0x%X, 0x%IX, 0x%IX)", uMsg, wParam, lParam); + switch (uMsg) + { + case WM_SIZE: + { + Width = LOWORD(lParam); + Height = HIWORD(lParam); + if (Bitmap != NULL) + { + if (!Bitmap->Enlarge(Width, Height)) + { + delete Bitmap; + Bitmap = NULL; + } + } + InvalidateRect(HWindow, NULL, FALSE); + PrepareForPaint(); + return 0; + } + + case WM_ERASEBKGND: + { + // background will erased in paint + return TRUE; + } + + case WM_ENABLE: + { + InvalidateRect(HWindow, NULL, FALSE); + PrepareForPaint(); + return 0; + } + + case WM_MOUSEMOVE: + { + if (ToolTipAssigned()) + { + POINT p; + DWORD messagePos = GetMessagePos(); + p.x = GET_X_LPARAM(messagePos); + p.y = GET_Y_LPARAM(messagePos); + if (TextHitTest(&p)) + { + if (ToolTipText != NULL) + SetCurrentToolTip(HWindow, 1); + else if (HToolTipNW != NULL) + SetCurrentToolTip(HWindow, ToolTipID); + } + else + SetCurrentToolTip(NULL, 0); + + if (!MouseIsTracked) + { + TRACKMOUSEEVENT tme; + tme.cbSize = sizeof(tme); + tme.dwFlags = TME_LEAVE; + tme.hwndTrack = HWindow; + MouseIsTracked = TrackMouseEvent(&tme); + } + } + break; + } + + case WM_SHOWWINDOW: + { + if (wParam == TRUE) + break; + // if someone hides us, we must dismiss the tooltip + if (MainWindow != NULL && MainWindow->ToolTip != NULL && MainWindow->ToolTip->HWindow != NULL) + MainWindow->ToolTip->Hide(); + //PostMessage(MainWindow->ToolTip->HWindow, WM_CANCELMODE, 0, 0); + } // fall through to WM_MOUSELEAVE + case WM_MOUSELEAVE: + { + if (ToolTipAssigned()) + { + SetCurrentToolTip(NULL, 0); + MouseIsTracked = FALSE; + } + break; + } + + case WM_USER_TTGETTEXT: + { + if (ToolTipText != NULL) + lstrcpyn((char*)lParam, ToolTipText, TOOLTIP_TEXT_MAX); + return 0; + } + + case WM_SETTEXT: + { + return SetText((char*)lParam); + } + + case WM_GETTEXT: + { + if (Text == NULL || wParam < 2) + return 0; + + int len = (int)strlen(Text); + if (len > (int)wParam - 1) + len = (int)wParam - 1; + memcpy((char*)lParam, Text, len); + ((char*)lParam)[len + 1] = 0; + return len; + } + + case WM_GETDLGCODE: + { + LRESULT ret = DLGC_STATIC; + if (HintMode) + ret |= DLGC_WANTARROWS; + return ret; + } + + case WM_SETFOCUS: + case WM_KILLFOCUS: + { + if (GetWindowLongPtr(HWindow, GWL_STYLE) & WS_TABSTOP) + { + DrawFocus(NULL); + } + break; + } + + case WM_LBUTTONDOWN: + { + if (HintMode) + ShowHint(); + break; + } + + case WM_KEYDOWN: + { + if (HintMode && (wParam == VK_SPACE || wParam == VK_UP || wParam == VK_DOWN)) + ShowHint(); + break; + } + + case WM_PAINT: + { + PAINTSTRUCT ps; + HANDLES(BeginPaint(HWindow, &ps)); + + // if we have a bitmap,we will draw into it, otherwise directly to the screen + HDC hDC; + if (Bitmap != NULL) + hDC = Bitmap->HMemDC; + else + hDC = ps.hdc; + + RECT r; + r.left = 0; + r.top = 0; + r.right = Width; + r.bottom = Height; + + // display our own text + if (Text != NULL) + { + // under XPTheme we have to let Windows erase the background + BOOL bkErased = FALSE; + if (IsAppThemed()) + { + DrawThemeParentBackground(HWindow, hDC, &r); + bkErased = TRUE; + } + + // set the DC parameters and store their original values + int oldBkMode = SetBkMode(hDC, TRANSPARENT); + + HWND hParent = GetParent(HWindow); + if (hParent != NULL) + SendMessage(hParent, WM_CTLCOLORSTATIC, (WPARAM)hDC, (LPARAM)HWindow); + if (Flags & STF_HYPERLINK_COLOR) + SetTextColor(hDC, RGB(0, 0, 255)); + BOOL enabled = IsWindowEnabled(HWindow); + if (!enabled) + SetTextColor(hDC, GetSysColor(COLOR_GRAYTEXT)); + + // COLORREF textClr; + // if (Flags & STF_HYPERLINK_COLOR) + // textClr = RGB(0, 0, 255); + // else + // textClr = GetSysColor(COLOR_BTNTEXT); + // COLORREF oldTextColor = SetTextColor(hDC, textClr); + // COLORREF oldBkColor = SetBkColor(hDC, GetSysColor(COLOR_BTNFACE)); + HFONT hOldFont = (HFONT)SelectObject(hDC, HFont); + + // we draw the text + // Use wide character APIs for proper Unicode support + BOOL useWide = (TextW != NULL && TextLenW > 0); + + if (Flags & STF_HANDLEPREFIX) + { + DWORD drawFlags = DT_SINGLELINE | DT_TOP; + if (Alignment == 1) + drawFlags |= DT_CENTER; + else if (Alignment == 2) + drawFlags |= DT_RIGHT; + else + drawFlags |= DT_LEFT; + // because ClearType spills beyond the control and leaves stray colored dots, we must + // clip everything; the issue is visible in the Plugins Manager Salamander 2.51 when scrolling + // the plugin list, leaving a red dot before the URL + // if (!ClipDraw) + drawFlags |= DT_NOCLIP; + + if (UIState & UISF_HIDEACCEL) + drawFlags |= DT_HIDEPREFIX; + + if (useWide) + DrawTextW(hDC, TextW, TextLenW, &r, drawFlags); + else + DrawText(hDC, Text, TextLen, &r, drawFlags); + } + else + { + DWORD drawFlags = (bkErased) ? 0 : ETO_OPAQUE; + // if (ClipDraw) // same problem as above + drawFlags |= ETO_CLIPPED; + + int xOffset = GetTextXOffset(); + + if (useWide) + { + const wchar_t* textW; + int textLenW; + if (Text2Draw && Text2W != NULL) + { + textW = Text2W; + textLenW = Text2LenW; + } + else + { + textW = TextW; + textLenW = TextLenW; + } + ExtTextOutW(hDC, r.left + xOffset, r.top, drawFlags, &r, textW, textLenW, NULL); + } + else + { + const char* text; + int textLen; + if (Text2Draw) + { + text = Text2; + textLen = Text2Len; + } + else + { + text = Text; + textLen = TextLen; + } + ExtTextOut(hDC, r.left + xOffset, r.top, drawFlags, &r, text, textLen, NULL); + } + } + + if (Flags & STF_DOTUNDERLINE) + { + // dotted underline + int xOffset = GetTextXOffset(); + + HPEN hDottedPen = HANDLES(CreatePen(PS_DOT, 0, GetTextColor(hDC))); + HPEN hOldPen = (HPEN)SelectObject(hDC, hDottedPen); + MoveToEx(hDC, r.left + xOffset, r.bottom - 1, NULL); + LineTo(hDC, r.left + xOffset + TextWidth, r.bottom - 1); + SelectObject(hDC, hOldPen); + HANDLES(DeleteObject(hDottedPen)); + } + + // restore the original DC values + SelectObject(hDC, hOldFont); + // SetBkColor(hDC, oldBkColor); + // SetTextColor(hDC, oldTextColor); + SetBkMode(hDC, oldBkMode); + } + else + { + // no text stored; we must at least erase the background + if (IsAppThemed()) + { + DrawThemeParentBackground(HWindow, hDC, &r); + } + else + FillRect(hDC, &r, (HBRUSH)(COLOR_BTNFACE + 1)); + } + + if ((GetWindowLongPtr(HWindow, GWL_STYLE) & WS_TABSTOP) && GetFocus() == HWindow) + DrawFocus(hDC); + + if (Bitmap != NULL) + { + // if using the cache, we must copy it to the window + BitBlt(ps.hdc, 0, 0, Width, Height, Bitmap->HMemDC, 0, 0, SRCCOPY); + } + + HANDLES(EndPaint(HWindow, &ps)); + return 0; + } + + case WM_UPDATEUISTATE: + { + // unfortunately we cannot rely on the standard static handling because + // under Vista (and maybe earlier) it draws the Alt underline at a nonsensical + // position; one solution would be to capture the text into our buffer and draw from it, + // but I chose a different approach and maintain the state ourselves + if (LOWORD(wParam) == UIS_CLEAR) + UIState &= ~HIWORD(wParam); + else if (LOWORD(wParam) == UIS_SET) + UIState |= HIWORD(wParam); + + BOOL showAccel = (LOWORD(wParam) == UIS_CLEAR) && ((HIWORD(wParam) & UISF_HIDEACCEL) != 0); + if (showAccel) + { + InvalidateRect(HWindow, NULL, TRUE); // if not cached we flicker a little, but never mind + UpdateWindow(HWindow); + } + return 0; + } + } + + return CWindow::WindowProc(uMsg, wParam, lParam); +} \ No newline at end of file diff --git a/src/mainwnd.h b/src/mainwnd.h index 047e0a59..a0cb2d5c 100644 --- a/src/mainwnd.h +++ b/src/mainwnd.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 Taskscape Ltd +// SPDX-FileCopyrightText: 2023 Taskscape Ltd // SPDX-License-Identifier: GPL-2.0-or-later // CommentsTranslationProject: TRANSLATED @@ -727,6 +727,10 @@ class CMainWindow : public CMainWindowAncestor void ApplyCommandLineParams(const CCommandLineParams* params, BOOL setActivePanelAndPanelPaths = TRUE); + // Message-handler methods extracted from WindowProc for clarity. + LRESULT HandleWmCommand(WPARAM wParam, LPARAM lParam); + LRESULT HandleShutdown(UINT uMsg, WPARAM wParam, LPARAM lParam); + void LockUI(BOOL lock, HWND hToolWnd, const char* lockReason); BOOL HasLockedUI() { return LockedUI; } void BringLockedUIToolWnd(); @@ -739,7 +743,7 @@ class CMainWindow : public CMainWindowAncestor // // **************************************************************************** -// C__MainWindowCS +// CMainWindowLock // // ensures access to the MainWindow variable outside the main thread, usage: // if (MainWindowCS.LockIfNotClosed()) @@ -750,19 +754,19 @@ class CMainWindow : public CMainWindowAncestor // MainWindowCS.Unlock(); // } -class C__MainWindowCS +class CMainWindowLock { protected: CRITICAL_SECTION cs; BOOL IsClosed; public: - C__MainWindowCS() + CMainWindowLock() { HANDLES(InitializeCriticalSection(&cs)); IsClosed = FALSE; } - ~C__MainWindowCS() { HANDLES(DeleteCriticalSection(&cs)); } + ~CMainWindowLock() { HANDLES(DeleteCriticalSection(&cs)); } void SetClosed() { @@ -783,7 +787,7 @@ class C__MainWindowCS void Unlock() { HANDLES(LeaveCriticalSection(&cs)); } }; -extern C__MainWindowCS MainWindowCS; +extern CMainWindowLock MainWindowCS; // // **************************************************************************** diff --git a/src/mainwnd3.cpp b/src/mainwnd3.cpp deleted file mode 100644 index 2db063e5..00000000 --- a/src/mainwnd3.cpp +++ /dev/null @@ -1,7157 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Taskscape Ltd -// SPDX-License-Identifier: GPL-2.0-or-later -// CommentsTranslationProject: TRANSLATED - -#include "precomp.h" - -#include -#undef PathIsPrefix // otherwise conflicts with CSalamanderGeneral::PathIsPrefix - -#include "htmlhelp.h" -#include "stswnd.h" -#include "editwnd.h" -#include "usermenu.h" -#include "execute.h" -#include "plugins.h" -#include "fileswnd.h" -#include "toolbar.h" -#include "mainwnd.h" -#include "cfgdlg.h" -#include "dialogs.h" -#include "execlog.h" -#include "snooper.h" -#include "shellib.h" -#include "menu.h" -#include "pack.h" -#include "filesbox.h" -#include "drivelst.h" -#include "cache.h" -#include "gui.h" -#include -#include "zip.h" -#include "tasklist.h" -#include "jumplist.h" -extern "C" -{ -#include "shexreg.h" -} -#include "salshlib.h" -#include "worker.h" -#include "find.h" -#include "viewer.h" - -// critical shutdown: the maximum time we can spend in WM_QUERYENDSESSION (after that, -// KILL comes from Windows). It is 5s (5s with an open message box, 10s without pumping -// messages). I left a 500ms reserve. Tested on Vista, Win7, Win8, Win10. -#define QUERYENDSESSION_TIMEOUT 4500 - -// variables used when saving configuration during shutdown, log-off or restart -// we must pump messages so the system does not kill us as "not responding" -CWaitWindow* GlobalSaveWaitWindow = NULL; // if a global wait window for Save exists, it's here (otherwise NULL) -int GlobalSaveWaitWindowProgress = 0; // current progress value of the global wait window for Save - -// borrow constants from a newer SDK -#define WM_APPCOMMAND 0x0319 -#define FAPPCOMMAND_MOUSE 0x8000 -#define FAPPCOMMAND_KEY 0 -#define FAPPCOMMAND_OEM 0x1000 -#define FAPPCOMMAND_MASK 0xF000 -#define GET_APPCOMMAND_LPARAM(lParam) ((short)(HIWORD(lParam) & ~FAPPCOMMAND_MASK)) -#define APPCOMMAND_BROWSER_BACKWARD 1 -#define APPCOMMAND_BROWSER_FORWARD 2 -/* not supported yet -#define APPCOMMAND_BROWSER_SEARCH 5 -#define APPCOMMAND_HELP 27 -#define APPCOMMAND_BROWSER_REFRESH 3 -#define APPCOMMAND_FIND 28 -#define APPCOMMAND_COPY 36 -#define APPCOMMAND_CUT 37 -#define APPCOMMAND_PASTE 38 -*/ - -const int SPLIT_LINE_WIDTH = 3; // width of the split line in points -// if the middle toolbar is visible, the composition will be SPLIT_LINE_WIDTH + toolbar + SPLIT_LINE_WIDTH - -const int MIN_WIN_WIDTH = 2; // minimal panel width - -extern BOOL CacheNextSetFocus; - -BOOL MainFrameIsActive = FALSE; - -// code for testing time losses -/* - const char *s1 = "aj hjka sakjSJKAHS AJKSH JKDSHFJSDH FJS HDFJSD HFJS"; - const char *s2 = "Aj hjka sakjSJKAHS AJKSH JKDSHFJSDH FJS HDFJSD HFJS"; - - LARGE_INTEGER t1, t2, t3, f; - - int len1 = strlen(s1); - int count = 100000; - QueryPerformanceCounter(&t1); - int c = 0; - int i; - for (i = 0; i < count; i++) - c += MemICmp(s1, s2, len1); - QueryPerformanceCounter(&t2); - c = 0; - for (i = 0; i < count; i++) - c += StrICmp(s1, len1, s2, len1); - QueryPerformanceCounter(&t3); - - QueryPerformanceFrequency(&f); - - char buff[200]; - double a = (double)(t2.QuadPart - t1.QuadPart) / f.QuadPart; - double b = (double)(t3.QuadPart - t2.QuadPart) / f.QuadPart; - sprintf(buff, "t1=%1.4lg\nt2=%1.4lg", a, b); - MessageBox(HWindow, buff, "Results", MB_OK); -*/ - -//**************************************************************************** -// -// HtmlHelp support -// - -// universal callback for our MessageBox when the user clicks the HELP button -// should be called, for example, like this: -// MSGBOXEX_PARAMS params; -// params.Flags = MSGBOXEX_OK | MSGBOXEX_HELP | MSGBOXEX_ICONEXCLAMATION; -// params.ContextHelpId = IDH_LICENSE; -// params.HelpCallback = MessageBoxHelpCallback; -void CALLBACK MessageBoxHelpCallback(LPHELPINFO helpInfo) -{ - OpenHtmlHelp(NULL, MainWindow->HWindow, HHCDisplayContext, (UINT)helpInfo->dwContextId, FALSE); // MSGBOXEX_PARAMS::ContextHelpId -} - -CSalamanderHelp SalamanderHelp; - -void CSalamanderHelp::OnHelp(HWND hWindow, UINT helpID, HELPINFO* helpInfo, - BOOL ctrlPressed, BOOL shiftPressed) -{ - if (!ctrlPressed && !shiftPressed) - { - OpenHtmlHelp(NULL, hWindow, HHCDisplayContext, helpID, FALSE); - } -} - -void CSalamanderHelp::OnContextMenu(HWND hWindow, WORD xPos, WORD yPos) -{ -} - -typedef struct tagHH_LAST_ERROR -{ - int cbStruct; - HRESULT hr; - BSTR description; -} HH_LAST_ERROR; - -BOOL OpenHtmlHelp(char* helpFileName, HWND parent, CHtmlHelpCommand command, DWORD_PTR dwData, BOOL quiet) -{ - // SalMessageBox(parent, "This beta version doesn't contain help.\nPlease wait for the next beta version.", - // "Open Salamander Help", MB_OK | MB_ICONINFORMATION); - - HANDLES(EnterCriticalSection(&OpenHtmlHelpCS)); - - char helpPath[MAX_PATH + 50]; - if (CurrentHelpDir[0] == 0) - { - char helpSubdir[MAX_PATH]; - helpSubdir[0] = 0; - CLanguage language; - if (language.Init(Configuration.LoadedSLGName, NULL)) - { - lstrcpyn(helpSubdir, language.HelpDir, MAX_PATH); - language.Free(); - } - if (helpSubdir[0] == 0) - { - TRACE_E("OpenHtmlHelp(): unable to get (or empty) SLGHelpDir!"); - strcpy(helpSubdir, "english"); - } - BOOL ok = FALSE; - if (GetModuleFileName(HInstance, CurrentHelpDir, MAX_PATH) != 0 && - CutDirectory(CurrentHelpDir) && - SalPathAppend(CurrentHelpDir, "help", MAX_PATH) && - DirExists(CurrentHelpDir)) - { - lstrcpyn(helpPath, CurrentHelpDir, MAX_PATH); - if (!SalPathAppend(helpPath, helpSubdir, MAX_PATH) || - !DirExists(helpPath)) - { // the directory from the current .slg file does not exist - lstrcpyn(helpPath, CurrentHelpDir, MAX_PATH); - if (_stricmp(helpSubdir, "english") == 0 || // we already tested "english" and it does not exist so no point in trying again - !SalPathAppend(helpPath, "english", MAX_PATH) || - !DirExists(helpPath)) - { // the ENGLISH directory does not exist - lstrcpyn(helpPath, CurrentHelpDir, MAX_PATH); - if (SalPathAppend(helpPath, "*", MAX_PATH)) - { // try to find at least some other directory - WIN32_FIND_DATAW dataW; - WIN32_FIND_DATA data; - CStrP helpPathW(ConvertAllocUtf8ToWide(helpPath, -1)); - HANDLE find = helpPathW != NULL ? HANDLES_Q(FindFirstFileW(helpPathW, &dataW)) : INVALID_HANDLE_VALUE; - if (find != INVALID_HANDLE_VALUE) - { - do - { - ConvertFindDataWToUtf8(dataW, &data); - if (strcmp(data.cFileName, ".") != 0 && strcmp(data.cFileName, "..") != 0 && - (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) // only if it is a directory - { - lstrcpyn(helpPath, CurrentHelpDir, MAX_PATH); - if (SalPathAppend(helpPath, data.cFileName, MAX_PATH)) - { - ok = TRUE; - break; - } - } - } while (FindNextFileW(find, &dataW)); - HANDLES(FindClose(find)); - } - } - } - else - ok = TRUE; - } - else - ok = TRUE; - if (ok) - lstrcpyn(CurrentHelpDir, helpPath, MAX_PATH); - } - if (!ok) - { - CurrentHelpDir[0] = 0; - - HANDLES(LeaveCriticalSection(&OpenHtmlHelpCS)); - - if (!quiet) - { - SalMessageBox(parent, LoadStr(IDS_FAILED_TO_FIND_HELP), - LoadStr(IDS_HELPERROR), MB_OK | MB_ICONEXCLAMATION); - } - return FALSE; - } - } - - HANDLES(LeaveCriticalSection(&OpenHtmlHelpCS)); - - HH_FTS_QUERY query; - DWORD uCommand = 0; - switch (command) - { - case HHCDisplayTOC: - { - uCommand = HH_DISPLAY_TOC; - break; - } - - case HHCDisplayIndex: - { - uCommand = HH_DISPLAY_INDEX; - if (dwData == 0) - dwData = 0; - break; - } - - case HHCDisplaySearch: - { - uCommand = HH_DISPLAY_SEARCH; - if (dwData == 0) - { - ZeroMemory(&query, sizeof(query)); - query.cbStruct = sizeof(query); - dwData = (DWORD_PTR)&query; - } - break; - } - - case HHCDisplayContext: - { - uCommand = HH_HELP_CONTEXT; - break; - } - - default: - { - TRACE_E("OpenHtmlHelp(): unknown command = " << command); - return FALSE; - } - } - - if (helpFileName != NULL) // plugin help: to open the window in the right position - { // with remembered Favorites, we must open "salamand.chm" first (then - // the plugin help opens in this same window) - lstrcpyn(helpPath, CurrentHelpDir, MAX_PATH); - if (SalPathAppend(helpPath, "salamand.chm", MAX_PATH) && - FileExists(helpPath)) - { - HtmlHelp(NULL, helpPath, HH_DISPLAY_TOC, 0); // ignore potential error - } - } - - BOOL ret = FALSE; - - lstrcpyn(helpPath, CurrentHelpDir, MAX_PATH); - if (SalPathAppend(helpPath, helpFileName == NULL ? "salamand.chm" : helpFileName, MAX_PATH) && - FileExists(helpPath)) - { - if (HtmlHelp(NULL, helpPath, uCommand, dwData) == NULL) - { - BOOL errorHandled = FALSE; - HH_LAST_ERROR lasterror; - lasterror.cbStruct = sizeof(lasterror); - if (HtmlHelp(NULL, NULL, HH_GET_LAST_ERROR, (DWORD_PTR)&lasterror) != NULL) - { - // Only report an error if we found one: - if (FAILED(lasterror.hr)) - { - // Is there a text message to display... - if (lasterror.description) - { - if (!quiet) - { - char buff[5000]; - // Convert the String to ANSI - WideCharToMultiByte(CP_ACP, 0, lasterror.description, -1, buff, 5000, NULL, NULL); - buff[5000 - 1] = 0; - SysFreeString(lasterror.description); - - // Display - SalMessageBox(parent, buff, LoadStr(IDS_HELPERROR), MB_OK); - } - errorHandled = TRUE; - } - } - } - if (!errorHandled && !quiet) - { - SalMessageBox(parent, LoadStr(IDS_FAILED_TO_LAUNCH_HELP), - LoadStr(IDS_HELPERROR), MB_OK | MB_ICONEXCLAMATION); - } - } - else - { - ret = TRUE; - } - } - else - { - if (!quiet) - { - SalMessageBox(parent, LoadStr(IDS_FAILED_TO_FIND_HELP), - LoadStr(IDS_HELPERROR), MB_OK | MB_ICONEXCLAMATION); - } - } - return ret; -} - -//**************************************************************************** -// -// CMWDropTarget -// -// used only for moving dragged images -// - -class CMWDropTarget : public IDropTarget -{ -private: - long RefCount; // object lifetime - -public: - CMWDropTarget() - { - RefCount = 1; - } - - virtual ~CMWDropTarget() - { - if (RefCount != 0) - TRACE_E("Preliminary destruction of object"); - } - - STDMETHOD(QueryInterface) - (REFIID refiid, void FAR* FAR* ppv) - { - if (refiid == IID_IUnknown || refiid == IID_IDropTarget) - { - *ppv = this; - AddRef(); - return NOERROR; - } - else - { - *ppv = NULL; - return E_NOINTERFACE; - } - } - - STDMETHOD_(ULONG, AddRef) - (void) { return ++RefCount; } - STDMETHOD_(ULONG, Release) - (void) - { - if (--RefCount == 0) - { - delete this; - return 0; // cannot touch the object anymore, it no longer exists - } - return RefCount; - } - - STDMETHOD(DragEnter) - (IDataObject* pDataObject, DWORD grfKeyState, - POINTL pt, DWORD* pdwEffect) - { - if (ImageDragging) - ImageDragEnter(pt.x, pt.y); - *pdwEffect = DROPEFFECT_NONE; - return S_OK; - } - - STDMETHOD(DragOver) - (DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) - { - if (ImageDragging) - ImageDragMove(pt.x, pt.y); - *pdwEffect = DROPEFFECT_NONE; - return S_OK; - } - - STDMETHOD(DragLeave) - () - { - if (ImageDragging) - ImageDragLeave(); - return E_UNEXPECTED; - } - - STDMETHOD(Drop) - (IDataObject* pDataObject, DWORD grfKeyState, POINTL pt, - DWORD* pdwEffect) - { - *pdwEffect = DROPEFFECT_NONE; - return E_UNEXPECTED; - } -}; - -// -// **************************************************************************** -// MyShutdownBlockReasonCreate a MyShutdownBlockReasonDestroy -// -// Vista+: dynamically obtain functions for setting/clearing shutdown block reasons -// - -BOOL MyShutdownBlockReasonCreate(HWND hWnd, LPCWSTR pwszReason) -{ - typedef BOOL(WINAPI * FT_ShutdownBlockReasonCreate)(HWND hWnd, LPCWSTR pwszReason); - static FT_ShutdownBlockReasonCreate shutdownBlockReasonCreate = NULL; - if (shutdownBlockReasonCreate == NULL && User32DLL != NULL && WindowsVistaAndLater) - { - shutdownBlockReasonCreate = (FT_ShutdownBlockReasonCreate)GetProcAddress(User32DLL, - "ShutdownBlockReasonCreate"); // Min: Vista - } - if (shutdownBlockReasonCreate != NULL) - return shutdownBlockReasonCreate(hWnd, pwszReason); - SetLastError(ERROR_CALL_NOT_IMPLEMENTED); - return FALSE; -} - -BOOL MyShutdownBlockReasonDestroy(HWND hWnd) -{ - typedef BOOL(WINAPI * FT_ShutdownBlockReasonDestroy)(HWND hWnd); - static FT_ShutdownBlockReasonDestroy shutdownBlockReasonDestroy = NULL; - if (shutdownBlockReasonDestroy == NULL && User32DLL != NULL && WindowsVistaAndLater) - { - shutdownBlockReasonDestroy = (FT_ShutdownBlockReasonDestroy)GetProcAddress(User32DLL, - "ShutdownBlockReasonDestroy"); // Min: Vista - } - if (shutdownBlockReasonDestroy != NULL) - return shutdownBlockReasonDestroy(hWnd); - SetLastError(ERROR_CALL_NOT_IMPLEMENTED); - return FALSE; -} - -// -// **************************************************************************** -// CMainWindow -// - -VOID CALLBACK SkipOneARTimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime) -{ - SkipOneActivateRefresh = FALSE; - KillTimer(hwnd, idEvent); -} - -void CMainWindow::SafeHandleMenuNewMsg2(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT* plResult) -{ - __try - { - IContextMenu3* contextMenu3 = NULL; - *plResult = 0; - if (uMsg == WM_MENUCHAR) - { - if (SUCCEEDED(ContextMenuNew->GetMenu2()->QueryInterface(IID_IContextMenu3, (void**)&contextMenu3))) - { - contextMenu3->HandleMenuMsg2(uMsg, wParam, lParam, plResult); - contextMenu3->Release(); - return; - } - } - // the menu is destroyed directly from the menu it was attached to - ContextMenuNew->GetMenu2()->HandleMenuMsg(uMsg, wParam, lParam); // this call occasionally crashes - } - __except (CCallStack::HandleException(GetExceptionInformation(), 11)) - { - MenuNewExceptionHasOccured++; - if (ContextMenuNew != NULL) - ContextMenuNew->Release(); // substitute for calling ReleaseMenuNew - // ReleaseMenuNew(); - } -} - -void CMainWindow::PostChangeOnPathNotification(const char* path, BOOL includingSubdirs) -{ - CALL_STACK_MESSAGE3("CMainWindow::PostChangeOnPathNotification(%s, %d)", path, includingSubdirs); - - HANDLES(EnterCriticalSection(&DispachChangeNotifCS)); - - // add this notification to the array (for later processing) - CChangeNotifData data; - lstrcpyn(data.Path, path, MAX_PATH); - data.IncludingSubdirs = includingSubdirs; - ChangeNotifArray.Add(data); - if (!ChangeNotifArray.IsGood()) - ChangeNotifArray.ResetState(); // ignore errors (at worst we won't refresh) - - // post a request to distribute path change notifications - HANDLES(EnterCriticalSection(&TimeCounterSection)); - int t1 = MyTimeCounter++; - HANDLES(LeaveCriticalSection(&TimeCounterSection)); - PostMessage(HWindow, WM_USER_DISPACHCHANGENOTIF, 0, t1); - - HANDLES(LeaveCriticalSection(&DispachChangeNotifCS)); -} - -void CMainWindowWindowProcAux(IContextMenu* menu2, CMINVOKECOMMANDINFO& ici) -{ - CALL_STACK_MESSAGE_NONE - - // temporarily lower the thread priority so a misbehaving shell extension does not hog the CPU - HANDLE hThread = GetCurrentThread(); // pseudo-handle, no need to release - int oldThreadPriority = GetThreadPriority(hThread); - SetThreadPriority(hThread, THREAD_PRIORITY_NORMAL); - - __try - { - menu2->InvokeCommand(&ici); - } - __except (CCallStack::HandleException(GetExceptionInformation(), 12)) - { - ICExceptionHasOccured++; - } - - SetThreadPriority(hThread, oldThreadPriority); -} - -void BroadcastConfigChanged() -{ - // Internal Viewer and Find: refresh all windows (for example after global font change) - ViewerWindowQueue.BroadcastMessage(WM_USER_CFGCHANGED, 0, 0); - FindDialogQueue.BroadcastMessage(WM_USER_CFGCHANGED, 0, 0); -} - -void CMainWindow::FillViewModeMenu(CMenuPopup* popup, int firstIndex, int type) -{ - char buff[VIEW_NAME_MAX + 10]; - - DWORD fistCMID; - CFilesWindow* panel; - - switch (type) - { - case 0: - { - fistCMID = CM_ACTIVEMODE_1; - panel = GetActivePanel(); - break; - } - - case 1: - { - fistCMID = CM_LEFTMODE_1; - panel = LeftPanel; - break; - } - - case 2: - { - fistCMID = CM_RIGHTMODE_1; - panel = RightPanel; - break; - } - - default: - { - TRACE_E("Uknown type=" << type); - return; - } - } - - MENU_ITEM_INFO mii; - mii.Mask = MENU_MASK_TYPE | MENU_MASK_STRING | MENU_MASK_STATE | - MENU_MASK_ID /*| MENU_MASK_SKILLLEVEL*/; - mii.Type = MENU_TYPE_STRING | MENU_TYPE_RADIOCHECK; - mii.String = buff; - int i; - for (i = 0; i < VIEW_TEMPLATES_COUNT; i++) - { - if (i == 0) // tree view is not shown yet - continue; - - CViewTemplate* tmpl = &ViewTemplates.Items[i]; - if (tmpl->Name[0] != 0) - { - sprintf(buff, "%s\tAlt+%d", tmpl->Name, i < VIEW_TEMPLATES_COUNT - 1 ? i + 1 : 0); - - mii.ID = fistCMID + i; - - // mii.SkillLevel = MENU_LEVEL_INTERMEDIATE | MENU_LEVEL_ADVANCED; - // if (i > 2) - // mii.SkillLevel |= MENU_LEVEL_BEGINNER; - - mii.State = panel->ViewTemplate == tmpl ? MENU_STATE_CHECKED : 0; - - popup->InsertItem(firstIndex, TRUE, &mii); - firstIndex++; - } - } -} - -void CMainWindow::SetDoNotLoadAnyPlugins(BOOL doNotLoad) -{ - if (doNotLoad) - { - DoNotLoadAnyPlugins = TRUE; - } - else - { - DoNotLoadAnyPlugins = FALSE; - if (!CriticalShutdown) - { - HANDLES(EnterCriticalSection(&TimeCounterSection)); - int t1 = MyTimeCounter++; - int t2 = MyTimeCounter++; - HANDLES(LeaveCriticalSection(&TimeCounterSection)); - - if (LeftPanel->GetViewMode() == vmThumbnails) - { - PostMessage(LeftPanel->HWindow, WM_USER_REFRESH_DIR, 0, t1); // ensure the icon cache is refilled (thumbnails can be shown again) - } - if (RightPanel->GetViewMode() == vmThumbnails) - { - PostMessage(RightPanel->HWindow, WM_USER_REFRESH_DIR, 0, t2); // ensure the icon cache is refilled (thumbnails can be shown again) - } - } - } -} - -void CMainWindow::ShowHideTwoDriveBarsInternal(BOOL show) -{ - LockWindowUpdate(HWindow); - - if (show) - { - REBARBANDINFO rbi; - rbi.cbSize = sizeof(REBARBANDINFO); - - int count = (int)SendMessage(HTopRebar, RB_GETBANDCOUNT, 0, 0); - // drive bar 1 - int index = (int)SendMessage(HTopRebar, RB_IDTOINDEX, BANDID_DRIVEBAR, 0); - SendMessage(HTopRebar, RB_MOVEBAND, (WPARAM)index, (LPARAM)count - 1); - rbi.fMask = RBBIM_STYLE; - rbi.fStyle = RBBS_NOGRIPPER | RBBS_BREAK; - SendMessage(HTopRebar, RB_SETBANDINFO, count - 1, (LPARAM)&rbi); - - // drive bar 2 - index = (int)SendMessage(HTopRebar, RB_IDTOINDEX, BANDID_DRIVEBAR2, 0); - SendMessage(HTopRebar, RB_MOVEBAND, (WPARAM)index, (LPARAM)count - 1); - rbi.fMask = RBBIM_STYLE; - rbi.fStyle = RBBS_NOGRIPPER; - SendMessage(HTopRebar, RB_SETBANDINFO, count - 1, (LPARAM)&rbi); - } - else - { - int index = (int)SendMessage(HTopRebar, RB_IDTOINDEX, BANDID_DRIVEBAR, 0); - SendMessage(HTopRebar, RB_SHOWBAND, index, FALSE); - - index = (int)SendMessage(HTopRebar, RB_IDTOINDEX, BANDID_DRIVEBAR2, 0); - SendMessage(HTopRebar, RB_SHOWBAND, index, FALSE); - } - - LockWindowUpdate(NULL); -} - -int CMainWindow::GetSplitBarWidth() -{ - if (MiddleToolBar != NULL && MiddleToolBar->HWindow != NULL) - return 2 * SPLIT_LINE_WIDTH + MiddleToolBar->GetNeededWidth(); - else - return SPLIT_LINE_WIDTH; -} - -BOOL CMainWindow::IsPanelZoomed(BOOL leftPanel) -{ - if (leftPanel) - return SplitPosition >= 0.99; - else - return SplitPosition <= 0.01; -} - -void CMainWindow::ToggleSmartColumnMode(CFilesWindow* panel) -{ - if (panel->GetViewMode() == vmDetailed) // the panel must be running in detailed mode - { - if (panel->Columns.Count < 1) - return; - CColumn* column = &panel->Columns[0]; - BOOL leftPanel = (panel == LeftPanel); - BOOL smartMode = !(!column->FixedWidth && - (leftPanel && panel->ViewTemplate->LeftSmartMode || - !leftPanel && panel->ViewTemplate->RightSmartMode)); - if (smartMode && column->FixedWidth) - { // smart mode works only for elastic columns (must be changed in the view template) - if (leftPanel) - panel->ViewTemplate->Columns[0].LeftFixedWidth = 0; - else - panel->ViewTemplate->Columns[0].RightFixedWidth = 0; - } - if (leftPanel) - { - panel->ViewTemplate->LeftSmartMode = smartMode; - LeftPanel->SelectViewTemplate(LeftPanel->GetViewTemplateIndex(), TRUE, FALSE, VALID_DATA_ALL, TRUE); - } - else - { - panel->ViewTemplate->RightSmartMode = smartMode; - RightPanel->SelectViewTemplate(RightPanel->GetViewTemplateIndex(), TRUE, FALSE, VALID_DATA_ALL, TRUE); - } - } -} - -BOOL CMainWindow::GetSmartColumnMode(CFilesWindow* panel) -{ - if (panel->Columns.Count < 1) - return FALSE; - CColumn* column = &panel->Columns[0]; - BOOL smartMode = (!column->FixedWidth && - (panel == LeftPanel && panel->ViewTemplate->LeftSmartMode || - panel == RightPanel && panel->ViewTemplate->RightSmartMode)); - return smartMode; -} - -void CMainWindow::SafeHandleMenuChngDrvMsg2(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT* plResult) -{ - CALL_STACK_MESSAGE_NONE - __try - { - IContextMenu3* contextMenu3 = NULL; - *plResult = 0; - if (uMsg == WM_MENUCHAR) - { - if (SUCCEEDED(ContextMenuChngDrv->QueryInterface(IID_IContextMenu3, (void**)&contextMenu3))) - { - contextMenu3->HandleMenuMsg2(uMsg, wParam, lParam, plResult); - contextMenu3->Release(); - return; - } - } - ContextMenuChngDrv->HandleMenuMsg(uMsg, wParam, lParam); - } - __except (CCallStack::HandleException(GetExceptionInformation(), 3)) - { - } -} - -void CMainWindow::ApplyCommandLineParams(const CCommandLineParams* cmdLineParams, BOOL setActivePanelAndPanelPaths) -{ - if (setActivePanelAndPanelPaths) - { - // first set the active panel - if (cmdLineParams->ActivatePanel == 1 && GetActivePanel() == RightPanel || - cmdLineParams->ActivatePanel == 2 && GetActivePanel() == LeftPanel) - { - ChangePanel(FALSE); - } - // then we can set the path in the active panel - if (cmdLineParams->LeftPath[0] == 0 && cmdLineParams->RightPath[0] == 0 && cmdLineParams->ActivePath[0] != 0) - GetActivePanel()->ChangeDir(cmdLineParams->ActivePath); // makes no sense to combine with setting the left/right panel - else - { - if (cmdLineParams->LeftPath[0] != 0) - LeftPanel->ChangeDir(cmdLineParams->LeftPath); - if (cmdLineParams->RightPath[0] != 0) - RightPanel->ChangeDir(cmdLineParams->RightPath); - } - } - - if (cmdLineParams->SetMainWindowIconIndex) - { - Configuration.MainWindowIconIndexForced = cmdLineParams->MainWindowIconIndex; - SetWindowIcon(); - } - if (cmdLineParams->SetTitlePrefix) - { - Configuration.UseTitleBarPrefixForced = TRUE; - lstrcpyn(Configuration.TitleBarPrefixForced, cmdLineParams->TitlePrefix, TITLE_PREFIX_MAX); - SetWindowTitle(); - } -} - -BOOL CMainWindow::SHChangeNotifyInitialize() -{ - if (SHChangeNotifyRegisterID != 0) - { - TRACE_E("SHChangeNotifyRegisterID != 0"); - return FALSE; - } - - LPITEMIDLIST pidl; - if (!SUCCEEDED(SHGetSpecialFolderLocation(HWindow, CSIDL_DESKTOP, &pidl))) - { - TRACE_E("SHGetSpecialFolderLocation failed on CSIDL_DESKTOP"); - return FALSE; - } - - SHChangeNotifyEntry entry; - entry.pidl = pidl; - entry.fRecursive = TRUE; - - // message WM_USER_SHCHANGENOTIFY, which will be delivered to us on notifications, crosses process boundaries - // by using the constant SHCNRF_NewDelivery (also known as SHCNF_NO_PROXY) we assume responsibility - // for accessing the memory passed with the message (via SHChangeNotification_Lock) and tell the OS not to - // create proxy windows (note: a bug has been reported on XP where the proxy window is created but not destroyed): - // http://groups.google.com/groups?selm=3CDFD449.6BA0CDB4%40ic.ac.uk&output=gplain - // - // through SHCNE_ASSOCCHANGED we receive notifications about association changes - SHChangeNotifyRegisterID = SHChangeNotifyRegister(HWindow, SHCNRF_ShellLevel | SHCNRF_NewDelivery, - SHCNE_MEDIAINSERTED | SHCNE_MEDIAREMOVED | SHCNE_DRIVEREMOVED | - SHCNE_DRIVEADD | SHCNE_NETSHARE | SHCNE_NETUNSHARE | - SHCNE_DRIVEADDGUI | SHCNE_ASSOCCHANGED | SHCNE_UPDATEITEM, - WM_USER_SHCHANGENOTIFY, - 1, &entry); - - // dealokace pidl - IMalloc* alloc; - if (SUCCEEDED(CoGetMalloc(1, &alloc))) - { - alloc->Free(pidl); - alloc->Release(); - } - - return TRUE; -} - -BOOL CMainWindow::SHChangeNotifyRelease() -{ - if (SHChangeNotifyRegisterID != 0) - { - SHChangeNotifyDeregister(SHChangeNotifyRegisterID); - SHChangeNotifyRegisterID = 0; - } - return TRUE; -} - -typedef WINSHELLAPI BOOL(WINAPI* FT_FileIconInit)( - BOOL bFullInit); - -BOOL CMainWindow::OnAssociationsChangedNotification(BOOL showWaitWnd) -{ - // tweak the icon size - - LoadSaveToRegistryMutex.Enter(); // users reported shrunken icons, see /viewtopic.php?t=638 - // this synchronization ensures that two Salamanders do not interfere with each other - // unfortunately the trick with changing "Shell Icon Size" to rebuild the cache is used by many tools (including Tweak UI), - // so if they refresh at the same time as Salamander, conflicts occur - // we try to avoid this by postponing the following mess using IDT_ASSOCIATIONSCHNG - - HKEY hKey; - if (HANDLES(RegOpenKeyEx(HKEY_CURRENT_USER, "Control Panel\\Desktop\\WindowMetrics", 0, KEY_READ | KEY_WRITE, &hKey)) == ERROR_SUCCESS) - { - // older SHELL32.DLL versions may not export this, fileIconInit will be NULL - FT_FileIconInit fileIconInit = NULL; - fileIconInit = (FT_FileIconInit)GetProcAddress(Shell32DLL, MAKEINTRESOURCE(660)); // no header available - - char size[50]; - BOOL deleteVal = FALSE; - if (!GetValueAux(NULL, hKey, "Shell Icon Size", REG_SZ, size, 50)) - { - // The values for the icon size are Shell Icon Size and - // Shell Small Icon Size (both are stored as strings - not - // DWORDs). You only need to change one of them to cause - // the refresh to happen (typically the large icon size). If those - // values don't exist, the shell uses the SM_CXICON metric - // (GetSystemMetrics) as the default size for large icons, and - // half of that for the small icon size. If you're trying to cause - // a refresh and the registry entry doesn't exist, you can just - // assume that the size is set to SM_CXICON. - sprintf(size, "%d", GetSystemMetrics(SM_CXICON)); - deleteVal = TRUE; - } - int val = atoi(size); - if (val > 0) // unfortunately (according to net) users set icon sizes randomly (72, 96, 128, etc.) so we cannot filter out "strange" sizes - { - IgnoreWM_SETTINGCHANGE = TRUE; - - sprintf(size, "%d", val - 1); - SetValueAux(NULL, hKey, "Shell Icon Size", REG_SZ, size, -1); - SendMessage(MainWindow->HWindow, WM_SETTINGCHANGE, SPI_SETICONMETRICS, (LPARAM) "WindowMetrics"); - if (fileIconInit != NULL) - fileIconInit(FALSE); - sprintf(size, "%d", val); - SetValueAux(NULL, hKey, "Shell Icon Size", REG_SZ, size, -1); - SendMessage(MainWindow->HWindow, WM_SETTINGCHANGE, SPI_SETICONMETRICS, (LPARAM) "WindowMetrics"); - if (fileIconInit != NULL) - fileIconInit(TRUE); - if (deleteVal) - RegDeleteValue(hKey, "Shell Icon Size"); // clean up after ourselves - HANDLES(RegCloseKey(hKey)); - - IgnoreWM_SETTINGCHANGE = FALSE; - } - } - - LoadSaveToRegistryMutex.Leave(); - - /* - if (fileIconInit != NULL) - fileIconInit(TRUE); - - // debug icon display - SHFILEINFO shi; - HIMAGELIST systemIL = (HIMAGELIST)SHGetFileInfo("C:\\TEST.QWE", 0, &shi, sizeof(shi), - SHGFI_SYSICONINDEX | SHGFI_SMALLICON | SHGFI_SHELLICONSIZE); - TRACE_I("systemIL="<RebuildDrives(); - copyDrivesListFrom = DriveBar; - } - } - if (DriveBar2 != NULL && DriveBar2->HWindow != NULL) - { - if (DriveBar2->GetCachedDrivesMask() != drivesMask || - checkCloudStorages && DriveBar2->GetCachedCloudStoragesMask() != cloudStoragesMask) - { - // notifications about drive changes or cloud storage availability do not work; rebuild the drive bar manually - TRACE_I("Forced drives rebuild for DriveBar2!"); - DriveBar2->RebuildDrives(copyDrivesListFrom); - } - } - } -} - -LRESULT -CMainWindow::WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - SLOW_CALL_STACK_MESSAGE4("CMainWindow::WindowProc(0x%X, 0x%IX, 0x%IX)", uMsg, wParam, lParam); - switch (uMsg) - { - case WM_CREATE: - { - SHChangeNotifyInitialize(); // request receiving Shell Notifications - ExecLogStartupPhase("main window create"); - - SetTimer(HWindow, IDT_ADDNEWMODULES, 15000, NULL); // timer after 15 seconds for AddNewlyLoadedModulesToGlobalModulesStore() - - CMWDropTarget* dropTarget = new CMWDropTarget(); - if (dropTarget != NULL) - { - HANDLES(RegisterDragDrop(HWindow, dropTarget)); - dropTarget->Release(); // RegisterDragDrop called AddRef() - } - - HMENU h = GetSystemMenu(HWindow, FALSE); - if (h != NULL) - { - int items = GetMenuItemCount(h); - int pos = items; // append new items at the end of the menu - - // if the last two menu items are a separator and Close, insert above them - // (users have long complained they accidentally click our AOT instead of the intended Close) - if (items > 2) - { - UINT predLastCmd = GetMenuItemID(h, items - 2); - UINT lastCmd = GetMenuItemID(h, items - 1); - if (predLastCmd == 0 && lastCmd == SC_CLOSE) - pos = items - 2; - } - - /* used by the export_mnu.py script which generates salmenu.mnu for Translator. - Keep this synchronized with the InsertMenu() call below... -MENU_TEMPLATE_ITEM AddToSystemMenu[] = -{ - {MNTT_PB, 0 - {MNTT_IT, IDS_ALWAYSONTOP - {MNTT_PE, 0 -}; -*/ - InsertMenu(h, pos, MF_BYPOSITION | MF_SEPARATOR, 0, NULL); - InsertMenu(h, pos + 1, MF_BYPOSITION | MF_STRING | MF_ENABLED | (Configuration.AlwaysOnTop ? MF_CHECKED : MF_UNCHECKED), - CM_ALWAYSONTOP, LoadStr(IDS_ALWAYSONTOP)); - } - SetWindowPos(HWindow, - Configuration.AlwaysOnTop ? HWND_TOPMOST : HWND_NOTOPMOST, - 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); - - HTopRebar = CreateWindowEx(WS_EX_TOOLWINDOW, REBARCLASSNAME, "", - WS_VISIBLE | WS_BORDER | WS_CHILD | - WS_CLIPCHILDREN | WS_CLIPSIBLINGS | - RBS_VARHEIGHT | CCS_NODIVIDER | - RBS_BANDBORDERS | CCS_NOPARENTALIGN | - RBS_AUTOSIZE, - 0, 0, 0, 0, // dummy - HWindow, (HMENU)0, HInstance, NULL); - if (HTopRebar == NULL) - { - TRACE_E("CreateWindowEx on " << REBARCLASSNAME); - return -1; - } - - // we do not want visual styles for the rebar - // disable them - SetWindowTheme(HTopRebar, (L" "), (L" ")); - - // enforce WS_BORDER which somehow "disappeared" - DWORD style = (DWORD)GetWindowLongPtr(HTopRebar, GWL_STYLE); - style |= WS_BORDER; - SetWindowLongPtr(HTopRebar, GWL_STYLE, style); - - MenuBar = new CMenuBar(&MainMenu, HWindow); - if (MenuBar == NULL) - { - TRACE_E(LOW_MEMORY); - return -1; - } - if (!MenuBar->CreateWnd(HTopRebar)) - return -1; - - LeftPanel = new CFilesWindow(this); - if (LeftPanel == NULL) - { - TRACE_E(LOW_MEMORY); - return -1; - } - if (!LeftPanel->Create(CWINDOW_CLASSNAME2, "", - WS_VISIBLE | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, - 0, 0, 0, 0, - HWindow, - NULL, - HInstance, - LeftPanel)) - { - TRACE_E("LeftPanel->Create failed"); - return -1; - } - SetActivePanel(LeftPanel); - // ReleaseMenuNew(); - RightPanel = new CFilesWindow(this); - if (RightPanel == NULL) - { - TRACE_E(LOW_MEMORY); - return -1; - } - if (!RightPanel->Create(CWINDOW_CLASSNAME2, "", - WS_VISIBLE | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, - 0, 0, 0, 0, - HWindow, - NULL, - HInstance, - RightPanel)) - { - TRACE_E("RightPanel->Create failed"); - return -1; - } - - EditWindow = new CEditWindow; - if (EditWindow == NULL || !EditWindow->IsGood()) - { - TRACE_E(LOW_MEMORY); - return -1; - } - - TopToolBar = new CMainToolBar(HWindow, mtbtTop); - if (TopToolBar == NULL) - { - TRACE_E(LOW_MEMORY); - return -1; - } - TopToolBar->SetImageList(HGrayToolBarImageList); - TopToolBar->SetHotImageList(HHotToolBarImageList); - TopToolBar->SetStyle(TLB_STYLE_IMAGE | TLB_STYLE_ADJUSTABLE); - TOOLBAR_PADDING padding; - TopToolBar->GetPadding(&padding); - padding.ToolBarVertical = 1; - padding.IconLeft = 2; - padding.IconRight = 3; - TopToolBar->SetPadding(&padding); - - MiddleToolBar = new CMainToolBar(HWindow, mtbtMiddle); - if (MiddleToolBar == NULL) - { - TRACE_E(LOW_MEMORY); - return -1; - } - MiddleToolBar->SetImageList(HGrayToolBarImageList); - MiddleToolBar->SetHotImageList(HHotToolBarImageList); - MiddleToolBar->SetStyle(TLB_STYLE_IMAGE | TLB_STYLE_ADJUSTABLE | TLB_STYLE_VERTICAL); - MiddleToolBar->GetPadding(&padding); - padding.ToolBarVertical = 1; - padding.IconLeft = 2; - padding.IconRight = 3; - MiddleToolBar->SetPadding(&padding); - - PluginsBar = new CPluginsBar(HWindow); - if (PluginsBar == NULL) - { - TRACE_E(LOW_MEMORY); - return -1; - } - - // AnimateBar = new CAnimate(HWorkerBitmap, 50, 0, RGB(255, 255, 255)); // 50 frames total, loop from 0, white background - // AnimateBar = new CAnimate(HWorkerBitmap, 43, 3, RGB(0, 0, 0)); // 43 frames total, loop from 3, black background - // if (AnimateBar == NULL) - // { - // TRACE_E(LOW_MEMORY); - // return -1; - // } - // if (!AnimateBar->IsGood()) - // return -1; - - // User Menu Bar - UMToolBar = new CUserMenuBar(HWindow); - if (UMToolBar == NULL) - { - TRACE_E(LOW_MEMORY); - return -1; - } - UMToolBar->GetPadding(&padding); - padding.IconLeft = 2; - padding.IconRight = 3; - padding.ButtonIconText = 2; - padding.TextRight = 4; - UMToolBar->SetPadding(&padding); - - // Hot Path Bar - HPToolBar = new CHotPathsBar(HWindow); - if (HPToolBar == NULL) - { - TRACE_E(LOW_MEMORY); - return -1; - } - HPToolBar->GetPadding(&padding); - padding.IconLeft = 2; - padding.IconRight = 3; - padding.ButtonIconText = 2; - padding.TextRight = 4; - HPToolBar->SetPadding(&padding); - - // Drive Bar - DriveBar = new CDriveBar(HWindow); - if (DriveBar == NULL) - { - TRACE_E(LOW_MEMORY); - return -1; - } - DriveBar2 = new CDriveBar(HWindow); - if (DriveBar2 == NULL) - { - TRACE_E(LOW_MEMORY); - return -1; - } - - BottomToolBar = new CBottomToolBar(HWindow); - if (BottomToolBar == NULL) - { - TRACE_E(LOW_MEMORY); - return -1; - } - BottomToolBar->SetImageList(HBottomTBImageList); - BottomToolBar->SetHotImageList(HHotBottomTBImageList); - - TaskbarRestartMsg = RegisterWindowMessage(TEXT("TaskbarCreated")); - - Created = TRUE; - ExecLogStartupComplete(); - return 0; - } - - // case WM_CHANGEUISTATE: // it seems both messages always arrive - case WM_UPDATEUISTATE: - { - if (MenuBar != NULL && MenuBar->HWindow != NULL) - SendMessage(MenuBar->HWindow, WM_UPDATEUISTATE, wParam, lParam); - // TRACE_I("KeyboardCuesAlwaysVisible="<RefreshListBox(-1, -1, LeftPanel->FocusedIndex, FALSE, FALSE); - RightPanel->RefreshListBox(-1, -1, RightPanel->FocusedIndex, FALSE, FALSE); - RefreshDiskFreeSpace(); - - // the font changed; notify plugins so their toolbars and menu bars call SetFont() - Plugins.Event(PLUGINEVENT_SETTINGCHANGE, 0); - - return 0; - } - - case WM_USER_SHCHANGENOTIFY: // received thanks to SHChangeNotifyRegister - { - LONG wEventId; - HANDLE hLock = NULL; - - // TRACE_E("WM_USER_SHCHANGENOTIFY lParam="<IconOverlaysChangedOnPath(szPath); - RightPanel->IconOverlaysChangedOnPath(szPath); - if (CutDirectory(szPath)) - { - LeftPanel->IconOverlaysChangedOnPath(szPath); - RightPanel->IconOverlaysChangedOnPath(szPath); - } - } - } - else - { - if (wEventId == SHCNE_ASSOCCHANGED) - { - // change in associations - // delay one second so we don't collide with other software using the icon size change trick to reset the icon cache - if (!SetTimer(HWindow, IDT_ASSOCIATIONSCHNG, 1000, NULL)) - OnAssociationsChangedNotification(FALSE); - } - else - { - // change in media or drives - - // after media insertion, automatically perform Retry in the "drive not ready" message box - // (if it is displayed for the drive with inserted media) - if (wEventId == SHCNE_MEDIAINSERTED) - { - if (CheckPathRootWithRetryMsgBox[0] != 0 && - HasTheSameRootPath(CheckPathRootWithRetryMsgBox, szPath)) - { - if (LastDriveSelectErrDlgHWnd != NULL) - PostMessage(LastDriveSelectErrDlgHWnd, WM_COMMAND, IDRETRY, 0); - } - } - - // if the Alt+F1/F2 menu is open, refresh (read the volume name) - CFilesWindow* panel = GetActivePanel(); - if (panel != NULL) - PostMessage(MainWindow->HWindow, WM_USER_DRIVES_CHANGE, 0, 0); - - // if the panels show CD-ROM or removable media, refresh them - while (1) - { - if ((panel->Is(ptDisk) || panel->Is(ptZIPArchive)) && !IsUNCPath(panel->GetPath())) - { - UINT type = MyGetDriveType(panel->GetPath()); - if (type == DRIVE_CDROM || type == DRIVE_REMOVABLE) - { - HANDLES(EnterCriticalSection(&TimeCounterSection)); // capture the time when a refresh is needed - int t1 = MyTimeCounter++; - HANDLES(LeaveCriticalSection(&TimeCounterSection)); - PostMessage(panel->HWindow, WM_USER_REFRESH_DIR, 0, t1); - } - if (type == DRIVE_NO_ROOT_DIR) // device disappeared (the drive is invalid) - { - if (LeftPanel == panel) - { - if (!ChangeLeftPanelToFixedWhenIdleInProgress) - ChangeLeftPanelToFixedWhenIdle = TRUE; - } - else - { - if (!ChangeRightPanelToFixedWhenIdleInProgress) - ChangeRightPanelToFixedWhenIdle = TRUE; - } - } - } - if (panel != GetNonActivePanel()) - panel = GetNonActivePanel(); - else - break; - } - } - } - break; - } - - /* - // WM_DEVICECHANGE didn't work well, for example under Win XP when connecting the DSC F707 camera. - // A notification about device connection arrived, but the subsequent device name detection - // (if the Alt+F1/2 menu was displayed) via SHGetFileInfo returned an empty string. - // I found a thread on Google where someone complains about the same problem - // - // http://groups.google.com/groups?hl=en&lr=&ie=UTF-8&oe=UTF-8&threadm=99a435fa.0203280715.69a286a8%40posting. - // google.com&rnum=1&prev=/groups%3Fhl%3Den%26lr%3D%26ie%3DUTF-8%26oe%3DUTF-8%26q%3Ddevice%2Bname%2Bshgetfileinfo - // - // and he solved it with a wait. People recommended abandoning WM_DEVICECHANGE and switching to - // the undocumented function SHChangeNotifyRegister... - // (http://www.geocities.com/SiliconValley/4942/notify.html) - case WM_DEVICECHANGE: - { - if (wParam == DBT_DEVICEARRIVAL || wParam == DBT_DEVICEREMOVECOMPLETE || - wParam == DBT_CONFIGCHANGED) // CD-ROM media change - { - // if the Alt+F1/F2 menu is open, refresh (read volume name) - CFilesWindow *panel = GetActivePanel(); - if (panel != NULL) - PostMessage(MainWindow->HWindow, WM_USER_DRIVES_CHANGE, 0, 0); - - // if the panels show CD-ROM or removable media, refresh them - while (1) - { - if (panel->Is(ptDisk) || panel->Is(ptZIPArchive)) - { - UINT type = MyGetDriveType(panel->GetPath()); - if (type == DRIVE_CDROM || type == DRIVE_REMOVABLE) - { - HANDLES(EnterCriticalSection(&TimeCounterSection)); // capture the time when a refresh is needed - int t1 = MyTimeCounter++; - HANDLES(LeaveCriticalSection(&TimeCounterSection)); - PostMessage(panel->HWindow, WM_USER_REFRESH_DIR, 0, t1); - } - } - if (panel != GetNonActivePanel()) panel = GetNonActivePanel(); - else break; - } - } - break; - } - */ - - case WM_USER_PROCESSDELETEMAN: - { - // delay data processing due to the main window activation after ESC from the viewer on WinXP; - // without this hack, it somehow did not catch up - the main window stayed inactive and the safe-wait window never appeared - if (!SetTimer(HWindow, IDT_DELETEMNGR_PROCESS, 200, NULL)) - DeleteManager.ProcessData(); // if the timer fails, run immediately; forget about WinXP - return 0; - } - - case WM_USER_DRIVES_CHANGE: - { - CFilesWindow* panel = GetActivePanel(); - if (panel->OpenedDrivesList != NULL) - { - // rebuild the menu - panel->OpenedDrivesList->RebuildMenu(); - } - CDriveBar* copyDrivesListFrom = NULL; - if (DriveBar != NULL && DriveBar->HWindow != NULL) - { - DriveBar->RebuildDrives(); - copyDrivesListFrom = DriveBar; - } - if (DriveBar2 != NULL && DriveBar2->HWindow != NULL) - DriveBar2->RebuildDrives(copyDrivesListFrom); - return 0; - } - - case WM_USER_ENTERMENULOOP: - case WM_USER_LEAVEMENULOOP: - { - // turn off any tooltip - SetCurrentToolTip(NULL, 0); - - // if someone is monitoring the mouse, end the monitoring - TRACKMOUSEEVENT tme; - tme.cbSize = sizeof(tme); - tme.dwFlags = TME_QUERY; - if (TrackMouseEvent(&tme) && tme.hwndTrack != NULL) - SendMessage(tme.hwndTrack, WM_MOUSELEAVE, 0, 0); - - // let the existing caret hide (or show again) so it does not distract the user - CancelPanelsUI(); // cancel QuickSearch and QuickEdit - if (EditMode) - { - if (uMsg == WM_USER_ENTERMENULOOP) - EditWindow->HideCaret(); - else - EditWindow->ShowCaret(); - } - - if (uMsg == WM_USER_ENTERMENULOOP) - UserMenuIconBkgndReader.BeginUserMenuIconsInUse(); - else - UserMenuIconBkgndReader.EndUserMenuIconsInUse(); - - // Ensure the enablers are set correctly so enabled items in the menu reflect - // the real state. Also update the bottom toolbar status. - OnEnterIdle(); - return 0; - } - - case WM_USER_TBDROPDOWN: - { - CToolBar* tlb = (CToolBar*)WindowsManager.GetWindowPtr((HWND)wParam); - if (tlb == NULL) - return 0; - int index = (int)lParam; - TLBI_ITEM_INFO2 tii; - tii.Mask = TLBI_MASK_ID; - if (!tlb->GetItemInfo2(index, TRUE, &tii)) - return 0; - - DWORD id = tii.ID; - - RECT r; - tlb->GetItemRect(index, r); - - switch (id) - { - case CM_LCHANGEDRIVE: - case CM_RCHANGEDRIVE: - { - SendMessage(HWindow, WM_COMMAND, id, 0); - break; - } - - case CM_OPENHOTPATHSDROP: - { - CMenuPopup menu; - HotPaths.FillHotPathsMenu(&menu, CM_ACTIVEHOTPATH_MIN); - menu.Track(0, r.left, r.bottom, HWindow, &r); - break; - } - - case CM_USERMENUDROP: - { - UserMenuIconBkgndReader.BeginUserMenuIconsInUse(); - CMenuPopup menu; - FillUserMenu(&menu); - // another lock/unlock cycle (BeginUserMenuIconsInUse + EndUserMenuIconsInUse) - // will occur in WM_USER_ENTERMENULOOP + WM_USER_LEAVEMENULOOP, but - // it is nested and lightweight, so we ignore it and do not fight it - menu.Track(0, r.left, r.bottom, HWindow, &r); - UserMenuIconBkgndReader.EndUserMenuIconsInUse(); - break; - } - - case CM_NEWDROP: - { - CMenuPopup menu(CML_FILES_NEW); - menu.Track(0, r.left, r.bottom, HWindow, &r); - break; - } - - case CM_OPEN_FOLDER_DROP: - { - CMenuPopup menu; - - CGUIMenuPopupAbstract* popup = MainMenu.GetSubMenu(CML_COMMANDS, FALSE); - if (popup != NULL) - { - popup = popup->GetSubMenu(CML_COMMANDS_FOLDERS, FALSE); - if (popup != NULL) - popup->Track(0, r.left, r.bottom, HWindow, &r); - } - break; - } - - case CM_ACTIVEBACK: - case CM_ACTIVEFORWARD: - case CM_LBACK: - case CM_LFORWARD: - case CM_RBACK: - case CM_RFORWARD: - { - BOOL forward = id == CM_ACTIVEFORWARD || id == CM_LFORWARD || id == CM_RFORWARD; - - CMenuPopup menu; - CFilesWindow* panel = GetActivePanel(); - if (id == CM_LBACK || id == CM_LFORWARD) - panel = LeftPanel; - if (id == CM_RBACK || id == CM_RFORWARD) - panel = RightPanel; - panel->PathHistory->FillBackForwardPopupMenu(&menu, forward); - DWORD cmd = menu.Track(MENU_TRACK_RETURNCMD, - r.left, r.bottom, - HWindow, &r); - if (cmd != 0) - panel->PathHistory->Execute(cmd, forward, panel); - break; - } - - case CM_ACTIVEVIEWMODE: - case CM_LEFTVIEWMODE: - case CM_RIGHTVIEWMODE: - { - CMenuPopup menu; - int type = 0; - if (id == CM_LEFTVIEWMODE) - type = 1; - else if (id == CM_RIGHTVIEWMODE) - type = 2; - FillViewModeMenu(&menu, 0, type); - menu.Track(0, r.left, r.bottom, HWindow, &r); - break; - } - - case CM_VIEW: - case CM_EDIT: - { - CFilesWindow* activePanel = GetActivePanel(); - if (activePanel == NULL) - break; - - CMenuPopup popup(id == CM_VIEW ? CML_FILES_VIEWWITH : 0); - - if (id == CM_VIEW) - activePanel->FillViewWithMenu(&popup); - else - activePanel->FillEditWithMenu(&popup); - - popup.Track(0, r.left, r.bottom, HWindow, &r); - break; - } - } - - if (id >= CM_USERMENU_MIN && id <= CM_USERMENU_MAX) - { - // user clicked a group in the User Menu Toolbar - int iterator = id - CM_USERMENU_MIN; - int endIndex = UserMenuItems->GetSubmenuEndIndex(iterator); - if (endIndex != -1) - { - UserMenuIconBkgndReader.BeginUserMenuIconsInUse(); - iterator++; - CMenuPopup menu; - FillUserMenu2(&menu, &iterator, endIndex); - // another lock/unlock cycle (BeginUserMenuIconsInUse + EndUserMenuIconsInUse) - // will occur in WM_USER_ENTERMENULOOP + WM_USER_LEAVEMENULOOP, - // but it is nested and lightweight, so we ignore it - menu.Track(0, r.left, r.bottom, HWindow, &r); - UserMenuIconBkgndReader.EndUserMenuIconsInUse(); - } - } - - if (id >= CM_PLUGINCMD_MIN && id <= CM_PLUGINCMD_MAX) - { - // user clicked on the plugin icon in the PluginsBar; - int index2 = id - CM_PLUGINCMD_MIN; // index of the plugin in CPlugions::Data - CMenuPopup menu(CML_PLUGINS_SUBMENU); - if (Plugins.InitPluginMenuItemsForBar(HWindow, index2, &menu)) - menu.Track(0, r.left, r.bottom, HWindow, &r); - } - - if (id >= CM_DRIVEBAR_MIN && id <= CM_DRIVEBAR_MAX) - DriveBar->Execute(id); - if (id >= CM_DRIVEBAR2_MIN && id <= CM_DRIVEBAR2_MAX) - DriveBar2->Execute(id); - return 0; - } - - case WM_USER_REPAINTALLICONS: - { - if (LeftPanel != NULL) - LeftPanel->RepaintIconOnly(-1); // all - if (RightPanel != NULL) - RightPanel->RepaintIconOnly(-1); // all - return 0; - } - - case WM_USER_REPAINTSTATUSBARS: - { - if (LeftPanel != NULL && LeftPanel->DirectoryLine != NULL) - LeftPanel->DirectoryLine->InvalidateAndUpdate(FALSE); - if (RightPanel != NULL && RightPanel->DirectoryLine != NULL) - RightPanel->DirectoryLine->InvalidateAndUpdate(FALSE); - return 0; - } - - case WM_USER_SHOWWINDOW: - { - if (!SalamanderBusy) - { - SalamanderBusy = TRUE; // now BUSY - LastSalamanderIdleTime = GetTickCount(); - BringWindowToTop(HWindow); // probably not important, but I saw it in a sample so I am adding it here too - if (IsIconic(HWindow)) - { - // SetForegroundWindow: this is crucial. If we don't call it and - // "only one instance" with the tray is active, Salamander sometimes - // appears in the background and only later moves to the front. - SetForegroundWindow(HWindow); - ShowWindow(HWindow, SW_RESTORE); - } - else - SetForegroundWindow(HWindow); - } - return 0; - } - - case WM_USER_SKIPONEREFRESH: - { - if (!SetTimer(NULL, 0, 500, SkipOneARTimerProc)) - { - SkipOneActivateRefresh = FALSE; - } - return 0; - } - - /* - case WM_USER_SETPATHS: - { - if (!SalamanderBusy && MainWindow != NULL && MainWindow->CanClose) // not BUSY and already started, otherwise ignore requests from other processes - { - SalamanderBusy = TRUE; // now BUSY - LastSalamanderIdleTime = GetTickCount(); - CSetPathsParams params; - ZeroMemory(¶ms, sizeof(params)); // default values - HANDLE sendingProcess = HANDLES_Q(OpenProcess(PROCESS_DUP_HANDLE, FALSE, wParam)); - HANDLE sendingFM = (HGLOBAL)lParam; - - HANDLE fm; - BOOL alreadyDone = FALSE; - if (sendingProcess != NULL && - HANDLES(DuplicateHandle(sendingProcess, sendingFM, // sending-process file-mapping - GetCurrentProcess(), &fm, // this process file-mapping - 0, FALSE, DUPLICATE_SAME_ACCESS))) - { - CSetPathsParams *unsafe = (CSetPathsParams *)HANDLES(MapViewOfFile(fm, FILE_MAP_WRITE, 0, 0, sizeof(CSetPathsParams))); // FIXME_X64 are we passing x86/x64 incompatible data? - if (unsafe != NULL) - { - alreadyDone = unsafe->Received; - if (!alreadyDone) - { - lstrcpyn(params.LeftPath, unsafe->LeftPath, MAX_PATH); - lstrcpyn(params.RightPath, unsafe->RightPath, MAX_PATH - 1); - - if (unsafe->MagicSignature1 == 0x07f2ab13 && unsafe->MagicSignature2 == 0x471e0901) - { - // new features since 2.52 - // WORD version = unsafe->StructVersion; // not used yet, the first version is recognized by the presence of signatures - lstrcpyn(params.ActivePath, unsafe->ActivePath, MAX_PATH); - params.ActivatePanel = unsafe->ActivatePanel; - } - // we return the result value having taken over the data - unsafe->Received = TRUE; - } - HANDLES(UnmapViewOfFile(unsafe)); - } - HANDLES(CloseHandle(fm)); - } - if (sendingProcess != NULL) HANDLES(CloseHandle(sendingProcess)); - - if (!alreadyDone) - ApplyCommandLineParams(¶ms); - } - return 0; - } -*/ - - case WM_USER_AUTOCONFIG: - { - PackAutoconfig(HWindow); - return 0; - } - - case WM_USER_VIEWERCONFIG: - { - if (GetForegroundWindow() != HWindow) - SetForegroundWindow(HWindow); // so we rise above the viewer - WindowProc(WM_USER_CONFIGURATION, 3, 0); - HWND hCaller = (HWND)wParam; - if (IsWindow(hCaller)) - { - // If the window that invoked us still exists, try to bring it to - // the foreground. This is a bit dirty because if it opens a modal - // dialog in the meantime, it won't get activation. But I don't care, - // the viewer will (hopefully) end up inside Salamander - in the plugin ;-) - SetForegroundWindow(hCaller); - } - return 0; - } - - case WM_USER_CONFIGURATION: - { - if (!SalamanderBusy) - { - SalamanderBusy = TRUE; // now BUSY - LastSalamanderIdleTime = GetTickCount(); - } - - BeginStopRefresh(); // snooper takes a break - - BOOL oldStatusArea = Configuration.StatusArea; - BOOL oldPanelCaption = Configuration.ShowPanelCaption; - BOOL oldPanelZoom = Configuration.ShowPanelZoom; - - UserMenuIconBkgndReader.ResetSysColorsChanged(); // now, we start watching system color changes (icon reload required) - BOOL readingUMIcons = UserMenuIconBkgndReader.IsReadingIcons(); - if (readingUMIcons) // new icons are on their way to the user menu; show them after configuration is done (on OK reload icons again so newly added ones are read as well) - UserMenuIconBkgndReader.BeginUserMenuIconsInUse(); - BOOL oldUseCustomPanelFont = UseCustomPanelFont; - LOGFONT oldLogFont = LogFont; - CConfigurationDlg dlg(HWindow, UserMenuItems, (int)wParam, (int)lParam); - int res = dlg.Execute(LoadStr(IDS_BUTTON_OK), LoadStr(IDS_BUTTON_CANCEL), - LoadStr(IDS_BUTTON_HELP)); - if (readingUMIcons) - UserMenuIconBkgndReader.EndUserMenuIconsInUse(); - - // dialog closed - the user could have changed the clipboard, check it - IdleRefreshStates = TRUE; // force status variable check on next Idle - IdleCheckClipboard = TRUE; // also check the clipboard - - if (res == IDOK) // values changed -> refresh everything possible - { - if (dlg.PageView.IsDirty()) - { - // user changed something in the view configuration - rebuild the columns - LeftPanel->SelectViewTemplate(LeftPanel->GetViewTemplateIndex(), TRUE, FALSE); - RightPanel->SelectViewTemplate(RightPanel->GetViewTemplateIndex(), TRUE, FALSE); - } - if (memcmp(&oldLogFont, &LogFont, sizeof(LogFont)) != 0 || - oldUseCustomPanelFont != UseCustomPanelFont) - { - SetFont(); - // if the header line is shown, we must set its correct size - LeftPanel->LayoutListBoxChilds(); - RightPanel->LayoutListBoxChilds(); - } - - if (Configuration.ThumbnailSize != LeftPanel->GetThumbnailSize() || - Configuration.ThumbnailSize != RightPanel->GetThumbnailSize()) - { - // if the thumbnail size changed, it must be propagated to the panels - LeftPanel->SetThumbnailSize(Configuration.ThumbnailSize); - RightPanel->SetThumbnailSize(Configuration.ThumbnailSize); - } - - if (oldStatusArea != Configuration.StatusArea) - { - if (Configuration.StatusArea) - AddTrayIcon(); - else - RemoveTrayIcon(); - } - - if (UMToolBar != NULL && UMToolBar->HWindow != NULL) - UMToolBar->CreateButtons(); - - if (HPToolBar != NULL && HPToolBar->HWindow != NULL) - HPToolBar->CreateButtons(); - - if (Windows7AndLater) - CreateJumpList(); - - // the user could have enabled/disabled Documents - CDriveBar* copyDrivesListFrom = NULL; - if (DriveBar != NULL && DriveBar->HWindow != NULL) - { - DriveBar->RebuildDrives(DriveBar); // we don't need slow drive enumeration - copyDrivesListFrom = DriveBar; - } - if (DriveBar2 != NULL && DriveBar2->HWindow != NULL) - DriveBar2->RebuildDrives(copyDrivesListFrom); - - if (oldPanelCaption != Configuration.ShowPanelCaption || oldPanelZoom != Configuration.ShowPanelZoom) - { - if (LeftPanel->DirectoryLine != NULL && LeftPanel->DirectoryLine->HWindow != NULL) - LeftPanel->DirectoryLine->Repaint(); - if (RightPanel->DirectoryLine != NULL && RightPanel->DirectoryLine->HWindow != NULL) - RightPanel->DirectoryLine->Repaint(); - } - - // main window icon - SetWindowIcon(); - // icon in progress windows - ProgressDlgArray.PostIconChange(); - - // tell both panels they need to refresh - LeftPanel->RefreshForConfig(); - RightPanel->RefreshForConfig(); - - // clear stored data in SalShExtPastedData (the archiver may have changed) - SalShExtPastedData.ReleaseStoredArchiveData(); - - // Internal Viewer and Find: refresh all windows (font already changed) - BroadcastConfigChanged(); - - // distribute this news among plugins as well - Plugins.Event(PLUGINEVENT_CONFIGURATIONCHANGED, 0); - } - - EndStopRefresh(); // snooper starts again now - return 0; - } - - case WM_SYSCOMMAND: - { - if (HasLockedUI()) - break; - - // if the user pressed the Alt button while the initial splash window was shown, - // the system menu could be entered before MainWindow appeared and the splash - // window remained open until the user pressed Escape - // if MainWindow is not yet visible, disable entering the Window menu - if (wParam == SC_KEYMENU && !IsWindowVisible(HWindow)) - return 0; - - // set status bar as appropriate - UINT nItemID = wParam != CM_ALWAYSONTOP ? ((UINT)wParam & 0xFFF0) : (UINT)wParam; - - // don't interfere with system commands if not in help mode - if (HelpMode) - { - switch (nItemID) - { - case SC_SIZE: - case SC_MOVE: - case SC_MINIMIZE: - case SC_MAXIMIZE: - case SC_NEXTWINDOW: - case SC_PREVWINDOW: - case SC_CLOSE: - case SC_RESTORE: - case SC_TASKLIST: - { - OpenHtmlHelp(NULL, HWindow, HHCDisplayContext, IDH_SYSMENUCMDS, FALSE); - return 0; - } - - case CM_ALWAYSONTOP: - { - OpenHtmlHelp(NULL, HWindow, HHCDisplayContext, nItemID, FALSE); - return 0; - } - } - } - - if (wParam == CM_ALWAYSONTOP) - WindowProc(WM_COMMAND, wParam, lParam); // pass it on - - if (Configuration.StatusArea && wParam == SC_MINIMIZE) - { - ShowWindow(HWindow, SW_MINIMIZE); - ShowWindow(HWindow, SW_HIDE); - return 0; - } - break; - } - - case WM_USER_FLASHWINDOW: - { - FlashWindow(HWindow, TRUE); - Sleep(100); - FlashWindow(HWindow, FALSE); - return 0; - } - - case WM_APPCOMMAND: - { - // we catch messages coming especially from newer mice (4th button and above) - // and multimedia keyboards - // viz /viewtopic.php?t=192 - DWORD cmd = GET_APPCOMMAND_LPARAM(lParam); - switch (cmd) - { - case APPCOMMAND_BROWSER_BACKWARD: - { - SendMessage(HWindow, WM_COMMAND, CM_ACTIVEBACK, 0); - return TRUE; - } - - case APPCOMMAND_BROWSER_FORWARD: - { - SendMessage(HWindow, WM_COMMAND, CM_ACTIVEFORWARD, 0); - return TRUE; - } - } - break; - } - - case WM_COMMAND: - { - if (HelpMode && (HWND)lParam == NULL && LOWORD(wParam) != CM_HELP_CONTEXT) - { - DWORD id = LOWORD(wParam); - - if (id >= CM_PLUGINCMD_MIN && id <= CM_PLUGINCMD_MAX) - { // command of a plugin (submenu of Plugins menu) - if (Plugins.HelpForMenuItem(HWindow, LOWORD(wParam))) - return 0; - else - id = CM_LAST_PLUGIN_CMD; // if the plugin has no help, show Salamander's help "Using Plugins" - } - - // adjust ranges to their first value - if (id > CM_USERMENU_MIN && id <= CM_USERMENU_MAX) - id = CM_USERMENU_MIN; - if (id > CM_DRIVEBAR_MIN && id <= CM_DRIVEBAR_MAX) - id = CM_DRIVEBAR_MIN; - if (id > CM_DRIVEBAR2_MIN && id <= CM_DRIVEBAR2_MAX) - id = CM_DRIVEBAR2_MIN; - if (id > CM_PLUGINCFG_MIN && id <= CM_PLUGINCFG_MAX) - id = CM_PLUGINCFG_MIN; - if (id > CM_PLUGINABOUT_MIN && id <= CM_PLUGINABOUT_MAX) - id = CM_PLUGINABOUT_MIN; - - if (id > CM_ACTIVEMODE_1 && id <= CM_ACTIVEMODE_10) - id = CM_ACTIVEMODE_1; - if (id > CM_LEFTMODE_1 && id <= CM_LEFTMODE_10) - id = CM_LEFTMODE_1; - if (id > CM_RIGHTMODE_1 && id <= CM_RIGHTMODE_10) - id = CM_RIGHTMODE_1; - - if (id > CM_LEFTSORTBY_MIN && id <= CM_LEFTSORTBY_MAX) - id = CM_LEFTSORTBY_MIN; - if (id > CM_RIGHTSORTBY_MIN && id <= CM_RIGHTSORTBY_MAX) - id = CM_RIGHTSORTBY_MIN; - - if (id > CM_LEFTHOTPATH_MIN && id <= CM_LEFTHOTPATH_MAX) - id = CM_LEFTHOTPATH_MIN; - if (id > CM_RIGHTHOTPATH_MIN && id <= CM_RIGHTHOTPATH_MAX) - id = CM_RIGHTHOTPATH_MIN; - - if (id > CM_LEFTHISTORYPATH_MIN && id <= CM_LEFTHISTORYPATH_MAX) - id = CM_LEFTHISTORYPATH_MIN; - if (id > CM_RIGHTHISTORYPATH_MIN && id <= CM_RIGHTHISTORYPATH_MAX) - id = CM_RIGHTHISTORYPATH_MIN; - - if (id > CM_CODING_MIN && id <= CM_CODING_MAX) - id = CM_CODING_MIN; - - if (id > CM_NEWMENU_MIN && id <= CM_NEWMENU_MAX) - id = CM_NEWMENU_MIN; - - OpenHtmlHelp(NULL, HWindow, HHCDisplayContext, id, FALSE); - - return 0; - } - CFilesWindow* activePanel = GetActivePanel(); - if (activePanel == NULL || LeftPanel == NULL || RightPanel == NULL) - { - TRACE_E("activePanel == NULL || LeftPanel == NULL || RightPanel == NULL"); - return 0; - } - - // exit quick-search mode - if (LOWORD(wParam) != CM_ACTIVEREFRESH && // except refresh in the active panel - LOWORD(wParam) != CM_LEFTREFRESH && // except refresh in the left panel - LOWORD(wParam) != CM_RIGHTREFRESH && // except refresh in the right panel - (HIWORD(wParam) == 0 || HIWORD(wParam) == 1)) // only from menu or accelerator - { - CancelPanelsUI(); // cancel QuickSearch and QuickEdit - } - - if (LOWORD(wParam) >= CM_NEWMENU_MIN && LOWORD(wParam) <= CM_NEWMENU_MAX) - { // command from the New menu - if (ContextMenuNew->MenuIsAssigned() && activePanel->CheckPath(TRUE) == ERROR_SUCCESS) - { - activePanel->UserWorkedOnThisPath = TRUE; - { - char newMenuDetail[64]; - _snprintf_s(newMenuDetail, _TRUNCATE, "id=%u", (unsigned)LOWORD(wParam)); - ExecLogFeatureStart("new item", newMenuDetail); - CALL_STACK_MESSAGE1("CMainWindow::WindowProc::menu_new"); - IContextMenu* menu2 = ContextMenuNew->GetMenu2(); - menu2->AddRef(); // just in case ContextMenuNew vanishes asynchronously (message loop) - CShellExecuteWnd shellExecuteWnd; - CMINVOKECOMMANDINFO ici; - ici.cbSize = sizeof(CMINVOKECOMMANDINFO); - ici.fMask = 0; - ici.hwnd = shellExecuteWnd.Create(HWindow, "SEW: CMainWindow::WindowProc cmd=%d", LOWORD(wParam) - CM_NEWMENU_MIN); - ici.lpVerb = MAKEINTRESOURCE((LOWORD(wParam) - CM_NEWMENU_MIN)); - ici.lpParameters = NULL; - ici.lpDirectory = activePanel->GetPath(); - ici.nShow = SW_SHOWNORMAL; - ici.dwHotKey = 0; - ici.hIcon = 0; - activePanel->FocusFirstNewItem = TRUE; // select the newly generated file/directory - - CMainWindowWindowProcAux(menu2, ici); - ExecLogFeatureResult("new item", newMenuDetail, TRUE); - - menu2->Release(); - } - //--- refresh directories that are not automatically refreshed - // announce a change in the current directory (a new file or directory is most likely created there) - MainWindow->PostChangeOnPathNotification(activePanel->GetPath(), FALSE); - } - else - { - ExecLogFeatureResult("new item", "menu unavailable", FALSE); - TRACE_E("ContextMenuNew is not valid anymore, it is not posible to invoke menu New command."); - } - return 0; - } - - if (LOWORD(wParam) >= CM_PLUGINABOUT_MIN && LOWORD(wParam) <= CM_PLUGINABOUT_MAX) - { - Plugins.OnPluginAbout(HWindow, LOWORD(wParam) - CM_PLUGINABOUT_MIN); - return 0; - } - - if (LOWORD(wParam) >= CM_PLUGINCFG_MIN && LOWORD(wParam) <= CM_PLUGINCFG_MAX) - { - Plugins.OnPluginConfiguration(HWindow, LOWORD(wParam) - CM_PLUGINCFG_MIN); - return 0; - } - - if (LOWORD(wParam) >= CM_PLUGINCMD_MIN && LOWORD(wParam) <= CM_PLUGINCMD_MAX) - { // command from a plugin menu - // lower the thread priority to "normal" (so operations don't burden the system) - SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL); - - char pluginCmdDetail[64]; - _snprintf_s(pluginCmdDetail, _TRUNCATE, "id=%u", (unsigned)LOWORD(wParam)); - ExecLogFeatureStart("plugin command", pluginCmdDetail); - BOOL pluginCmdResult = Plugins.ExecuteMenuItem(activePanel, HWindow, LOWORD(wParam)); - ExecLogFeatureResult("plugin command", pluginCmdDetail, pluginCmdResult); - if (pluginCmdResult) - { - activePanel->StoreSelection(); // save selection for Restore Selection command - activePanel->SetSel(FALSE, -1, TRUE); // explicit redraw - PostMessage(activePanel->HWindow, WM_USER_SELCHANGED, 0, 0); // sel-change notify - } - - // raise the thread priority again, the operation has finished - SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL); - - // restoring the contents of non-automatic panels is up to plugins - - UpdateWindow(HWindow); - return 0; - } - - if (LOWORD(wParam) == CM_LAST_PLUGIN_CMD) - { // Plugins/Last Command action - // lower the thread priority to "normal" (so operations don't burden the system) - SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL); - - ExecLogFeatureStart("plugin last command", ""); - BOOL lastCmdResult = Plugins.OnLastCommand(activePanel, HWindow); - ExecLogFeatureResult("plugin last command", "", lastCmdResult); - if (lastCmdResult) - { - activePanel->StoreSelection(); // save selection for Restore Selection command - activePanel->SetSel(FALSE, -1, TRUE); // explicit redraw - PostMessage(activePanel->HWindow, WM_USER_SELCHANGED, 0, 0); // sel-change notify - } - - // raise the thread priority again, the operation has finished - SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL); - - // restoring the contents of non-automatic panels is up to plugins - - UpdateWindow(HWindow); - return 0; - } - - if (LOWORD(wParam) >= CM_USERMENU_MIN && LOWORD(wParam) <= CM_USERMENU_MAX) - { - if (activePanel->Is(ptDisk)) - { - activePanel->UserWorkedOnThisPath = TRUE; - activePanel->StoreSelection(); // save selection for Restore Selection command - - CUserMenuAdvancedData userMenuAdvancedData; - - char* list = userMenuAdvancedData.ListOfSelNames; - char* listEnd = list + USRMNUARGS_MAXLEN - 1; - BOOL smallBuf = FALSE; - if (activePanel->SelectedCount > 0) - { - int count = activePanel->Files->Count + activePanel->Dirs->Count; - int i; - for (i = 0; i < count; i++) - { - CFileData* file = (i < activePanel->Dirs->Count) ? &activePanel->Dirs->At(i) : &activePanel->Files->At(i - activePanel->Dirs->Count); - if (file->Selected) - { - if (list > userMenuAdvancedData.ListOfSelNames) - { - if (list < listEnd) - *list++ = ' '; - else - break; - } - if (!AddToListOfNames(&list, listEnd, file->Name, file->NameLen)) - break; - } - } - if (i < count) - smallBuf = TRUE; - } - else // take the focused item - { - BOOL subDir; - if (activePanel->Dirs->Count > 0) - subDir = (strcmp(activePanel->Dirs->At(0).Name, "..") == 0); - else - subDir = FALSE; - int index = activePanel->GetCaretIndex(); - if (index >= 0 && index < activePanel->Files->Count + activePanel->Dirs->Count && - (index != 0 || !subDir)) - { - CFileData* file = (index < activePanel->Dirs->Count) ? &activePanel->Dirs->At(index) : &activePanel->Files->At(index - activePanel->Dirs->Count); - if (!AddToListOfNames(&list, listEnd, file->Name, file->NameLen)) - smallBuf = TRUE; - } - } - if (smallBuf) - { - userMenuAdvancedData.ListOfSelNames[0] = 0; // small buffer for the list of selected names - userMenuAdvancedData.ListOfSelNamesIsEmpty = FALSE; - } - else - { - *list = 0; - userMenuAdvancedData.ListOfSelNamesIsEmpty = userMenuAdvancedData.ListOfSelNames[0] == 0; - } - - char* listFull = userMenuAdvancedData.ListOfSelFullNames; - char* listFullEnd = listFull + USRMNUARGS_MAXLEN - 1; - smallBuf = FALSE; - char fullName[MAX_PATH]; - if (activePanel->SelectedCount > 0) - { - int count = activePanel->Files->Count + activePanel->Dirs->Count; - int i; - for (i = 0; i < count; i++) - { - CFileData* file = (i < activePanel->Dirs->Count) ? &activePanel->Dirs->At(i) : &activePanel->Files->At(i - activePanel->Dirs->Count); - if (file->Selected) - { - if (listFull > userMenuAdvancedData.ListOfSelFullNames) - { - if (listFull < listFullEnd) - *listFull++ = ' '; - else - break; - } - lstrcpyn(fullName, activePanel->GetPath(), MAX_PATH); - if (!SalPathAppend(fullName, file->Name, MAX_PATH) || - !AddToListOfNames(&listFull, listFullEnd, fullName, (int)strlen(fullName))) - break; - } - } - if (i < count) - smallBuf = TRUE; - } - else // take the focused item - { - BOOL subDir; - if (activePanel->Dirs->Count > 0) - subDir = (strcmp(activePanel->Dirs->At(0).Name, "..") == 0); - else - subDir = FALSE; - int index = activePanel->GetCaretIndex(); - if (index >= 0 && index < activePanel->Files->Count + activePanel->Dirs->Count && - (index != 0 || !subDir)) - { - CFileData* file = (index < activePanel->Dirs->Count) ? &activePanel->Dirs->At(index) : &activePanel->Files->At(index - activePanel->Dirs->Count); - lstrcpyn(fullName, activePanel->GetPath(), MAX_PATH); - if (!SalPathAppend(fullName, file->Name, MAX_PATH) || - !AddToListOfNames(&listFull, listFullEnd, fullName, (int)strlen(fullName))) - { - smallBuf = TRUE; - } - } - } - if (smallBuf) - { - userMenuAdvancedData.ListOfSelFullNames[0] = 0; // small buffer for the list of selected full names - userMenuAdvancedData.ListOfSelFullNamesIsEmpty = FALSE; - } - else - { - *listFull = 0; - userMenuAdvancedData.ListOfSelFullNamesIsEmpty = userMenuAdvancedData.ListOfSelFullNames[0] == 0; - } - - if (LeftPanel->Is(ptDisk)) - { - lstrcpyn(userMenuAdvancedData.FullPathLeft, LeftPanel->GetPath(), MAX_PATH); - if (!SalPathAddBackslash(userMenuAdvancedData.FullPathLeft, MAX_PATH)) - userMenuAdvancedData.FullPathLeft[0] = 0; - } - else - userMenuAdvancedData.FullPathLeft[0] = 0; - if (RightPanel->Is(ptDisk)) - { - lstrcpyn(userMenuAdvancedData.FullPathRight, RightPanel->GetPath(), MAX_PATH); - if (!SalPathAddBackslash(userMenuAdvancedData.FullPathRight, MAX_PATH)) - userMenuAdvancedData.FullPathRight[0] = 0; - } - else - userMenuAdvancedData.FullPathRight[0] = 0; - userMenuAdvancedData.FullPathInactive = (activePanel == LeftPanel) ? userMenuAdvancedData.FullPathRight : userMenuAdvancedData.FullPathLeft; - - userMenuAdvancedData.CompareName1[0] = 0; - userMenuAdvancedData.CompareName2[0] = 0; - userMenuAdvancedData.CompareNamesAreDirs = FALSE; - userMenuAdvancedData.CompareNamesReversed = FALSE; - CFilesWindow* inactivePanel = (activePanel == LeftPanel) ? RightPanel : LeftPanel; - CFileData* f1 = NULL; - CFileData* f2 = NULL; - BOOL f2FromInactPanel = FALSE; - int focus = activePanel->GetCaretIndex(); - BOOL focusOnUpDir = (focus == 0 && activePanel->Dirs->Count > 0 && - strcmp(activePanel->Dirs->At(0).Name, "..") == 0); - int indexes[3]; - int selCount = activePanel->GetSelItems(3, indexes); // interested in: 0-2=number selected, 3=more than two - int tgtIndexes[2]; - int tgtSelCount = inactivePanel->Is(ptDisk) ? inactivePanel->GetSelItems(2, tgtIndexes) : 0; // interested in: 0-1=number selected, 2=more than one - if (selCount == 2) // two selected items in the source panel - { - if ((indexes[0] < activePanel->Dirs->Count) == (indexes[1] < activePanel->Dirs->Count)) // both items are files/directories - { - f1 = (indexes[0] < activePanel->Dirs->Count) ? &activePanel->Dirs->At(indexes[0]) : &activePanel->Files->At(indexes[0] - activePanel->Dirs->Count); - f2 = (indexes[1] < activePanel->Dirs->Count) ? &activePanel->Dirs->At(indexes[1]) : &activePanel->Files->At(indexes[1] - activePanel->Dirs->Count); - userMenuAdvancedData.CompareNamesAreDirs = (indexes[0] < activePanel->Dirs->Count); - } - } - else - { - if (selCount == 1) // one selected item in the source panel - { - f1 = (indexes[0] < activePanel->Dirs->Count) ? &activePanel->Dirs->At(indexes[0]) : &activePanel->Files->At(indexes[0] - activePanel->Dirs->Count); - userMenuAdvancedData.CompareNamesAreDirs = (indexes[0] < activePanel->Dirs->Count); - if (!focusOnUpDir && focus != indexes[0] && tgtSelCount != 1) - { - if ((focus < activePanel->Dirs->Count) == userMenuAdvancedData.CompareNamesAreDirs) // both items are files/directories - { - f2 = (focus < activePanel->Dirs->Count) ? &activePanel->Dirs->At(focus) : &activePanel->Files->At(focus - activePanel->Dirs->Count); - } - } - } - else - { - if (selCount == 0) // no selected item in the source panel, take the focus - { - if (!focusOnUpDir) - { - if (focus >= 0 && focus < activePanel->Dirs->Count + activePanel->Files->Count) - { - f1 = (focus < activePanel->Dirs->Count) ? &activePanel->Dirs->At(focus) : &activePanel->Files->At(focus - activePanel->Dirs->Count); - userMenuAdvancedData.CompareNamesAreDirs = (focus < activePanel->Dirs->Count); - } - } - } - } - } - if (f1 != NULL && f2 == NULL) - { - if (tgtSelCount == 1 && - (tgtIndexes[0] < inactivePanel->Dirs->Count) == userMenuAdvancedData.CompareNamesAreDirs) // both items are files/directories - { - f2 = (tgtIndexes[0] < inactivePanel->Dirs->Count) ? &inactivePanel->Dirs->At(tgtIndexes[0]) : &inactivePanel->Files->At(tgtIndexes[0] - inactivePanel->Dirs->Count); - f2FromInactPanel = TRUE; - } - else - { - if (inactivePanel->Is(ptDisk)) - { - int c = inactivePanel->Dirs->Count + inactivePanel->Files->Count; - int i; - for (i = 0; i < c; i++) - { - CFileData* f = (i < inactivePanel->Dirs->Count) ? &inactivePanel->Dirs->At(i) : &inactivePanel->Files->At(i - inactivePanel->Dirs->Count); - if (f->NameLen == f1->NameLen && - StrICmp(f->Name, f1->Name) == 0) - { - if ((i < inactivePanel->Dirs->Count) == userMenuAdvancedData.CompareNamesAreDirs) // both items are files/directories - { - f2 = f; - f2FromInactPanel = TRUE; - } - break; - } - } - } - } - } - if (f1 != NULL) - { - lstrcpyn(userMenuAdvancedData.CompareName1, activePanel->GetPath(), MAX_PATH); - if (!SalPathAppend(userMenuAdvancedData.CompareName1, f1->Name, MAX_PATH)) - userMenuAdvancedData.CompareName1[0] = 0; - } - if (f2 != NULL) - { - lstrcpyn(userMenuAdvancedData.CompareName2, - (f2FromInactPanel ? inactivePanel : activePanel)->GetPath(), MAX_PATH); - if (!SalPathAppend(userMenuAdvancedData.CompareName2, f2->Name, MAX_PATH)) - userMenuAdvancedData.CompareName2[0] = 0; - else - { - if (f2FromInactPanel && inactivePanel == LeftPanel) - userMenuAdvancedData.CompareNamesReversed = TRUE; - } - } - if (userMenuAdvancedData.CompareName1[0] != 0 && - userMenuAdvancedData.CompareName2[0] == 0 && activePanel == RightPanel) - { - userMenuAdvancedData.CompareNamesReversed = TRUE; - } - - CUMDataFromPanel data(activePanel); - SetCurrentDirectory(activePanel->GetPath()); - UserMenu(HWindow, LOWORD(wParam) - CM_USERMENU_MIN, GetNextFileFromPanel, - &data, &userMenuAdvancedData); - SetCurrentDirectoryToSystem(); - } - return 0; - } - - if (LOWORD(wParam) >= CM_VIEWWITH_MIN && LOWORD(wParam) <= CM_VIEWWITH_MAX) - { - activePanel->UserWorkedOnThisPath = TRUE; - activePanel->OnViewFileWith(LOWORD(wParam) - CM_VIEWWITH_MIN); - return 0; - } - - if (LOWORD(wParam) >= CM_EDITWITH_MIN && LOWORD(wParam) <= CM_EDITWITH_MAX) - { - activePanel->UserWorkedOnThisPath = TRUE; - activePanel->OnEditFileWith(LOWORD(wParam) - CM_EDITWITH_MIN); - return 0; - } - - if (LOWORD(wParam) >= CM_DRIVEBAR_MIN && LOWORD(wParam) <= CM_DRIVEBAR_MAX) - { - DriveBar->Execute(LOWORD(wParam)); - return 0; - } - - if (LOWORD(wParam) >= CM_DRIVEBAR2_MIN && LOWORD(wParam) <= CM_DRIVEBAR2_MAX) - { - DriveBar2->Execute(LOWORD(wParam)); - return 0; - } - - if (LOWORD(wParam) >= CM_ACTIVEHOTPATH_MIN && LOWORD(wParam) < CM_ACTIVEHOTPATH_MIN + HOT_PATHS_COUNT) - { - activePanel->GotoHotPath(LOWORD(wParam) - CM_ACTIVEHOTPATH_MIN); - return 0; - } - - if (LOWORD(wParam) >= CM_LEFTHOTPATH_MIN && LOWORD(wParam) < CM_LEFTHOTPATH_MIN + HOT_PATHS_COUNT) - { - LeftPanel->GotoHotPath(LOWORD(wParam) - CM_LEFTHOTPATH_MIN); - return 0; - } - - if (LOWORD(wParam) >= CM_RIGHTHOTPATH_MIN && LOWORD(wParam) < CM_RIGHTHOTPATH_MIN + HOT_PATHS_COUNT) - { - RightPanel->GotoHotPath(LOWORD(wParam) - CM_RIGHTHOTPATH_MIN); - return 0; - } - - if (LOWORD(wParam) >= CM_LEFTHISTORYPATH_MIN && LOWORD(wParam) <= CM_LEFTHISTORYPATH_MAX) - { - DirHistory->Execute(LOWORD(wParam) - CM_LEFTHISTORYPATH_MIN + 1, FALSE, LeftPanel, TRUE, FALSE); - return 0; - } - - if (LOWORD(wParam) >= CM_RIGHTHISTORYPATH_MIN && LOWORD(wParam) <= CM_RIGHTHISTORYPATH_MAX) - { - DirHistory->Execute(LOWORD(wParam) - CM_RIGHTHISTORYPATH_MIN + 1, FALSE, RightPanel, TRUE, FALSE); - return 0; - } - - if (LOWORD(wParam) >= CM_ACTIVEMODE_1 && LOWORD(wParam) <= CM_ACTIVEMODE_10) - { - int index = LOWORD(wParam) - CM_ACTIVEMODE_1; - if (activePanel->IsViewTemplateValid(index)) - activePanel->SelectViewTemplate(index, TRUE, FALSE); - return 0; - } - - if (LOWORD(wParam) >= CM_LEFTMODE_1 && LOWORD(wParam) <= CM_LEFTMODE_10) - { - int index = LOWORD(wParam) - CM_LEFTMODE_1; - if (LeftPanel->IsViewTemplateValid(index)) - LeftPanel->SelectViewTemplate(index, TRUE, FALSE); - return 0; - } - - if (LOWORD(wParam) >= CM_RIGHTMODE_1 && LOWORD(wParam) <= CM_RIGHTMODE_10) - { - int index = LOWORD(wParam) - CM_RIGHTMODE_1; - if (RightPanel->IsViewTemplateValid(index)) - RightPanel->SelectViewTemplate(index, TRUE, FALSE); - return 0; - } - - if (LOWORD(wParam) >= CM_ACTIVEHOTPATH_MIN && LOWORD(wParam) < CM_ACTIVEHOTPATH_MIN + HOT_PATHS_COUNT) - { - activePanel->GotoHotPath(LOWORD(wParam) - CM_ACTIVEHOTPATH_MIN); - return 0; - } - - switch (LOWORD(wParam)) - { - case CM_HELP_CONTEXT: - { - OnContextHelp(); - return 0; - } - - /* - case CM_HELP_KEYBOARD: - { - ShellExecute(HWindow, "open", "https://www.taskscape.com/salam_en/features/keyboard.html", NULL, NULL, SW_SHOWNORMAL); - return 0; - } -*/ - case CM_FORUM: - { - ShellExecute(HWindow, "open", "/", NULL, NULL, SW_SHOWNORMAL); - return 0; - } - - case CM_HELP_CONTENTS: - case CM_HELP_SEARCH: - case CM_HELP_INDEX: - case CM_HELP_KEYBOARD: - { - CHtmlHelpCommand command; - DWORD_PTR dwData = 0; - switch (LOWORD(wParam)) - { - case CM_HELP_CONTENTS: - { - OpenHtmlHelp(NULL, HWindow, HHCDisplayTOC, 0, TRUE); // we don't want two message boxes in a row - command = HHCDisplayContext; - dwData = IDH_INTRODUCTION; - break; - } - - case CM_HELP_INDEX: - { - command = HHCDisplayIndex; - break; - } - - case CM_HELP_SEARCH: - { - command = HHCDisplaySearch; - break; - } - - case CM_HELP_KEYBOARD: - { - command = HHCDisplayContext; - dwData = CM_HELP_KEYBOARD; - break; - } - } - - OpenHtmlHelp(NULL, HWindow, command, dwData, FALSE); - - return 0; - } - - case CM_HELP_ABOUT: - { - CAboutDialog dlg(HWindow); - dlg.Execute(); - return 0; - } - - /* - case CM_HELP_TIP: - { - BOOL openQuiet = lParam == 0xffffffff; - if (TipOfTheDayDialog != NULL) - { - TipOfTheDayDialog->IncrementTipIndex(); - TipOfTheDayDialog->InvalidateTipWindow(); - SetForegroundWindow(TipOfTheDayDialog->HWindow); - } - else - { - TipOfTheDayDialog = new CTipOfTheDayDialog(openQuiet); - if (TipOfTheDayDialog != NULL) - { - if (TipOfTheDayDialog->IsGood()) - { - TipOfTheDayDialog->Create(); - } - else - { - delete TipOfTheDayDialog; - TipOfTheDayDialog = NULL; - // the file probably does not exist - next time we won't even try at startup - if (openQuiet) - Configuration.ShowTipOfTheDay = FALSE; - } - } - } - return 0; - } -*/ - case CM_ALWAYSONTOP: - { - if (!Configuration.AlwaysOnTop && Configuration.CnfrmAlwaysOnTop) - { - BOOL dontShow = !Configuration.CnfrmAlwaysOnTop; - - MSGBOXEX_PARAMS params; - memset(¶ms, 0, sizeof(params)); - params.HParent = HWindow; - params.Flags = MSGBOXEX_OKCANCEL | MSGBOXEX_ICONINFORMATION | MSGBOXEX_SILENT | MSGBOXEX_HINT; - params.Caption = LoadStr(IDS_INFOTITLE); - params.Text = LoadStr(IDS_WANTALWAYSONTOP); - params.CheckBoxText = LoadStr(IDS_DONTSHOWAGAINAT); - params.CheckBoxValue = &dontShow; - int ret = SalMessageBoxEx(¶ms); - Configuration.CnfrmAlwaysOnTop = !dontShow; - if (ret == IDCANCEL) - return 0; - } - - Configuration.AlwaysOnTop = !Configuration.AlwaysOnTop; - HMENU h = GetSystemMenu(HWindow, FALSE); - if (h != NULL) - { - CheckMenuItem(h, CM_ALWAYSONTOP, MF_BYCOMMAND | (Configuration.AlwaysOnTop ? MF_CHECKED : MF_UNCHECKED)); - } - - SetWindowPos(HWindow, - Configuration.AlwaysOnTop ? HWND_TOPMOST : HWND_NOTOPMOST, - 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); - - return 0; - } - - case CM_MINIMIZE: - MinimizeApp(MainWindow->HWindow); - return 0; - - case CM_TASKLIST: - { - CTaskListDialog(HWindow).Execute(); - return 0; - } - - case CM_CLIPCOPYFULLNAME: - { - activePanel->UserWorkedOnThisPath = TRUE; - activePanel->CopyFocusedNameToClipboard(cfnmFull); - return 0; - } - - case CM_CLIPCOPYNAME: - { - activePanel->UserWorkedOnThisPath = TRUE; - activePanel->CopyFocusedNameToClipboard(cfnmShort); - return 0; - } - - case CM_CLIPCOPYFULLPATH: - { - activePanel->UserWorkedOnThisPath = TRUE; - activePanel->CopyCurrentPathToClipboard(); - return 0; - } - - case CM_CLIPCOPYUNCNAME: - { - activePanel->UserWorkedOnThisPath = TRUE; - activePanel->CopyFocusedNameToClipboard(cfnmUNC); - return 0; - } - - case CM_OPEN_IN_OTHER_PANEL: - case CM_OPEN_IN_OTHER_PANEL_ACT: - { - activePanel->OpenFocusedInOtherPanel(LOWORD(wParam) == CM_OPEN_IN_OTHER_PANEL_ACT); - return 0; - } - - case CM_PLUGINS: - { - BeginStopRefresh(); // snooper takes a break - - CPluginsDlg dlg(HWindow); - dlg.Execute(); - if (dlg.GetRefreshPanels()) - { - UpdateWindow(HWindow); - - if ((LeftPanel->Is(ptDisk) || LeftPanel->Is(ptZIPArchive)) && - IsUNCPath(LeftPanel->GetPath()) && - LeftPanel->DirectoryLine != NULL) - { - LeftPanel->DirectoryLine->BuildHotTrackItems(); - } - if ((RightPanel->Is(ptDisk) || RightPanel->Is(ptZIPArchive)) && - IsUNCPath(RightPanel->GetPath()) && - RightPanel->DirectoryLine != NULL) - { - RightPanel->DirectoryLine->BuildHotTrackItems(); - } - - HANDLES(EnterCriticalSection(&TimeCounterSection)); - int t1 = MyTimeCounter++; - int t2 = MyTimeCounter++; - HANDLES(LeaveCriticalSection(&TimeCounterSection)); - SendMessage(LeftPanel->HWindow, WM_USER_REFRESH_DIR, 0, t1); - SendMessage(RightPanel->HWindow, WM_USER_REFRESH_DIR, 0, t2); - } - if (dlg.GetRefreshPanels() || // also refresh drive bars because of the Nethood plugin (Network Neighborhood icon appears/disappears) - dlg.GetDrivesBarChange()) // change in visibility of the FS item in the Drive bars - { - PostMessage(HWindow, WM_USER_DRIVES_CHANGE, 0, 0); - } - - const char* focusPlugin = dlg.GetFocusPlugin(); - if (focusPlugin[0] != 0) - { - char newPath[MAX_PATH]; - lstrcpyn(newPath, focusPlugin, MAX_PATH); - const char* newName; - char* p = strrchr(newPath, '\\'); - if (p != NULL) - { - p++; - *p = 0; - newName = focusPlugin + int(p - newPath); - } - else - newName = ""; - SendMessage(GetActivePanel()->HWindow, WM_USER_FOCUSFILE, (WPARAM)newName, (LPARAM)newPath); - } - - EndStopRefresh(); // snooper starts again now - return 0; - } - - case CM_SAVECONFIG: - { - // if an exported configuration already exists, show a warning - if (FileExists(ConfigurationName)) - { - char buff[3000]; - _snprintf_s(buff, _TRUNCATE, LoadStr(IDS_SAVECFG_EXPFILEEXISTS), ConfigurationName); - int ret = SalMessageBox(HWindow, buff, LoadStr(IDS_INFOTITLE), - MB_ICONINFORMATION | MB_OKCANCEL); - if (ret == IDCANCEL) - { - // navigate the user to the correct directory and focus the configuration file to make it easier - char path[MAX_PATH]; - char* s = strrchr(ConfigurationName, '\\'); - if (s != NULL) - { - memcpy(path, ConfigurationName, s - ConfigurationName); - path[s - ConfigurationName] = 0; - SendMessage(activePanel->HWindow, WM_USER_FOCUSFILE, (WPARAM)(s + 1), (LPARAM)path); - } - return 0; - } - } - SaveConfig(); - return 0; - } - - case CM_EXPORTCONFIG: - { - int ret = SalMessageBox(HWindow, LoadStr(IDS_PREDCONFIGEXPORT), - LoadStr(IDS_QUESTION), MB_YESNOCANCEL | MB_ICONQUESTION); - if (ret == IDCANCEL) - return 0; - - if (ret == IDYES) - { - SaveConfig(); - } - - char file[MAX_PATH]; - char defDir[MAX_PATH]; - strcpy(file, "config_.reg"); - - BOOL clearKeyBeforeImport = TRUE; - - MSGBOXEX_PARAMS params; - memset(¶ms, 0, sizeof(params)); - params.HParent = HWindow; - params.Flags = MSGBOXEX_OK | MSGBOXEX_ICONINFORMATION | MSGBOXEX_SILENT; - params.Caption = LoadStr(IDS_INFOTITLE); - params.Text = LoadStr(WindowsVistaAndLater ? IDS_CONFIGEXPVISTA : IDS_CONFIGEXPUPTOXP); - params.CheckBoxText = LoadStr(IDS_CONFIGEXPCLEARKEY); - params.CheckBoxValue = &clearKeyBeforeImport; - SalMessageBoxEx(¶ms); - - if (WindowsVistaAndLater) - { - if (!CreateOurPathInRoamingAPPDATA(defDir, _countof(defDir))) - { - TRACE_E("CM_EXPORTCONFIG: unexpected situation: unable to get our directory under CSIDL_APPDATA"); - return 0; - } - } - else - { - GetModuleFileName(HInstance, defDir, MAX_PATH); - *strrchr(defDir, '\\') = 0; - } - OPENFILENAME ofn; - memset(&ofn, 0, sizeof(OPENFILENAME)); - ofn.lStructSize = sizeof(OPENFILENAME); - ofn.hwndOwner = HWindow; - char* s = LoadStr(IDS_REGFILTER); - ofn.lpstrFilter = s; - while (*s != 0) // create a double-null-terminated list - { - if (*s == '|') - *s = 0; - s++; - } - ofn.nFilterIndex = 1; - ofn.lpstrFile = file; - ofn.nMaxFile = MAX_PATH; - ofn.lpstrInitialDir = defDir; - - ofn.Flags = OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY; - ofn.lpstrDefExt = "reg"; - - if (SafeGetSaveFileName(&ofn)) - { - if (SalGetFullName(file)) - { - // perform the export - if (ExportConfiguration(HWindow, file, clearKeyBeforeImport)) - { - SalMessageBox(HWindow, LoadStr(IDS_CONFIGEXPORTED), LoadStr(IDS_INFOTITLE), - MB_OK | MB_ICONINFORMATION); - } - else - DeleteFileUtf8(file); - } - } - return 0; - } - - case CM_IMPORTCONFIG: - { - SalMessageBox(HWindow, LoadStr(IDS_CONFIGHOWTOIMPORT), LoadStr(IDS_INFOTITLE), - MB_OK | MB_ICONINFORMATION); - return 0; - } - - case CM_SHARES: - { - CSharesDialog dlg(HWindow); - if (dlg.Execute() == IDOK) - { - // user chose Focus - const char* path = dlg.GetFocusedPath(); - if (path != NULL) - { - char newPath[MAX_PATH]; - lstrcpyn(newPath, path, MAX_PATH); - const char* newName; - char* p = strrchr(newPath, '\\'); - if (p != NULL) - { - p++; - *p = 0; - newName = path + int(p - newPath); - } - else - newName = ""; - SendMessage(GetActivePanel()->HWindow, WM_USER_FOCUSFILE, (WPARAM)newName, (LPARAM)newPath); - } - } - break; - } - - case CM_SKILLLEVEL: - { - CSkillLevelDialog dlg(HWindow, &Configuration.SkillLevel); - if (dlg.Execute() == IDOK) - MainMenu.SetSkillLevel(CfgSkillLevelToMenu(Configuration.SkillLevel)); - break; - } - - case CM_CONFIGURATION: - { - PostMessage(HWindow, WM_USER_CONFIGURATION, 0, 0); // standard configuration - break; - } - - case CM_AUTOCONFIG: - { - PostMessage(HWindow, WM_USER_AUTOCONFIG, 0, 0); - break; - } - - case CM_LCUSTOMIZEVIEW: - { - PostMessage(HWindow, WM_USER_CONFIGURATION, 4, LeftPanel->GetViewTemplateIndex()); - return 0; - } - - case CM_RCUSTOMIZEVIEW: - { - PostMessage(HWindow, WM_USER_CONFIGURATION, 4, RightPanel->GetViewTemplateIndex()); - return 0; - } - - case CM_LEFTNAME: - { - LeftPanel->ChangeSortType(stName, TRUE); - return 0; - } - - case CM_LEFTEXT: - { - LeftPanel->ChangeSortType(stExtension, TRUE); - return 0; - } - - case CM_LEFTTIME: - { - LeftPanel->ChangeSortType(stTime, TRUE); - return 0; - } - - case CM_LEFTSIZE: - { - LeftPanel->ChangeSortType(stSize, TRUE); - return 0; - } - - case CM_LEFTATTR: - { - LeftPanel->ChangeSortType(stAttr, TRUE); - return 0; - } - // change sorting in the right panel - case CM_RIGHTNAME: - { - RightPanel->ChangeSortType(stName, TRUE); - return 0; - } - - case CM_RIGHTEXT: - { - RightPanel->ChangeSortType(stExtension, TRUE); - return 0; - } - - case CM_RIGHTTIME: - { - RightPanel->ChangeSortType(stTime, TRUE); - return 0; - } - - case CM_RIGHTSIZE: - { - RightPanel->ChangeSortType(stSize, TRUE); - return 0; - } - - case CM_RIGHTATTR: - { - RightPanel->ChangeSortType(stAttr, TRUE); - return 0; - } - // change sorting in the current panel - case CM_ACTIVENAME: - activePanel->ChangeSortType(stName, TRUE); - return 0; - case CM_ACTIVEEXT: - activePanel->ChangeSortType(stExtension, TRUE); - return 0; - case CM_ACTIVETIME: - activePanel->ChangeSortType(stTime, TRUE); - return 0; - case CM_ACTIVESIZE: - activePanel->ChangeSortType(stSize, TRUE); - return 0; - case CM_ACTIVEATTR: - activePanel->ChangeSortType(stAttr, TRUE); - return 0; - - case CM_SORTOPTIONS: - { - PostMessage(HWindow, WM_USER_CONFIGURATION, 5, 0); - return 0; - } - - // toggle Smart Mode (Ctrl+N) - case CM_ACTIVE_SMARTMODE: - ToggleSmartColumnMode(activePanel); - return 0; - case CM_LEFT_SMARTMODE: - ToggleSmartColumnMode(LeftPanel); - return 0; - case CM_RIGHT_SMARTMODE: - ToggleSmartColumnMode(RightPanel); - return 0; - - // change the current drive in the left panel - case CM_LCHANGEDRIVE: - { - if (activePanel != LeftPanel) - { - ChangePanel(); - if (GetActivePanel() != LeftPanel) - return 0; // the panel cannot be activated - UpdateWindow(HWindow); // render the focus before the menu appears - } - if (LeftPanel->DirectoryLine != NULL) - LeftPanel->DirectoryLine->SetDrivePressed(TRUE); - LeftPanel->ChangeDrive(); - if (LeftPanel->DirectoryLine != NULL) - LeftPanel->DirectoryLine->SetDrivePressed(FALSE); - return 0; - } - // change of the current drive in the right panel - case CM_RCHANGEDRIVE: - { - if (activePanel != RightPanel) - { - ChangePanel(); - if (GetActivePanel() != RightPanel) - return 0; // the panel cannot be activated - UpdateWindow(HWindow); // render the focus before the menu appears - } - if (RightPanel->DirectoryLine != NULL) - RightPanel->DirectoryLine->SetDrivePressed(TRUE); - RightPanel->ChangeDrive(); - if (RightPanel->DirectoryLine != NULL) - RightPanel->DirectoryLine->SetDrivePressed(FALSE); - return 0; - } - // change the file filter - case CM_LCHANGEFILTER: - { - LeftPanel->ChangeFilter(); - return 0; - } - - case CM_RCHANGEFILTER: - { - RightPanel->ChangeFilter(); - return 0; - } - - case CM_CHANGEFILTER: - activePanel->ChangeFilter(); - return 0; - - case CM_ACTIVEPARENTDIR: - { - activePanel->CtrlPageUpOrBackspace(); - return 0; - } - - case CM_LPARENTDIR: - { - LeftPanel->CtrlPageUpOrBackspace(); - return 0; - } - - case CM_RPARENTDIR: - { - RightPanel->CtrlPageUpOrBackspace(); - return 0; - } - - case CM_ACTIVEROOTDIR: - { - activePanel->GotoRoot(); - return 0; - } - - case CM_LROOTDIR: - { - LeftPanel->GotoRoot(); - return 0; - } - - case CM_RROOTDIR: - { - RightPanel->GotoRoot(); - return 0; - } - // enabling/diabling the left panel status line - case CM_LEFTSTATUS: - { - LeftPanel->ToggleStatusLine(); - IdleRefreshStates = TRUE; // on the next Idle, force a check of status variables - return 0; - } - // enabling/disabling the right panel status line - case CM_RIGHTSTATUS: - { - RightPanel->ToggleStatusLine(); - IdleRefreshStates = TRUE; // on the next Idle, force a check of status variables - return 0; - } - // enabling/disabling the left panel directory line - case CM_LEFTDIRLINE: - { - LeftPanel->ToggleDirectoryLine(); - IdleRefreshStates = TRUE; // on the next Idle, force a check of status variables - return 0; - } - // enabling/disabling the right panel directory line - case CM_RIGHTDIRLINE: - { - RightPanel->ToggleDirectoryLine(); - IdleRefreshStates = TRUE; // on the next Idle, force a check of status variables - return 0; - } - - case CM_LEFTHEADER: - { - LeftPanel->ToggleHeaderLine(); - LeftPanel->HeaderLineVisible = !LeftPanel->HeaderLineVisible; - return 0; - } - - case CM_RIGHTHEADER: - { - RightPanel->ToggleHeaderLine(); - RightPanel->HeaderLineVisible = !RightPanel->HeaderLineVisible; - return 0; - } - - case CM_LEFTREFRESH: // refresh the left panel - { - LeftPanel->NextFocusName[0] = 0; - while (SnooperSuspended) - EndSuspendMode(); // safety catch to resume refreshing - while (StopRefresh) - EndStopRefresh(FALSE); // safety catch to resume refreshing - while (StopIconRepaint) - EndStopIconRepaint(FALSE); // safety catch to resume refreshing - HANDLES(EnterCriticalSection(&TimeCounterSection)); - int t1 = MyTimeCounter++; - HANDLES(LeaveCriticalSection(&TimeCounterSection)); - SendMessage(LeftPanel->HWindow, WM_USER_REFRESH_DIR, 0, t1); - RebuildDriveBarsIfNeeded(FALSE, 0, FALSE, 0); // maybe the user refreshed to update the drives list? - return 0; - } - - case CM_RIGHTREFRESH: // refresh the right panel - { - RightPanel->NextFocusName[0] = 0; - while (SnooperSuspended) - EndSuspendMode(); // safety catch to resume refreshing - while (StopRefresh) - EndStopRefresh(FALSE); // safety catch to resume refreshing - while (StopIconRepaint) - EndStopIconRepaint(FALSE); // safety catch to resume refreshing - HANDLES(EnterCriticalSection(&TimeCounterSection)); - int t1 = MyTimeCounter++; - HANDLES(LeaveCriticalSection(&TimeCounterSection)); - SendMessage(RightPanel->HWindow, WM_USER_REFRESH_DIR, 0, t1); - RebuildDriveBarsIfNeeded(FALSE, 0, FALSE, 0); // maybe the user refreshed to update the drives list? - return 0; - } - - case CM_ACTIVEREFRESH: // refresh the right panel - { - activePanel->NextFocusName[0] = 0; - while (SnooperSuspended) - EndSuspendMode(); // safety catch to resume refreshing - while (StopRefresh) - EndStopRefresh(FALSE); // safety catch to resume refreshing - while (StopIconRepaint) - EndStopIconRepaint(FALSE); // safety catch to resume refreshing - HANDLES(EnterCriticalSection(&TimeCounterSection)); - int t1 = MyTimeCounter++; - HANDLES(LeaveCriticalSection(&TimeCounterSection)); - SendMessage(activePanel->HWindow, WM_USER_REFRESH_DIR, 0, t1); - RebuildDriveBarsIfNeeded(FALSE, 0, FALSE, 0); // maybe the user refreshed to update the drives list? - return 0; - } - - case CM_ACTIVEFORWARD: - { - activePanel->PathHistory->Execute(1, TRUE, activePanel); - return 0; - } - - case CM_ACTIVEBACK: - { - activePanel->PathHistory->Execute(2, FALSE, activePanel); - return 0; - } - - case CM_LFORWARD: - { - LeftPanel->PathHistory->Execute(1, TRUE, LeftPanel); - return 0; - } - - case CM_LBACK: - { - LeftPanel->PathHistory->Execute(2, FALSE, LeftPanel); - return 0; - } - - case CM_RFORWARD: - { - RightPanel->PathHistory->Execute(1, TRUE, RightPanel); - return 0; - } - - case CM_RBACK: - { - RightPanel->PathHistory->Execute(2, FALSE, RightPanel); - return 0; - } - - case CM_REFRESHASSOC: // reload associations from the Registry - { - OnAssociationsChangedNotification(TRUE); - return 0; - } - - case CM_EMAILFILES: // emailing files and directories - { - if (!EnablerFilesOnDisk) - return 0; - activePanel->UserWorkedOnThisPath = TRUE; - activePanel->StoreSelection(); // save selection for Restore Selection command - - // if no item is selected, select the focused one and store its name - char temporarySelected[MAX_PATH]; - activePanel->SelectFocusedItemAndGetName(temporarySelected, MAX_PATH); - - activePanel->EmailFiles(); - - // if we selected an item, deselect it again - activePanel->UnselectItemWithName(temporarySelected); - - return 0; - } - - case CM_COPYFILES: // copy files and directories - if (!EnablerFilesCopy) - return 0; - case CM_MOVEFILES: // move/rename files and directories - if (LOWORD(wParam) == CM_MOVEFILES && !EnablerFilesMove) - return 0; - case CM_DELETEFILES: // delete files and directories - if (LOWORD(wParam) == CM_DELETEFILES && !EnablerFilesDelete) - return 0; - case CM_OCCUPIEDSPACE: // calculate occupied disk space - if (LOWORD(wParam) == CM_OCCUPIEDSPACE && !EnablerOccupiedSpace) - return 0; - case CM_CHANGECASE: // change case in names - { - if (LOWORD(wParam) == CM_CHANGECASE && !EnablerFilesOnDisk) - return 0; - activePanel->UserWorkedOnThisPath = TRUE; - activePanel->StoreSelection(); // save selection for Restore Selection command - - // if no item is selected, select the focused one and store its name - char temporarySelected[MAX_PATH]; - activePanel->SelectFocusedItemAndGetName(temporarySelected, MAX_PATH); - - if (activePanel->Is(ptDisk)) // source is disk - all operations go here - { - CActionType type; - switch (LOWORD(wParam)) - { - case CM_COPYFILES: - type = atCopy; - break; - case CM_MOVEFILES: - type = atMove; - break; - case CM_DELETEFILES: - type = atDelete; - break; - case CM_OCCUPIEDSPACE: - type = atCountSize; - break; - case CM_CHANGECASE: - type = atChangeCase; - break; - } - - // perform the action - activePanel->FilesAction(type, GetNonActivePanel()); - } - else - { - if (activePanel->Is(ptZIPArchive)) // source is an archive - all operations go here - { - BOOL archMaybeUpdated; - activePanel->OfferArchiveUpdateIfNeeded(HWindow, IDS_ARCHIVECLOSEEDIT2, &archMaybeUpdated); - if (!archMaybeUpdated) - { - switch (LOWORD(wParam)) - { - case CM_OCCUPIEDSPACE: - activePanel->CalculateOccupiedZIPSpace(); - break; - case CM_COPYFILES: - activePanel->UnpackZIPArchive(GetNonActivePanel()); - break; - case CM_DELETEFILES: - activePanel->DeleteFromZIPArchive(); - break; - } - } - } - else - { - if (activePanel->Is(ptPluginFS)) // source is a FS - all operations go here - { - CPluginFSActionType type; - switch (LOWORD(wParam)) - { - case CM_COPYFILES: - type = fsatCopy; - break; - case CM_MOVEFILES: - type = fsatMove; - break; - case CM_DELETEFILES: - type = fsatDelete; - break; - case CM_OCCUPIEDSPACE: - type = fsatCountSize; - break; - } - activePanel->PluginFSFilesAction(type); - } - } - } - - // if we selected an item temporarily, deselect it again - activePanel->UnselectItemWithName(temporarySelected); - - return 0; - } - - case CM_MENU: - { - MenuBar->EnterMenu(); - return 0; - } - - case CM_DIRMENU: - { - ShellAction(activePanel, saContextMenu, FALSE, FALSE); - return 0; - } - - case CM_CONTEXTMENU: - { // panel type checks are done later in ShellAction - activePanel->UserWorkedOnThisPath = TRUE; - activePanel->StoreSelection(); // save selection for Restore Selection command - ShellAction(activePanel, saContextMenu, TRUE, FALSE); - return 0; - } - - case CM_CALCDIRSIZES: - { - activePanel->UserWorkedOnThisPath = TRUE; - activePanel->CalculateDirSizes(); - return 0; - } - - case CM_RENAMEFILE: - { - activePanel->UserWorkedOnThisPath = TRUE; - activePanel->RenameFile(); - return 0; - } - - case CM_CHANGEATTR: - { - if (EnablerChangeAttrs) - { - activePanel->UserWorkedOnThisPath = TRUE; - activePanel->StoreSelection(); // save selection for Restore Selection command - activePanel->ChangeAttr(); - } - return 0; - } - - case CM_CONVERTFILES: - { - if (activePanel->Is(ptDisk)) - { - activePanel->UserWorkedOnThisPath = TRUE; - activePanel->StoreSelection(); // save selection for Restore Selection command - - // if no item is selected, choose the one under the focus and store its name - char temporarySelected[MAX_PATH]; - activePanel->SelectFocusedItemAndGetName(temporarySelected, MAX_PATH); - - activePanel->Convert(); - - // if we selected an item temporarily, deselect it again - activePanel->UnselectItemWithName(temporarySelected); - } - return 0; - } - - case CM_COMPRESS: - { - if (activePanel->Is(ptDisk)) - { - activePanel->UserWorkedOnThisPath = TRUE; - activePanel->StoreSelection(); // save selection for Restore Selection command - activePanel->ChangeAttr(TRUE, TRUE); - } - return 0; - } - - case CM_UNCOMPRESS: - { - if (activePanel->Is(ptDisk)) - { - activePanel->UserWorkedOnThisPath = TRUE; - activePanel->StoreSelection(); // save selection for Restore Selection command - activePanel->ChangeAttr(TRUE, FALSE); - } - return 0; - } - - case CM_ENCRYPT: - { - if (activePanel->Is(ptDisk)) - { - ExecLogFeatureStart("encrypt", activePanel->GetPath()); - activePanel->UserWorkedOnThisPath = TRUE; - activePanel->StoreSelection(); // save selection for Restore Selection command - activePanel->ChangeAttr(FALSE, FALSE, TRUE, TRUE); - } - return 0; - } - - case CM_DECRYPT: - { - if (activePanel->Is(ptDisk)) - { - ExecLogFeatureStart("decrypt", activePanel->GetPath()); - activePanel->UserWorkedOnThisPath = TRUE; - activePanel->StoreSelection(); // save selection for Restore Selection command - activePanel->ChangeAttr(FALSE, FALSE, TRUE, FALSE); - } - return 0; - } - - case CM_PACK: - { - if (activePanel->Is(ptDisk)) - { - ExecLogFeatureStart("pack", activePanel->GetPath()); - activePanel->UserWorkedOnThisPath = TRUE; - activePanel->StoreSelection(); // save selection for Restore Selection command - activePanel->Pack(GetNonActivePanel()); - } - return 0; - } - - case CM_UNPACK: - { - if (activePanel->Is(ptDisk)) - { - ExecLogFeatureStart("unpack", activePanel->GetPath()); - activePanel->UserWorkedOnThisPath = TRUE; - activePanel->StoreSelection(); // save selection for Restore Selection command - activePanel->Unpack(GetNonActivePanel()); - } - return 0; - } - - case CM_AFOCUSSHORTCUT: - { - if (EnablerFileOrDirLinkOnDisk) // enabler for activePanel - { - // activePanel->UserWorkedOnThisPath = TRUE; // it's just navigation, don't mark the path dirty - activePanel->FocusShortcutTarget(activePanel); - } - return 0; - } - - case CM_PROPERTIES: - { - if (EnablerShowProperties) - { - ExecLogFeatureStart("properties", activePanel->GetPath()); - activePanel->UserWorkedOnThisPath = TRUE; - activePanel->StoreSelection(); // save selection for Restore Selection command - ShellAction(activePanel, saProperties, TRUE, FALSE); - } - return 0; - } - - case CM_OPEN: - { - ExecLogFeatureStart("open", activePanel->GetPath()); - activePanel->UserWorkedOnThisPath = TRUE; - activePanel->CtrlPageDnOrEnter(VK_RETURN); - return 0; - } - - case CM_VIEW: - { - ExecLogFeatureStart("view", activePanel->GetPath()); - activePanel->UserWorkedOnThisPath = TRUE; - activePanel->ViewFile(NULL, FALSE, 0xFFFFFFFF, activePanel->Is(ptDisk) ? activePanel->EnumFileNamesSourceUID : -1, -1); - return 0; - } - - case CM_ALTVIEW: - { - ExecLogFeatureStart("alt view", activePanel->GetPath()); - activePanel->UserWorkedOnThisPath = TRUE; - activePanel->ViewFile(NULL, TRUE, 0xFFFFFFFF, activePanel->Is(ptDisk) ? activePanel->EnumFileNamesSourceUID : -1, -1); - return 0; - } - - case CM_VIEW_WITH: - { - POINT menuPos; - ExecLogFeatureStart("view with", activePanel->GetPath()); - activePanel->UserWorkedOnThisPath = TRUE; - activePanel->GetContextMenuPos(&menuPos); - activePanel->ViewFileWith(NULL, HWindow, &menuPos, NULL, - activePanel->Is(ptDisk) ? activePanel->EnumFileNamesSourceUID : -1, -1); - return 0; - } - - case CM_EDIT: - { - if (EnablerFileOnDiskOrArchive) - { - ExecLogFeatureStart("edit", activePanel->GetPath()); - activePanel->UserWorkedOnThisPath = TRUE; - if (activePanel->Is(ptZIPArchive)) - { - int index = activePanel->GetCaretIndex(); - if (index >= activePanel->Dirs->Count && - index < activePanel->Dirs->Count + activePanel->Files->Count) - { - activePanel->ExecuteFromArchive(index, TRUE); - } - } - else - activePanel->EditFile(NULL); - } - return 0; - } - - case CM_EDITNEW: - { - if (activePanel->Is(ptDisk)) - { - ExecLogFeatureStart("edit new", activePanel->GetPath()); - activePanel->UserWorkedOnThisPath = TRUE; - activePanel->EditNewFile(); - } - return 0; - } - - case CM_EDIT_WITH: - { - activePanel->UserWorkedOnThisPath = TRUE; - POINT menuPos; - ExecLogFeatureStart("edit with", activePanel->GetPath()); - activePanel->GetContextMenuPos(&menuPos); - if (activePanel->Is(ptDisk)) - { - activePanel->EditFileWith(NULL, HWindow, &menuPos); - } - else - { - if (activePanel->Is(ptZIPArchive)) - { - int index = activePanel->GetCaretIndex(); - if (index >= activePanel->Dirs->Count && - index < activePanel->Dirs->Count + activePanel->Files->Count) - { - activePanel->ExecuteFromArchive(index, TRUE, HWindow, &menuPos); - } - } - } - return 0; - } - - case CM_FINDFILE: - { - ExecLogFeatureStart("find file", activePanel->GetPath()); - if (activePanel->Is(ptDisk)) // does Find relate to the current path? (archives and FS not yet) - { - activePanel->UserWorkedOnThisPath = TRUE; - } - - activePanel->FindFile(); - return 0; - } - - case CM_DRIVEINFO: - { - activePanel->DriveInfo(); - return 0; - } - - case CM_CREATEDIR: - { - ExecLogFeatureStart("create dir", activePanel->GetPath()); - activePanel->UserWorkedOnThisPath = TRUE; - activePanel->CreateDir(GetNonActivePanel()); - return 0; - } - - case CM_ACTIVE_CHANGEDIR: - { - ExecLogFeatureStart("change dir", activePanel->GetPath()); - activePanel->ChangeDir(); - return 0; - } - - case CM_LEFT_CHANGEDIR: - { - ExecLogFeatureStart("change dir", LeftPanel->GetPath()); - LeftPanel->ChangeDir(); - return 0; - } - - case CM_RIGHT_CHANGEDIR: - { - ExecLogFeatureStart("change dir", RightPanel->GetPath()); - RightPanel->ChangeDir(); - return 0; - } - - case CM_ACTIVE_AS_OTHER: - { - ExecLogFeatureStart("sync path", "active to other"); - activePanel->ChangePathToOtherPanelPath(); - return 0; - } - - case CM_LEFT_AS_OTHER: - { - ExecLogFeatureStart("sync path", "left to other"); - LeftPanel->ChangePathToOtherPanelPath(); - return 0; - } - - case CM_RIGHT_AS_OTHER: - { - ExecLogFeatureStart("sync path", "right to other"); - RightPanel->ChangePathToOtherPanelPath(); - return 0; - } - - case CM_ACTIVESELECTALL: - { - activePanel->SelectUnselect(TRUE, TRUE, FALSE); - return 0; - } - - case CM_ACTIVEUNSELECTALL: - { - activePanel->SelectUnselect(TRUE, FALSE, FALSE); - return 0; - } - - case CM_ACTIVESELECT: - { - activePanel->SelectUnselect(FALSE, TRUE, TRUE); - return 0; - } - - case CM_ACTIVEUNSELECT: - { - activePanel->SelectUnselect(FALSE, FALSE, TRUE); - return 0; - } - - case CM_ACTIVEINVERTSEL: - { - activePanel->InvertSelection(FALSE); - return 0; - } - - case CM_ACTIVEINVERTSELALL: - { - activePanel->InvertSelection(TRUE); - return 0; - } - - case CM_RESELECT: - { - activePanel->Reselect(); - return 0; - } - - case CM_SELECTBYFOCUSEDNAME: - { - activePanel->SelectUnselectByFocusedItem(TRUE, TRUE); - return 0; - } - - case CM_UNSELECTBYFOCUSEDNAME: - { - activePanel->SelectUnselectByFocusedItem(FALSE, TRUE); - return 0; - } - - case CM_SELECTBYFOCUSEDEXT: - { - activePanel->SelectUnselectByFocusedItem(TRUE, FALSE); - return 0; - } - - case CM_UNSELECTBYFOCUSEDEXT: - { - activePanel->SelectUnselectByFocusedItem(FALSE, FALSE); - return 0; - } - - case CM_HIDE_SELECTED_NAMES: - { - activePanel->ShowHideNames(1); // hide selected - return 0; - } - - case CM_HIDE_UNSELECTED_NAMES: - { - activePanel->ShowHideNames(2); // hide unselected - return 0; - } - - case CM_SHOW_ALL_NAME: - { - activePanel->ShowHideNames(0); // show all - return 0; - } - - case CM_STORESEL: - { - activePanel->StoreGlobalSelection(); - return 0; - } - - case CM_RESTORESEL: - { - activePanel->RestoreGlobalSelection(); - return 0; - } - - case CM_GOTO_PREV_SEL: - case CM_GOTO_NEXT_SEL: - { - activePanel->GotoSelectedItem(LOWORD(wParam) == CM_GOTO_NEXT_SEL); - return 0; - } - - case CM_COMPAREDIRS: - { - // currently we support only ptDisk<->ptDisk, ptDisk<->ptZIPArchive and ptZIPArchive<->ptZIPArchive - //if (LeftPanel->Is(ptPluginFS) || RightPanel->Is(ptPluginFS)) - //{ - // SalMessageBox(HWindow, LoadStr(IDS_COMPARE_FS), LoadStr(IDS_COMPAREDIRSTITLE), MB_OK | MB_ICONINFORMATION); - // return 0; - //} - - // if both panels point to the same path, exit - char leftPath[2 * MAX_PATH]; - char rightPath[2 * MAX_PATH]; - LeftPanel->GetGeneralPath(leftPath, 2 * MAX_PATH); - RightPanel->GetGeneralPath(rightPath, 2 * MAX_PATH); - char compareDetail[4 * MAX_PATH]; - _snprintf_s(compareDetail, _TRUNCATE, "left=%s, right=%s", leftPath, rightPath); - ExecLogFeatureStart("compare dirs", compareDetail); - if (strcmp(leftPath, rightPath) == 0) // case sensitive; if this condition fails, it's fine - { - ExecLogFeatureResult("compare dirs", compareDetail, FALSE); - SalMessageBox(HWindow, LoadStr(IDS_COMPARE_SAMEPATH), LoadStr(IDS_COMPAREDIRSTITLE), MB_OK | MB_ICONINFORMATION); - return 0; - } - - BOOL enableByDateAndTime = (LeftPanel->ValidFileData & (VALID_DATA_DATE | VALID_DATA_PL_DATE)) && - (RightPanel->ValidFileData & (VALID_DATA_DATE | VALID_DATA_PL_DATE)); - BOOL enableBySize = (LeftPanel->ValidFileData & (VALID_DATA_SIZE | VALID_DATA_PL_SIZE)) && - (RightPanel->ValidFileData & (VALID_DATA_SIZE | VALID_DATA_PL_SIZE)); - BOOL enableByAttrs = (LeftPanel->ValidFileData & VALID_DATA_ATTRIBUTES) && - (RightPanel->ValidFileData & VALID_DATA_ATTRIBUTES); - BOOL enableByContent = LeftPanel->Is(ptDisk) && RightPanel->Is(ptDisk); - BOOL enableSubdirs = !LeftPanel->Is(ptPluginFS) && !RightPanel->Is(ptPluginFS); - BOOL enableCompAttrsOfSubdirs = enableSubdirs && enableByAttrs; - CCompareDirsDialog dlg(HWindow, enableByDateAndTime, enableBySize, enableByAttrs, - enableByContent, enableSubdirs, enableCompAttrsOfSubdirs, - LeftPanel, RightPanel); - if (dlg.Execute() == IDOK) - { - activePanel->UserWorkedOnThisPath = TRUE; - DWORD flags = 0; - if (enableByDateAndTime && Configuration.CompareByTime) - flags |= COMPARE_DIRECTORIES_BYTIME; - if (enableBySize && Configuration.CompareBySize) - flags |= COMPARE_DIRECTORIES_BYSIZE; - if (enableByContent && Configuration.CompareByContent) - flags |= COMPARE_DIRECTORIES_BYCONTENT; - if (enableByAttrs && Configuration.CompareByAttr) - flags |= COMPARE_DIRECTORIES_BYATTR; - if (enableSubdirs && Configuration.CompareSubdirs) - flags |= COMPARE_DIRECTORIES_SUBDIRS; - else - { - if (Configuration.CompareOnePanelDirs) - { - flags |= COMPARE_DIRECTORIES_ONEPANELDIRS; - Configuration.CompareSubdirs = FALSE; // handles case when CompareSubdirs is enabled and a compare is run for FS and the user toggles CompareOnePanelDirs - without this line, on the next open of the disk dialog, CompareSubdirs would take precedence over CompareOnePanelDirs, which isn’t quite right... - } - } - if (enableCompAttrsOfSubdirs && Configuration.CompareSubdirsAttr) - flags |= COMPARE_DIRECTORIES_SUBDIRS_ATTR; - if (Configuration.CompareIgnoreFiles) - flags |= COMPARE_DIRECTORIES_IGNFILENAMES; - if ((enableSubdirs && Configuration.CompareSubdirs || Configuration.CompareOnePanelDirs) && - Configuration.CompareIgnoreDirs) - flags |= COMPARE_DIRECTORIES_IGNDIRNAMES; - CompareDirectories(flags); - ExecLogFeatureResult("compare dirs", compareDetail, TRUE); - } - else - { - ExecLogFeatureResult("compare dirs", compareDetail, FALSE); - } - return 0; - } - - case CM_EXIT: - { - PostMessage(HWindow, WM_USER_CLOSE_MAINWND, 0, 0); - return 0; - } - - case CM_CONNECTNET: - { - activePanel->ConnectNet(FALSE); - return 0; - } - - case CM_DISCONNECTNET: - { - activePanel->DisconnectNet(); - return 0; - } - - case CM_FILEHISTORY: - { - if (!FileHistory->HasItem()) - return 0; - MainWindow->CancelPanelsUI(); // cancel QuickSearch and QuickEdit - - BeginStopRefresh(); // snooper takes a break - - RECT r; - GetWindowRect(HWindow, &r); - int x = r.left + (r.right - r.left) / 2; - int y = r.top + (r.bottom - r.top) / 2; - - CMenuPopup menu; - FileHistory->FillPopupMenu(&menu); - DWORD cmd = menu.Track(MENU_TRACK_RETURNCMD | MENU_TRACK_CENTERALIGN | MENU_TRACK_VCENTERALIGN, - x, y, HWindow, NULL); - if (cmd != 0) - FileHistory->Execute(cmd); - - EndStopRefresh(); // snooper starts again now - - return 0; - } - - case CM_DIRHISTORY: - { - activePanel->OpenDirHistory(); - return 0; - } - - case CM_USERMENU: - { - if (activePanel->Is(ptDisk)) - { - BeginStopRefresh(); // no refreshes needed - - MainWindow->CancelPanelsUI(); // cancel QuickSearch and QuickEdit - - UserMenuIconBkgndReader.BeginUserMenuIconsInUse(); - CMenuPopup menu; - FillUserMenu(&menu); - POINT p; - activePanel->GetContextMenuPos(&p); - // another lock/unlock cycle (BeginUserMenuIconsInUse + EndUserMenuIconsInUse) will occur - // in WM_USER_ENTERMENULOOP + WM_USER_LEAVEMENULOOP, but it is nested and lightweight, - // so we ignore it and do not fight it - menu.Track(0, p.x, p.y, HWindow, NULL); - UserMenuIconBkgndReader.EndUserMenuIconsInUse(); - - EndStopRefresh(); - } - return 0; - } - - case CM_OPENHOTPATHS: - { - BeginStopRefresh(); // no refreshes needed - - MainWindow->CancelPanelsUI(); // cancel QuickSearch and QuickEdit - - RECT r; - GetWindowRect(GetActivePanelHWND(), &r); - int dirHeight = GetDirectoryLineHeight(); - - CMenuPopup menu; - HotPaths.FillHotPathsMenu(&menu, CM_ACTIVEHOTPATH_MIN); - menu.Track(0, r.left, r.top + dirHeight, HWindow, NULL); - - EndStopRefresh(); - return 0; - } - - case CM_CUSTOMIZE_HOTPATHS: - { - PostMessage(HWindow, WM_USER_CONFIGURATION, 1, -1); - return 0; - } - - case CM_CUSTOMIZE_USERMENU: - { - PostMessage(HWindow, WM_USER_CONFIGURATION, 2, 0); - return 0; - } - - case CM_EDITLINE: - { - if (SystemPolicies.GetNoRun()) - { - MSGBOXEX_PARAMS params; - memset(¶ms, 0, sizeof(params)); - params.HParent = HWindow; - params.Flags = MSGBOXEX_OK | MSGBOXEX_HELP | MSGBOXEX_ICONEXCLAMATION; - params.Caption = LoadStr(IDS_POLICIESRESTRICTION_TITLE); - params.Text = LoadStr(IDS_POLICIESRESTRICTION); - params.ContextHelpId = IDH_GROUPPOLICY; - params.HelpCallback = MessageBoxHelpCallback; - SalMessageBoxEx(¶ms); - return 0; - } - if (EditWindow->HWindow != NULL) - { - if (EditWindow->IsEnabled()) - SetFocus(EditWindow->HWindow); - } - else - { - if (EditPermanentVisible || EditWindow->IsEnabled()) // there may be an archive in the panel - ShowCommandLine(); - } - return 0; - } - - case CM_TOGGLEEDITLINE: - { - if (SystemPolicies.GetNoRun()) - { - MSGBOXEX_PARAMS params; - memset(¶ms, 0, sizeof(params)); - params.HParent = HWindow; - params.Flags = MSGBOXEX_OK | MSGBOXEX_HELP | MSGBOXEX_ICONEXCLAMATION; - params.Caption = LoadStr(IDS_POLICIESRESTRICTION_TITLE); - params.Text = LoadStr(IDS_POLICIESRESTRICTION); - params.ContextHelpId = IDH_GROUPPOLICY; - params.HelpCallback = MessageBoxHelpCallback; - SalMessageBoxEx(¶ms); - return 0; - } - EditPermanentVisible = !EditPermanentVisible; - if (EditWindow->HWindow != NULL && !EditPermanentVisible) - HideCommandLine(); - else if (EditWindow->HWindow == NULL) - { - if (EditPermanentVisible) - { - ShowCommandLine(); - if (lParam == 0) - SetFocus(EditWindow->HWindow); - } - } - return 0; - } - - case CM_TOGGLETOPTOOLBAR: - { - ToggleTopToolBar(); - // LayoutWindows(); - break; - } - - case CM_TOGGLEPLUGINSBAR: - { - TogglePluginsBar(); - break; - } - - case CM_TOGGLEMIDDLETOOLBAR: - { - ToggleMiddleToolBar(); - InvalidateRect(HWindow, NULL, FALSE); - LayoutWindows(); - break; - } - - case CM_TOGGLEUSERMENUTOOLBAR: - { - ToggleUserMenuToolBar(); - IdleRefreshStates = TRUE; // on the next Idle, force a check of status variables - // LayoutWindows(); - break; - } - - case CM_TOGGLEHOTPATHSBAR: - { - ToggleHotPathsBar(); - IdleRefreshStates = TRUE; // on the next Idle, force a check of status variables - // LayoutWindows(); - break; - } - - case CM_TOGGLEDRIVEBAR: - case CM_TOGGLEDRIVEBAR2: - { - ToggleDriveBar(LOWORD(wParam) == CM_TOGGLEDRIVEBAR2); - // LayoutWindows(); - break; - } - - case CM_TOGGLEBOTTOMTOOLBAR: - { - ToggleBottomToolBar(); - IdleRefreshStates = TRUE; // on the next Idle, force a check of status variables - LayoutWindows(); - break; - } - - case CM_TOGGLE_UMLABELS: - { - UMToolBar->ToggleLabels(); - break; - } - - // case CM_TOGGLE_HPLABELS: - // { - // HPToolBar->ToggleLabels(); - // break; - // } - - case CM_TOGGLE_GRIPS: - { - ToggleToolBarGrips(); - break; - } - - case CM_CUSTOMIZETOP: - { - if (TopToolBar->HWindow == NULL) - { - ToggleTopToolBar(); - IdleRefreshStates = TRUE; // on the next Idle, force a check of status variables - LayoutWindows(); - } - TopToolBar->Customize(); - break; - } - - case CM_CUSTOMIZEPLUGINS: - { - if (PluginsBar->HWindow == NULL) - { - TogglePluginsBar(); - LayoutWindows(); - } - // let the Plugins Manager open - PostMessage(MainWindow->HWindow, WM_COMMAND, CM_PLUGINS, 0); - break; - } - - case CM_CUSTOMIZEMIDDLE: - { - if (MiddleToolBar->HWindow == NULL) - { - ToggleMiddleToolBar(); - IdleRefreshStates = TRUE; // on the next Idle, force a check of status variables - LayoutWindows(); - } - MiddleToolBar->Customize(); - break; - } - - case CM_CUSTOMIZEUM: - { - if (UMToolBar->HWindow == NULL) - { - ToggleUserMenuToolBar(); - IdleRefreshStates = TRUE; // on the next Idle, force a check of status variables - LayoutWindows(); - } - // expand the UserMenu page and edit the item at the given index - PostMessage(HWindow, WM_USER_CONFIGURATION, 2, 0); - break; - } - - case CM_CUSTOMIZEHP: - { - if (HPToolBar->HWindow == NULL) - { - ToggleHotPathsBar(); - IdleRefreshStates = TRUE; // on the next Idle, force a check of status variables - LayoutWindows(); - } - // let the HotPaths page expand - PostMessage(HWindow, WM_USER_CONFIGURATION, 1, -1); - break; - } - - case CM_CUSTOMIZELEFT: - { - if (LeftPanel->DirectoryLine->HWindow == NULL) - LeftPanel->ToggleDirectoryLine(); - if (LeftPanel->DirectoryLine->ToolBar != NULL) - LeftPanel->DirectoryLine->ToolBar->Customize(); - break; - } - - case CM_CUSTOMIZERIGHT: - { - if (RightPanel->DirectoryLine->HWindow == NULL) - RightPanel->ToggleDirectoryLine(); - if (RightPanel->DirectoryLine->ToolBar != NULL) - RightPanel->DirectoryLine->ToolBar->Customize(); - break; - } - - case CM_DOSSHELL: - { - activePanel->UserWorkedOnThisPath = TRUE; - - char cmd[MAX_PATH]; - if (!GetEnvironmentVariable("COMSPEC", cmd, MAX_PATH)) - cmd[0] = 0; - - if (SystemPolicies.GetNoRun() || - (SystemPolicies.GetMyRunRestricted() && !SystemPolicies.GetMyCanRun(cmd))) - { - MSGBOXEX_PARAMS params; - memset(¶ms, 0, sizeof(params)); - params.HParent = HWindow; - params.Flags = MSGBOXEX_OK | MSGBOXEX_HELP | MSGBOXEX_ICONEXCLAMATION; - params.Caption = LoadStr(IDS_POLICIESRESTRICTION_TITLE); - params.Text = LoadStr(IDS_POLICIESRESTRICTION); - params.ContextHelpId = IDH_GROUPPOLICY; - params.HelpCallback = MessageBoxHelpCallback; - SalMessageBoxEx(¶ms); - return 0; - } - - AddDoubleQuotesIfNeeded(cmd, MAX_PATH); // CreateProcess requires the name with spaces in quotes (otherwise it tries various options; see help) - ExecLogFeatureStart("command shell", cmd); - - SetDefaultDirectories(); - - STARTUPINFO si; - memset(&si, 0, sizeof(STARTUPINFO)); - si.cb = sizeof(STARTUPINFO); - si.lpTitle = LoadStr(IDS_COMMANDSHELL); - // There is an undocumented flag 0x400 where we can pass the monitor handle into si.hStdOutput - // Unfortunately it works with SOL.EXE but not with CMD.EXE, so we use the old method - // with a dummy window - // On W2K the flag appears as #define STARTF_HASHMONITOR 0x00000400 // same as HASSHELLDATA - // STARTF_MONITOR was mentioned online in an article about undocumented features - si.dwFlags = STARTF_USESHOWWINDOW; - POINT p; - if (MultiMonGetDefaultWindowPos(MainWindow->HWindow, &p)) - { - // if the main window is on another monitor we should open - // the new window there as well, preferably at the default position (same as on the primary) - si.dwFlags |= STARTF_USEPOSITION; - si.dwX = p.x; - si.dwY = p.y; - // TRACE_I("MultiMonGetDefaultWindowPos(): x = " << p.x << ", y = " << p.y); - } - si.wShowWindow = SW_SHOWNORMAL; - - PROCESS_INFORMATION pi; - - BOOL createProcessOk = HANDLES(CreateProcess(NULL, cmd, NULL, NULL, FALSE, - CREATE_DEFAULT_ERROR_MODE | NORMAL_PRIORITY_CLASS, NULL, - (activePanel->Is(ptDisk) || activePanel->Is(ptZIPArchive)) ? activePanel->GetPath() : NULL, &si, &pi)); - ExecLogFeatureResult("command shell", cmd, createProcessOk); - if (!createProcessOk) - { - DWORD err = GetLastError(); - SalMessageBox(HWindow, GetErrorText(err), - LoadStr(IDS_ERROREXECPROMPT), MB_OK | MB_ICONEXCLAMATION); - } - else - { - HANDLES(CloseHandle(pi.hProcess)); - HANDLES(CloseHandle(pi.hThread)); - } - - return 0; - } - - case CM_FILELIST: - { - ExecLogFeatureStart("file list", activePanel->GetPath()); - activePanel->UserWorkedOnThisPath = TRUE; - activePanel->StoreSelection(); // save selection for Restore Selection command - MakeFileList(); - return 0; - } - - case CM_OPENACTUALFOLDER: - { - ExecLogFeatureStart("open active folder", activePanel->GetPath()); - activePanel->OpenActiveFolder(); - return 0; - } - - case CM_SWAPPANELS: - { - // swap panels - CFilesWindow* swap = LeftPanel; - LeftPanel = RightPanel; - RightPanel = swap; - // swap toolbar records - char buff[1024]; - lstrcpy(buff, Configuration.LeftToolBar); - lstrcpy(Configuration.LeftToolBar, Configuration.RightToolBar); - lstrcpy(Configuration.RightToolBar, buff); - // set panel variables and load the toolbars - LeftPanel->DirectoryLine->SetLeftPanel(TRUE); - RightPanel->DirectoryLine->SetLeftPanel(FALSE); - // the icon must be changed in the image list - LeftPanel->UpdateDriveIcon(FALSE); - RightPanel->UpdateDriveIcon(FALSE); - - // if the active panel was ZOOMed, after Ctrl+U, the minimized panel would remain active - if (GetActivePanel() == LeftPanel && IsPanelZoomed(FALSE) || - GetActivePanel() == RightPanel && IsPanelZoomed(TRUE)) - { - // so activate the visible one - ChangePanel(TRUE); - } - - LockWindowUpdate(HWindow); - LayoutWindows(); - LockWindowUpdate(NULL); - - // reload columns again (column widths are not swapped) - LeftPanel->SelectViewTemplate(LeftPanel->GetViewTemplateIndex(), TRUE, FALSE); - RightPanel->SelectViewTemplate(RightPanel->GetViewTemplateIndex(), TRUE, FALSE); - - // distribute this news among plugins as well - Plugins.Event(PLUGINEVENT_PANELSSWAPPED, 0); - - return 0; - } - - case CM_OPENRECYCLEBIN: - { - OpenSpecFolder(HWindow, CSIDL_BITBUCKET); - return 0; - } - - case CM_OPENCONROLPANEL: - { - OpenSpecFolder(HWindow, CSIDL_CONTROLS); - return 0; - } - - case CM_OPENDESKTOP: - { - OpenSpecFolder(HWindow, CSIDL_DESKTOP); - return 0; - } - - case CM_OPENMYCOMP: - { - OpenSpecFolder(HWindow, CSIDL_DRIVES); - return 0; - } - - case CM_OPENFONTS: - { - OpenSpecFolder(HWindow, CSIDL_FONTS); - return 0; - } - - case CM_OPENNETNEIGHBOR: - { - OpenSpecFolder(HWindow, CSIDL_NETWORK); - return 0; - } - - case CM_OPENPRINTERS: - { - OpenSpecFolder(HWindow, CSIDL_PRINTERS); - return 0; - } - - case CM_OPENDESKTOPDIR: - { - OpenSpecFolder(HWindow, CSIDL_DESKTOPDIRECTORY); - return 0; - } - - case CM_OPENPERSONAL: - { - OpenSpecFolder(HWindow, CSIDL_PERSONAL); - return 0; - } - - case CM_OPENPROGRAMS: - { - OpenSpecFolder(HWindow, CSIDL_PROGRAMS); - return 0; - } - - case CM_OPENRECENT: - { - OpenSpecFolder(HWindow, CSIDL_RECENT); - return 0; - } - - case CM_OPENSENDTO: - { - OpenSpecFolder(HWindow, CSIDL_SENDTO); - return 0; - } - - case CM_OPENSTARTMENU: - { - OpenSpecFolder(HWindow, CSIDL_STARTMENU); - return 0; - } - - case CM_OPENSTARTUP: - { - OpenSpecFolder(HWindow, CSIDL_STARTUP); - return 0; - } - - case CM_OPENTEMPLATES: - { - OpenSpecFolder(HWindow, CSIDL_TEMPLATES); - return 0; - } - - case CM_CLIPCOPY: - { - if (activePanel->Is(ptDisk) || activePanel->Is(ptZIPArchive)) - { - activePanel->UserWorkedOnThisPath = TRUE; - activePanel->StoreSelection(); // save selection for Restore Selection command - activePanel->ClipboardCopy(); - } - return 0; - } - - case CM_CLIPCUT: - { - if (activePanel->Is(ptDisk)) - { - activePanel->UserWorkedOnThisPath = TRUE; - activePanel->StoreSelection(); // save selection for Restore Selection command - activePanel->ClipboardCut(); - } - return 0; - } - - case CM_CLIPPASTE: - { - activePanel->UserWorkedOnThisPath = TRUE; - if (!activePanel->Is(ptDisk) || !activePanel->ClipboardPaste()) // attempt to paste files to disk - { - if (!activePanel->Is(ptZIPArchive) && !activePanel->Is(ptPluginFS) || - !activePanel->ClipboardPasteToArcOrFS(FALSE, NULL)) // attempt to paste files into an archive or the file system - { - activePanel->ClipboardPastePath(); // or change the current path - } - } - return 0; - } - - case CM_CLIPPASTELINKS: - { - if (activePanel->Is(ptDisk)) - { - activePanel->UserWorkedOnThisPath = TRUE; - activePanel->ClipboardPasteLinks(); - } - return 0; - } - - case CM_TOGGLEELASTICSMART: - { - ToggleSmartColumnMode(activePanel); - return 0; - } - - case CM_TOGGLEHIDDENFILES: - { - Configuration.NotHiddenSystemFiles = !Configuration.NotHiddenSystemFiles; - HANDLES(EnterCriticalSection(&TimeCounterSection)); - int t1 = MyTimeCounter++; - int t2 = MyTimeCounter++; - HANDLES(LeaveCriticalSection(&TimeCounterSection)); - SendMessage(LeftPanel->HWindow, WM_USER_REFRESH_DIR, 0, t1); - SendMessage(RightPanel->HWindow, WM_USER_REFRESH_DIR, 0, t2); - - // distribute this news among plug-ins as well - Plugins.Event(PLUGINEVENT_CONFIGURATIONCHANGED, 0); - return 0; - } - - case CM_SEC_PERMISSIONS: - { - if (EnablerPermissions) - { - activePanel->UserWorkedOnThisPath = TRUE; - activePanel->StoreSelection(); // save selection for Restore Selection command - ShellAction(activePanel, saPermissions, TRUE, FALSE); - } - return 0; - } - - case CM_ACTIVEZOOMPANEL: - case CM_LEFTZOOMPANEL: - case CM_RIGHTZOOMPANEL: - { - if (IsPanelZoomed(TRUE) || IsPanelZoomed(FALSE)) - { - SplitPosition = BeforeZoomSplitPosition; - // better protect ourselves against a bad value in BeforeZoomSplitPosition - if (IsPanelZoomed(TRUE) || IsPanelZoomed(FALSE)) - SplitPosition = 0.5; - } - else - { - BeforeZoomSplitPosition = SplitPosition; - if (LOWORD(wParam) == CM_ACTIVEZOOMPANEL) - { - if (activePanel == LeftPanel) - SplitPosition = 1.0; - else - SplitPosition = 0.0; - } - else - { - if (LOWORD(wParam) == CM_LEFTZOOMPANEL) - SplitPosition = 1.0; - else - SplitPosition = 0.0; - } - } - LayoutWindows(); - FocusPanel(GetActivePanel()); - return 0; - } - - case CM_FULLSCREEN: - { - if (IsZoomed(HWindow)) - ShowWindow(HWindow, SW_RESTORE); - else - ShowWindow(HWindow, SW_MAXIMIZE); - return 0; - } - } - break; - } - - case WM_USER_DISPACHCHANGENOTIF: - { - if (LastDispachChangeNotifTime < lParam) // not an outdated message - { - if (AlreadyInPlugin || StopRefresh > 0) - NeedToResentDispachChangeNotif = TRUE; - else - { - char path[MAX_PATH]; - BOOL includingSubdirs; - BOOL ok = TRUE; - while (1) - { - HANDLES(EnterCriticalSection(&DispachChangeNotifCS)); - if (ChangeNotifArray.Count > 0) - { - CChangeNotifData* item = &ChangeNotifArray[ChangeNotifArray.Count - 1]; - strcpy(path, item->Path); - includingSubdirs = item->IncludingSubdirs; - ChangeNotifArray.Delete(ChangeNotifArray.Count - 1); - if (!ChangeNotifArray.IsGood()) - { - ChangeNotifArray.ResetState(); - ChangeNotifArray.DestroyMembers(); - ChangeNotifArray.ResetState(); - ok = FALSE; - } - } - else - ok = FALSE; - if (!ok) // store the time of the last refresh (still in the critical section) - { - HANDLES(EnterCriticalSection(&TimeCounterSection)); - LastDispachChangeNotifTime = MyTimeCounter++; - HANDLES(LeaveCriticalSection(&TimeCounterSection)); - } - HANDLES(LeaveCriticalSection(&DispachChangeNotifCS)); - - if (ok) // distribute a notification about the change on 'path' with 'includingSubdirs' - { - // send the message to all loaded plugins - Plugins.AcceptChangeOnPathNotification(path, includingSubdirs); - - if (GetNonActivePanel() != NULL) // non-active panel first (due to timestamps of subdirectory changes on NTFS) - { - GetNonActivePanel()->AcceptChangeOnPathNotification(path, includingSubdirs); - } - if (GetActivePanel() != NULL) // then the active panel - { - GetActivePanel()->AcceptChangeOnPathNotification(path, includingSubdirs); - } - - if (DetachedFSList->Count > 0) - { - // for better input/output optimization with plugins, the EnterPlugin/LeavePlugin section - // is exported here (not inside the interface encapsulation) - EnterPlugin(); - int i; - for (i = 0; i < DetachedFSList->Count; i++) - { - CPluginFSInterfaceEncapsulation* fs = DetachedFSList->At(i); - fs->AcceptChangeOnPathNotification(fs->GetPluginFSName(), path, includingSubdirs); - } - LeavePlugin(); - } - } - else - break; // end of loop - } - } - } - return 0; - } - - case WM_USER_DISPACHCFGCHANGE: - { - // broadcast a message about configuration changes to the plugins - Plugins.Event(PLUGINEVENT_CONFIGURATIONCHANGED, 0); - return 0; - } - - case WM_USER_TBCHANGED: - { - HWND hToolBar = (HWND)wParam; - if (TopToolBar != NULL && hToolBar == TopToolBar->HWindow) - { - TopToolBar->Save(Configuration.TopToolBar); - } - if (MiddleToolBar != NULL && hToolBar == MiddleToolBar->HWindow) - { - MiddleToolBar->Save(Configuration.MiddleToolBar); - } - if (LeftPanel->DirectoryLine->ToolBar != NULL && hToolBar == LeftPanel->DirectoryLine->ToolBar->HWindow) - { - LeftPanel->DirectoryLine->LayoutWindow(); - LeftPanel->DirectoryLine->ToolBar->Save(Configuration.LeftToolBar); - } - if (RightPanel->DirectoryLine->ToolBar != NULL && hToolBar == RightPanel->DirectoryLine->ToolBar->HWindow) - { - RightPanel->DirectoryLine->LayoutWindow(); - RightPanel->DirectoryLine->ToolBar->Save(Configuration.RightToolBar); - } - return FALSE; // we have no buttons - } - - case WM_USER_TBENUMBUTTON2: - { - HWND hToolBar = (HWND)wParam; - // we forward it to our toolbar - if (TopToolBar != NULL && hToolBar == TopToolBar->HWindow) - return TopToolBar->OnEnumButton(lParam); - if (MiddleToolBar != NULL && hToolBar == MiddleToolBar->HWindow) - return MiddleToolBar->OnEnumButton(lParam); - if (LeftPanel->DirectoryLine->ToolBar != NULL && hToolBar == LeftPanel->DirectoryLine->ToolBar->HWindow) - return LeftPanel->DirectoryLine->ToolBar->OnEnumButton(lParam); - if (RightPanel->DirectoryLine->ToolBar != NULL && hToolBar == RightPanel->DirectoryLine->ToolBar->HWindow) - return RightPanel->DirectoryLine->ToolBar->OnEnumButton(lParam); - return FALSE; // we have no buttons - } - - case WM_USER_TBRESET: - { - HWND hToolBar = (HWND)wParam; - // forward to our toolbar - if (TopToolBar != NULL && hToolBar == TopToolBar->HWindow) - TopToolBar->OnReset(); - if (MiddleToolBar != NULL && hToolBar == MiddleToolBar->HWindow) - MiddleToolBar->OnReset(); - if (LeftPanel->DirectoryLine->ToolBar != NULL && hToolBar == LeftPanel->DirectoryLine->ToolBar->HWindow) - LeftPanel->DirectoryLine->ToolBar->OnReset(); - if (RightPanel->DirectoryLine->ToolBar != NULL && hToolBar == RightPanel->DirectoryLine->ToolBar->HWindow) - RightPanel->DirectoryLine->ToolBar->OnReset(); - return FALSE; // we have no buttons - } - - case WM_USER_TBGETTOOLTIP: - { - HWND hToolBar = (HWND)wParam; - // we forward it to our toolbar - if (TopToolBar != NULL && hToolBar == TopToolBar->HWindow) - TopToolBar->OnGetToolTip(lParam); - if (MiddleToolBar != NULL && hToolBar == MiddleToolBar->HWindow) - MiddleToolBar->OnGetToolTip(lParam); - if (PluginsBar != NULL && hToolBar == PluginsBar->HWindow) - PluginsBar->OnGetToolTip(lParam); - if (UMToolBar != NULL && hToolBar == UMToolBar->HWindow) - UMToolBar->OnGetToolTip(lParam); - if (HPToolBar != NULL && hToolBar == HPToolBar->HWindow) - HPToolBar->OnGetToolTip(lParam); - if (DriveBar != NULL && hToolBar == DriveBar->HWindow) - DriveBar->OnGetToolTip(lParam); - if (DriveBar2 != NULL && hToolBar == DriveBar2->HWindow) - DriveBar2->OnGetToolTip(lParam); - if (LeftPanel->DirectoryLine->ToolBar != NULL && hToolBar == LeftPanel->DirectoryLine->ToolBar->HWindow) - LeftPanel->DirectoryLine->ToolBar->OnGetToolTip(lParam); - if (RightPanel->DirectoryLine->ToolBar != NULL && hToolBar == RightPanel->DirectoryLine->ToolBar->HWindow) - RightPanel->DirectoryLine->ToolBar->OnGetToolTip(lParam); - if (BottomToolBar != NULL && hToolBar == BottomToolBar->HWindow) - BottomToolBar->OnGetToolTip(lParam); - return FALSE; // we have no buttons - } - - case WM_USER_TBENDADJUST: - { - // some toolbar was configured - force an update - IdleForceRefresh = TRUE; - IdleRefreshStates = TRUE; - return 0; - } - - case WM_USER_LEAVEMENULOOP2: - { - // this message arrives after the command, so any New menu command has already been processed - if (ContextMenuNew != NULL) - ContextMenuNew->Release(); - return 0; - } - - case WM_USER_UNINITMENUPOPUP: - { - CMenuPopup* popup = (CMenuPopup*)(CGUIMenuPopupAbstract*)wParam; - WORD popupID = HIWORD(lParam); - - switch (popupID) - { - case CML_OPTIONS_PLUGINS: - case CML_HELP_ABOUTPLUGINS: - case CML_PLUGINS: - case CML_PLUGINS_SUBMENU: - case CML_FILES_VIEWWITH: - { - HIMAGELIST hIcons = popup->GetImageList(); - if (hIcons != NULL) - { - popup->SetImageList(NULL); // just to be safe, so the popup doesn't own an invalid handle - ImageList_Destroy(hIcons); - } - hIcons = popup->GetHotImageList(); - if (hIcons != NULL) - { - popup->SetHotImageList(NULL); // just to be safe, so the popup doesn't own an invalid handle - ImageList_Destroy(hIcons); - } - if (popupID == CML_PLUGINS) // closing the Plugins menu; dynamic icons can be freed (they are rebuilt before each next menu opening) - Plugins.ReleasePluginDynMenuIcons(); - break; - } - - case CML_FILES_NEW: - { - popup->SetTemplateMenu(NULL); - EndStopRefresh(); // closed in WM_USER_UNINITMENUPOPUP/WM_USER_INITMENUPOPUP - break; - } - } - return 0; - } - - case WM_USER_INITMENUPOPUP: - { - CMenuPopup* popup = (CMenuPopup*)(CGUIMenuPopupAbstract*)wParam; - WORD popupID = HIWORD(lParam); - - switch (popupID) - { - case CML_LEFT: - case CML_RIGHT: - { - BOOL left = popupID == CML_LEFT; - - popup->CheckItem(left ? CM_LCHANGEFILTER : CM_RCHANGEFILTER, FALSE, - (left ? LeftPanel : RightPanel)->FilterEnabled); - - DWORD firstID = left ? CML_LEFT_VIEWS1 : CML_RIGHT_VIEWS1; - DWORD lastID = left ? CML_LEFT_VIEWS2 : CML_RIGHT_VIEWS2; - // find the separator above and below the views - int firstIndex = popup->FindItemPosition(firstID); - int lastIndex = popup->FindItemPosition(lastID); - if (firstIndex == -1 || lastIndex == -1) - { - TRACE_E("Requested items were not found"); - } - else - { - // remove the current contents - if (firstIndex + 1 < lastIndex - 1) - popup->RemoveItemsRange(firstIndex + 1, lastIndex - 1); - - // populate the list of views - FillViewModeMenu(popup, firstIndex + 1, left ? 1 : 2); - } - break; - } - - case CML_LEFT_GO: - case CML_RIGHT_GO: - { - static int GO_ITEMS_COUNT = -1; - - int count = popup->GetItemCount(); - if (GO_ITEMS_COUNT == -1) - GO_ITEMS_COUNT = count; - - if (count > GO_ITEMS_COUNT) - { - // remove the existing contents - popup->RemoveItemsRange(GO_ITEMS_COUNT, count - 1); - } - - // append hot paths, if any exist - DWORD firstID = popupID == CML_LEFT_GO ? CM_LEFTHOTPATH_MIN : CM_RIGHTHOTPATH_MIN; - HotPaths.FillHotPathsMenu(popup, firstID, FALSE, FALSE, FALSE, TRUE); - - // append directory history, at most 10 items - firstID = popupID == CML_LEFT_GO ? CM_LEFTHISTORYPATH_MIN : CM_RIGHTHISTORYPATH_MIN; - DirHistory->FillHistoryPopupMenu(popup, firstID, 10, TRUE); - break; - } - - case CML_LEFT_VISIBLE: - { - popup->CheckItem(CM_LEFTDIRLINE, FALSE, LeftPanel->DirectoryLine->HWindow != NULL); - popup->EnableItem(CM_LEFTHEADER, FALSE, LeftPanel->GetViewMode() == vmDetailed); - popup->CheckItem(CM_LEFTHEADER, FALSE, LeftPanel->GetViewMode() == vmDetailed && LeftPanel->HeaderLineVisible); - popup->CheckItem(CM_LEFTSTATUS, FALSE, LeftPanel->StatusLine->HWindow != NULL); - break; - } - - case CML_RIGHT_VISIBLE: - { - popup->CheckItem(CM_RIGHTDIRLINE, FALSE, RightPanel->DirectoryLine->HWindow != NULL); - popup->EnableItem(CM_RIGHTHEADER, FALSE, RightPanel->GetViewMode() == vmDetailed); - popup->CheckItem(CM_RIGHTHEADER, FALSE, RightPanel->GetViewMode() == vmDetailed && RightPanel->HeaderLineVisible); - popup->CheckItem(CM_RIGHTSTATUS, FALSE, RightPanel->StatusLine->HWindow != NULL); - break; - } - - case CML_LEFT_SORTBY: - case CML_RIGHT_SORTBY: - { - BOOL left = popupID == CML_LEFT_SORTBY; - (left ? LeftPanel : RightPanel)->FillSortByMenu(popup); - break; - } - - case CML_FILES: - { - break; - } - - case CML_EDIT: - { - // If this is a "change directory" paste operation, show it in the Paste item - char text[220]; - char tail[50]; - tail[0] = 0; - - strcpy(text, LoadStr(IDS_MENU_EDIT_PASTE)); - - CFilesWindow* activePanel = GetActivePanel(); - BOOL activePanelIsDisk = (activePanel != NULL && activePanel->Is(ptDisk)); - if (EnablerPastePath && - (!activePanelIsDisk || !EnablerPasteFiles) && // PasteFiles has higher priority - !EnablerPasteFilesToArcOrFS) // PasteFilesToArcOrFS has higher priority - { - char* p = strrchr(text, '\t'); - if (p != NULL) - strcpy(tail, p); - else - p = text + strlen(text); - - sprintf(p, " (%s)%s", LoadStr(IDS_PASTE_CHANGE_DIRECTORY), tail); - } - - MENU_ITEM_INFO mii; - mii.Mask = MENU_MASK_STRING; - mii.String = text; - popup->SetItemInfo(CM_CLIPPASTE, FALSE, &mii); - break; - } - - case CML_FILES_NEW: - { - CFilesWindow* activePanel = GetActivePanel(); - if (activePanel == NULL) - break; - BeginStopRefresh(); // we close in WM_USER_UNINITMENUPOPUP/CML_FILES_NEW, - // which is guaranteed to pair with this entry - - // if the menu does not exist, let it be created - if ((!ContextMenuNew->MenuIsAssigned()) && activePanel->Is(ptDisk) && - activePanel->CheckPath(FALSE) == ERROR_SUCCESS) - GetNewOrBackgroundMenu(HWindow, activePanel->GetPath(), ContextMenuNew, CM_NEWMENU_MIN, CM_NEWMENU_MAX, FALSE); - - // if the menu exists, build our menu based on it - if (ContextMenuNew->MenuIsAssigned()) - popup->SetTemplateMenu(ContextMenuNew->GetMenu()); - else - { - // otherwise insert a message that the New menu is unavailable - popup->RemoveAllItems(); - MENU_ITEM_INFO mii; - mii.Mask = MENU_MASK_TYPE | MENU_MASK_STRING | MENU_MASK_STATE; - mii.Type = MENU_TYPE_STRING; - mii.String = LoadStr(IDS_NEWISNOTAVAILABLE); - mii.State = MENU_STATE_GRAYED; - popup->InsertItem(0, TRUE, &mii); - } - break; - } - - case CML_FILES_VIEWWITH: - { - CFilesWindow* activePanel = GetActivePanel(); - if (activePanel == NULL) - break; - - HIMAGELIST hIcons = Plugins.CreateIconsList(FALSE); // the image list will be destroyed in WM_USER_UNINITMENUPOPUP - HIMAGELIST hIconsGray = Plugins.CreateIconsList(TRUE); - popup->SetImageList(hIconsGray); - popup->SetHotImageList(hIcons); - - activePanel->FillViewWithMenu(popup); - break; - } - - case CML_FILES_EDITWITH: - { - CFilesWindow* activePanel = GetActivePanel(); - if (activePanel == NULL) - break; - activePanel->FillEditWithMenu(popup); - break; - } - - case CML_COMMANDS_USERMENU: - { - popup->RemoveAllItems(); - FillUserMenu(popup); // expanding the user menu here is handled via WM_USER_ENTERMENULOOP/WM_USER_LEAVEMENULOOP (UserMenuIconBkgndReader.BeginUserMenuIconsInUse / EndUserMenuIconsInUse) - break; - } - - case CML_PLUGINS: - { - // initialize the Plugins menu - HIMAGELIST hIcons = Plugins.CreateIconsList(FALSE); // the image list will be destroyed in WM_USER_UNINITMENUPOPUP - HIMAGELIST hIconsGray = Plugins.CreateIconsList(TRUE); - popup->SetImageList(hIconsGray); - popup->SetHotImageList(hIcons); - - Plugins.InitMenuItems(HWindow, popup); - popup->AssignHotKeys(); - break; - } - - case CML_PLUGINS_SUBMENU: - { - // initialize a submenu of one of the plugins - Plugins.InitSubMenuItems(HWindow, popup); - break; - } - - case CML_OPTIONS: - { - popup->CheckItem(CM_ALWAYSONTOP, FALSE, Configuration.AlwaysOnTop); - break; - } - - case CML_OPTIONS_PLUGINS: - { - popup->RemoveAllItems(); - - HIMAGELIST hIcons = Plugins.CreateIconsList(FALSE); // the image list will be destroyed in WM_USER_UNINITMENUPOPUP - HIMAGELIST hIconsGray = Plugins.CreateIconsList(TRUE); - popup->SetImageList(hIconsGray); - popup->SetHotImageList(hIcons); - // we want only plugins with configuration options - if (Plugins.AddNamesToMenu(popup, CM_PLUGINCFG_MIN, CM_PLUGINCFG_MAX - CM_PLUGINCFG_MIN, TRUE)) - popup->AssignHotKeys(); - break; - } - - case CML_OPTIONS_VISIBLE: - { - popup->CheckItem(CM_TOGGLETOPTOOLBAR, FALSE, TopToolBar->HWindow != NULL); - popup->CheckItem(CM_TOGGLEPLUGINSBAR, FALSE, PluginsBar->HWindow != NULL); - popup->CheckItem(CM_TOGGLEMIDDLETOOLBAR, FALSE, MiddleToolBar->HWindow != NULL); - popup->CheckItem(CM_TOGGLEUSERMENUTOOLBAR, FALSE, UMToolBar->HWindow != NULL); - popup->CheckItem(CM_TOGGLEHOTPATHSBAR, FALSE, HPToolBar->HWindow != NULL); - popup->CheckItem(CM_TOGGLEDRIVEBAR, FALSE, DriveBar->HWindow != NULL && DriveBar2->HWindow == NULL); - popup->CheckItem(CM_TOGGLEDRIVEBAR2, FALSE, DriveBar2->HWindow != NULL); - popup->CheckItem(CM_TOGGLEEDITLINE, FALSE, EditPermanentVisible); - popup->CheckItem(CM_TOGGLEBOTTOMTOOLBAR, FALSE, BottomToolBar->HWindow != NULL); - popup->CheckItem(CM_TOGGLE_UMLABELS, FALSE, Configuration.UserMenuToolbarLabels); - popup->CheckItem(CM_TOGGLE_GRIPS, FALSE, !Configuration.GripsVisible); - break; - } - - case CML_HELP_ABOUTPLUGINS: - { - popup->RemoveAllItems(); - - HIMAGELIST hIcons = Plugins.CreateIconsList(FALSE); // the image list will be destroyed in WM_USER_UNINITMENUPOPUP - HIMAGELIST hIconsGray = Plugins.CreateIconsList(TRUE); - popup->SetImageList(hIconsGray); - popup->SetHotImageList(hIcons); - // we want all plugins - if (Plugins.AddNamesToMenu(popup, CM_PLUGINABOUT_MIN, CM_PLUGINABOUT_MAX - CM_PLUGINABOUT_MIN, FALSE)) - popup->AssignHotKeys(); - break; - } - } - return 0; - } - - case WM_INITMENUPOPUP: // note: similar code is also in CFilesBox - case WM_DRAWITEM: - case WM_MEASUREITEM: - case WM_MENUCHAR: - { - LRESULT plResult = 0; - if (ContextMenuChngDrv != NULL) - { - // if the user right-clicks HotPath in the ChangeDrive menu, it comes here - CALL_STACK_MESSAGE1("CMainWindow::WindowProc::ContextMenuChngDrv"); - SafeHandleMenuChngDrvMsg2(uMsg, wParam, lParam, &plResult); - } - if (ContextMenuNew != NULL && ContextMenuNew->MenuIsAssigned()) - { - CALL_STACK_MESSAGE1("CMainWindow::WindowProc::SafeHandleMenuMsg2"); - SafeHandleMenuNewMsg2(uMsg, wParam, lParam, &plResult); - } - return plResult; - } - - case WM_SETCURSOR: - { - if (HasLockedUI()) - break; - if (HelpMode) - { - SetCursor(HHelpCursor); - return TRUE; - } - POINT p, p2; - GetCursorPos(&p); - p2 = p; - ScreenToClient(HWindow, &p); - RECT r; - GetSplitRect(r); - if (IsWindowEnabled(HWindow) && PtInRect(&r, p) && GetCapture() == NULL) - { - BOOL aboveMiddle = FALSE; - if (MiddleToolBar != NULL && MiddleToolBar->HWindow != NULL) - { - GetWindowRect(MiddleToolBar->HWindow, &r); - aboveMiddle = PtInRect(&r, p2); - } - if (!aboveMiddle) - { - SetCursor(LoadCursor(NULL, IDC_SIZEWE)); - return TRUE; - } - } - break; - } - - case WM_CONTEXTMENU: - { - if (HasLockedUI()) - break; - if (!DragMode) - { - OnWmContextMenu((HWND)wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); - } - break; - } - - case WM_LBUTTONDOWN: - case WM_LBUTTONDBLCLK: - { - if (HasLockedUI()) - break; - - POINT p; - p.x = (short)LOWORD(lParam); - p.y = (short)HIWORD(lParam); - - RECT r; - GetSplitRect(r); - - if (PtInRect(&r, p)) - { - if (uMsg == WM_LBUTTONDOWN) // click -> start dragging - { - UpdateWindow(HWindow); // if Salamander is underneath, repaint all windows - MainWindow->CancelPanelsUI(); // cancel QuickSearch and QuickEdit - BeginStopIconRepaint(); // we do not want any icon repaints - if (!DragFullWindows) - BeginStopStatusbarRepaint(); // skip throbber repaints when dragging the XOR split bar - - DragMode = TRUE; - DragAnchorX = p.x - r.left; - SetCapture(HWindow); - - HWND toolTip = CreateWindowEx(0, - TOOLTIPS_CLASS, - NULL, - TTS_ALWAYSTIP | TTS_NOPREFIX, - CW_USEDEFAULT, - CW_USEDEFAULT, - CW_USEDEFAULT, - CW_USEDEFAULT, - NULL, - NULL, - HInstance, - NULL); - ToolTipWindow.AttachToWindow(toolTip); - ToolTipWindow.SetToolWindow(HWindow); - TOOLINFO ti; - ti.cbSize = sizeof(TOOLINFO); - ti.uFlags = TTF_SUBCLASS | TTF_ABSOLUTE | TTF_TRACK; - ti.hwnd = HWindow; - ti.uId = 1; - GetClientRect(HWindow, &ti.rect); - ti.hinst = HInstance; - ti.lpszText = LPSTR_TEXTCALLBACK; - SendMessage(ToolTipWindow.HWindow, TTM_ADDTOOL, 0, (LPARAM)&ti); - - int splitWidth = MainWindow->GetSplitBarWidth(); - DragSplitPosition = SplitPosition; - POINT mp; - GetCursorPos(&mp); - POINT p2; - p2.x = r.left; - p2.y = 0; - ClientToScreen(HWindow, &p2); - mp.x = p2.x; - SendMessage(ToolTipWindow.HWindow, TTM_TRACKPOSITION, 0, (LPARAM)(DWORD)MAKELONG(mp.x + splitWidth + 2, mp.y + 10)); - SendMessage(ToolTipWindow.HWindow, TTM_TRACKACTIVATE, TRUE, (LPARAM)&ti); - - GetWindowSplitRect(r); - DragSplitX = p.x - DragAnchorX; - DrawSplitLine(HWindow, DragSplitX, -1, r); - return 0; - } - if (uMsg == WM_LBUTTONDBLCLK) - { - if (SplitPosition != 0.5) - { - SplitPosition = 0.5; - LayoutWindows(); - FocusPanel(GetActivePanel()); - } - return 0; - } - } - break; - } - - case WM_MOUSEMOVE: - { - if (HasLockedUI()) - break; - if (DragMode && (wParam & MK_LBUTTON)) - { - int x = (short)LOWORD(lParam); - RECT r; - GetWindowSplitRect(r); - - int splitWidth = MainWindow->GetSplitBarWidth(); - - // stopper at the center - double splitPosition = (double)(x - DragAnchorX) / (WindowWidth - splitWidth); - - if (splitPosition >= 0.49 && splitPosition <= 0.51) - { - x = (WindowWidth - splitWidth) / 2 + DragAnchorX; - splitPosition = 0.5; - } - - if (splitPosition < 0) - splitPosition = 0; - if (splitPosition > 1) - splitPosition = 1; - - int leftWidth = x - DragAnchorX; - if (leftWidth < MIN_WIN_WIDTH + 1) - leftWidth = MIN_WIN_WIDTH + 1; - int rightWidth = WindowWidth - 2 - leftWidth - splitWidth; - if (rightWidth < MIN_WIN_WIDTH - 1) - { - rightWidth = MIN_WIN_WIDTH - 1; - leftWidth = WindowWidth - 2 - splitWidth - rightWidth; - } - - TOOLINFO ti; - ti.cbSize = sizeof(TOOLINFO); - ti.uFlags = 0; - ti.hwnd = HWindow; - ti.uId = 1; - GetClientRect(HWindow, &ti.rect); - - DragSplitPosition = splitPosition; - - POINT p; - GetCursorPos(&p); - POINT p2; - p2.x = leftWidth; - p2.y = 0; - ClientToScreen(HWindow, &p2); - p.x = p2.x; - SendMessage(ToolTipWindow.HWindow, TTM_TRACKPOSITION, 0, (LPARAM)(DWORD)MAKELONG(p.x + splitWidth + 2, p.y + 10)); - UpdateWindow(HWindow); - - if (DragFullWindows) - { - if (DragSplitX != leftWidth) - { - DragSplitX = leftWidth; - SplitPosition = DragSplitPosition; - LayoutWindows(); - } - } - else - { - DrawSplitLine(HWindow, leftWidth, DragSplitX, r); - DragSplitX = leftWidth; - } - - // ti.hinst = HInstance; - // ti.lpszText = LPSTR_TEXTCALLBACK; - // SendMessage(ToolTipWindow.HWindow, TTM_UPDATETIPTEXT, 0, (LPARAM)&ti); - } - break; - } - - case WM_CANCELMODE: - case WM_LBUTTONUP: - { - if (HasLockedUI()) - break; - if (DragMode) - { - RECT r; - GetClientRect(HWindow, &r); - RECT r2; - GetSplitRect(r2); - r2.left = r.left; - r2.right = r.right; - DrawSplitLine(HWindow, -1, DragSplitX, r2); - SendMessage(ToolTipWindow.HWindow, TTM_ACTIVATE, FALSE, 0); - DestroyWindow(ToolTipWindow.HWindow); // just detaches the tooltip from the control - if (uMsg == WM_LBUTTONUP) - { - // accept the position only when the drag finishes legally - // int splitWidth = MainWindow->GetSplitBarWidth(); - // SplitPosition = (double)DragSplitX / (WindowWidth - splitWidth); - SplitPosition = DragSplitPosition; - LayoutWindows(); - } - DragMode = FALSE; - ReleaseCapture(); - FocusPanel(GetActivePanel()); - EndStopIconRepaint(TRUE); // resume icon repainting and repaint them now - if (!DragFullWindows) - EndStopStatusbarRepaint(); // resume throbber repaints when dragging the XOR split bar - return 0; - } - break; - } - - case WM_NOTIFY: - { - if (!Created) - break; - if (HasLockedUI()) - break; - LPNMHDR lphdr = (LPNMHDR)lParam; - if (lphdr->code == TTN_NEEDTEXT && lphdr->hwndFrom == ToolTipWindow.HWindow) - { - char* text = ((LPTOOLTIPTEXT)lParam)->szText; - sprintf(text, "%.1lf %%", DragSplitPosition * 100); - PointToLocalDecimalSeparator(text, 15); - return 0; - } - - if (lphdr->code == NM_RCLICK && - (LeftPanel->DirectoryLine->ToolBar != NULL && - lphdr->hwndFrom == LeftPanel->DirectoryLine->ToolBar->HWindow)) - { - CToolBar* toolBar = LeftPanel->DirectoryLine->ToolBar; - DWORD pos = GetMessagePos(); - POINT p; - p.x = GET_X_LPARAM(pos); - p.y = GET_Y_LPARAM(pos); - ScreenToClient(toolBar->HWindow, &p); - int index = toolBar->HitTest(p.x, p.y); - if (index >= 0) - { - TLBI_ITEM_INFO2 tii; - tii.Mask = TLBI_MASK_ID; - if (toolBar->GetItemInfo2(index, TRUE, &tii)) - { - if (tii.ID == CM_LCHANGEDRIVE) - { - LeftPanel->UserWorkedOnThisPath = TRUE; - ShellAction(LeftPanel, saContextMenu, FALSE); - return 1; - } - } - } - break; - } - - if (lphdr->code == NM_RCLICK && - (RightPanel->DirectoryLine->ToolBar != NULL && - lphdr->hwndFrom == RightPanel->DirectoryLine->ToolBar->HWindow)) - { - CToolBar* toolBar = RightPanel->DirectoryLine->ToolBar; - DWORD pos = GetMessagePos(); - POINT p; - p.x = GET_X_LPARAM(pos); - p.y = GET_Y_LPARAM(pos); - ScreenToClient(toolBar->HWindow, &p); - int index = toolBar->HitTest(p.x, p.y); - if (index >= 0) - { - TLBI_ITEM_INFO2 tii; - tii.Mask = TLBI_MASK_ID; - if (toolBar->GetItemInfo2(index, TRUE, &tii)) - { - if (tii.ID == CM_RCHANGEDRIVE) - { - RightPanel->UserWorkedOnThisPath = TRUE; - ShellAction(RightPanel, saContextMenu, FALSE); - return 1; - } - } - } - break; - } - - if (lphdr->code == NM_RCLICK && - (DriveBar != NULL && DriveBar->HWindow != NULL && - lphdr->hwndFrom == DriveBar->HWindow)) - { - if (DriveBar->OnContextMenu()) - return 1; - break; - } - - if (lphdr->code == NM_RCLICK && - (DriveBar2 != NULL && DriveBar2->HWindow != NULL && - lphdr->hwndFrom == DriveBar2->HWindow)) - { - if (DriveBar2->OnContextMenu()) - return 1; - break; - } - - if (lphdr->code == TBN_TOOLBARCHANGE) - { - if (LeftPanel->DirectoryLine->ToolBar != NULL && - lphdr->hwndFrom == LeftPanel->DirectoryLine->ToolBar->HWindow) - LeftPanel->DirectoryLine->LayoutWindow(); - if (RightPanel->DirectoryLine->ToolBar != NULL && - lphdr->hwndFrom == RightPanel->DirectoryLine->ToolBar->HWindow) - RightPanel->DirectoryLine->LayoutWindow(); - IdleRefreshStates = TRUE; // on the next Idle, force a check of status variables - return 0; - } - if (lphdr->code == RBN_AUTOSIZE) - { - LPNMRBAUTOSIZE lpnmas = (LPNMRBAUTOSIZE)lParam; - LayoutWindows(); - return 0; - } - if (lphdr->code == RBN_LAYOUTCHANGED) - { - StoreBandsPos(); - return 0; - } - - if (lphdr->code == RBN_BEGINDRAG && DriveBar2->HWindow != NULL) - { - // hide the drive bars while dragging bands - ShowHideTwoDriveBarsInternal(FALSE); - return 0; - } - - if (lphdr->code == RBN_ENDDRAG && DriveBar2->HWindow != NULL) - { - // after dragging, show our two bands again and move them to the end - ShowHideTwoDriveBarsInternal(TRUE); - return 0; - } - - break; - } - - case WM_WINDOWPOSCHANGED: - { - GetWindowRect(HWindow, &WindowRect); - break; - } - - case WM_SIZE: // panel size adjustment - { - // at Tonda's, WM_SIZE arrives before WM_CREATE finishes - // (bug report execution address = 0x004743C3) - if (!Created) - { - PostMessage(HWindow, uMsg, wParam, lParam); - break; - } - - WindowWidth = LOWORD(lParam); - WindowHeight = HIWORD(lParam); - - if (SplitPosition < 0) - SplitPosition = 0; - if (SplitPosition > 1) - SplitPosition = 1; - - int splitWidth = GetSplitBarWidth(); - int middleToolbarWidth = 0; - if (MiddleToolBar->HWindow != NULL) - middleToolbarWidth = MiddleToolBar->GetNeededWidth(); - - int leftWidth = (int)((WindowWidth - splitWidth) * SplitPosition) - 1; - if (leftWidth < MIN_WIN_WIDTH) - leftWidth = MIN_WIN_WIDTH; - int rightWidth = WindowWidth - 2 - leftWidth - splitWidth; - if (rightWidth < MIN_WIN_WIDTH) - { - rightWidth = MIN_WIN_WIDTH; - leftWidth = WindowWidth - 2 - rightWidth - splitWidth; - } - SplitPositionPix = 1 + leftWidth; - - TopRebarHeight = 0; - BottomToolBarHeight = 0; - EditHeight = 0; - PanelsHeight = WindowHeight - 1; - - int windowsCount = 3; - - RECT rebRect; - GetWindowRect(HTopRebar, &rebRect); - TopRebarHeight = rebRect.bottom - rebRect.top; - - if (MiddleToolBar->HWindow != NULL) - { - windowsCount++; - } - if (BottomToolBar->HWindow != NULL) - { - windowsCount++; - BottomToolBarHeight = BottomToolBar->GetNeededHeight(); - } - if (EditWindow->HWindow != NULL) - { - windowsCount++; - EditHeight = EditWindow->GetNeededHeight() + 1; - } - - PanelsHeight -= TopRebarHeight + BottomToolBarHeight + EditHeight; - if (PanelsHeight < 0) - PanelsHeight = 0; - - HDWP hdwp = HANDLES(BeginDeferWindowPos(windowsCount)); - if (hdwp != NULL) - { - hdwp = HANDLES(DeferWindowPos(hdwp, HTopRebar, NULL, - 0, 0, WindowWidth, TopRebarHeight, - SWP_NOACTIVATE | SWP_NOZORDER)); - - hdwp = HANDLES(DeferWindowPos(hdwp, LeftPanel->HWindow, NULL, - 1, TopRebarHeight, leftWidth, PanelsHeight, - SWP_NOACTIVATE | SWP_NOZORDER)); - hdwp = HANDLES(DeferWindowPos(hdwp, RightPanel->HWindow, NULL, - SplitPositionPix + splitWidth, TopRebarHeight, rightWidth, PanelsHeight, - SWP_NOACTIVATE | SWP_NOZORDER)); - - if (MiddleToolBar->HWindow != NULL) - { - // move the toolbar down if any panel has a directory line - int offset1 = 0; - int offset2 = 0; - if (LeftPanel->DirectoryLine != NULL && LeftPanel->DirectoryLine->HWindow != NULL) - offset1 = LeftPanel->DirectoryLine->GetNeededHeight(); - if (RightPanel->DirectoryLine != NULL && RightPanel->DirectoryLine->HWindow != NULL) - offset2 = RightPanel->DirectoryLine->GetNeededHeight(); - int offset = max(offset1, offset2); - hdwp = HANDLES(DeferWindowPos(hdwp, MiddleToolBar->HWindow, NULL, - SplitPositionPix + SPLIT_LINE_WIDTH, TopRebarHeight + offset, - middleToolbarWidth, PanelsHeight - offset, - SWP_NOACTIVATE | SWP_NOZORDER)); - } - - // HWND_BOTTOM - prevents flickering during window resize - // if the bottom toolbar ends up down there, it flickers when resizing - if (EditWindow->HWindow != NULL) - hdwp = HANDLES(DeferWindowPos(hdwp, EditWindow->HWindow, HWND_BOTTOM, - 0, TopRebarHeight + PanelsHeight + 2, WindowWidth, EditHeight + 150, - SWP_NOACTIVATE /*| SWP_NOZORDER*/)); - - if (BottomToolBar->HWindow != NULL) - hdwp = HANDLES(DeferWindowPos(hdwp, BottomToolBar->HWindow, NULL, - 1, TopRebarHeight + PanelsHeight + EditHeight + 1, WindowWidth - 2, BottomToolBarHeight, - SWP_NOACTIVATE | SWP_NOZORDER)); - HANDLES(EndDeferWindowPos(hdwp)); - } - if (DriveBar2->HWindow != NULL) - { - REBARBANDINFO rbi; - rbi.cbSize = sizeof(REBARBANDINFO); - rbi.fMask = RBBIM_SIZE; - - RECT r; - // at Tomas Jelinek the second band strip could stick to the right side after maximizing the main window - // and refused to move; this might solve the problem - GetClientRect(RightPanel->HWindow, &r); - rbi.cx = r.right; - int index = (int)SendMessage(HTopRebar, RB_IDTOINDEX, BANDID_DRIVEBAR2, 0); - SendMessage(HTopRebar, RB_SETBANDINFO, index, (LPARAM)&rbi); - - GetClientRect(LeftPanel->HWindow, &r); - rbi.cx = r.right + MainWindow->GetSplitBarWidth() / 2 - 1; - index = (int)SendMessage(HTopRebar, RB_IDTOINDEX, BANDID_DRIVEBAR, 0); - SendMessage(HTopRebar, RB_SETBANDINFO, index, (LPARAM)&rbi); - } - break; - } - - case WM_NCACTIVATE: - { - // set the global variable indicating the main window frame state - CaptionIsActive = (BOOL)wParam; - - // repaint the directory line of the active window - // if selection is being lost, request an update quickly so we don't - // destroy the buffer of the opening window with CS_SAVEBITS - CFilesWindow* panel = GetActivePanel(); - if (panel != NULL && panel->DirectoryLine != NULL) - panel->DirectoryLine->InvalidateAndUpdate(!CaptionIsActive); - - if (!CaptionIsActive) - { - // let the bottom toolbar reset to its default position - UpdateBottomToolBar(); - } - break; - } - - case WM_ENABLE: - { - if (WindowsVistaAndLater) - { - // Windows Vista UAC patch: when starting a file from the panels caused the UAC elevation prompt to appear - // and then was closed using Cancel, Salamander would lose focus from the panel. - // The main window is disabled at the time messages like WM_ACTIVATE or WM_SETFOCUS arrive, and the focus is received by Microsoft IME-supported popups. - BOOL enabled = (BOOL)wParam; - if (enabled) - { - HWND hFocused = GetFocus(); - HWND hPanelListbox = NULL; - CFilesWindow* activePanel = GetActivePanel(); - if (activePanel != NULL && !EditMode) - { - hPanelListbox = activePanel->GetListBoxHWND(); - if (hFocused == NULL || hFocused != hPanelListbox) - FocusPanel(activePanel); - } - } - } - break; - } - - case WM_ACTIVATE: - { - int active = LOWORD(wParam); - if (active == WA_INACTIVE) - CacheNextSetFocus = TRUE; // for a smooth switch to Salamander; otherwise focus would be drawn aggressively (like old versions) - else - SuppressToolTipOnCurrentMousePos(); // suppress an unwanted tooltip when switching to the window - ExitHelpMode(); - - // ensure hiding/showing the Wait window if it exists - ShowSafeWaitWindow(active != WA_INACTIVE); - - if (active != WA_INACTIVE) - BringLockedUIToolWnd(); - - if (active == WA_ACTIVE || active == WA_CLICKACTIVE) - { - if (!EditMode) - { - if (GetActivePanel() != NULL) - { - FocusPanel(GetActivePanel()); - return 0; - } - } - else - { - if (EditWindow->HWindow != NULL) - { - SetFocus(EditWindow->HWindow); - return 0; - } - } - } - break; - } - - case WM_USER_POSTCMDORUNLOADPLUGIN: - { - CPluginData* data = Plugins.GetPluginData((CPluginInterfaceAbstract*)wParam); - if (data != NULL && data->GetLoaded()) - { - if (lParam == 0) - data->ShouldUnload = TRUE; // set the flag to unload the plugin - else - { - if (lParam == 1) - data->ShouldRebuildMenu = TRUE; // set the flag to rebuild the plugin menu - else - data->Commands.Add(LOWORD(lParam - 2)); // add salCmd/menuCmd - } - ExecCmdsOrUnloadMarkedPlugins = TRUE; // inform Salamander to scan all plugin data - } - else - { - // may occur while waiting for Release(force==TRUE) method of the plugin to finish - // TRACE_E("Unexpected situation in WM_USER_POSTCMDORUNLOADPLUGIN."); - } - return 0; - } - - case WM_USER_POSTMENUEXTCMD: - { - CPluginData* data = Plugins.GetPluginData((CPluginInterfaceAbstract*)wParam); - if (data != NULL && data->GetLoaded()) - { - if (data->GetPluginInterfaceForMenuExt()->NotEmpty()) - { - CALL_STACK_MESSAGE4("CPluginInterfaceForMenuExt::ExecuteMenuItem(, , %d,) (%s v. %s)", - (int)lParam, data->DLLName, data->Version); - - // lower the thread priority to "normal" (so operations don't burden the system) - SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL); - - data->GetPluginInterfaceForMenuExt()->ExecuteMenuItem(NULL, HWindow, (int)lParam, 0); - - // raise the thread priority again, the operation has finished - SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL); - } - else - { - TRACE_E("Plugin must have PluginInterfaceForMenuExt when " - "calling CSalamanderGeneral::PostMenuExtCommand()!"); - } - } - else - { - // it must be loaded because post-menu-ext-cmd was invoked from a loaded plugin... - // post-unload runs during "idle", so the unload couldn't have happened yet... - TRACE_E("Unexpected situation in WM_USER_POSTMENUEXTCMD."); - } - return 0; - } - - case WM_USER_SALSHEXT_TRYRELDATA: - { - // TRACE_I("WM_USER_SALSHEXT_TRYRELDATA: begin"); - if (SalShExtSharedMemView != NULL) // shared memory is available (we cannot handle cut/copy&paste errors) - { - WaitForSingleObject(SalShExtSharedMemMutex, INFINITE); - BOOL needRelease = TRUE; - if (!SalShExtSharedMemView->BlockPasteDataRelease) - { - if (!SalShExtPastedData.IsLocked()) - { - BOOL isOnClipboard = FALSE; - if (SalShExtSharedMemView->DoPasteFromSalamander && - SalShExtSharedMemView->SalamanderMainWndPID == GetCurrentProcessId() && - SalShExtSharedMemView->SalamanderMainWndTID == GetCurrentThreadId() && - SalShExtSharedMemView->PastedDataID == SalShExtPastedData.GetDataID()) - { - ReleaseMutex(SalShExtSharedMemMutex); - needRelease = FALSE; - - IDataObject* dataObj; - if (OleGetClipboard(&dataObj) == S_OK && dataObj != NULL) - { - if (IsFakeDataObject(dataObj, NULL, NULL, 0)) - { - isOnClipboard = TRUE; - } - dataObj->Release(); - } - } - - if (!isOnClipboard) - { - if (needRelease) - ReleaseMutex(SalShExtSharedMemMutex); - needRelease = FALSE; - - //TRACE_I("WM_USER_SALSHEXT_TRYRELDATA: clearing paste-data!"); - SalShExtPastedData.Clear(); - } - // else TRACE_I("WM_USER_SALSHEXT_TRYRELDATA: fake-data-object is still on clipboard"); - } - // else TRACE_I("WM_USER_SALSHEXT_TRYRELDATA: paste-data is locked"); - } - // else TRACE_I("WM_USER_SALSHEXT_TRYRELDATA: release of paste-data is blocked"); - if (needRelease) - ReleaseMutex(SalShExtSharedMemMutex); - } - // TRACE_I("WM_USER_SALSHEXT_TRYRELDATA: end"); - return 0; - } - - case WM_USER_SALSHEXT_PASTE: - { - // TRACE_I("WM_USER_SALSHEXT_PASTE: begin"); - if (SalShExtSharedMemView != NULL) // shared memory is available (we cannot handle cut/copy&paste errors) - { - BOOL tmpPasteDone = FALSE; - char tgtPath[MAX_PATH]; - tgtPath[0] = 0; - int operation = 0; - DWORD dataID = -1; - WaitForSingleObject(SalShExtSharedMemMutex, INFINITE); - if (SalShExtSharedMemView->PostMsgIndex == (int)wParam) // process only the "current" messages - { - if (SalamanderBusy) - SalShExtSharedMemView->SalBusyState = 2 /* Salamander is busy, postpone paste for later */; - else - { - SalamanderBusy = TRUE; - SalShExtPastedData.SetLock(TRUE); - LastSalamanderIdleTime = GetTickCount(); - SalShExtSharedMemView->SalBusyState = 1 /* Salamander is not busy and now is waiting for a paste operation */; - SalShExtSharedMemView->PasteDone = FALSE; - - int count = 0; - while (count++ < 50) // wait no longer than 5 seconds - { - ReleaseMutex(SalShExtSharedMemMutex); - Sleep(100); // give the copy hook 100 ms to respond - WaitForSingleObject(SalShExtSharedMemMutex, INFINITE); - if (SalShExtSharedMemView->PasteDone) // copy hook supplied the target path for Paste and other data - { - // TRACE_I("WM_USER_SALSHEXT_PASTE: copy hook returned: paste done!"); - lstrcpyn(tgtPath, SalShExtSharedMemView->TargetPath, MAX_PATH); - operation = SalShExtSharedMemView->Operation; - dataID = SalShExtSharedMemView->PastedDataID; - tmpPasteDone = TRUE; - break; - } - } - SalamanderBusy = FALSE; - } - } - ReleaseMutex(SalShExtSharedMemMutex); - - if (tmpPasteDone && operation == SALSHEXT_COPY && SalShExtPastedData.GetDataID() == dataID) // perform the Paste operation - { - SalamanderBusy = TRUE; - LastSalamanderIdleTime = GetTickCount(); - // TRACE_I("WM_USER_SALSHEXT_PASTE: calling SalShExtPastedData.DoPasteOperation"); - ProgressDialogActivateDrop = LastWndFromPasteGetData; - SalShExtPastedData.DoPasteOperation(operation == SALSHEXT_COPY, tgtPath); - ProgressDialogActivateDrop = NULL; // clear global variable for next use of the progress dialog - LastWndFromPasteGetData = NULL; // reset for the next Paste operation here - SHChangeNotify(SHCNE_UPDATEDIR, SHCNF_PATH, tgtPath, NULL); - SalamanderBusy = FALSE; - } - SalShExtPastedData.SetLock(FALSE); - PostMessage(HWindow, WM_USER_SALSHEXT_TRYRELDATA, 0, 0); // after unlocking, optionally release the data - } - // TRACE_I("WM_USER_SALSHEXT_PASTE: end"); - return 0; - } - - case WM_USER_REFRESH_SHARES: - { - Shares.Refresh(); - HANDLES(EnterCriticalSection(&TimeCounterSection)); - int t1 = MyTimeCounter++; - int t2 = MyTimeCounter++; - HANDLES(LeaveCriticalSection(&TimeCounterSection)); - if (LeftPanel != NULL && LeftPanel->Is(ptDisk) && !LeftPanel->GetNetworkDrive()) - { - PostMessage(LeftPanel->HWindow, WM_USER_REFRESH_DIR, 0, t1); - } - if (RightPanel != NULL && RightPanel->Is(ptDisk) && !RightPanel->GetNetworkDrive()) - { - PostMessage(RightPanel->HWindow, WM_USER_REFRESH_DIR, 0, t1); - } - return 0; - } - - case WM_USER_END_SUSPMODE: - { - // if the main window is minimized (slow restore or opening a context menu), - // postpone panel content check ("retry" may occur when removing a disk, etc.) - if (IsIconic(HWindow)) - { - SetTimer(HWindow, IDT_POSTENDSUSPMODE, 500, NULL); - // originally instead of using a timer: PostMessage(HWindow, WM_USER_END_SUSPMODE, 0, 0); - return 0; - } - - if (--ActivateSuspMode < 0) - { - ActivateSuspMode = 0; - // TRACE_E("WM_USER_END_SUSPMODE: problem 2"); // opening a message box with a NULL parent resends WM_ACTIVATEAPP "activate" (Salamander is already active) - return 0; // the message was already cancelled - } - HCURSOR oldCur = SetCursor(LoadCursor(NULL, IDC_WAIT)); - - // first we must finish activating the window - static BOOL recursion = FALSE; - if (!recursion) - { - recursion = TRUE; - MSG msg; - CanCloseButInEndSuspendMode = CanClose; - BOOL oldCanClose = CanClose; - CanClose = FALSE; // don't let ourselves be closed; we are inside the method - BOOL postWM_USER_CLOSE_MAINWND = FALSE; - BOOL postWM_USER_FORCECLOSE_MAINWND = FALSE; - while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) - { - if (msg.message == WM_USER_CLOSE_MAINWND && msg.hwnd == HWindow) - postWM_USER_CLOSE_MAINWND = TRUE; - else - { - if (msg.message == WM_USER_FORCECLOSE_MAINWND && msg.hwnd == HWindow) - postWM_USER_FORCECLOSE_MAINWND = TRUE; - else - { - TranslateMessage(&msg); - DispatchMessage(&msg); - } - } - } - CanClose = oldCanClose; - CanCloseButInEndSuspendMode = FALSE; - - if (postWM_USER_CLOSE_MAINWND) - PostMessage(HWindow, WM_USER_CLOSE_MAINWND, 0, 0); - if (postWM_USER_FORCECLOSE_MAINWND) - PostMessage(HWindow, WM_USER_FORCECLOSE_MAINWND, 0, 0); - - recursion = FALSE; - } - // else - // { - //#pragma message (__FILE__ " (2120): remove") - // SalMessageBox(HWindow, "problem3", "problem3", MB_OK); // debug message - // } - - // window is activated, perform a refresh - // EndSuspendMode(); // removed, we want to refresh even when the main window is inactive - - LeftPanel->Activate(FALSE); - RightPanel->Activate(FALSE); - - // if OneDrive Personal/Business was connected or disconnected, refresh the Drive bars - // so the icon or drop down menu disappears or appears - BOOL oneDrivePersonal = OneDrivePath[0] != 0; - int oneDriveBusinessStoragesCount = OneDriveBusinessStorages.Count; - InitOneDrivePath(); - if (oneDrivePersonal != (OneDrivePath[0] != 0) || - oneDriveBusinessStoragesCount != OneDriveBusinessStorages.Count) - { - PostMessage(HWindow, WM_USER_DRIVES_CHANGE, 0, 0); - } - - SetCursor(oldCur); - return 0; - } - - case WM_TIMER: - { - switch (wParam) - { - case IDT_DELETEMNGR_PROCESS: - { - KillTimer(HWindow, IDT_DELETEMNGR_PROCESS); - DeleteManager.ProcessData(); - break; - } - - case IDT_POSTENDSUSPMODE: - { - KillTimer(HWindow, IDT_POSTENDSUSPMODE); - PostMessage(HWindow, WM_USER_END_SUSPMODE, 0, 0); // if ActivateSuspMode < 1, nothing happens - break; - } - - case IDT_ADDNEWMODULES: - { - AddNewlyLoadedModulesToGlobalModulesStore(); - break; - } - - case IDT_PLUGINFSTIMERS: - { - Plugins.HandlePluginFSTimers(); - break; - } - - case IDT_ASSOCIATIONSCHNG: - { - KillTimer(HWindow, IDT_ASSOCIATIONSCHNG); - OnAssociationsChangedNotification(FALSE); - break; - } - - default: - { - TRACE_E("Unknown WM_TIMER wParam=" << wParam); - break; - } - } - break; - } - - case WM_USER_SLGINCOMPLETE: - { - char buff[1000]; - sprintf(buff, "%s\n", LoadStr(IDS_SLGINCOMPLETE_TEXT)); - Configuration.ShowSLGIncomplete = FALSE; - CMessageBox(HWindow, MSGBOXEX_OK | MSGBOXEX_ESCAPEENABLED | MSGBOXEX_SILENT | MSGBOXEX_ICONINFORMATION, - LoadStr(IDS_SLGINCOMPLETE_TITLE), buff, NULL, - NULL, NULL, 0, NULL, NULL, IsSLGIncomplete, NULL) - .Execute(); - break; - } - - case WM_USER_USERMENUICONS_READY: - { - CUserMenuIconDataArr* bkgndReaderData = (CUserMenuIconDataArr*)wParam; - DWORD threadID = (DWORD)lParam; - if (bkgndReaderData != NULL && // "always true" - UserMenuIconBkgndReader.EnterCSIfCanUpdateUMIcons(&bkgndReaderData, threadID)) - { // if the user menu still wants these icons: - // if icons can be updated immediately, lock user menu access from Find and update them; otherwise - // postpone the update until the menu with icons closes (we cannot pull the rug from under it) or after closing - // configuration dialog: after OK the newly loaded icons would be overwritten and reloading wouldn't start, leaving icons unloaded - for (int i = 0; i < UserMenuItems->Count; i++) - UserMenuItems->At(i)->GetIconHandle(bkgndReaderData, TRUE); - UserMenuIconBkgndReader.LeaveCSAfterUMIconsUpdate(); - if (UMToolBar != NULL && UMToolBar->HWindow != NULL) // refresh the user menu toolbar - UMToolBar->CreateButtons(); - } - if (bkgndReaderData != NULL) - delete bkgndReaderData; - break; - } - - case WM_ACTIVATEAPP: - { - // TRACE_I("WM_ACTIVATEAPP: " << (wParam == TRUE ? "activate" : "deactivate")); - if (FirstActivateApp) - { - if (IsWindowVisible(HWindow)) - FirstActivateApp = FALSE; - else - break; - } - - // do the work for lost and undelivered messages - int actSusMode = (wParam == TRUE) ? 1 : 0; // ActivateSuspMode should be 1 when activating, otherwise 0 - if (ActivateSuspMode < 0) - { - ActivateSuspMode = 0; - TRACE_E("WM_USER_END_SUSPMODE: problem 6"); - } - else - { - if (ActivateSuspMode != actSusMode) // e.g. two deactivations in a row or missed activation - { - KillTimer(HWindow, IDT_POSTENDSUSPMODE); // if activation hasn't happened yet, cancel (it may start again) - - MSG msg; // pump WM_USER_END_SUSPMODE from the queue, otherwise suspend mode ends shortly (e.g. opening File Comparator triggers activation+deactivation after 10ms) - while (PeekMessage(&msg, HWindow, WM_USER_END_SUSPMODE, WM_USER_END_SUSPMODE, PM_REMOVE)) - ; - - while (ActivateSuspMode > actSusMode) - { - // EndSuspendMode(); // removed, we want to refresh even when the main window is inactive - ActivateSuspMode--; - } - } - } - - // if (IsWindowVisible(HWindow)) // now handled by FirstActivateApp - // { - if (wParam == TRUE) // activating the app - { - if (!LeftPanel->DontClearNextFocusName) - LeftPanel->NextFocusName[0] = 0; - else - LeftPanel->DontClearNextFocusName = FALSE; - if (!RightPanel->DontClearNextFocusName) - RightPanel->NextFocusName[0] = 0; - else - RightPanel->DontClearNextFocusName = FALSE; - if (Windows7AndLater && IsIconic(HWindow)) - { - SetTimer(HWindow, IDT_POSTENDSUSPMODE, 200, NULL); // hopefully we'll never find out why this timer existed; commented out because it delays directory refresh by 200 ms after operations (e.g. moving a file into a subdirectory, it is visible on a local disk) - } - else - { - // until 2.53b1 only this branch existed and the timer version was commented out - // on Windows 7 users reported activation issues when icon grouping was enabled - // and Salamander was minimized; sometimes clicking its preview (or Alt+Tab) - // would not restore Salamander, only a beep; see /viewtopic.php?f=6&t=3791 - // - // so we enable the delayed variant (200ms) again, but only on W7 and only if the window is minimized - PostMessage(HWindow, WM_USER_END_SUSPMODE, 0, 0); // if ActivateSuspMode is not >= 1, nothing happens - } - IdleRefreshStates = TRUE; // on the next Idle, force a check of status variables - IdleCheckClipboard = TRUE; // also let it check the clipboard - } - else // deactivating the app - { - // when the main window deactivates, cancel quick search and quick rename modes - CancelPanelsUI(); - - // BeginSuspendMode(); // removed, we want refresh even with inactive main window - ActivateSuspMode++; - // } - // } - // if (wParam == FALSE) // when deactivating, leave directories displayed in panels - // { // so other software can delete or disconnect them - if (CanChangeDirectory()) - { - SetCurrentDirectoryToSystem(); - } - } - break; - } - - case WM_CLOSE: - { - PostMessage(HWindow, WM_USER_CLOSE_MAINWND, 0, 0); - return 0; - } - - case WM_ENDSESSION: - { - if (!wParam) - return 0; // no shutdown or log off requested, nothing to handle - - // normal shutdown/log off should not come here at all; it is handled when - // WM_QUERYENDSESSION arrives, at its end the main window closes and Salamander - // is killed. Theoretically, TRUE should be returned to call WM_ENDSESSION so this - // could arrive, everything is already done, just return 0 according to MSDN, but so far all Windows versions prefer killing the app. - // - // here we handle so-called "critical shutdown" (including log off) which - // has the ENDSESSION_CRITICAL flag in lParam; it can be triggered using calling (EWX_FORCE is crucial): - // ExitWindowsEx(EWX_LOGOFF | EWX_FORCE, SHTDN_REASON_MAJOR_OPERATINGSYSTEM | - // SHTDN_REASON_MINOR_UPGRADE | SHTDN_REASON_FLAG_PLANNED); - // the code is here (search for SE_SHUTDOWN_NAME): https://msdn.microsoft.com/en-us/library/windows/desktop/aa376871%28v=vs.85%29.aspx - // - // Vista+ only: we also handle shutdown with EWX_FORCEIFHUNG flag here; it doesn't have - // the ENDSESSION_CRITICAL flag set but forces the application to exit regardless of the return value - // WM_QUERYENDSESSION is followed by WM_ENDSESSION and after it completes the app is killed - // (unless the user interrupts the action with Cancel from the system dialog shown after 5s): - // - I call this mode "forced shutdown" - // - the system won't kill our process, no timeout is running, we won't forcibly terminate anything - // - we must notify the user if they want to interrupt the shutdown; if they cancel, after processing this WM_ENDSESSION, the software will continue running normally - // - // during a "critical shutdown" (including log off): - // - on W2K the system kills our process without warning, nothing to handle - // - on XP it's annoying that WM_QUERYENDSESSION doesn't reveal it's "critical shutdown", - // so unless something stops it we'll start saving the configuration and if we don't finish within 5s - // Windows kills the process and the configuration is lost; theoretically, making a copy each during every shutdown would solve it, - // but XP rarely loses configuration, - // on XP, regardless of WM_QUERYENDSESSION's return value (even if no response comes within 5s, - // e.g. a prompt asking to cancel ongoing disk operations), WM_ENDSESSION is still sent, - // so we don't save configuration in WM_ENDSESSION, only perform the worst-case cleanup - // (stop ongoing disk operations) - // - on Vista+ we first back up the configuration registry key in WM_QUERYENDSESSION (5s limit), - // then return TRUE to continue shutdown, and the system gives us another 5s to finish in WM_ENDSESSION, which we dedicate to saving the configuration, - // it might not finish in time, then we get killed and the configuration is left broken; - // on the next start we delete it and copy the last configuration from the backup created in WM_QUERYENDSESSION; - // on Vista+ when closing without saving configuration, we first wait 5s in WM_QUERYENDSESSION - // for disk operations to finish and then another 5s here in WM_ENDSESSION - - // Experimentally determined behavior during three types of shutdowns: - // - // EWX_FORCE: (since Vista the ENDSESSION_CRITICAL flag is set) - // W2K: kill without anything - // XP: WM_QUERYENDSESSION, without a reply: WM_ENDSESSION arrives after 5s and after another 5s a kill - // WM_QUERYENDSESSION returns TRUE/FALSE: WM_ENDSESSION follows, kill after 5s - // Win7-10: WM_QUERYENDSESSION, if there is no response: in 5s -> kill - // +Vista WM_QUERYENDSESSION returns TRUE/FALSE: WM_ENDSESSION will follow, then a kill after 5s - // - // EWX_FORCEIFHUNG: (when the ENDSESSION_CRITICAL flag is not set) - // W2K: behaves like Log Off from the Start menu - // XP: same as W2K: Log Off from the Start menu - // Win7-10: WM_QUERYENDSESSION, if there is no response in 5s: a black screen and Kill/Cancel (Cancel aborts the shutdown) - // +Vista WARNING: if the main window is not disabled (no parent message box or wait window) it kills after 5s, - // WM_QUERYENDSESSION returns TRUE/FALSE: WM_ENDSESSION follows, - // after 5s a black screen Kill/Cancel (Cancel aborts the shutdown) - // - // Log Off from the Start menu: - // W2K: WM_QUERYENDSESSION, after 5s a message box Kill/Cancel (Cancel aborts the shutdown), - // WM_QUERYENDSESSION returns TRUE -> WM_ENDSESSION arrives, after 5s a Kill/Cancel message box (Cancel acts like Kill) - // WM_QUERYENDSESSION returns FALSE - aborts shutdown - // XP: same as W2K - // Win7-10: WM_QUERYENDSESSION, if there is no response: after 5s a black screen with Kill/Cancel (Cancel aborts the shutdown), - // +Vista WM_QUERYENDSESSION returns TRUE: WM_ENDSESSION arrives, - // after 5s a black screen with Kill/Cancel (Cancel aborts the shutdown) - // WM_QUERYENDSESSION returns FALSE: immediately shows a black screen with Kill/Cancel (Cancel aborts the shutdown) - - // see above for "forced shutdown" description; give the user a chance to stop the shutdown manually, - // if they refuse, they can at least cancel running disk operations and will only lose configuration saving - if (!SaveCfgInEndSession && !WaitInEndSession && WindowsVistaAndLater && - (lParam & ENDSESSION_CRITICAL) == 0) - { - if (ProgressDlgArray.RemoveFinishedDlgs() > 0) - { - WCHAR blockReason[MAX_STR_BLOCKREASON]; - if (HLanguage != NULL && - LoadStringW(HLanguage, IDS_BLOCKSHUTDOWNDISKOPER, blockReason, _countof(blockReason))) - { - MyShutdownBlockReasonCreate(HWindow, blockReason); - } - - if (SalMessageBox(HWindow, LoadStr(IDS_FORCEDSHUTDOWNDISKOPER), - SALAMANDER_TEXT_VERSION, MB_YESNO | MB_ICONQUESTION) == IDYES) - { - ProgressDlgArray.PostCancelToAllDlgs(); // dialogs and workers run in their own threads, they may exit - while (ProgressDlgArray.RemoveFinishedDlgs() > 0) - Sleep(200); // wait until disk operations cancel - } - - MyShutdownBlockReasonDestroy(HWindow); - } - else - SalMessageBox(HWindow, LoadStr(IDS_FORCEDSHUTDOWN), SALAMANDER_TEXT_VERSION, MB_OK | MB_ICONINFORMATION); - // unfortunately there's no way to tell whether shutdown is still running or the user - // has cancelled it (black full screen window on Win7). If not, the OS kills the app; we - // already warned the user, nothing more to do. - // Disk operations may still be running; if they don't get canceled, files remain in an - // incomplete state, e.g. during copying the full file size is allocated but the content is not - // copied, just filled with zeros, and the configuration won't be saved. - return 0; - } - - if (!SaveCfgInEndSession) // configuration should not be saved (handled later) - { // WaitInEndSession or XP "critical shutdown": wait for disk operations to complete (if any are running) - if (!WindowsVistaAndLater && ProgressDlgArray.RemoveFinishedDlgs() > 0) - ProgressDlgArray.PostCancelToAllDlgs(); // dialogs and workers run in their own threads; there is a chance that they might exit - - while (ProgressDlgArray.RemoveFinishedDlgs() > 0) - Sleep(200); - return 0; // let the software close - } - - if ((lParam & ENDSESSION_CRITICAL) == 0) // theoretically cannot happen (SaveCfgInEndSession is always TRUE) - { - TRACE_E("WM_ENDSESSION: unexpected SaveCfgInEndSession: it is not ENDSESSION_CRITICAL!"); - return 0; - } - - // break; // this break is not missing! only "critical shutdown" (including log off) continues below to save configuration - } - case WM_QUERYENDSESSION: - case WM_USER_CLOSE_MAINWND: - case WM_USER_FORCECLOSE_MAINWND: - { - CALL_STACK_MESSAGE1("WM_USER_CLOSE_MAINWND::1"); - - DWORD msgArrivalTime = GetTickCount(); // critical shutdown lasts 5s + 5s; if exceeded, we are killed, so we measure the time - - if (uMsg == WM_QUERYENDSESSION) - { - TRACE_I("WM_QUERYENDSESSION: message received"); - SaveCfgInEndSession = FALSE; - WaitInEndSession = FALSE; - - if ((lParam & ENDSESSION_CRITICAL) != 0) - { - // precaution against WM_ENDSESSION being triggered from the code handling IdleCheckClipboard - // when OnEnterIdle() and CannotCloseSalMainWnd were TRUE (WM_ENDSESSION would refuse to run) - // the program is about to terminate; handling IDLE makes no sense here, and it only causes delays - DisableIdleProcessing = TRUE; - } - } - - // Windows XP: during critical shutdown they don't set ENDSESSION_CRITICAL, W2K: during critical - // shutdown they don't even send WM_QUERYENDSESSION, see above at WM_ENDSESSION - - // Vista+: endAfterCleanup: TRUE = critical shutdown (killed within 5s) cannot be refused, the app - // will 100% terminate, so perform at least the worst-case cleanup: cancel ongoing disk operations - // (stopping searches and closing Find windows and viewers is pointless, just read) - BOOL endAfterCleanup = FALSE; - - if (!CanClose) - { - if (CanCloseButInEndSuspendMode && - (uMsg == WM_QUERYENDSESSION || uMsg == WM_ENDSESSION)) - { // CanClose is FALSE only because of window activation; it doesn't prevent shutdown - } - else // "startup not completed" or "window close postponed until activation", exit now - { - if (uMsg == WM_QUERYENDSESSION) - TRACE_I("WM_QUERYENDSESSION: cancelling shutdown: CanClose is FALSE"); - if (uMsg == WM_QUERYENDSESSION && (lParam & ENDSESSION_CRITICAL) != 0) - endAfterCleanup = TRUE; // cannot be refused -> perform minimal cleanup - else - return 0; // refuse close/shutdown/logoff; a forced shutdown will be detected in WM_ENDSESSION - } - } - - if (!endAfterCleanup && CannotCloseSalMainWnd) - { - TRACE_E("WM_USER_CLOSE_MAINWND: CannotCloseSalMainWnd == TRUE!"); - if (uMsg == WM_QUERYENDSESSION) - TRACE_I("WM_QUERYENDSESSION: cancelling shutdown: CannotCloseSalMainWnd is TRUE"); - if (uMsg == WM_QUERYENDSESSION && (lParam & ENDSESSION_CRITICAL) != 0) - endAfterCleanup = TRUE; // cannot be refused -> perform minimal cleanup - else - return 0; // refuse close/shutdown/logoff; a forced shutdown will be detected in WM_ENDSESSION - } - - if (!endAfterCleanup && uMsg != WM_ENDSESSION) - { // with WM_ENDSESSION the busy state was set in WM_QUERYENDSESSION, skip the test - if (!SalamanderBusy) - { - SalamanderBusy = TRUE; // already BUSY, continue processing WM_USER_CLOSE_MAINWND - LastSalamanderIdleTime = GetTickCount(); - } - else - { - if (uMsg == WM_QUERYENDSESSION && (lParam & ENDSESSION_CRITICAL) != 0) - endAfterCleanup = TRUE; // cannot be refused -> perform minimal cleanup - else - { - if (LockedUIReason != NULL && HasLockedUI()) - SalMessageBox(HWindow, LockedUIReason, SALAMANDER_TEXT_VERSION, MB_OK | MB_ICONINFORMATION); - else - TRACE_E("WM_USER_CLOSE_MAINWND: SalamanderBusy == TRUE!"); - if (uMsg == WM_QUERYENDSESSION) - TRACE_I("WM_QUERYENDSESSION: cancelling shutdown: SalamanderBusy is TRUE"); - return 0; // refuse close/shutdown/logoff; a forced shutdown will be detected in WM_ENDSESSION - } - } - } - - if (!endAfterCleanup && AlreadyInPlugin > 0) - { - TRACE_E("WM_USER_CLOSE_MAINWND: AlreadyInPlugin > 0!"); - if (uMsg == WM_QUERYENDSESSION) - TRACE_I("WM_QUERYENDSESSION: cancelling shutdown: AlreadyInPlugin > 0"); - if (uMsg == WM_QUERYENDSESSION && (lParam & ENDSESSION_CRITICAL) != 0) - endAfterCleanup = TRUE; // cannot be refused -> perform minimal cleanup - else // cannot unload the plugin while we are in it! - return 0; // refuse close/shutdown/logoff; a forced shutdown will be detected in WM_ENDSESSION - } - - // if OnClose confirmation is enabled, ask the user to confirm closing the program - if (uMsg == WM_USER_CLOSE_MAINWND && Configuration.CnfrmOnSalClose) - { - MSGBOXEX_PARAMS params; - memset(¶ms, 0, sizeof(params)); - params.HParent = HWindow; - params.Flags = MSGBOXEX_YESNO | MSGBOXEX_ESCAPEENABLED | MSGBOXEX_ICONQUESTION | MSGBOXEX_SILENT | MSGBOXEX_HINT; - params.Caption = LoadStr(IDS_QUESTION); - params.Text = LoadStr(IDS_CANCLOSESALAMANDER); - params.CheckBoxText = LoadStr(IDS_DONTSHOWAGAINCS); - BOOL dontShow = !Configuration.CnfrmOnSalClose; - params.CheckBoxValue = &dontShow; - int ret = SalMessageBoxEx(¶ms); - Configuration.CnfrmOnSalClose = !dontShow; - - if (ret != IDYES) - return 0; - } - - // we have some dialogs with disk operations running - WCHAR blockReason[MAX_STR_BLOCKREASON]; - if (ProgressDlgArray.RemoveFinishedDlgs() > 0) - { - if ((uMsg == WM_QUERYENDSESSION || uMsg == WM_ENDSESSION) && (lParam & ENDSESSION_CRITICAL) != 0) - { // "critical shutdown" (including log off) = no time to discuss, cancel everything so - // no "unfinished" mess remains on disk - if (uMsg == WM_QUERYENDSESSION) // cancel only upon the first critical shutdown message - ProgressDlgArray.PostCancelToAllDlgs(); // dialogs and workers run in their own threads, there is a chance that they may exit - } - else // report it in a window and wait for everything to finish; WM_ENDSESSION cannot arrive here - { - if (uMsg == WM_QUERYENDSESSION && HLanguage != NULL && - LoadStringW(HLanguage, IDS_BLOCKSHUTDOWNDISKOPER, blockReason, _countof(blockReason))) - { - MyShutdownBlockReasonCreate(HWindow, blockReason); - } - CExitingOpenSal dlg(HWindow); - INT_PTR res = dlg.Execute(); - if (uMsg == WM_QUERYENDSESSION) - MyShutdownBlockReasonDestroy(HWindow); - if (res == IDCANCEL) - { - if (uMsg == WM_QUERYENDSESSION) - TRACE_I("WM_QUERYENDSESSION: cancelling shutdown: user rejects to close all disk operation progress dialogs"); - // the user does not want to exit yet - return 0; // refuse closing/shutdown/logoff; any "forced shutdown" will be detected later in WM_ENDSESSION - } - UpdateWindow(HWindow); - } - } - - // critical shutdown cannot be refused; alternative solution: don't save the configuration, do only - // the bare minimum cleanup and then terminate the app (the system may kill us sooner, current mode: kill within 5s), - // for simplicity we do not proceed with closing Find and viewer windows, the first is unnecessary, - // the second would be nice (temporary files in TEMP would vanish) - if (endAfterCleanup) - { // always true: uMsg == WM_QUERYENDSESSION && (lParam & ENDSESSION_CRITICAL) != 0 - // wait up to five seconds from receiving WM_QUERYENDSESSION for disk operations to finish - while (ProgressDlgArray.RemoveFinishedDlgs() > 0 && - GetTickCount() - msgArrivalTime <= QUERYENDSESSION_TIMEOUT - 200) - Sleep(200); - WaitInEndSession = TRUE; - return TRUE; // continue to WM_ENDSESSION where we will either finish or be killed while waiting - } - - int i = 0; - TDirectArray destroyArray(10, 5); // array of windows to destroy - if (uMsg != WM_ENDSESSION) - { - BeginStopRefresh(); // we no longer want any panel refreshes - - // gather all Find windows - FindDialogQueue.AddToArray(destroyArray); - } - - CALL_STACK_MESSAGE1("WM_USER_CLOSE_MAINWND::2"); - - if (uMsg != WM_ENDSESSION) - { - HCURSOR hOldCursor = NULL; - CWaitWindow closingFindWin(HWindow, IDS_CLOSINGFINDWINDOWS, FALSE, ooStatic); - BOOL showCloseFindWin = destroyArray.Count > 0; - if (showCloseFindWin) - { - if (uMsg == WM_QUERYENDSESSION && HLanguage != NULL && - LoadStringW(HLanguage, IDS_BLOCKSHUTDOWNFINDFILES, blockReason, _countof(blockReason))) - { - MyShutdownBlockReasonCreate(HWindow, blockReason); - } - - hOldCursor = SetCursor(LoadCursor(NULL, IDC_WAIT)); - closingFindWin.Create(); - EnableWindow(HWindow, FALSE); - } - - // ask whether Find windows can be closed; running searches will be stopped if requested - BOOL endProcessing = FALSE; - for (i = 0; i < destroyArray.Count; i++) - { - if (IsWindow(destroyArray[i])) // if the window still exists - { - BOOL canclose = TRUE; // in case the upcoming SendMessage fails - - WindowsManager.CS.Enter(); // we do not want any changes to WindowsManager - CFindDialog* findDlg = (CFindDialog*)WindowsManager.GetWindowPtr(destroyArray[i]); - if (findDlg != NULL) // if the window still exists, we send it a close query (otherwise it is pointless) - { - BOOL myPost = findDlg->StateOfFindCloseQuery == sofcqNotUsed; - if (myPost) // if this is not nesting (maybe possible, not verified but unlikely) - { - findDlg->StateOfFindCloseQuery = sofcqSentToFind; - PostMessage(destroyArray[i], WM_USER_QUERYCLOSEFIND, 0, - uMsg == WM_QUERYENDSESSION && (lParam & ENDSESSION_CRITICAL) != 0); // during critical shutdown we don't ask, we just cancel - } - BOOL cont = TRUE; - while (cont) - { - cont = FALSE; - WindowsManager.CS.Leave(); - // pretend we are responding software by pumping messages - MSG msg; - while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) - { - TranslateMessage(&msg); - DispatchMessage(&msg); - } - // give the Find thread some time to react - Sleep(50); - // time to check whether our query has been answered - WindowsManager.CS.Enter(); // no changes to WindowsManager allowed - findDlg = (CFindDialog*)WindowsManager.GetWindowPtr(destroyArray[i]); - if (findDlg != NULL) // handle only if the window still exists (otherwise it is pointless) - { - if (findDlg->StateOfFindCloseQuery == sofcqCanClose || - findDlg->StateOfFindCloseQuery == sofcqCannotClose) - { // decision made, we are done - if (findDlg->StateOfFindCloseQuery == sofcqCannotClose) - canclose = FALSE; - if (myPost) - findDlg->StateOfFindCloseQuery = sofcqNotUsed; - } - else - cont = TRUE; // keep waiting for a response from the Find thread - } - } - } - WindowsManager.CS.Leave(); - - if (!canclose) - { - if (uMsg == WM_QUERYENDSESSION) - { - TRACE_I("WM_QUERYENDSESSION: cancelling shutdown: unable to close all Find windows"); - MyShutdownBlockReasonDestroy(HWindow); - } - if (uMsg == WM_QUERYENDSESSION && (lParam & ENDSESSION_CRITICAL) != 0) - { - // EndStopRefresh(); // during critical shutdown we don't end stop-refresh (refreshes are sent to panels) - endAfterCleanup = TRUE; // cannot be refused -> perform minimal cleanup - } - else - { - EndStopRefresh(); - endProcessing = TRUE; - } - break; - } - } - } - - if (showCloseFindWin) - { - EnableWindow(HWindow, TRUE); - DestroyWindow(closingFindWin.HWindow); - SetCursor(hOldCursor); - } - - if (endProcessing) - return 0; // refuse close/shutdown/logoff; a forced shutdown will be detected later in WM_ENDSESSION - - // let Find windows close in their own thread - // not done during critical shutdown: closing Find windows is pointless (column widths, - // window size and a few other minor things won't be saved, but we ignore that) - // note: the !endAfterCleanup check here is unnecessary because outside critical shutdown - // endAfterCleanup is always FALSE - if (uMsg != WM_QUERYENDSESSION || (lParam & ENDSESSION_CRITICAL) == 0) // mimo criticky shutdown - { - for (i = 0; i < destroyArray.Count; i++) - { - if (IsWindow(destroyArray[i])) // if the window still exists - SendMessage(destroyArray[i], WM_USER_CLOSEFIND, 0, 0); - } - } - - if (showCloseFindWin && !endAfterCleanup && uMsg == WM_QUERYENDSESSION) - MyShutdownBlockReasonDestroy(HWindow); - - if (!endAfterCleanup) - { - // close viewer windows (they are not child windows -> WM_DESTROY is not sent automatically) - // we also do this during critical shutdown so TEMP files get cleaned up, - // which might otherwise be harmful (e.g. a viewer with a decrypted file starts - // shredding the temporary file after closing and the system kills us during shredding). - // A better approach is to shred properly after system restart, handled in DeleteTmpCopy() method; - // shredding does not happen during critical shutdown - ViewerWindowQueue.BroadcastMessage(WM_CLOSE, 0, 0); - - // add a delay before calling plugin unload - if there are Find windows or the internal viewer - // they have time to close here (they might hold Encrypt files) - int winsCount = ViewerWindowQueue.GetWindowCount() + FindDialogQueue.GetWindowCount(); - int timeOut = 3; - while (winsCount > 0 && timeOut--) - { - Sleep(100); - int c = ViewerWindowQueue.GetWindowCount() + FindDialogQueue.GetWindowCount(); - if (winsCount > c) // windows are still closing; wait at least another 300 ms - { - winsCount = c; - timeOut = 3; - } - } - } - } - - // a critical shutdown cannot be refused; workaround: skip saving the configuration, - // perform the bare minimum cleanup and then exit the software (the system may kill us earlier, current mode: kill within 5s), - if (endAfterCleanup) - { - // always true: uMsg == WM_QUERYENDSESSION && (lParam & ENDSESSION_CRITICAL) != 0 - // wait up to five seconds from WM_QUERYENDSESSION for disk operations to finish - while (ProgressDlgArray.RemoveFinishedDlgs() > 0 && - GetTickCount() - msgArrivalTime <= QUERYENDSESSION_TIMEOUT - 200) - Sleep(200); - - WaitInEndSession = TRUE; - return TRUE; // continue to WM_ENDSESSION where we finish or are killed if we wait longer - } - - if (uMsg == WM_QUERYENDSESSION && (lParam & ENDSESSION_CRITICAL) != 0) // this applies to Vista+ - { - BOOL cfgOK = FALSE; - if (SALAMANDER_ROOT_REG != NULL) - { - // ensure exclusive access to the configuration in the registry - LoadSaveToRegistryMutex.Enter(); - - HKEY salamander; - if (OpenKeyAux(NULL, HKEY_CURRENT_USER, SALAMANDER_ROOT_REG, salamander)) - { - DWORD saveInProgress; - if (!GetValueAux(NULL, salamander, SALAMANDER_SAVE_IN_PROGRESS, REG_DWORD, &saveInProgress, sizeof(DWORD))) - { // configuration is not corrupted - cfgOK = TRUE; - } - CloseKeyAux(salamander); - } - if (!cfgOK) - LoadSaveToRegistryMutex.Leave(); // done with the configuration; exit the section - // NOTE: LoadSaveToRegistryMutex.Leave() is called again in WM_ENDSESSION after saving the config (see below) - } - - BOOL backupOK = FALSE; - if (cfgOK) // old configuration seems OK; back it up in case saving the new configuration fails - { - char backup[200]; - sprintf_s(backup, "%s.backup.63A7CD13", SALAMANDER_ROOT_REG); // "63A7CD13" prevents the key name from matching a user key - SHDeleteKey(HKEY_CURRENT_USER, backup); // delete the old backup if one exists - HKEY salBackup; - if (!OpenKeyAux(NULL, HKEY_CURRENT_USER, backup, salBackup)) // check that no backup exists - { - if (CreateKeyAux(NULL, HKEY_CURRENT_USER, backup, salBackup)) // create a key for the backup - { - // I tried RegCopyTree (without KEY_ALL_ACCESS it failed) and it was as fast as SHCopyKey - if (SHCopyKey(HKEY_CURRENT_USER, SALAMANDER_ROOT_REG, salBackup, 0) == ERROR_SUCCESS) - { // creating the backup - DWORD copyIsOK = 1; - if (SetValueAux(NULL, salBackup, SALAMANDER_COPY_IS_OK, REG_DWORD, ©IsOK, sizeof(DWORD))) - backupOK = TRUE; - } - CloseKeyAux(salBackup); - } - } - else - CloseKeyAux(salBackup); - if (!backupOK) - LoadSaveToRegistryMutex.Leave(); // done with the configuration; exit the section - } - - // wait up to five seconds from WM_QUERYENDSESSION for disk operations to finish - while (ProgressDlgArray.RemoveFinishedDlgs() > 0 && - GetTickCount() - msgArrivalTime <= QUERYENDSESSION_TIMEOUT - 200) - Sleep(200); - - if (backupOK) // backup done, configuration will be saved in WM_ENDSESSION, - SaveCfgInEndSession = TRUE; // if we get killed during it, the configuration will load from the backup - else - { - // EndStopRefresh(); // during critical shutdown we don't end stop-refresh (refreshes are sent to panels) - WaitInEndSession = TRUE; // backup failed, we won't risk saving the configuration - } - return TRUE; // we want 5s in WM_ENDSESSION, so return TRUE - } - - if ((uMsg == WM_QUERYENDSESSION || uMsg == WM_ENDSESSION) && HLanguage != NULL && - LoadStringW(HLanguage, IDS_BLOCKSHUTDOWNSAVECFG, blockReason, _countof(blockReason))) - { - MyShutdownBlockReasonCreate(HWindow, blockReason); - } - - HCURSOR hOldCursor = NULL; - CWaitWindow analysing(HWindow, IDS_SAVINGCONFIGURATION, FALSE, ooStatic, TRUE); - HWND oldPluginMsgBoxParent = PluginMsgBoxParent; - BOOL shutdown = uMsg == WM_QUERYENDSESSION || uMsg == WM_ENDSESSION; - if (shutdown) // during shutdown/log-off/restart show a wait window for all Saves (including plugins) and process the message loop (so we aren't marked as "not responding" and killed early) - { - // start a thread that will handle registry work while saving the configuration; - // meanwhile this (main) thread will pump messages in the message loop - RegistryWorkerThread.StartThread(); - - hOldCursor = SetCursor(LoadCursor(NULL, IDC_WAIT)); - analysing.SetProgressMax(7 /* number from CMainWindow::SaveConfig() -- MUST stay in sync! */ + - Plugins.GetPluginSaveCount()); // minus one so they can enjoy a viewing 100% - analysing.Create(); - GlobalSaveWaitWindow = &analysing; - GlobalSaveWaitWindowProgress = 0; - EnableWindow(HWindow, FALSE); - - // SaveConfiguration of plugins will be called too -> parent must be set for their message boxes - PluginMsgBoxParent = analysing.HWindow; - } - - // declare a "critical shutdown" so all routines should respect it and terminate everything as quickly as possible - CriticalShutdown = uMsg == WM_ENDSESSION && (lParam & ENDSESSION_CRITICAL) != 0; - - // unload all plugins (paths in panels may point to fixed drives) - SetDoNotLoadAnyPlugins(TRUE); // for now due to thumbnails - if (!Plugins.UnloadAll(shutdown ? analysing.HWindow : HWindow)) - { - SetDoNotLoadAnyPlugins(FALSE); - - if (uMsg == WM_QUERYENDSESSION) - TRACE_I("WM_QUERYENDSESSION: cancelling shutdown: unable to unload all plugins"); - - EXIT_WM_USER_CLOSE_MAINWND: - if (shutdown) - { - GlobalSaveWaitWindow = NULL; - GlobalSaveWaitWindowProgress = 0; - EnableWindow(HWindow, TRUE); - PluginMsgBoxParent = oldPluginMsgBoxParent; - DestroyWindow(analysing.HWindow); - SetCursor(hOldCursor); - - // stop the thread that handled registry work during configuration saving... - RegistryWorkerThread.StopThread(); - } - if (uMsg == WM_QUERYENDSESSION || uMsg == WM_ENDSESSION) - MyShutdownBlockReasonDestroy(HWindow); - - if (uMsg != WM_ENDSESSION) // during critical shutdown we don't end stop-refresh (refreshes are sent to the panels) - { - EndStopRefresh(); - return 0; // refuse close/shutdown/logoff; any "forced shutdown" will be detected in WM_ENDSESSION - } - else - { - // wait for disk operations to finish; the drive system might kill our process before that - while (ProgressDlgArray.RemoveFinishedDlgs() > 0) - Sleep(200); - CriticalShutdown = FALSE; // just to be safe - return 0; // application exit - } - } - - // if CShellExecuteWnd windows exist, offer to abort closing or send a bug report and terminate - char reason[BUG_REPORT_REASON_MAX]; // problem reason + list of windows (multiline) - strcpy(reason, "Some faulty shell extension has locked our main window."); - if (EnumCShellExecuteWnd(shutdown ? analysing.HWindow : HWindow, - reason + (int)strlen(reason), BUG_REPORT_REASON_MAX - ((int)strlen(reason) + 1)) > 0) - { - // ask whether Salamander should continue or generate a bug report - if (CriticalShutdown || // during critical shutdown there's no point in asking anything, let the system terminate us quietly - SalMessageBox(shutdown ? analysing.HWindow : HWindow, - LoadStr(IDS_SHELLEXTBREAK3), SALAMANDER_TEXT_VERSION, - MSGBOXEX_CONTINUEABORT | MB_ICONINFORMATION) != IDABORT) - { - if (uMsg == WM_QUERYENDSESSION) - TRACE_I("WM_QUERYENDSESSION: cancelling shutdown: some faulty shell extension has locked our main window"); - goto EXIT_WM_USER_CLOSE_MAINWND; // we should continue - } - - // and break here - strcpy(BugReportReasonBreak, reason); - TaskList.FireEvent(TASKLIST_TODO_BREAK, GetCurrentProcessId()); - // freeze this thread - while (1) - Sleep(1000); - } - - CALL_STACK_MESSAGE1("WM_USER_CLOSE_MAINWND::3"); - - // ask the panels whether we can exit - if (LeftPanel != NULL && RightPanel != NULL) - { - BOOL canClose = FALSE; - BOOL detachFS1, detachFS2; - if (LeftPanel->PrepareCloseCurrentPath(shutdown ? analysing.HWindow : LeftPanel->HWindow, TRUE, FALSE, detachFS1, FSTRYCLOSE_UNLOADCLOSEFS /* unnecessary - plugins (including FS) already unloaded */)) - { - if (RightPanel->PrepareCloseCurrentPath(shutdown ? analysing.HWindow : RightPanel->HWindow, TRUE, FALSE, detachFS2, FSTRYCLOSE_UNLOADCLOSEFS /* unnecessary - plugins (including FS) already unloaded */)) - { - canClose = TRUE; // close both panels at the same time or none - if (RightPanel->UseSystemIcons || RightPanel->UseThumbnails) - RightPanel->SleepIconCacheThread(); - RightPanel->CloseCurrentPath(shutdown ? analysing.HWindow : RightPanel->HWindow, FALSE, detachFS2, FALSE, FALSE, TRUE); // close the right panel - - // protect the list box from errors caused by redraw requests (we just cut its data) - RightPanel->ListBox->SetItemsCount(0, 0, 0, TRUE); - RightPanel->SelectedCount = 0; - // If WM_USER_UPDATEPANEL is delivered, the panel contents are redrawn - // and scroll bars adjusted. The message loop may deliver it when creating - // a message box. Otherwise the panel would appear unchanged and the message - // would be removed from the queue. - PostMessage(RightPanel->HWindow, WM_USER_UPDATEPANEL, 0, 0); - } - if (canClose) - { - if (LeftPanel->UseSystemIcons || LeftPanel->UseThumbnails) - LeftPanel->SleepIconCacheThread(); - LeftPanel->CloseCurrentPath(shutdown ? analysing.HWindow : LeftPanel->HWindow, FALSE, detachFS1, FALSE, FALSE, TRUE); // close the left panel - - // Protect the list box from errors caused by redraw requests (after we just cut the data) - LeftPanel->ListBox->SetItemsCount(0, 0, 0, TRUE); - LeftPanel->SelectedCount = 0; - // If WM_USER_UPDATEPANEL is delivered, the panel contents are redrawn - // and scroll bars adjusted. The message loop may deliver it when creating - // a message box. Otherwise the panel would appear unchanged and the message - // would be removed from the queue. - PostMessage(LeftPanel->HWindow, WM_USER_UPDATEPANEL, 0, 0); - } - else - LeftPanel->CloseCurrentPath(shutdown ? analysing.HWindow : LeftPanel->HWindow, TRUE, detachFS1, FALSE, FALSE, TRUE); // cancel closing the left panel - } - if (!canClose) - { - SetDoNotLoadAnyPlugins(FALSE); - if (uMsg == WM_QUERYENDSESSION) - TRACE_I("WM_QUERYENDSESSION: cancelling shutdown: unable to close paths in panels"); - goto EXIT_WM_USER_CLOSE_MAINWND; // panels cannot be closed - } - } - - CALL_STACK_MESSAGE1("WM_USER_CLOSE_MAINWND::4"); - - // !!! WARNING: from this point (until DestroyWindow) no interruption must occur, - // if the window opens up, the user would find both panels empty (listing released). - // This is already violated during Shutdown / Log Off / Restart because we must distribute - // messages, otherwise we are considered "not responding" and the system kills us prematurely. - - if (StrICmp(Configuration.SLGName, Configuration.LoadedSLGName) != 0) // if the user changed Salamander's language - { - Plugins.ClearLastSLGNames(); // so that a new fallback language will be selected for all plugins if needed - Configuration.UseAsAltSLGInOtherPlugins = FALSE; - Configuration.AltPluginSLGName[0] = 0; - } - - if (Configuration.AutoSave) - SaveConfig(); - - if (uMsg == WM_ENDSESSION) - LoadSaveToRegistryMutex.Leave(); // pairs with Enter() called when WM_QUERYENDSESSION was received - - if (shutdown) - { - GlobalSaveWaitWindow = NULL; - GlobalSaveWaitWindowProgress = 0; - EnableWindow(HWindow, TRUE); - PluginMsgBoxParent = oldPluginMsgBoxParent; - DestroyWindow(analysing.HWindow); - SetCursor(hOldCursor); - - // stop the thread that handled registry work during configuration saving... - RegistryWorkerThread.StopThread(); - } - - CALL_STACK_MESSAGE1("WM_USER_CLOSE_MAINWND::5"); - - DiskCache.PrepareForShutdown(); // clean any empty tmp directories from disk - - // if (TipOfTheDayDialog != NULL) - // DestroyWindow(TipOfTheDayDialog->HWindow); // the dialog already saved its data (transfer happens there at runtime) - - MainWindowCS.SetClosed(); - - CanDestroyMainWindow = TRUE; // it's now safe to call DestroyWindow on MainWindow - - DestroyWindow(HWindow); - - // WM_QUERYENDSESSION and WM_ENDSESSION: all Windows versions kill the process as soon as - // the main window is destroyed during shutdown, so the following code is dead code in that case - - CriticalShutdown = FALSE; // just to be safe - - if (uMsg == WM_QUERYENDSESSION) - { - TRACE_I("WM_QUERYENDSESSION: allowing shutdown..."); - // main window already closed - nobody to deliver WM_ENDSESSION to, neither WaitInEndSession - // and SaveCfgInEndSession needs to be set - return TRUE; // if it gets this far, allow the shutdown - } - return 0; // return value for WM_USER_CLOSE_MAINWND, WM_USER_FORCECLOSE_MAINWND and WM_ENDSESSION - } - - case WM_ERASEBKGND: - { - /* - HDC dc = (HDC)wParam; - HPEN oldPen = (HPEN)SelectObject(dc, BtnFacePen); - MoveToEx(dc, 0, 0, NULL); - LineTo(dc, 0, WindowHeight - 1); - LineTo(dc, WindowWidth - 1, WindowHeight - 1); - LineTo(dc, WindowWidth - 1, 0); - SelectObject(dc, oldPen); -*/ - return TRUE; - } - - case WM_PAINT: - { - PAINTSTRUCT ps; - - HDC dc = HANDLES(BeginPaint(HWindow, &ps)); - HPEN oldPen = (HPEN)SelectObject(dc, BtnShadowPen); - - RECT r; - if (TopToolBar->HWindow != NULL) - { - MoveToEx(dc, 0, 0, NULL); - LineTo(dc, WindowWidth + 1, 0); - SelectObject(dc, BtnHilightPen); - MoveToEx(dc, 0, 1, NULL); - LineTo(dc, WindowWidth + 1, 1); - } - - if (PanelsHeight > 0) - { - r.left = SplitPositionPix; - r.top = TopRebarHeight; - r.right = SplitPositionPix + MainWindow->GetSplitBarWidth(); - // SelectObject(dc, shadowPen); - // MoveToEx(dc, r.left, r.top, NULL); - // LineTo(dc, r.right, r.top); - // SelectObject(dc, lightPen); - // MoveToEx(dc, r.left, r.top + 1, NULL); - // LineTo(dc, r.right, r.top + 1); - r.bottom = r.top + PanelsHeight; - FillRect(dc, &r, HDialogBrush); - - SelectObject(dc, BtnFacePen); - MoveToEx(dc, 0, 0, NULL); - LineTo(dc, 0, WindowHeight - 1); - LineTo(dc, WindowWidth - 1, WindowHeight - 1); - LineTo(dc, WindowWidth - 1, 0); - } - - if (EditWindow->HWindow != NULL) - { - r.left = 0; - r.top = TopRebarHeight + PanelsHeight; - r.right = WindowWidth; - r.bottom = r.top + 2; - FillRect(dc, &r, HDialogBrush); - } - - if (BottomToolBar->HWindow != NULL) - { - r.left = 0; - r.top = TopRebarHeight + PanelsHeight + EditHeight; - r.right = WindowWidth; - r.bottom = r.top + 2; - FillRect(dc, &r, HDialogBrush); - } - - SelectObject(dc, oldPen); - HANDLES(EndPaint(HWindow, &ps)); - return 0; - } - - case WM_DESTROY: - { - if (!CanDestroyMainWindow) - { - // some crazy shell extension has just called DestroyWindow on Salamander's main window - - MSG msg; // flush the message queue (WMP9 buffered Enter and dismissed our OK) - // while (PeekMessage(&msg, HWindow, 0, 0, PM_REMOVE)); // Petr: I replaced it by discarding key messages only; without TranslateMessage and DispatchMessage we risk an endless loop (discovered during unloading Automation with memory leaks; before showing the leak message box, an infinite loop occurred because WM_PAINT kept being added to the queue and we kept discarding it) - while (PeekMessage(&msg, NULL, WM_KEYFIRST, WM_KEYLAST, PM_REMOVE)) - ; - - // ask the user to send us a break report - SalMessageBox(HWindow, LoadStr(IDS_SHELLEXTBREAK), SALAMANDER_TEXT_VERSION, - MB_OK | MB_ICONSTOP); - - // and break here - strcpy(BugReportReasonBreak, "Some faulty shell extension destroyed our main window."); - TaskList.FireEvent(TASKLIST_TODO_BREAK, GetCurrentProcessId()); - // freeze this thread - // MainWindow no longer exists anyway; we would crash at the next opportunity - while (1) - Sleep(1000); - } - - // notify the task list that we are exiting - TaskList.SetProcessState(PROCESS_STATE_ENDING, NULL); - - UserMenuIconBkgndReader.EndProcessing(); - - SHChangeNotifyRelease(); // we no longer accept Shell Notifications - KillTimer(HWindow, IDT_ADDNEWMODULES); - HANDLES(RevokeDragDrop(HWindow)); - if (Configuration.StatusArea) - RemoveTrayIcon(); - //--- destroy child windows - if (EditWindow != NULL) - { - if (EditWindow->HWindow != NULL) - DestroyWindow(EditWindow->HWindow); - delete EditWindow; - EditWindow = NULL; - } - if (TopToolBar != NULL) - { - if (TopToolBar->HWindow != NULL) - DestroyWindow(TopToolBar->HWindow); - delete TopToolBar; - TopToolBar = NULL; - } - if (PluginsBar != NULL) - { - if (PluginsBar->HWindow != NULL) - DestroyWindow(PluginsBar->HWindow); - delete PluginsBar; - PluginsBar = NULL; - } - if (MiddleToolBar != NULL) - { - if (MiddleToolBar->HWindow != NULL) - DestroyWindow(MiddleToolBar->HWindow); - delete MiddleToolBar; - MiddleToolBar = NULL; - } - if (UMToolBar != NULL) - { - if (UMToolBar->HWindow != NULL) - DestroyWindow(UMToolBar->HWindow); - delete UMToolBar; - UMToolBar = NULL; - } - if (HPToolBar != NULL) - { - if (HPToolBar->HWindow != NULL) - DestroyWindow(HPToolBar->HWindow); - delete HPToolBar; - HPToolBar = NULL; - } - if (DriveBar != NULL) - { - if (DriveBar->HWindow != NULL) - DestroyWindow(DriveBar->HWindow); - delete DriveBar; - DriveBar = NULL; - } - if (DriveBar2 != NULL) - { - if (DriveBar2->HWindow != NULL) - DestroyWindow(DriveBar2->HWindow); - delete DriveBar2; - DriveBar2 = NULL; - } - if (BottomToolBar != NULL) - { - if (BottomToolBar->HWindow != NULL) - DestroyWindow(BottomToolBar->HWindow); - delete BottomToolBar; - BottomToolBar = NULL; - } - if (MenuBar != NULL) - { - if (MenuBar->HWindow != NULL) - DestroyWindow(MenuBar->HWindow); - delete MenuBar; - MenuBar = NULL; - } - SetMessagesParent(NULL); - PostQuitMessage(0); - break; - } - - case WM_USER_ICON_NOTIFY: - { - UINT uID = (UINT)wParam; - if (uID != TASKBAR_ICON_ID) - break; - UINT uMouseMsg = (UINT)lParam; - if (uMouseMsg == WM_LBUTTONDOWN) - { - if (!IsWindowVisible(HWindow)) - { - ShowWindow(HWindow, SW_SHOW); - if (IsIconic(HWindow)) - ShowWindow(HWindow, SW_RESTORE); - } - else - { - SetForegroundWindow(GetLastActivePopup(HWindow)); - } - } - if (uMouseMsg == WM_LBUTTONDBLCLK) - { - if (GetActiveWindow() == HWindow) - { - ShowWindow(HWindow, SW_MINIMIZE); - ShowWindow(HWindow, SW_HIDE); - } - } - if (uMouseMsg == WM_RBUTTONDOWN) - { - /* used by the export_mnu.py script which generates salmenu.mnu for the Translator; - keep synchronized with the InsertMenu() call below... -MENU_TEMPLATE_ITEM TaskBarIconMenu[] = -{ - {MNTT_PB, 0 - {MNTT_IT, IDS_CONTEXTMENU_EXIT - {MNTT_PE, 0 -}; -*/ - HMENU hMenu = CreatePopupMenu(); - InsertMenu(hMenu, 0, MF_BYPOSITION | MF_STRING, CM_EXIT, LoadStr(IDS_CONTEXTMENU_EXIT)); - - POINT p; - GetCursorPos(&p); - - DWORD cmd = TrackPopupMenu(hMenu, TPM_RETURNCMD | TPM_LEFTBUTTON | TPM_RIGHTBUTTON, - p.x, p.y, 0, HWindow, NULL); - DestroyMenu(hMenu); - if (cmd != 0) - PostMessage(HWindow, WM_COMMAND, CM_EXIT, 0); - } - break; - } - -#if (_MSC_VER < 1700) - // handle messages sent from the file manager extension - case FM_GETDRIVEINFOW: - { - TRACE_E("FM_GETDRIVEINFOW not implemented"); - break; - } - - case FM_GETFILESELW: - { - TRACE_E("FM_GETFILESELW not implemented"); - break; - } - - case FM_GETFILESELLFNW: - { - if (!GetActivePanel()->Is(ptDisk)) - return 0; // we operate only on the disk - - int index = (int)wParam; - FMS_GETFILESELW* fs = (FMS_GETFILESELW*)lParam; - CFilesWindow* activePanel = GetActivePanel(); - - int count = activePanel->GetSelCount(); - if (count != 0) - { - // determine the index of the nth (index) selected item - int totalCount = activePanel->Dirs->Count + activePanel->Files->Count; - if (totalCount == 0 || index >= totalCount) - return 0; - int selectedCount = 0; - int i; - for (i = 0; i < totalCount; i++) - { - CFileData* f = (i < activePanel->Dirs->Count) ? &activePanel->Dirs->At(i) : &activePanel->Files->At(i - activePanel->Dirs->Count); - if (f->Selected == 1) - { - if (index == selectedCount) - { - index = i; - break; - } - selectedCount++; - } - } - } - else - { - index = GetActivePanel()->GetCaretIndex(); - } - - CFileData* f; - f = (index < GetActivePanel()->Dirs->Count) ? &GetActivePanel()->Dirs->At(index) : &GetActivePanel()->Files->At(index - GetActivePanel()->Dirs->Count); - - char buff[MAX_PATH]; - strcpy(buff, GetActivePanel()->GetPath()); - if (buff[strlen(buff) - 1] != '\\') - strcat(buff, "\\"); - strcat(buff, f->Name); - MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, buff, -1, fs->szName, sizeof(fs->szName) / 2); - fs->szName[sizeof(fs->szName) / 2 - 1] = 0; - fs->ftTime = f->LastWrite; - fs->dwSize = f->Size.LoDWord; - fs->bAttr = (BYTE)f->Attr; - return 0; - } - - case FM_GETFOCUS: - { - return FMFOCUS_DIR; - } - - case FM_GETSELCOUNT: - { - TRACE_E("FM_GETSELCOUNT not implemented"); - return 0; - } - - case FM_GETSELCOUNTLFN: - { - if (!GetActivePanel()->Is(ptDisk)) - return 0; // we operate only on the disk - - CFilesWindow* activePanel = GetActivePanel(); - - if (activePanel->Dirs->Count + activePanel->Files->Count == 0) - return 0; - int count = GetActivePanel()->GetSelCount(); - if (count == 0) - { - int index = GetActivePanel()->GetCaretIndex(); - if (index == 0 && GetActivePanel()->Dirs->Count > 0 && - strcmp(GetActivePanel()->Dirs->At(0).Name, "..") == 0) - count = 0; - else - count = 1; - } - return count; - } - - case FM_REFRESH_WINDOWS: - { - CFilesWindow* panel = GetActivePanel(); - if (panel != NULL && panel->Is(ptDisk)) - { - //--- refresh directories that are not automatically refreshed - // a change in the directory shown in the panel and preferably its subdirectories (who knows what the system does) - PostChangeOnPathNotification(panel->GetPath(), TRUE); - } - break; - } - - case FM_RELOAD_EXTENSIONS: - { - break; - } -#endif // _MSC_VER < 1700 - - default: - { - if (uMsg == TaskbarRestartMsg && Configuration.StatusArea) - AddTrayIcon(); - if (TaskbarBtnCreatedMsg != 0 && uMsg == TaskbarBtnCreatedMsg) - TaskBarList3.Init(HWindow); - break; - } - } - return CWindow::WindowProc(uMsg, wParam, lParam); -} diff --git a/src/mainwnd4.cpp b/src/mainwnd4.cpp deleted file mode 100644 index eeb85638..00000000 --- a/src/mainwnd4.cpp +++ /dev/null @@ -1,2075 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Taskscape Ltd -// SPDX-License-Identifier: GPL-2.0-or-later -// CommentsTranslationProject: TRANSLATED - -#include "precomp.h" - -#include "stswnd.h" -#include "editwnd.h" -#include "plugins.h" -#include "fileswnd.h" -#include "usermenu.h" -#include "mainwnd.h" -#include "cfgdlg.h" -#include "dialogs.h" -#include "execute.h" -#include "cache.h" -#include "toolbar.h" -#include "salinflt.h" -#include "menu.h" -extern "C" -{ -#include "shexreg.h" -} -#include "salshlib.h" -#include "zip.h" - -BOOL ImageDragging = FALSE; -BOOL ImageDraggingVisible = FALSE; -BOOL ShowCaretAfterDrop = FALSE; -int ImageDraggingVisibleLevel = 0; -int ImageDragX = INT_MAX; -int ImageDragY = INT_MAX; -int ImageDragW = INT_MAX; -int ImageDragH = INT_MAX; -int ImageDragDxHotspot = INT_MAX; -int ImageDragDyHotspot = INT_MAX; - -//**************************************************************************** -// -// CMainWindow -// - -void RecursiveFindAndCopy(char* srcPath, char* dstPath, char** fromBuf, char** toBuf, int* freeBufNames); - -void CMainWindow::ClearPluginFSFromHistory(CPluginFSInterfaceAbstract* fs) -{ - DirHistory->ClearPluginFSFromHistory(fs); -} - -void CMainWindow::DirHistoryAddPathUnique(int type, const char* pathOrArchiveOrFSName, - const char* archivePathOrFSUserPart, HICON hIcon, - CPluginFSInterfaceAbstract* pluginFS, - CPluginFSInterfaceEncapsulation* curPluginFS) -{ - if (CanAddToDirHistory) - { - DirHistory->AddPathUnique(type, pathOrArchiveOrFSName, archivePathOrFSUserPart, hIcon, - pluginFS, curPluginFS); - if (LeftPanel != NULL) - LeftPanel->DirectoryLine->SetHistory(DirHistory->HasPaths()); - if (RightPanel != NULL) - RightPanel->DirectoryLine->SetHistory(DirHistory->HasPaths()); - } - else - { - if (hIcon != NULL) - HANDLES(DestroyIcon(hIcon)); - } -} - -void CMainWindow::DirHistoryRemoveActualPath(CFilesWindow* panel) -{ - if (panel->Is(ptZIPArchive)) - { - DirHistory->RemoveActualPath(1, panel->GetZIPArchive(), panel->GetZIPPath(), NULL, NULL); - } - else - { - if (panel->Is(ptDisk)) - { - DirHistory->RemoveActualPath(0, panel->GetPath(), NULL, NULL, NULL); - } - else - { - if (panel->Is(ptPluginFS)) - { - char curPath[MAX_PATH]; - if (panel->GetPluginFS()->NotEmpty() && panel->GetPluginFS()->GetCurrentPath(curPath)) - { - DirHistory->RemoveActualPath(2, panel->GetPluginFS()->GetPluginFSName(), curPath, - panel->GetPluginFS()->GetInterface(), panel->GetPluginFS()); - } - } - } - } - if (LeftPanel != NULL) - LeftPanel->DirectoryLine->SetHistory(DirHistory->HasPaths()); - if (RightPanel != NULL) - RightPanel->DirectoryLine->SetHistory(DirHistory->HasPaths()); -} - -void CMainWindow::GetSplitRect(RECT& r) -{ - r.left = SplitPositionPix; - r.top = TopRebarHeight; - r.right = SplitPositionPix + MainWindow->GetSplitBarWidth(); - r.bottom = WindowHeight - EditHeight - BottomToolBarHeight; -} - -void CMainWindow::GetWindowSplitRect(RECT& r) -{ - GetClientRect(HWindow, &r); - r.top = TopRebarHeight; - r.bottom = WindowHeight - EditHeight - BottomToolBarHeight; -} - -BOOL CMainWindow::PtInChild(HWND hChild, POINT p) -{ - if (hChild == NULL) - return FALSE; - RECT r; - GetWindowRect(hChild, &r); - MapWindowPoints(NULL, HWindow, (POINT*)&r, 2); - return PtInRect(&r, p); -} - -BOOL CMainWindow::CloseDetachedFS(HWND parent, CPluginFSInterfaceEncapsulation* detachedFS) -{ - CALL_STACK_MESSAGE1("CMainWindow::CloseDetachedFS()"); - BOOL dummy; // ignored return value - if (!detachedFS->TryCloseOrDetach(CriticalShutdown, FALSE, dummy, FSTRYCLOSE_UNLOADCLOSEDETACHEDFS) && - !CriticalShutdown) // test close; forceClose==TRUE only during a "critical shutdown" - { // ask the user whether to close it even against the FS wishes - char path[2 * MAX_PATH]; - strcpy(path, detachedFS->GetPluginFSName()); - strcat(path, ":"); - char* s = path + strlen(path); - if (!detachedFS->NotEmpty() || !detachedFS->GetCurrentPath(s)) - *s = 0; // cannot obtain the user portion - - char buf[2 * MAX_PATH + 100]; - sprintf(buf, LoadStr(IDS_FSFORCECLOSE), path); - if (SalMessageBox(parent, buf, LoadStr(IDS_QUESTION), - MB_YESNO | MB_ICONQUESTION) == IDYES) // user chooses "close" - { - detachedFS->TryCloseOrDetach(TRUE, FALSE, dummy, FSTRYCLOSE_UNLOADCLOSEDETACHEDFS); - } - else - return FALSE; // user doesn't want to close the detached FS - } - - // close the FS - CPluginInterfaceForFSEncapsulation plugin(detachedFS->GetPluginInterfaceForFS()->GetInterface(), - detachedFS->GetPluginInterfaceForFS()->GetBuiltForVersion()); - if (plugin.NotEmpty()) - { - detachedFS->ReleaseObject(parent); - plugin.CloseFS(detachedFS->GetInterface()); - } - else - TRACE_E("Unexpected situation (2) in CMainWindow::CloseDetachedFS()"); - - return TRUE; // FS is closed -} - -BOOL CMainWindow::CanUnloadPlugin(HWND parent, CPluginInterfaceAbstract* plugin) -{ - CALL_STACK_MESSAGE1("CMainWindow::CanUnloadPlugin()"); - if (LeftPanel != NULL && !LeftPanel->CanUnloadPlugin(parent, plugin)) - return FALSE; - if (RightPanel != NULL && !RightPanel->CanUnloadPlugin(parent, plugin)) - return FALSE; - - // find detached FS belonging to the plug-in 'plugin' and attempt to close them - int i; - for (i = DetachedFSList->Count - 1; i >= 0; i--) // iterate backwards; as we will be deleting from the array (quadratic complexity) - { - CPluginFSInterfaceEncapsulation* detachedFS = DetachedFSList->At(i); - if (detachedFS->GetPluginInterface() == plugin) // belongs to plug-in 'plugin' - { - if (CloseDetachedFS(parent, detachedFS)) - { - DetachedFSList->Delete(i); // remove the detached FS from DetachedFSList - if (!DetachedFSList->IsGood()) - DetachedFSList->ResetState(); - } - else - return FALSE; // unload cannot proceed (user refused to close the plug-in's detached FS) - } - } - - // check if data from the plugin is not in SalShExtPastedData - // (panels leaving archives might have stored them there due to plug-in unload) - if (!SalShExtPastedData.CanUnloadPlugin(parent, plugin)) - return FALSE; // unload cannot proceed - - return TRUE; // unload is possible; all plug-in resources were released -} - -void CMainWindow::MakeFileList() -{ - CALL_STACK_MESSAGE1("CMainWindow::MakeFileList()"); - - BOOL files = FALSE; // cursor is on a file or directory or there is a selection - BOOL upDir = FALSE; // presence of ".." - - CFilesWindow* panel = GetActivePanel(); - - upDir = (panel->Dirs->Count != 0 && strcmp(panel->Dirs->At(0).Name, "..") == 0); - int caret = panel->GetCaretIndex(); - if (caret >= 0) - { - if (caret == 0) - { - if (!upDir) - files = (panel->Dirs->Count + panel->Files->Count > 0); - else - { - int count = panel->GetSelCount(); - if (count == 1) - { - files = (panel->GetSel(0) == FALSE); - } - else - files = (count > 0); - } - } - else - files = TRUE; - } - - if (!files) - return; - - // restore DefaultDir - MainWindow->UpdateDefaultDir(TRUE); - - BeginStopRefresh(); // snooper takes a break - - CFileListDialog dlg(HWindow); - if (dlg.Execute() == IDOK) - { - char fileName[MAX_PATH]; - - switch (Configuration.FileListDestination) - { - case 0: // clipboard - case 1: // viewer - { - if (!SalGetTempFileName(NULL, "MFL", fileName, _countof(fileName), TRUE)) - { - DWORD err = GetLastError(); - char errorText[200 + 2 * MAX_PATH]; - sprintf(errorText, "%s\n\n%s", LoadStr(IDS_ERRORCREATINGTMPFILE), - GetErrorText(err)); - SalMessageBox(HWindow, errorText, LoadStr(IDS_ERRORTITLE), MB_OK | MB_ICONEXCLAMATION); - fileName[0] = 0; - } - break; - } - - case 2: // file - { - strcpy(fileName, Configuration.FileListName); - int errTextID; - if (!SalGetFullName(fileName, &errTextID, GetActivePanel()->Is(ptDisk) ? GetActivePanel()->GetPath() : NULL, panel->NextFocusName)) - { - SalMessageBox(HWindow, LoadStr(errTextID), LoadStr(IDS_ERRORTITLE), MB_OK | MB_ICONEXCLAMATION); - fileName[0] = 0; - } - break; - } - - default: - { - TRACE_E("Unknown destination!"); - fileName[0] = 0; - } - } - - if (fileName[0] != 0) - { - BOOL append = (Configuration.FileListDestination == 2 && Configuration.FileListAppend); - HANDLE hFile = HANDLES_Q(CreateFileUtf8(fileName, GENERIC_WRITE | GENERIC_READ, - FILE_SHARE_READ, NULL, - append ? OPEN_ALWAYS : CREATE_ALWAYS, - FILE_FLAG_RANDOM_ACCESS, - NULL)); - if (hFile != INVALID_HANDLE_VALUE) - { - // position the file pointer - SetFilePointer(hFile, 0, NULL, append ? FILE_END : FILE_BEGIN); - - // fill the file with data -- insert one entry for each file or directory - BOOL deleteFile = TRUE; - if (panel->MakeFileList(hFile)) - { - panel->SetSel(FALSE, -1, TRUE); // force redraw - PostMessage(panel->HWindow, WM_USER_SELCHANGED, 0, 0); // sel-change notify - deleteFile = FALSE; - } - - if (!deleteFile && Configuration.FileListDestination == 0) // clipboard - { - DWORD fileSize = GetFileSize(hFile, NULL); - if (fileSize != INVALID_FILE_SIZE && fileSize > 0) - { - SetFilePointer(hFile, 0, NULL, FILE_BEGIN); - char* buff = (char*)malloc(fileSize); - if (buff != NULL) - { - DWORD read; - if (ReadFile(hFile, buff, fileSize, &read, NULL)) - { - CopyTextToClipboard(buff, fileSize, FALSE, NULL); - } - else - { - DWORD err = GetLastError(); - SalMessageBox(HWindow, GetErrorText(err), LoadStr(IDS_ERRORTITLE), MB_OK | MB_ICONEXCLAMATION); - } - free(buff); - } - else - TRACE_E(LOW_MEMORY); - } - } - HANDLES(CloseHandle(hFile)); - - // if the destination was the clipboard, delete the temporary file - if (deleteFile || Configuration.FileListDestination == 0) // clipboard - DeleteFileUtf8(fileName); - else - { - if (Configuration.FileListDestination == 1) // viewer - { - // show the file in the internal viewer, which will delete it afterwards - CSalamanderPluginInternalViewerData viewerData; - viewerData.Size = sizeof(viewerData); - viewerData.FileName = fileName; - viewerData.Mode = 0; // text mode - char title[200]; - lstrcpyn(title, LoadStr(IDS_MAKEFILELIST_OUTPUT), 200); - viewerData.Caption = title; - viewerData.WholeCaption = TRUE; - int error; - if (!ViewFileInPluginViewer(NULL, &viewerData, TRUE, NULL, "mfl.txt", error)) - { - // delete the file even when opening fails - } - } - } - - if (Configuration.FileListDestination == 2) // file - { - //--- refresh manually refreshed directories - // change in the directory where the file list was created - CutDirectory(fileName); - MainWindow->PostChangeOnPathNotification(fileName, FALSE); - } - } - else - { - DWORD err = GetLastError(); - char message[MAX_PATH + 100]; - sprintf(message, LoadStr(IDS_FILEERRORFORMAT), fileName, GetErrorText(err)); - SalMessageBox(HWindow, message, LoadStr(IDS_ERRORTITLE), MB_OK | MB_ICONEXCLAMATION); - } - } - } - EndStopRefresh(); // the snooper will start again now -} - -// see description in mainwnd.h -BOOL GetNextFileFromPanel(int index, char* path, char* name, void* param) -{ - CALL_STACK_MESSAGE2("GetNextFileFromPanel(%d, , ,)", index); - CUMDataFromPanel* data = (CUMDataFromPanel*)param; - if (data->Count == -1) // retrieving data - { - BOOL upDir = (data->Window->Dirs->Count != 0 && - strcmp(data->Window->Dirs->At(0).Name, "..") == 0); - data->Count = data->Window->GetSelCount(); - if (data->Count < 0) - data->Count = 0; - if (data->Count == 0) // no selection -> use the focused item - { - index = data->Window->GetCaretIndex(); - data->Index = NULL; - strcpy(path, data->Window->GetPath()); - if (index < 0 || index >= data->Window->Dirs->Count + data->Window->Files->Count || - index == 0 && upDir) - { - name[0] = 0; // for up-dir or for the first item of an empty panel the name will be empty... - } - else // copy the name for others - { - CFileData* f = &((index < data->Window->Dirs->Count) ? data->Window->Dirs->At(index) : data->Window->Files->At(index - data->Window->Dirs->Count)); - strcpy(name, f->Name); - } - return TRUE; - } - data->Index = new int[data->Count]; - if (data->Index == NULL) - return FALSE; // error - data->Window->GetSelItems(data->Count, data->Index); - } - if (index >= 0 && index < data->Count) - { - CFileData* f = &((data->Index[index] < data->Window->Dirs->Count) ? data->Window->Dirs->At(data->Index[index]) : data->Window->Files->At(data->Index[index] - data->Window->Dirs->Count)); - strcpy(path, data->Window->GetPath()); - strcpy(name, f->Name); - return TRUE; - } - else - { - if (data->Index != NULL) - { - delete[] (data->Index); - data->Index = NULL; - } - data->Window->SetSel(FALSE, -1, TRUE); // explicit redraw - PostMessage(data->Window->HWindow, WM_USER_SELCHANGED, 0, 0); // sel-change notify - return FALSE; - } -} - -BOOL CheckIfCanBeExecuted(BOOL buildBat, int commandLen, int argumentsLen) -{ - /* MEASURED LIMITS: - Batch file: - W2K: 2041 including executable name, spaces and parameters - XP64/XP: 8185 including executable name, spaces and parameters - Win7/Vista: 32776 including executable name, spaces and parameters - - ShellExecuteEx: - XP/XP64/Vista/W2K: 2080 including executable name (without quotes), spaces and parameters - Win7: 32764 including executable name (without quotes), spaces and parameters -*/ - - // WARNING: if a .bat file is executed that runs a .exe and passes all parameters (%*), a long .exe name - // can still trigger "too long name" error even when respecting the limit here. The limit is exceeded once - // parameters are passed to the .exe. (I wouldn't solve this issue; it would require parsing - // .bat files etc., which is just nonsense.) - - int cmdLineLen = commandLen + argumentsLen + 1; // +1 for the space between command and arguments - if (buildBat) // launching via a .bat file - { - if (WindowsVistaAndLater) - return cmdLineLen <= 8191; // Vista/Win7: in reality it's 32776 but only 8191 works (with longer parameters, probably due to a Windows bug, characters get erased; tested on Vista and Win7) - return cmdLineLen <= 8185; // XP/XP64 - } - else // launching via ShellExecuteEx - { - if (Windows7AndLater) - return cmdLineLen <= 32764; // Win7 - return cmdLineLen <= 2080; // W2K/XP/XP64/Vista - } -} - -//***************************************************************************** -// -// ExpandCommand2 -// -// parent - parent window (for error dialogs) -// cmd - buffer for the expanded command -// cmdSize - size of 'cmd' buffer -// args - buffer for receiving arguments -// argsSize - size of 'args' buffer -// buildBat - if TRUE, arguments are placed into 'cmd' -// initDir - buffer for the path where the execution should take place -// initDirSize - length of initDir buffer -// item - user-menu item -// path - long path to the file -// longName - long filename -// fileNameUsed - returns TRUE if a file name or path was used during argument expansion -// userMenuAdvancedData - advanced parameter`s values for User Menu: the Arguments array -// ignoreEnvVarNotFoundOrTooLong - see ExpandVarString description -// -// returns success of the operation - -BOOL ExpandCommand2(HWND parent, - char* cmd, int cmdSize, - char* args, int argsSize, BOOL buildBat, - char* initDir, int initDirSize, - CUserMenuItem* item, - const char* path, - const char* longName, - BOOL* fileNameUsed, - CUserMenuAdvancedData* userMenuAdvancedData, - BOOL ignoreEnvVarNotFoundOrTooLong) -{ - CALL_STACK_MESSAGE5("ExpandCommand2(, , %d, , %d, , %s, %s, )", - cmdSize, initDirSize, path, longName); - - *fileNameUsed = FALSE; - char command[MAX_PATH]; - if (ExpandCommand(parent, item->UMCommand, command, MAX_PATH, ignoreEnvVarNotFoundOrTooLong)) - { - char fileName[MAX_PATH]; - if (path[0] != 0) - { - int l = (int)strlen(path); - if (path[l - 1] == '\\') - l--; - memcpy(fileName, path, l); - - char dosName[MAX_PATH]; - if (longName[0] != 0) - { - if (l + 1 + lstrlen(longName) > MAX_PATH - 1) - { - SalMessageBox(parent, LoadStr(IDS_TOOLONGNAME), LoadStr(IDS_ERRORTITLE), - MB_OK | MB_ICONEXCLAMATION); - goto EXIT; - } - fileName[l++] = '\\'; - strcpy(fileName + l, longName); - if (GetShortPathName(fileName, dosName, MAX_PATH) == 0) - { - TRACE_E("GetShortPathName() failed"); - dosName[0] = 0; - } - } - else - { - if (l == 2 && fileName[1] == ':') // we must append '\\' after a standard root path - { - fileName[l++] = '\\'; - } - fileName[l] = 0; - if (GetShortPathName(fileName, dosName, MAX_PATH) == 0) - { - TRACE_E("GetShortPathName() failed"); - dosName[0] = 0; - } - else - { - SalPathAddBackslash(dosName, MAX_PATH); - } - SalPathAddBackslash(fileName, MAX_PATH); - } - - char expArguments[USRMNUARGS_MAXLEN]; - if (ExpandUserMenuArguments(parent, fileName, dosName, item->Arguments, expArguments, - USRMNUARGS_MAXLEN, fileNameUsed, userMenuAdvancedData, - ignoreEnvVarNotFoundOrTooLong) && - ExpandInitDir(parent, fileName, dosName, item->InitDir, initDir, initDirSize, - ignoreEnvVarNotFoundOrTooLong)) - { - int len = (int)strlen(command); - int lArgs = (int)strlen(expArguments); - if (CheckIfCanBeExecuted(buildBat, len, lArgs)) - { - if (!buildBat) // launching via ShellExecuteEx: pass arguments separately - { - if (len + 1 <= cmdSize && lArgs + 1 <= argsSize) - { - memcpy(cmd, command, len + 1); - memcpy(args, expArguments, lArgs + 1); - return TRUE; - } - } - else // launching via .bat: append arguments after the command - { - if (len + lArgs + 2 <= cmdSize) - { - memcpy(cmd, command, len); - cmd[len] = ' '; - memcpy(cmd + len + 1, expArguments, lArgs + 1); - args[0] = 0; - return TRUE; - } - } - } - SalMessageBox(parent, LoadStr(IDS_USRMNUTOOLONGCMDORARGS), LoadStr(IDS_ERRORTITLE), - MB_OK | MB_ICONEXCLAMATION); - } - } - else - { - int len = (int)strlen(command); - if (len + 1 < cmdSize) - { - memcpy(cmd, command, len + 1); - args[0] = 0; - initDir[0] = 0; - return TRUE; - } - else - { - SalMessageBox(parent, LoadStr(IDS_USRMNUTOOLONGCMDORARGS), LoadStr(IDS_ERRORTITLE), - MB_OK | MB_ICONEXCLAMATION); - } - } - } -EXIT: - cmd[0] = 0; - args[0] = 0; - initDir[0] = 0; - return FALSE; -} - -/* -void RemoveRedundantBackslahes(char *text) -{ - if (text == NULL) - { - TRACE_E("Unexpected situation in RemoveRedundantBackslahes()."); - return; - } - if (strlen(text) < 3) - return; - char *s = text + 2; - char *d = s; - while (*s != 0) - { - *d = *s; - if (*s == '\\') - { - while (*s == '\\') s++; - d++; - } - else - { - s++; - d++; - } - } - *d = 0; -} -*/ - -void CMainWindow::UserMenu(HWND parent, int itemIndex, UM_GetNextFileName getNextFile, void* data, - CUserMenuAdvancedData* userMenuAdvancedData) -{ - CALL_STACK_MESSAGE2("CMainWindow::UserMenu(%d, ,)", itemIndex); - if (itemIndex >= 0 && itemIndex < UserMenuItems->Count) - { - UpdateWindow(parent); - - int errorPos1, errorPos2; - CUserMenuValidationData userMenuValidationData; - BOOL ok = TRUE; - if (ValidateUserMenuArguments(parent, UserMenuItems->At(itemIndex)->Arguments, errorPos1, errorPos2, - &userMenuValidationData)) - { - if (userMenuValidationData.UsesListOfSelNames && userMenuAdvancedData->ListOfSelNames[0] == 0) - { - SalMessageBox(parent, LoadStr(userMenuAdvancedData->ListOfSelNamesIsEmpty ? IDS_EMPTYLISTOFSELNAMES : IDS_TOOLONGLISTOFSELNAMES), - LoadStr(IDS_USERMENUERROR), MB_OK | MB_ICONEXCLAMATION); - ok = FALSE; - } - if (ok && userMenuValidationData.UsesListOfSelFullNames && userMenuAdvancedData->ListOfSelFullNames[0] == 0) - { - SalMessageBox(parent, LoadStr(userMenuAdvancedData->ListOfSelFullNamesIsEmpty ? IDS_EMPTYLISTOFSELFULLNAMES : IDS_TOOLONGLISTOFSELFULLNAMES), - LoadStr(IDS_USERMENUERROR), MB_OK | MB_ICONEXCLAMATION); - ok = FALSE; - } - if (ok && userMenuValidationData.UsesFullPathLeft && userMenuAdvancedData->FullPathLeft[0] == 0) - { - SalMessageBox(parent, LoadStr(IDS_NOTDEFFULLPATHLEFT), - LoadStr(IDS_USERMENUERROR), MB_OK | MB_ICONEXCLAMATION); - ok = FALSE; - } - if (ok && userMenuValidationData.UsesFullPathRight && userMenuAdvancedData->FullPathRight[0] == 0) - { - SalMessageBox(parent, LoadStr(IDS_NOTDEFFULLPATHRIGHT), - LoadStr(IDS_USERMENUERROR), MB_OK | MB_ICONEXCLAMATION); - ok = FALSE; - } - if (ok && userMenuValidationData.UsesFullPathInactive && userMenuAdvancedData->FullPathInactive[0] == 0) - { - SalMessageBox(parent, LoadStr(IDS_NOTDEFFULLPATHINACTIVE), - LoadStr(IDS_USERMENUERROR), MB_OK | MB_ICONEXCLAMATION); - ok = FALSE; - } - if (ok && userMenuValidationData.UsedCompareType != 0) - { - if ((userMenuValidationData.UsedCompareType == 6 /* file-or-dir-left-right */ || - userMenuValidationData.UsedCompareType == 7 /* file-or-dir-active-inactive */) && - userMenuAdvancedData->CompareName1[0] == 0 && - userMenuAdvancedData->CompareName2[0] == 0) - { // we don't know if files or directories should be compared, ask the user (the name selection dialog differs for files/directories) - MSGBOXEX_PARAMS params; - memset(¶ms, 0, sizeof(params)); - params.HParent = parent; - params.Flags = MB_YESNO | MB_ICONQUESTION | MSGBOXEX_SILENT; - params.Caption = LoadStr(IDS_QUESTION); - params.Text = LoadStr(IDS_COMPAREFILESORDIRS); - char aliasBtnNames[200]; - /* used by the export_mnu.py script that generates salmenu.mnu for the Translator - we let the message box buttons resolve hotkey collisions by pretending it's a menu -MENU_TEMPLATE_ITEM MsgBoxButtons[] = -{ - {MNTT_PB, 0 - {MNTT_IT, IDS_MSGBOXBTN_FILES - {MNTT_IT, IDS_MSGBOXBTN_DIRS - {MNTT_PE, 0 -}; -*/ - sprintf(aliasBtnNames, "%d\t%s\t%d\t%s", - DIALOG_YES, LoadStr(IDS_MSGBOXBTN_FILES), - DIALOG_NO, LoadStr(IDS_MSGBOXBTN_DIRS)); - params.AliasBtnNames = aliasBtnNames; - userMenuAdvancedData->CompareNamesAreDirs = (SalMessageBoxEx(¶ms) == DIALOG_NO); - } - - BOOL swapNames = FALSE; - BOOL clearNames = FALSE; - BOOL comparingFiles = TRUE; - switch (userMenuValidationData.UsedCompareType) - { - case 1: // file-left-right - { - if (userMenuAdvancedData->CompareNamesReversed) - swapNames = TRUE; - if (userMenuAdvancedData->CompareNamesAreDirs) - clearNames = TRUE; - break; - } - - case 2: // file-active-inactive - { - if (userMenuAdvancedData->CompareNamesAreDirs) - clearNames = TRUE; - break; - } - - case 3: // dir-left-right - { - comparingFiles = FALSE; - if (userMenuAdvancedData->CompareNamesReversed) - swapNames = TRUE; - if (!userMenuAdvancedData->CompareNamesAreDirs) - clearNames = TRUE; - break; - } - - case 4: // dir-active-inactive - { - comparingFiles = FALSE; - if (!userMenuAdvancedData->CompareNamesAreDirs) - clearNames = TRUE; - break; - } - - case 6: // file-or-dir-left-right - { - comparingFiles = !userMenuAdvancedData->CompareNamesAreDirs; - if (userMenuAdvancedData->CompareNamesReversed) - swapNames = TRUE; - break; - } - - case 7: // file-or-dir-active-inactive - { - comparingFiles = !userMenuAdvancedData->CompareNamesAreDirs; - break; - } - } - if (clearNames) - { - userMenuAdvancedData->CompareName1[0] = 0; - userMenuAdvancedData->CompareName2[0] = 0; - } - else - { - if (swapNames) - { - char swap[MAX_PATH]; - lstrcpyn(swap, userMenuAdvancedData->CompareName1, MAX_PATH); - lstrcpyn(userMenuAdvancedData->CompareName1, userMenuAdvancedData->CompareName2, MAX_PATH); - lstrcpyn(userMenuAdvancedData->CompareName2, swap, MAX_PATH); - } - } - if (Configuration.CnfrmShowNamesToCompare || - userMenuAdvancedData->CompareName1[0] == 0 || - userMenuAdvancedData->CompareName2[0] == 0) - { - CCompareArgsDlg dlg(parent, comparingFiles, userMenuAdvancedData->CompareName1, - userMenuAdvancedData->CompareName2, &Configuration.CnfrmShowNamesToCompare); - if (dlg.Execute() != IDOK) - ok = FALSE; - } - } - } - else - ok = FALSE; - if (ok) - { - BOOL buildBat = UserMenuItems->At(itemIndex)->ThroughShell; - BOOL batNotEmpty = FALSE; - char* batName; - HANDLE file; - char batUniqueName[50]; // we need a unique name for the batch file in the cache - DWORD lastErr; - - _TRY_AGAIN: - - sprintf(batUniqueName, "Usermenu %X", GetTickCount()); - if (buildBat) - { - BOOL exists; - batName = (char*)DiskCache.GetName(batUniqueName, "usermenu.bat", &exists, TRUE, NULL, FALSE, NULL, NULL); - if (batName == NULL) // error (if 'exists' is TRUE -> fatal, otherwise "file already exists") - { - if (!exists) // file exists -> almost impossible, handle anyway - { - Sleep(100); - goto _TRY_AGAIN; - } - return; // fatal error - } - file = HANDLES_Q(CreateFileUtf8(batName, GENERIC_WRITE, 0, NULL, CREATE_NEW, - FILE_ATTRIBUTE_TEMPORARY, NULL)); - if (file == INVALID_HANDLE_VALUE) - { - lastErr = GetLastError(); - goto ERROR_LABEL; - } - } - - // build the .bat file - int index; - index = 0; - char cmdLine[USRMNUCMDLINE_MAXLEN]; - char arguments[USRMNUARGS_MAXLEN]; - char initDir[MAX_PATH]; - char prevInitDir[MAX_PATH]; - initDir[0] = 0; - arguments[0] = 0; - char path[MAX_PATH], name[MAX_PATH]; - BOOL error; - error = FALSE; - BOOL skipErrorMessage; - skipErrorMessage = FALSE; - DWORD written; - BOOL fileNameUsed; - BOOL firstRound; - firstRound = TRUE; - while (getNextFile(index, path, name, data)) - { - strcpy(prevInitDir, initDir); - BOOL expandOK = ExpandCommand2(parent, - cmdLine, USRMNUCMDLINE_MAXLEN, - arguments, USRMNUARGS_MAXLEN, buildBat, // if we are running via a batch file, allow - initDir, MAX_PATH, // arguments will be inserted into cmdLine - UserMenuItems->At(itemIndex), - path, name, &fileNameUsed, - userMenuAdvancedData, - !firstRound); - if (!expandOK) - { - error = TRUE; - skipErrorMessage = TRUE; - break; - } - if (expandOK && (firstRound || fileNameUsed || strcmp(initDir, prevInitDir) != 0)) // block running the same command for all items (a user mistake that happens often) - { - if (buildBat) // building a .bat file - { - char initDirOEM[MAX_PATH]; - char cmdLineOEM[USRMNUCMDLINE_MAXLEN]; - CharToOem(initDir, initDirOEM); - CharToOem(cmdLine, cmdLineOEM); - batNotEmpty = TRUE; - if ((initDirOEM[0] != 0 && - (initDirOEM[1] == ':' && // "@C:" - (!WriteFile(file, "@", 1, &written, NULL) || - !WriteFile(file, initDirOEM, 2, &written, NULL) || - !WriteFile(file, "\r\n", 2, &written, NULL))) || - (initDirOEM[1] == ':' && // "@cd C:\\path" - (!WriteFile(file, "@cd \"", 5, &written, NULL) || - !WriteFile(file, initDirOEM, (DWORD)strlen(initDirOEM), &written, NULL) || - !WriteFile(file, "\"\r\n", 3, &written, NULL)))) || - !WriteFile(file, "call ", 5, &written, NULL) || - !WriteFile(file, cmdLineOEM, (DWORD)strlen(cmdLineOEM), &written, NULL) || - !WriteFile(file, "\r\n", 2, &written, NULL)) - { - error = TRUE; - break; - } - } - else // direct execution - { - // the original launching via CreateProcess couldn't run screen savers (*.SCR) - // or Control Panel items (*.cpl) and people kept complaining - // - // try executing it via ShellExecuteEx - it appears to work ;-) - // additionally, launch restrictions will be handled - - // set correct default directories for individual drives - MainWindow->SetDefaultDirectories((initDir[0] != 0) ? initDir : NULL); - - // to work with old configurations, remove the " character from the start and end of cmdLine - int cmdLen = (int)strlen(cmdLine); - if (cmdLen > 1 && cmdLine[0] == '\"' && cmdLine[cmdLen - 1] == '\"') - { - memmove(cmdLine, cmdLine + 1, cmdLen - 2); - cmdLine[cmdLen - 2] = 0; - } - // better not swallow backslashes so that we don't destroy some OLE paths - //RemoveRedundantBackslahes(cmdLine); // ShellExecuteEx dislikes multiple backslashes, "$(SalDir)\salamand.exe" - - CShellExecuteWnd shellExecuteWnd; - SHELLEXECUTEINFO sei; - memset(&sei, 0, sizeof(SHELLEXECUTEINFO)); - sei.cbSize = sizeof(SHELLEXECUTEINFO); - sei.hwnd = shellExecuteWnd.Create(parent, "SEW: CMainWindow::UserMenu"); // handle to any message boxes that the system might produce while executing - sei.lpFile = cmdLine; - sei.lpParameters = arguments; - sei.lpDirectory = (initDir[0] != 0) ? initDir : NULL; - sei.nShow = SW_SHOWNORMAL; - - if (!ShellExecuteEx(&sei)) - { - DWORD err = GetLastError(); - char buff[4 * MAX_PATH]; - if (strlen(cmdLine) > 2 * MAX_PATH) // "always false" (arguments are in 'arguments'): shorten overly long command lines for error display - strcpy(cmdLine + 2 * MAX_PATH - 4, "..."); - sprintf(buff, LoadStr(IDS_EXECERROR), cmdLine, GetErrorText(err)); - SalMessageBox(parent, buff, LoadStr(IDS_ERRORTITLE), MB_OK | MB_ICONEXCLAMATION); - break; - } - } - } - index++; - firstRound = FALSE; - if (userMenuValidationData.MustHandleItemsAsGroup) - break; // in this mode, only one command is executed for all selected items - } - - if (buildBat) - { - lastErr = GetLastError(); - DWORD size; - size = GetFileSize(file, NULL); - HANDLES(CloseHandle(file)); - - DiskCache.NamePrepared(batUniqueName, CQuadWord(size, 0)); - - if (!error) // run the .bat - { - if (batNotEmpty) - { - MainWindow->SetDefaultDirectories((initDir[0] != 0) ? initDir : NULL); - - STARTUPINFO si; - memset(&si, 0, sizeof(STARTUPINFO)); - si.cb = sizeof(STARTUPINFO); - si.lpTitle = LoadStr(IDS_COMMANDSHELL); - si.dwFlags = STARTF_USESHOWWINDOW; - POINT p; - if (UserMenuItems->At(itemIndex)->UseWindow && - MultiMonGetDefaultWindowPos(MainWindow->HWindow, &p)) - { - // if the main window is on another monitor, we should open - // the new window there, preferably at the default position (as on the primary monitor) - si.dwFlags |= STARTF_USEPOSITION; - si.dwX = p.x; - si.dwY = p.y; - } - si.wShowWindow = (UserMenuItems->At(itemIndex)->UseWindow ? SW_SHOWNORMAL : SW_HIDE); - - PROCESS_INFORMATION pi; - - GetEnvironmentVariable("COMSPEC", cmdLine, USRMNUCMDLINE_MAXLEN - 20); - AddDoubleQuotesIfNeeded(cmdLine, USRMNUCMDLINE_MAXLEN - 10); // CreateProcess requires the name with spaces in quotes (otherwise it tries various options, see help) - if (!UserMenuItems->At(itemIndex)->UseWindow || UserMenuItems->At(itemIndex)->CloseShell) - strcat(cmdLine, " /C "); // run command and close immediately after it finishes - else - strcat(cmdLine, " /K "); // run command and keep the shell open after it finishes - - char* s = cmdLine + strlen(cmdLine); - if ((s - cmdLine) + strlen(batName) < USRMNUCMDLINE_MAXLEN - 2) - sprintf(s, "\"%s\"", batName); - else - strcpy(cmdLine, batName); - - if (!HANDLES(CreateProcess(NULL, cmdLine, NULL, NULL, FALSE, - CREATE_DEFAULT_ERROR_MODE | NORMAL_PRIORITY_CLASS, - NULL, NULL, &si, &pi))) - { - DWORD err = GetLastError(); - char buff[4 * MAX_PATH]; - if (strlen(cmdLine) > 2 * MAX_PATH) - strcpy(cmdLine + 2 * MAX_PATH, "..."); // shorten just in case (probably never needed) - sprintf(buff, LoadStr(IDS_EXECERROR), cmdLine, GetErrorText(err)); - DiskCache.ReleaseName(batUniqueName, FALSE); - SalMessageBox(parent, buff, LoadStr(IDS_ERRORTITLE), MB_OK | MB_ICONEXCLAMATION); - } - else - { - DiskCache.AssignName(batUniqueName, pi.hProcess, TRUE, crtDirect); - // HANDLES(CloseHandle(pi.hProcess)); // handled by DiskCache - HANDLES(CloseHandle(pi.hThread)); - } - } - else // an empty .BAT is not worth running (in case of low memory or other crazy errors) - { - DiskCache.ReleaseName(batUniqueName, FALSE); - } - } - else - { - ERROR_LABEL: - - DiskCache.ReleaseName(batUniqueName, FALSE); - if (!skipErrorMessage) - { - SalMessageBox(parent, GetErrorText(lastErr), LoadStr(IDS_ERRORTITLE), - MB_OK | MB_ICONEXCLAMATION); - } - } - } - } - } - UpdateWindow(parent); -} - -void CMainWindow::SetDefaultDirectories(const char* curPath) -{ - CALL_STACK_MESSAGE2("CMainWindow::SetDefaultDirectories(%s)", curPath); - //--- restore DefaultDir - MainWindow->UpdateDefaultDir(TRUE); - //--- set environment variables - char name[4] = "= :"; - const char* dir; - char d; - for (d = 'a'; d <= 'z'; d++) - { - name[1] = d; - if (curPath != NULL && d == LowerCase[curPath[0]]) // UNC paths are ignored - dir = curPath; - else - dir = DefaultDir[d - 'a']; - - if (dir[1] == ':' && dir[2] == '\\' && dir[3] == 0) - SetEnvironmentVariable(name, NULL); - else - SetEnvironmentVariable(name, dir); - } -} - -BOOL CMainWindow::HandleCtrlLetter(char c) -{ - CALL_STACK_MESSAGE2("CMainWindow::HandleCtrlLetter(%u)", c); - if ((GetKeyState(VK_SHIFT) & 0x8000) != 0) - { // change drive via Shift+letter - GetActivePanel()->ChangeDrive(c); - } - else // NC + Windows Ctrl+? hotkeys - { - WPARAM cmd; - switch (c) // only upper-case characters reach here - { - case 'A': - cmd = CM_ACTIVESELECTALL; - break; - - case 'C': // copy - case 'X': // cut - { - BOOL files = FALSE; - if (GetActivePanel() != NULL) - { - if (GetActivePanel()->GetCaretIndex() == 0) - { - if (0 == GetActivePanel()->Dirs->Count || - strcmp(GetActivePanel()->Dirs->At(0).Name, "..") != 0) - { - files = GetActivePanel()->Dirs->Count + GetActivePanel()->Files->Count > 0; - } - else - { - int count = GetActivePanel()->GetSelCount(); - if (count == 1) - { - int index; - GetActivePanel()->GetSelItems(1, &index); - files = index != 0; - } - else - files = count > 0; - } - } - else - files = GetActivePanel()->GetCaretIndex() > 0; - } - if (!files) - return FALSE; // cut and copy cannot be performed - cmd = (c == 'C') ? CM_CLIPCOPY : CM_CLIPCUT; - break; - } - - case 'D': - cmd = CM_ACTIVEUNSELECTALL; - break; - case 'E': - cmd = CM_EMAILFILES; - break; - case 'F': - cmd = CM_FINDFILE; - break; - case 'G': - cmd = CM_ACTIVE_CHANGEDIR; - break; - case 'H': - cmd = CM_TOGGLEHIDDENFILES; - break; - case 'I': - cmd = CM_LAST_PLUGIN_CMD; - break; - case 'K': - cmd = CM_CONVERTFILES; - break; - case 'L': - cmd = CM_DRIVEINFO; - break; - case 'M': - cmd = CM_FILELIST; - break; - case 'N': - cmd = CM_TOGGLEELASTICSMART; - break; - case 'P': - cmd = CM_SEC_PERMISSIONS; - break; - case 'Q': - cmd = CM_OCCUPIEDSPACE; - break; - case 'R': - cmd = CM_ACTIVEREFRESH; - break; - case 'S': - cmd = CM_CLIPPASTELINKS; - break; - case 'T': - cmd = CM_AFOCUSSHORTCUT; - break; - case 'U': - cmd = CM_SWAPPANELS; - break; - case 'V': - cmd = CM_CLIPPASTE; - break; - case 'W': - cmd = CM_RESELECT; - break; - - default: - return FALSE; - } - SendMessage(HWindow, WM_COMMAND, cmd, 0); - } - return TRUE; -} - -void CMainWindow::ChangePanel(BOOL force) -{ - CALL_STACK_MESSAGE1("CMainWindow::ChangePanel()"); - - MainWindow->CancelPanelsUI(); // cancel QuickSearch and QuickEdit - if (IsIconic(HWindow)) - return; - - CFilesWindow* p1 = GetActivePanel(); - CFilesWindow* p2 = GetNonActivePanel(); - - BOOL change = FALSE; - if (force || p2->CanBeFocused()) - change = TRUE; - else - { - // if a panel is ZOOMed, minimize it and ZOOM the other one - if (IsPanelZoomed(TRUE) || IsPanelZoomed(FALSE)) - { - if (IsPanelZoomed(TRUE)) - SplitPosition = 0.0; - else - SplitPosition = 1.0; - LayoutWindows(); - change = TRUE; - } - } - - if (change) - { - SetActivePanel(p2); - - // ensure the active panel header is redrawn - if (p1->DirectoryLine != NULL) - p1->DirectoryLine->InvalidateAndUpdate(FALSE); - if (p2->DirectoryLine != NULL) - p2->DirectoryLine->InvalidateAndUpdate(FALSE); - - UpdateDriveBars(); // press the correct drive in the drive bar - - // ReleaseMenuNew(); - if (EditMode) - { - p1->RedrawIndex(p1->FocusedIndex); - int i = p2->GetCaretIndex(); - i = max(i, 0); - p2->SetCaretIndex(i, TRUE); - p2->RedrawIndex(i); - } - else - { - if (GetFocus() != p2->GetListBoxHWND()) - SetFocus(p2->GetListBoxHWND()); - int i = p2->GetCaretIndex(); - i = max(i, 0); - p2->SetCaretIndex(i, FALSE); - } - EditWindowSetDirectory(); - IdleRefreshStates = TRUE; // on the next Idle, force checking of state variables - MainWindow->UpdateDefaultDir(TRUE); - - // broadcast this news to all loaded plugins - Plugins.Event(PLUGINEVENT_PANELACTIVATED, p2 == LeftPanel ? PANEL_LEFT : PANEL_RIGHT); - } -} - -void CMainWindow::FocusPanel(CFilesWindow* focus, BOOL testIfMainWndActive) -{ - CALL_STACK_MESSAGE2("CMainWindow::FocusPanel(, %d)", testIfMainWndActive); - MainWindow->CancelPanelsUI(); // cancel QuickSearch and QuickEdit - - if (!IsIconic(HWindow) && !focus->CanBeFocused()) - focus = ((focus == LeftPanel) ? RightPanel : LeftPanel); - - if (GetFocus() != focus->GetListBoxHWND()) - { - if (!testIfMainWndActive || GetForegroundWindow() == HWindow) // focus only if main window is active (FTP plugin with non-modal Welcome Message window could focus the panel on command line shutdown -> deactivate Welcome Message) - SetFocus(focus->GetListBoxHWND()); - else - focus->OnSetFocus(FALSE); // simulate focus in the panel - } - - CFilesWindow* old = GetActivePanel(); - SetActivePanel(focus); - - UpdateDriveBars(); // press the correct drive in the drive bar - - // ensure the active panel header is redrawn - if (old != focus) - { - // activated a different panel, let it set its enablers - RefreshCommandStates(); - // fixes a bug (present in 2.5b10) when users had one panel active with focus on UpDir - // and then right-clicked a file in the passive panel and chose DELETE from the context menu - // nothing happened because the EnablerFilesDelete enabler was FALSE (not updated for the new panel) - // see /viewtopic.php?t=181 - - // repaint the directory line of both panels - if (old->DirectoryLine != NULL) - old->DirectoryLine->InvalidateAndUpdate(FALSE); - if (focus->DirectoryLine != NULL) - focus->DirectoryLine->InvalidateAndUpdate(FALSE); - // ReleaseMenuNew(); - EditWindowSetDirectory(); - IdleRefreshStates = TRUE; // on the next Idle, force checking of state variables - // broadcast this news to loaded plugins - Plugins.Event(PLUGINEVENT_PANELACTIVATED, focus == LeftPanel ? PANEL_LEFT : PANEL_RIGHT); - } - //--- restore DefaultDir - MainWindow->UpdateDefaultDir(TRUE); -} - -void CMainWindow::ShowCommandLine() -{ - CALL_STACK_MESSAGE1("CMainWindow::ShowCommandLine()"); - if (EditWindow == NULL || EditWindow->HWindow != NULL) - return; - - if (!EditWindow->Create(HWindow, IDC_EDITWINDOW)) - TRACE_E("Unable to create EditWindow."); - else - { - LayoutWindows(); - EditWindow->RestoreContent(); - ShowWindow(EditWindow->HWindow, SW_SHOW); - if (EditWindow->IsEnabled()) - SetFocus(EditWindow->HWindow); - IdleRefreshStates = TRUE; // on the next Idle, force checking of state variables - } -} - -void CMainWindow::HideCommandLine(BOOL storeContent, BOOL focusPanel) -{ - if (EditWindow == NULL || EditWindow->HWindow == NULL) - return; - - if (storeContent) - EditWindow->StoreContent(); - - DestroyWindow(EditWindow->HWindow); - if (focusPanel) - FocusPanel(GetActivePanel()); - LayoutWindows(); - IdleRefreshStates = TRUE; // on the next Idle, force checking of state variables -} - -//**************************************************************************** -// -// Image Drag functions -// - -void ImageDragBegin(int width, int height, int dxHotspot, int dyHotspot) -{ - if (ImageDragging) - TRACE_E("ImageDragging == TRUE - this should never happen"); - ImageDragW = width; - ImageDragH = height; - ImageDragDxHotspot = dxHotspot; - ImageDragDyHotspot = dyHotspot; - ImageDragging = TRUE; -} - -void ImageDragEnd() -{ - if (!ImageDragging) - TRACE_E("ImageDragging == FALSE - this should never happen"); - ImageDragX = INT_MAX; - ImageDragY = INT_MAX; - ImageDragW = INT_MAX; - ImageDragH = INT_MAX; - ImageDragging = FALSE; -} - -BOOL ImageDragInterfereRect(const RECT* rect) -{ - if (!ImageDraggingVisible) - return FALSE; - if (ImageDragX == INT_MAX || ImageDragY == INT_MAX) - { - TRACE_E("ImageDragX == INT_MAX || ImageDragY == INT_MAX"); - return TRUE; // just to be safe - } - if (ImageDragW == INT_MAX || ImageDragH == INT_MAX) - { - TRACE_E("ImageDragW == INT_MAX || ImageDragH == INT_MAX"); - return TRUE; // just to be safe - } - RECT r; - r.left = ImageDragX - ImageDragDxHotspot; - r.top = ImageDragY - ImageDragDyHotspot; - r.right = r.left + ImageDragW; - r.bottom = r.top + ImageDragH; - RECT dstR; - IntersectRect(&dstR, rect, &r); - return !IsRectEmpty(&dstR); -} - -void ImageDragEnter(int x, int y) -{ - CALL_STACK_MESSAGE3("ImageDragEnter(%d, %d)", x, y); - if (ImageDraggingVisible) - TRACE_E("ImageDraggingVisible == TRUE - this should never happen"); - if (!ImageDragging) - TRACE_E("ImageDragging == FALSE - this should never happen"); - ImageDragX = x; - ImageDragY = y; - ShowCaretAfterDrop = MainWindow->EditWindow->HideCaret(); - ImageList_DragEnter(MainWindow->HWindow, x - MainWindow->WindowRect.left, y - MainWindow->WindowRect.top); - ImageDraggingVisible = TRUE; - ImageDraggingVisibleLevel = 1; -} - -void ImageDragMove(int x, int y) -{ - CALL_STACK_MESSAGE3("ImageDragMove(%d, %d)", x, y); - if (!ImageDragging) - TRACE_E("ImageDragging == FALSE - this should never happen"); - ImageDragX = x; - ImageDragY = y; - ImageList_DragMove(x - MainWindow->WindowRect.left, y - MainWindow->WindowRect.top); -} - -void ImageDragLeave() -{ - CALL_STACK_MESSAGE1("ImageDragLeave()"); - if (!ImageDragging) - TRACE_E("ImageDragging == FALSE - this should never happen"); - ImageList_DragLeave(MainWindow->HWindow); - ImageDraggingVisible = FALSE; - ImageDraggingVisibleLevel = 0; - ImageDragX = INT_MAX; - ImageDragY = INT_MAX; - if (ShowCaretAfterDrop) - { - MainWindow->EditWindow->ShowCaret(); - ShowCaretAfterDrop = FALSE; - } -} - -void ImageDragShow(BOOL show) -{ - CALL_STACK_MESSAGE2("ImageDragShow(%d)", show); - if (!ImageDragging) - TRACE_E("ImageDragging == FALSE - this should never happen"); - ImageDraggingVisibleLevel += show ? 1 : -1; - - if (show && ImageDraggingVisibleLevel == 1) - { - ImageList_DragShowNolock(TRUE); - ImageDraggingVisible = TRUE; - } - if (!show && ImageDraggingVisibleLevel == 0) - { - ImageList_DragShowNolock(FALSE); - ImageDraggingVisible = FALSE; - } -} - -//**************************************************************************** -// -// Context Help (Shift+F1) support -// - -///////////////////////////////////////////////////////////////////////////// -// useful message ranges - -#define WM_SYSKEYFIRST WM_SYSKEYDOWN -#define WM_SYSKEYLAST WM_SYSDEADCHAR - -#define WM_NCMOUSEFIRST WM_NCMOUSEMOVE -#define WM_NCMOUSELAST WM_NCMBUTTONDBLCLK - -HWND GetParentOwner(HWND hWnd) -{ - CALL_STACK_MESSAGE_NONE - // return parent in the Windows sense - return (GetWindowLongPtr(hWnd, GWL_STYLE) & WS_CHILD) ? GetParent(hWnd) : GetWindow(hWnd, GW_OWNER); -} - -HWND GetTopLevelParent(HWND hWindow) -{ - CALL_STACK_MESSAGE_NONE - HWND hWndParent = hWindow; - HWND hWndT; - while ((hWndT = GetParentOwner(hWndParent)) != NULL) - hWndParent = hWndT; - - return hWndParent; -} - -BOOL IsDescendant(HWND hWndParent, HWND hWndChild) -{ - CALL_STACK_MESSAGE_NONE - // helper for detecting whether child descendent of parent - // (works with owned popups as well) - if (!IsWindow(hWndParent)) - { - TRACE_E("hWndParent is not window"); - return FALSE; - } - if (!IsWindow(hWndChild)) - { - TRACE_E("hWndChild is not window"); - return FALSE; - } - - do - { - if (hWndParent == hWndChild) - return TRUE; - - hWndChild = GetParentOwner(hWndChild); - } while (hWndChild != NULL); - - return FALSE; -} - -DWORD -CMainWindow::MapClientArea(POINT point) -{ - DWORD dwContext = 0; - - CMainWindowsHitTestEnum hit = HitTest(point.x, point.y); - CToolBar* toolbar = NULL; - - switch (hit) - { - case mwhteMenu: - dwContext = IDH_MENUBAR; - break; - - case mwhteTopToolbar: - { - dwContext = IDH_TOPTOOLBAR; - toolbar = TopToolBar; - break; - } - - case mwhtePluginsBar: - dwContext = IDH_PLUGINSBAR; - break; - - case mwhteMiddleToolbar: - { - dwContext = IDH_MIDDLETOOLBAR; - toolbar = MiddleToolBar; - break; - } - - case mwhteUMToolbar: - dwContext = IDH_UMTOOLBAR; - break; - - case mwhteHPToolbar: - dwContext = IDH_HPTOOLBAR; - break; - - case mwhteDriveBar: - dwContext = IDH_DRIVEBAR; - break; - - case mwhteCmdLine: - dwContext = IDH_COMMANDLINE; - break; - - case mwhteBottomToolbar: - { - dwContext = IDH_BOTTOMTOOLBAR; - toolbar = BottomToolBar; - break; - } - - case mwhteSplitLine: - dwContext = IDH_SPLITBAR; - break; - - case mwhteLeftDirLine: - { - dwContext = IDH_DIRECTORYLINE; - - if (LeftPanel->DirectoryLine->ToolBar != NULL && - LeftPanel->DirectoryLine->ToolBar->HWindow != NULL) - toolbar = LeftPanel->DirectoryLine->ToolBar; - break; - } - - case mwhteRightDirLine: - { - dwContext = IDH_DIRECTORYLINE; - - if (RightPanel->DirectoryLine->ToolBar != NULL && - RightPanel->DirectoryLine->ToolBar->HWindow != NULL) - toolbar = RightPanel->DirectoryLine->ToolBar; - break; - } - - case mwhteLeftHeaderLine: - case mwhteRightHeaderLine: - dwContext = IDH_HEADERLINE; - break; - - case mwhteLeftStatusLine: - case mwhteRightStatusLine: - dwContext = IDH_INFOLINE; - break; - - case mwhteLeftWorkingArea: - case mwhteRightWorkingArea: - dwContext = IDH_WORKINGAREA; - break; - } - - // get the ID of the button the user clicked - if (toolbar != NULL) - { - POINT p; - p = point; - ScreenToClient(toolbar->HWindow, &p); - int index = toolbar->HitTest(p.x, p.y); - if (index != -1) - { - TLBI_ITEM_INFO2 tii; - tii.Mask = TLBI_MASK_ID; - if (toolbar->GetItemInfo2(index, TRUE, &tii)) - dwContext = tii.ID; - } - } - return dwContext; -} - -DWORD -CMainWindow::MapNonClientArea(int iHit) -{ - DWORD dwContext = 0; - switch (iHit) - { - /* - case HTBORDER: - case HTBOTTOM: - case HTBOTTOMLEFT: - case HTBOTTOMRIGHT: - case HTLEFT: - case HTRIGHT: - case HTTOP: - case HTTOPLEFT: - case HTTOPRIGHT: - case HTCAPTION: - case HTREDUCE: - case HTZOOM: -*/ - - case HTMINBUTTON: - case HTMAXBUTTON: - case HTCLOSE: - dwContext = IDH_MINMAXCLOSEBTNS; - break; - } - return dwContext; -} - -BOOL CMainWindow::CanEnterHelpMode() -{ - CALL_STACK_MESSAGE1("CMainWindow::CanEnterHelpMode()"); - if (HelpMode == HELP_ACTIVE) // already in help mode? - return FALSE; - - if (HHelpCursor == NULL) - { - HHelpCursor = LoadCursor(NULL, IDC_HELP); - if (HHelpCursor == NULL) - return FALSE; - } - - return TRUE; -} - -void CMainWindow::OnContextHelp() -{ - CALL_STACK_MESSAGE1("CMainWindow::OnContextHelp()"); - // don't enter twice, and don't enter if initialization fails - if (HelpMode == HELP_ACTIVE || !CanEnterHelpMode()) - return; - - // don't enter help mode with pending WM_USER_EXITHELPMODE message - MSG msg; - if (PeekMessage(&msg, HWindow, WM_USER_EXITHELPMODE, WM_USER_EXITHELPMODE, PM_REMOVE | PM_NOYIELD)) - return; - - BOOL bHelpMode = HelpMode; - if (HelpMode != HELP_INACTIVE && HelpMode != HELP_ENTERING) - return; - HelpMode = HELP_ACTIVE; - - if (bHelpMode == HELP_INACTIVE) - { - // need to delay help startup until later - PostMessage(HWindow, WM_COMMAND, CM_HELP_CONTEXT, 0); - HelpMode = HELP_ENTERING; - return; - } - - IdleRefreshStates = TRUE; // trigger idle update - OnEnterIdle(); // redraw the toolbar - - if (HelpMode != HELP_ACTIVE) - return; - - MenuBar->SetHelpMode(TRUE); - - // reset the bottom toolbar to its normal state - BottomToolBar->SetState(btbsNormal); - BottomToolBar->UpdateItemsState(); - - // if someone is monitoring the mouse, stop monitoring - TRACKMOUSEEVENT tme; - tme.cbSize = sizeof(tme); - tme.dwFlags = TME_QUERY; - if (TrackMouseEvent(&tme) && tme.hwndTrack != NULL) - SendMessage(tme.hwndTrack, WM_MOUSELEAVE, 0, 0); - - DWORD dwContext = 0; - POINT point; - - GetCursorPos(&point); - SetHelpCapture(point, NULL); - LONG lIdleCount = 0; - - BOOL first = TRUE; - - HWND hDirtyWindow = NULL; - while (HelpMode) - { - if (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) - { - if (!ProcessHelpMsg(msg, &dwContext, &hDirtyWindow)) - break; - if (dwContext != 0) - return; - } - else - { - if (first) - { - // buffer a mouse move to fall through to the toolbar when the cursor is over disabled buttons - POINT p; - GetCursorPos(&p); - ScreenToClient(HWindow, &p); - PostMessage(HWindow, WM_MOUSEMOVE, 0, MAKELPARAM(point.x, point.y)); - // originally this buffering was before the loop, but that misbehaved - // if a tooltip for a disabled button was shown and I pressed Shift+F1: - // when the tooltip vanished, WM_MOUSELEAVE was delivered (PeekMessage distributes messages, see MSDN). - // The button under the cursor then drew as enabled and then immediately as disabled again. - // With this trick I wait until all messages are processed and MOUSEMOVE is definitely handled - - first = FALSE; - } - else - { - WaitMessage(); - } - } - } - if (hDirtyWindow != NULL) - SendMessage(hDirtyWindow, WM_USER_HELP_MOUSELEAVE, 0, 0); - - MenuBar->SetHelpMode(FALSE); - HelpMode = HELP_INACTIVE; - ReleaseCapture(); - - // make sure the cursor is set appropriately - SetCapture(HWindow); - ReleaseCapture(); - - if (dwContext != 0) - OpenHtmlHelp(NULL, HWindow, HHCDisplayContext, dwContext, FALSE); - - IdleRefreshStates = TRUE; // trigger idle update -} - -HWND CMainWindow::SetHelpCapture(POINT point, BOOL* pbDescendant) -// set or release capture, depending on where the mouse is -// also assign the proper cursor to be displayed. -{ - CALL_STACK_MESSAGE1("CMainWindow::SetHelpCapture(,)"); - if (!HelpMode) - return NULL; - - HWND hWndCapture = GetCapture(); - HWND hWndHit = WindowFromPoint(point); - HWND hTopHit = GetTopLevelParent(hWndHit); - HWND hTopActive = GetTopLevelParent(GetActiveWindow()); - BOOL bDescendant = FALSE; - DWORD hCurTask = GetCurrentThreadId(); - DWORD hTaskHit = hWndHit != NULL ? GetWindowThreadProcessId(hWndHit, NULL) : NULL; - - if (hTopActive == NULL || hWndHit == GetDesktopWindow()) - { - if (hWndCapture == HWindow) - ReleaseCapture(); - SetCursor(HHelpCursor); - } - else if (hTopActive == NULL || - hWndHit == NULL || hCurTask != hTaskHit || - !IsDescendant(HWindow, hWndHit)) - { - if (hCurTask != hTaskHit) - hWndHit = NULL; - if (hWndCapture == HWindow) - ReleaseCapture(); - } - else - { - bDescendant = TRUE; - if (hTopActive != hTopHit) - hWndHit = NULL; - else - { - if (hWndCapture != HWindow) - SetCapture(HWindow); - SetCursor(HHelpCursor); - } - } - if (pbDescendant != NULL) - *pbDescendant = bDescendant; - return hWndHit; -} - -BOOL CMainWindow::ProcessHelpMsg(MSG& msg, DWORD* pContext, HWND* hDirtyWindow) -{ - CALL_STACK_MESSAGE4("CMainWindow::ProcessHelpMsg(0x%X, 0x%IX, 0x%IX,)", msg.message, msg.wParam, msg.lParam); - - if (pContext == NULL) - { - TRACE_E("pContext == NULL"); - return FALSE; - } - if (msg.message == WM_USER_EXITHELPMODE || - (msg.message == WM_KEYDOWN && msg.wParam == VK_ESCAPE)) - { - PeekMessage(&msg, NULL, msg.message, msg.message, PM_REMOVE); - return FALSE; - } - - POINT point; - if ((msg.message >= WM_MOUSEFIRST && msg.message <= WM_MOUSELAST) || - (msg.message >= WM_NCMOUSEFIRST && msg.message <= WM_NCMOUSELAST)) - { - BOOL bDescendant; - HWND hWndHit = SetHelpCapture(msg.pt, &bDescendant); - if (hWndHit == NULL) - { - PeekMessage(&msg, NULL, msg.message, msg.message, PM_REMOVE); // eat the message - return TRUE; - } - - if (bDescendant) - { - if (msg.message != WM_LBUTTONDOWN) - { - // Hit one of our owned windows -- eat the message. - PeekMessage(&msg, NULL, msg.message, msg.message, PM_REMOVE); - - // notify windows that wish to highlight items during Shift+F1 mode - if (msg.message == WM_MOUSEMOVE) - { - if (*hDirtyWindow != NULL && *hDirtyWindow != hWndHit) - SendMessage(*hDirtyWindow, WM_USER_HELP_MOUSELEAVE, 0, 0); - *hDirtyWindow = hWndHit; // this window will need to receive a LEAVE message - - POINT p = msg.pt; - ScreenToClient(hWndHit, &p); - SendMessage(hWndHit, WM_USER_HELP_MOUSEMOVE, 0, MAKELPARAM(p.x, p.y)); - } - return TRUE; - } - int iHit = (int)SendMessage(hWndHit, WM_NCHITTEST, 0, - MAKELONG(msg.pt.x, msg.pt.y)); - if (iHit == HTSYSMENU) - { - if (GetCapture() != HWindow) - { - TRACE_E("GetCapture() != HWindow"); - return FALSE; - } - ReleaseCapture(); - // the message we peeked changes into a non-client because - // of the release capture. - GetMessage(&msg, NULL, WM_NCLBUTTONDOWN, WM_NCLBUTTONDOWN); - DispatchMessage(&msg); - GetCursorPos(&point); - SetHelpCapture(point, NULL); - } - else if (iHit == HTCLIENT) - { - if (hWndHit == MenuBar->HWindow) - { - PeekMessage(&msg, NULL, msg.message, msg.message, PM_REMOVE); - ReleaseCapture(); - POINT p = msg.pt; - ScreenToClient(hWndHit, &p); - msg.lParam = MAKELPARAM(p.x, p.y); - msg.hwnd = hWndHit; - msg.message = WM_MOUSEMOVE; - DispatchMessage(&msg); - msg.message = WM_LBUTTONDOWN; - DispatchMessage(&msg); - GetCursorPos(&point); - SetHelpCapture(point, NULL); - } - else - { - *pContext = MapClientArea(msg.pt); - PeekMessage(&msg, NULL, msg.message, msg.message, PM_REMOVE); - return FALSE; - } - } - else - { - *pContext = MapNonClientArea(iHit); - PeekMessage(&msg, NULL, msg.message, msg.message, PM_REMOVE); - return FALSE; - } - } - else - { - // Hit one of our apps windows (or desktop) -- dispatch the message. - PeekMessage(&msg, NULL, msg.message, msg.message, PM_REMOVE); - - // Dispatch mouse messages that hit the desktop! - DispatchMessage(&msg); - } - } - else if (msg.message == WM_SYSCOMMAND || - (msg.message >= WM_KEYFIRST && msg.message <= WM_KEYLAST)) - { - if (GetCapture() != NULL) - { - ReleaseCapture(); - MSG msg2; - while (PeekMessage(&msg2, NULL, WM_MOUSEFIRST, WM_MOUSELAST, PM_REMOVE | PM_NOYIELD)) - ; - } - if (PeekMessage(&msg, NULL, msg.message, msg.message, PM_NOREMOVE)) - { - GetMessage(&msg, NULL, msg.message, msg.message); - - // ensure sending messages to our menu (avoiding the need for a keyboard hook) - // this supports entering the menu via Alt/F10/Alt+letter during help mode - if (MenuBar == NULL || !MenuBar->IsMenuBarMessage(&msg)) - { - TranslateMessage(&msg); - if (msg.message == WM_SYSCOMMAND || - (msg.message >= WM_SYSKEYFIRST && - msg.message <= WM_SYSKEYLAST)) - { - // only dispatch system keys and system commands - DispatchMessage(&msg); - } - } - } - GetCursorPos(&point); - SetHelpCapture(point, NULL); - } - else - { - // allow all other messages to go through (capture still set) - if (PeekMessage(&msg, NULL, msg.message, msg.message, PM_REMOVE)) - DispatchMessage(&msg); - } - - return TRUE; -} - -void CMainWindow::ExitHelpMode() -{ - CALL_STACK_MESSAGE1("CMainWindow::ExitHelpMode()"); - // if not in help mode currently, this is a no-op - if (!HelpMode) - return; - - // only post new WM_EXITHELPMODE message if one doesn't already exist - // in the queue. - MSG msg; - if (!PeekMessage(&msg, HWindow, WM_USER_EXITHELPMODE, WM_USER_EXITHELPMODE, PM_REMOVE | PM_NOYIELD)) - PostMessage(HWindow, WM_USER_EXITHELPMODE, 0, 0); - - // release capture if this window has it - if (GetCapture() == HWindow) - ReleaseCapture(); - - HelpMode = HELP_INACTIVE; - IdleRefreshStates = TRUE; // trigger idle update -} - -void CMainWindow::UpdateDriveBars() -{ - if (DriveBar == NULL || DriveBar2 == NULL) - return; - - if (DriveBar->HWindow == NULL) - return; - - if (DriveBar2->HWindow == NULL) - { - // when there is only one drive bar it belongs to the active panel - DriveBar->SetCheckedDrive(GetActivePanel()); - } - else - { - DriveBar->SetCheckedDrive(LeftPanel); - DriveBar2->SetCheckedDrive(RightPanel); - } -} - -void CMainWindow::CancelPanelsUI() -{ - LeftPanel->CancelUI(); - RightPanel->CancelUI(); -} - -BOOL CMainWindow::QuickRenameWindowActive() -{ - return (LeftPanel->IsQuickRenameActive() || RightPanel->IsQuickRenameActive()); -} - -BOOL CMainWindow::DoQuickRename() -{ - if (LeftPanel->IsQuickRenameActive()) - return LeftPanel->HandeQuickRenameWindowKey(VK_RETURN); - if (RightPanel->IsQuickRenameActive()) - return RightPanel->HandeQuickRenameWindowKey(VK_RETURN); - return TRUE; // OK -} - -// -// **************************************************************************** -// LockUI -// - -void CMainWindow::LockUI(BOOL lock, HWND hToolWnd, const char* lockReason) -{ - if (LockedUI && lock) - { - TRACE_E("CMainWindow::LockUI(): main window is already locked! Ignoring this request..."); - return; - } - if (!LockedUI && !lock) - { - TRACE_E("CMainWindow::LockUI(): main window is not locked! Ignoring this request..."); - return; - } - - LockedUI = lock; - if (lock) - { - LockedUIToolWnd = hToolWnd; - if (lockReason != NULL) - LockedUIReason = DupStr(lockReason); - } - else - { - LockedUIToolWnd = NULL; - if (LockedUIReason != NULL) - free(LockedUIReason); - } - - if (HTopRebar != NULL) - EnableWindow(HTopRebar, !lock); - if (MiddleToolBar != NULL && MiddleToolBar->HWindow != NULL) - EnableWindow(MiddleToolBar->HWindow, !lock); - if (BottomToolBar != NULL && BottomToolBar->HWindow != NULL) - EnableWindow(BottomToolBar->HWindow, !lock); - LeftPanel->LockUI(lock); - RightPanel->LockUI(lock); -} - -void CMainWindow::BringLockedUIToolWnd() -{ - if (LockedUIToolWnd != NULL) - SetWindowPos(LockedUIToolWnd, HWindow, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOSENDCHANGING | SWP_NOREDRAW); -} - -// **************************************************************************** - -CFilesWindow* -CMainWindow::GetPanel(int panel) -{ - switch (panel) - { - case PANEL_SOURCE: - return GetActivePanel(); - case PANEL_TARGET: - return GetNonActivePanel(); - case PANEL_LEFT: - return LeftPanel; - case PANEL_RIGHT: - return RightPanel; - default: - TRACE_E("Invalid panel (PANEL_XXX) constant: " << panel); - return NULL; - } -} - -void CMainWindow::PostFocusNameInPanel(int panel, const char* path, const char* name) -{ - CALL_STACK_MESSAGE4("CMainWindow::FocusNameInPanel(%d, %s, %s)", panel, path, name); - CFilesWindow* p = GetPanel(panel); - if (p != NULL) - { - static char pathBackup[MAX_PATH + 200]; - static char nameBackup[MAX_PATH + 200]; - lstrcpyn(pathBackup, path, MAX_PATH + 200); - lstrcpyn(nameBackup, name, MAX_PATH + 200); - PostMessage(p->HWindow, WM_USER_FOCUSFILE, (WPARAM)nameBackup, (LPARAM)pathBackup); - } -} diff --git a/src/mainwnd_commands.cpp b/src/mainwnd_commands.cpp new file mode 100644 index 00000000..41e5aeae --- /dev/null +++ b/src/mainwnd_commands.cpp @@ -0,0 +1,4636 @@ +// SPDX-FileCopyrightText: 2023 Taskscape Ltd +// SPDX-License-Identifier: GPL-2.0-or-later +// CommentsTranslationProject: TRANSLATED + +#include "precomp.h" + +#include "stswnd.h" +#include "editwnd.h" +#include "plugins.h" +#include "fileswnd.h" +#include "usermenu.h" +#include "mainwnd.h" +#include "cfgdlg.h" +#include "dialogs.h" +#include "execute.h" +#include "cache.h" +#include "toolbar.h" +#include "salinflt.h" +#include "menu.h" +extern "C" +{ +#include "shexreg.h" +} +#include "salshlib.h" +#include "zip.h" + +BOOL ImageDragging = FALSE; +BOOL ImageDraggingVisible = FALSE; +BOOL ShowCaretAfterDrop = FALSE; +int ImageDraggingVisibleLevel = 0; +int ImageDragX = INT_MAX; +int ImageDragY = INT_MAX; +int ImageDragW = INT_MAX; +int ImageDragH = INT_MAX; +int ImageDragDxHotspot = INT_MAX; +int ImageDragDyHotspot = INT_MAX; + +//**************************************************************************** +// +// CMainWindow +// + +void RecursiveFindAndCopy(char* srcPath, char* dstPath, char** fromBuf, char** toBuf, int* freeBufNames); + +void CMainWindow::ClearPluginFSFromHistory(CPluginFSInterfaceAbstract* fs) +{ + DirHistory->ClearPluginFSFromHistory(fs); +} + +void CMainWindow::DirHistoryAddPathUnique(int type, const char* pathOrArchiveOrFSName, + const char* archivePathOrFSUserPart, HICON hIcon, + CPluginFSInterfaceAbstract* pluginFS, + CPluginFSInterfaceEncapsulation* curPluginFS) +{ + if (CanAddToDirHistory) + { + DirHistory->AddPathUnique(type, pathOrArchiveOrFSName, archivePathOrFSUserPart, hIcon, + pluginFS, curPluginFS); + if (LeftPanel != NULL) + LeftPanel->DirectoryLine->SetHistory(DirHistory->HasPaths()); + if (RightPanel != NULL) + RightPanel->DirectoryLine->SetHistory(DirHistory->HasPaths()); + } + else + { + if (hIcon != NULL) + HANDLES(DestroyIcon(hIcon)); + } +} + +void CMainWindow::DirHistoryRemoveActualPath(CFilesWindow* panel) +{ + if (panel->Is(ptZIPArchive)) + { + DirHistory->RemoveActualPath(1, panel->GetZIPArchive(), panel->GetZIPPath(), NULL, NULL); + } + else + { + if (panel->Is(ptDisk)) + { + DirHistory->RemoveActualPath(0, panel->GetPath(), NULL, NULL, NULL); + } + else + { + if (panel->Is(ptPluginFS)) + { + char curPath[MAX_PATH]; + if (panel->GetPluginFS()->NotEmpty() && panel->GetPluginFS()->GetCurrentPath(curPath)) + { + DirHistory->RemoveActualPath(2, panel->GetPluginFS()->GetPluginFSName(), curPath, + panel->GetPluginFS()->GetInterface(), panel->GetPluginFS()); + } + } + } + } + if (LeftPanel != NULL) + LeftPanel->DirectoryLine->SetHistory(DirHistory->HasPaths()); + if (RightPanel != NULL) + RightPanel->DirectoryLine->SetHistory(DirHistory->HasPaths()); +} + +void CMainWindow::GetSplitRect(RECT& r) +{ + r.left = SplitPositionPix; + r.top = TopRebarHeight; + r.right = SplitPositionPix + MainWindow->GetSplitBarWidth(); + r.bottom = WindowHeight - EditHeight - BottomToolBarHeight; +} + +void CMainWindow::GetWindowSplitRect(RECT& r) +{ + GetClientRect(HWindow, &r); + r.top = TopRebarHeight; + r.bottom = WindowHeight - EditHeight - BottomToolBarHeight; +} + +BOOL CMainWindow::PtInChild(HWND hChild, POINT p) +{ + if (hChild == NULL) + return FALSE; + RECT r; + GetWindowRect(hChild, &r); + MapWindowPoints(NULL, HWindow, (POINT*)&r, 2); + return PtInRect(&r, p); +} + +BOOL CMainWindow::CloseDetachedFS(HWND parent, CPluginFSInterfaceEncapsulation* detachedFS) +{ + CALL_STACK_MESSAGE1("CMainWindow::CloseDetachedFS()"); + BOOL dummy; // ignored return value + if (!detachedFS->TryCloseOrDetach(CriticalShutdown, FALSE, dummy, FSTRYCLOSE_UNLOADCLOSEDETACHEDFS) && + !CriticalShutdown) // test close; forceClose==TRUE only during a "critical shutdown" + { // ask the user whether to close it even against the FS wishes + char path[2 * MAX_PATH]; + strcpy(path, detachedFS->GetPluginFSName()); + strcat(path, ":"); + char* s = path + strlen(path); + if (!detachedFS->NotEmpty() || !detachedFS->GetCurrentPath(s)) + *s = 0; // cannot obtain the user portion + + char buf[2 * MAX_PATH + 100]; + sprintf(buf, LoadStr(IDS_FSFORCECLOSE), path); + if (SalMessageBox(parent, buf, LoadStr(IDS_QUESTION), + MB_YESNO | MB_ICONQUESTION) == IDYES) // user chooses "close" + { + detachedFS->TryCloseOrDetach(TRUE, FALSE, dummy, FSTRYCLOSE_UNLOADCLOSEDETACHEDFS); + } + else + return FALSE; // user doesn't want to close the detached FS + } + + // close the FS + CPluginInterfaceForFSEncapsulation plugin(detachedFS->GetPluginInterfaceForFS()->GetInterface(), + detachedFS->GetPluginInterfaceForFS()->GetBuiltForVersion()); + if (plugin.NotEmpty()) + { + detachedFS->ReleaseObject(parent); + plugin.CloseFS(detachedFS->GetInterface()); + } + else + TRACE_E("Unexpected situation (2) in CMainWindow::CloseDetachedFS()"); + + return TRUE; // FS is closed +} + +BOOL CMainWindow::CanUnloadPlugin(HWND parent, CPluginInterfaceAbstract* plugin) +{ + CALL_STACK_MESSAGE1("CMainWindow::CanUnloadPlugin()"); + if (LeftPanel != NULL && !LeftPanel->CanUnloadPlugin(parent, plugin)) + return FALSE; + if (RightPanel != NULL && !RightPanel->CanUnloadPlugin(parent, plugin)) + return FALSE; + + // find detached FS belonging to the plug-in 'plugin' and attempt to close them + int i; + for (i = DetachedFSList->Count - 1; i >= 0; i--) // iterate backwards; as we will be deleting from the array (quadratic complexity) + { + CPluginFSInterfaceEncapsulation* detachedFS = DetachedFSList->At(i); + if (detachedFS->GetPluginInterface() == plugin) // belongs to plug-in 'plugin' + { + if (CloseDetachedFS(parent, detachedFS)) + { + DetachedFSList->Delete(i); // remove the detached FS from DetachedFSList + if (!DetachedFSList->IsGood()) + DetachedFSList->ResetState(); + } + else + return FALSE; // unload cannot proceed (user refused to close the plug-in's detached FS) + } + } + + // check if data from the plugin is not in SalShExtPastedData + // (panels leaving archives might have stored them there due to plug-in unload) + if (!SalShExtPastedData.CanUnloadPlugin(parent, plugin)) + return FALSE; // unload cannot proceed + + return TRUE; // unload is possible; all plug-in resources were released +} + +void CMainWindow::MakeFileList() +{ + CALL_STACK_MESSAGE1("CMainWindow::MakeFileList()"); + + BOOL files = FALSE; // cursor is on a file or directory or there is a selection + BOOL upDir = FALSE; // presence of ".." + + CFilesWindow* panel = GetActivePanel(); + + upDir = (panel->Dirs->Count != 0 && strcmp(panel->Dirs->At(0).Name, "..") == 0); + int caret = panel->GetCaretIndex(); + if (caret >= 0) + { + if (caret == 0) + { + if (!upDir) + files = (panel->Dirs->Count + panel->Files->Count > 0); + else + { + int count = panel->GetSelCount(); + if (count == 1) + { + files = (panel->GetSel(0) == FALSE); + } + else + files = (count > 0); + } + } + else + files = TRUE; + } + + if (!files) + return; + + // restore DefaultDir + MainWindow->UpdateDefaultDir(TRUE); + + BeginStopRefresh(); // snooper takes a break + + CFileListDialog dlg(HWindow); + if (dlg.Execute() == IDOK) + { + char fileName[MAX_PATH]; + + switch (Configuration.FileListDestination) + { + case 0: // clipboard + case 1: // viewer + { + if (!SalGetTempFileName(NULL, "MFL", fileName, _countof(fileName), TRUE)) + { + DWORD err = GetLastError(); + char errorText[200 + 2 * MAX_PATH]; + sprintf(errorText, "%s\n\n%s", LoadStr(IDS_ERRORCREATINGTMPFILE), + GetErrorText(err)); + SalMessageBox(HWindow, errorText, LoadStr(IDS_ERRORTITLE), MB_OK | MB_ICONEXCLAMATION); + fileName[0] = 0; + } + break; + } + + case 2: // file + { + strcpy(fileName, Configuration.FileListName); + int errTextID; + if (!SalGetFullName(fileName, &errTextID, GetActivePanel()->Is(ptDisk) ? GetActivePanel()->GetPath() : NULL, panel->NextFocusName)) + { + SalMessageBox(HWindow, LoadStr(errTextID), LoadStr(IDS_ERRORTITLE), MB_OK | MB_ICONEXCLAMATION); + fileName[0] = 0; + } + break; + } + + default: + { + TRACE_E("Unknown destination!"); + fileName[0] = 0; + } + } + + if (fileName[0] != 0) + { + BOOL append = (Configuration.FileListDestination == 2 && Configuration.FileListAppend); + HANDLE hFile = HANDLES_Q(CreateFileUtf8(fileName, GENERIC_WRITE | GENERIC_READ, + FILE_SHARE_READ, NULL, + append ? OPEN_ALWAYS : CREATE_ALWAYS, + FILE_FLAG_RANDOM_ACCESS, + NULL)); + if (hFile != INVALID_HANDLE_VALUE) + { + // position the file pointer + SetFilePointer(hFile, 0, NULL, append ? FILE_END : FILE_BEGIN); + + // fill the file with data -- insert one entry for each file or directory + BOOL deleteFile = TRUE; + if (panel->MakeFileList(hFile)) + { + panel->SetSel(FALSE, -1, TRUE); // force redraw + PostMessage(panel->HWindow, WM_USER_SELCHANGED, 0, 0); // sel-change notify + deleteFile = FALSE; + } + + if (!deleteFile && Configuration.FileListDestination == 0) // clipboard + { + DWORD fileSize = GetFileSize(hFile, NULL); + if (fileSize != INVALID_FILE_SIZE && fileSize > 0) + { + SetFilePointer(hFile, 0, NULL, FILE_BEGIN); + char* buff = (char*)malloc(fileSize); + if (buff != NULL) + { + DWORD read; + if (ReadFile(hFile, buff, fileSize, &read, NULL)) + { + CopyTextToClipboard(buff, fileSize, FALSE, NULL); + } + else + { + DWORD err = GetLastError(); + SalMessageBox(HWindow, GetErrorText(err), LoadStr(IDS_ERRORTITLE), MB_OK | MB_ICONEXCLAMATION); + } + free(buff); + } + else + TRACE_E(LOW_MEMORY); + } + } + HANDLES(CloseHandle(hFile)); + + // if the destination was the clipboard, delete the temporary file + if (deleteFile || Configuration.FileListDestination == 0) // clipboard + DeleteFileUtf8(fileName); + else + { + if (Configuration.FileListDestination == 1) // viewer + { + // show the file in the internal viewer, which will delete it afterwards + CSalamanderPluginInternalViewerData viewerData; + viewerData.Size = sizeof(viewerData); + viewerData.FileName = fileName; + viewerData.Mode = 0; // text mode + char title[200]; + lstrcpyn(title, LoadStr(IDS_MAKEFILELIST_OUTPUT), 200); + viewerData.Caption = title; + viewerData.WholeCaption = TRUE; + int error; + if (!ViewFileInPluginViewer(NULL, &viewerData, TRUE, NULL, "mfl.txt", error)) + { + // delete the file even when opening fails + } + } + } + + if (Configuration.FileListDestination == 2) // file + { + //--- refresh manually refreshed directories + // change in the directory where the file list was created + CutDirectory(fileName); + MainWindow->PostChangeOnPathNotification(fileName, FALSE); + } + } + else + { + DWORD err = GetLastError(); + char message[MAX_PATH + 100]; + sprintf(message, LoadStr(IDS_FILEERRORFORMAT), fileName, GetErrorText(err)); + SalMessageBox(HWindow, message, LoadStr(IDS_ERRORTITLE), MB_OK | MB_ICONEXCLAMATION); + } + } + } + EndStopRefresh(); // the snooper will start again now +} + +// see description in mainwnd.h +BOOL GetNextFileFromPanel(int index, char* path, char* name, void* param) +{ + CALL_STACK_MESSAGE2("GetNextFileFromPanel(%d, , ,)", index); + CUMDataFromPanel* data = (CUMDataFromPanel*)param; + if (data->Count == -1) // retrieving data + { + BOOL upDir = (data->Window->Dirs->Count != 0 && + strcmp(data->Window->Dirs->At(0).Name, "..") == 0); + data->Count = data->Window->GetSelCount(); + if (data->Count < 0) + data->Count = 0; + if (data->Count == 0) // no selection -> use the focused item + { + index = data->Window->GetCaretIndex(); + data->Index = NULL; + strcpy(path, data->Window->GetPath()); + if (index < 0 || index >= data->Window->Dirs->Count + data->Window->Files->Count || + index == 0 && upDir) + { + name[0] = 0; // for up-dir or for the first item of an empty panel the name will be empty... + } + else // copy the name for others + { + CFileData* f = &((index < data->Window->Dirs->Count) ? data->Window->Dirs->At(index) : data->Window->Files->At(index - data->Window->Dirs->Count)); + strcpy(name, f->Name); + } + return TRUE; + } + data->Index = new int[data->Count]; + if (data->Index == NULL) + return FALSE; // error + data->Window->GetSelItems(data->Count, data->Index); + } + if (index >= 0 && index < data->Count) + { + CFileData* f = &((data->Index[index] < data->Window->Dirs->Count) ? data->Window->Dirs->At(data->Index[index]) : data->Window->Files->At(data->Index[index] - data->Window->Dirs->Count)); + strcpy(path, data->Window->GetPath()); + strcpy(name, f->Name); + return TRUE; + } + else + { + if (data->Index != NULL) + { + delete[] (data->Index); + data->Index = NULL; + } + data->Window->SetSel(FALSE, -1, TRUE); // explicit redraw + PostMessage(data->Window->HWindow, WM_USER_SELCHANGED, 0, 0); // sel-change notify + return FALSE; + } +} + +BOOL CheckIfCanBeExecuted(BOOL buildBat, int commandLen, int argumentsLen) +{ + /* MEASURED LIMITS: + Batch file: + W2K: 2041 including executable name, spaces and parameters + XP64/XP: 8185 including executable name, spaces and parameters + Win7/Vista: 32776 including executable name, spaces and parameters + + ShellExecuteEx: + XP/XP64/Vista/W2K: 2080 including executable name (without quotes), spaces and parameters + Win7: 32764 including executable name (without quotes), spaces and parameters +*/ + + // WARNING: if a .bat file is executed that runs a .exe and passes all parameters (%*), a long .exe name + // can still trigger "too long name" error even when respecting the limit here. The limit is exceeded once + // parameters are passed to the .exe. (I wouldn't solve this issue; it would require parsing + // .bat files etc., which is just nonsense.) + + int cmdLineLen = commandLen + argumentsLen + 1; // +1 for the space between command and arguments + if (buildBat) // launching via a .bat file + { + if (WindowsVistaAndLater) + return cmdLineLen <= 8191; // Vista/Win7: in reality it's 32776 but only 8191 works (with longer parameters, probably due to a Windows bug, characters get erased; tested on Vista and Win7) + return cmdLineLen <= 8185; // XP/XP64 + } + else // launching via ShellExecuteEx + { + if (Windows7AndLater) + return cmdLineLen <= 32764; // Win7 + return cmdLineLen <= 2080; // W2K/XP/XP64/Vista + } +} + +//***************************************************************************** +// +// ExpandCommand2 +// +// parent - parent window (for error dialogs) +// cmd - buffer for the expanded command +// cmdSize - size of 'cmd' buffer +// args - buffer for receiving arguments +// argsSize - size of 'args' buffer +// buildBat - if TRUE, arguments are placed into 'cmd' +// initDir - buffer for the path where the execution should take place +// initDirSize - length of initDir buffer +// item - user-menu item +// path - long path to the file +// longName - long filename +// fileNameUsed - returns TRUE if a file name or path was used during argument expansion +// userMenuAdvancedData - advanced parameter`s values for User Menu: the Arguments array +// ignoreEnvVarNotFoundOrTooLong - see ExpandVarString description +// +// returns success of the operation + +BOOL ExpandCommand2(HWND parent, + char* cmd, int cmdSize, + char* args, int argsSize, BOOL buildBat, + char* initDir, int initDirSize, + CUserMenuItem* item, + const char* path, + const char* longName, + BOOL* fileNameUsed, + CUserMenuAdvancedData* userMenuAdvancedData, + BOOL ignoreEnvVarNotFoundOrTooLong) +{ + CALL_STACK_MESSAGE5("ExpandCommand2(, , %d, , %d, , %s, %s, )", + cmdSize, initDirSize, path, longName); + + *fileNameUsed = FALSE; + char command[MAX_PATH]; + if (ExpandCommand(parent, item->UMCommand, command, MAX_PATH, ignoreEnvVarNotFoundOrTooLong)) + { + char fileName[MAX_PATH]; + if (path[0] != 0) + { + int l = (int)strlen(path); + if (path[l - 1] == '\\') + l--; + memcpy(fileName, path, l); + + char dosName[MAX_PATH]; + if (longName[0] != 0) + { + if (l + 1 + lstrlen(longName) > MAX_PATH - 1) + { + SalMessageBox(parent, LoadStr(IDS_TOOLONGNAME), LoadStr(IDS_ERRORTITLE), + MB_OK | MB_ICONEXCLAMATION); + goto EXIT; + } + fileName[l++] = '\\'; + strcpy(fileName + l, longName); + if (GetShortPathName(fileName, dosName, MAX_PATH) == 0) + { + TRACE_E("GetShortPathName() failed"); + dosName[0] = 0; + } + } + else + { + if (l == 2 && fileName[1] == ':') // we must append '\\' after a standard root path + { + fileName[l++] = '\\'; + } + fileName[l] = 0; + if (GetShortPathName(fileName, dosName, MAX_PATH) == 0) + { + TRACE_E("GetShortPathName() failed"); + dosName[0] = 0; + } + else + { + SalPathAddBackslash(dosName, MAX_PATH); + } + SalPathAddBackslash(fileName, MAX_PATH); + } + + char expArguments[USRMNUARGS_MAXLEN]; + if (ExpandUserMenuArguments(parent, fileName, dosName, item->Arguments, expArguments, + USRMNUARGS_MAXLEN, fileNameUsed, userMenuAdvancedData, + ignoreEnvVarNotFoundOrTooLong) && + ExpandInitDir(parent, fileName, dosName, item->InitDir, initDir, initDirSize, + ignoreEnvVarNotFoundOrTooLong)) + { + int len = (int)strlen(command); + int lArgs = (int)strlen(expArguments); + if (CheckIfCanBeExecuted(buildBat, len, lArgs)) + { + if (!buildBat) // launching via ShellExecuteEx: pass arguments separately + { + if (len + 1 <= cmdSize && lArgs + 1 <= argsSize) + { + memcpy(cmd, command, len + 1); + memcpy(args, expArguments, lArgs + 1); + return TRUE; + } + } + else // launching via .bat: append arguments after the command + { + if (len + lArgs + 2 <= cmdSize) + { + memcpy(cmd, command, len); + cmd[len] = ' '; + memcpy(cmd + len + 1, expArguments, lArgs + 1); + args[0] = 0; + return TRUE; + } + } + } + SalMessageBox(parent, LoadStr(IDS_USRMNUTOOLONGCMDORARGS), LoadStr(IDS_ERRORTITLE), + MB_OK | MB_ICONEXCLAMATION); + } + } + else + { + int len = (int)strlen(command); + if (len + 1 < cmdSize) + { + memcpy(cmd, command, len + 1); + args[0] = 0; + initDir[0] = 0; + return TRUE; + } + else + { + SalMessageBox(parent, LoadStr(IDS_USRMNUTOOLONGCMDORARGS), LoadStr(IDS_ERRORTITLE), + MB_OK | MB_ICONEXCLAMATION); + } + } + } +EXIT: + cmd[0] = 0; + args[0] = 0; + initDir[0] = 0; + return FALSE; +} + +/* +void RemoveRedundantBackslahes(char *text) +{ + if (text == NULL) + { + TRACE_E("Unexpected situation in RemoveRedundantBackslahes()."); + return; + } + if (strlen(text) < 3) + return; + char *s = text + 2; + char *d = s; + while (*s != 0) + { + *d = *s; + if (*s == '\\') + { + while (*s == '\\') s++; + d++; + } + else + { + s++; + d++; + } + } + *d = 0; +} +*/ + +void CMainWindow::UserMenu(HWND parent, int itemIndex, UM_GetNextFileName getNextFile, void* data, + CUserMenuAdvancedData* userMenuAdvancedData) +{ + CALL_STACK_MESSAGE2("CMainWindow::UserMenu(%d, ,)", itemIndex); + if (itemIndex >= 0 && itemIndex < UserMenuItems->Count) + { + UpdateWindow(parent); + + int errorPos1, errorPos2; + CUserMenuValidationData userMenuValidationData; + BOOL ok = TRUE; + if (ValidateUserMenuArguments(parent, UserMenuItems->At(itemIndex)->Arguments, errorPos1, errorPos2, + &userMenuValidationData)) + { + if (userMenuValidationData.UsesListOfSelNames && userMenuAdvancedData->ListOfSelNames[0] == 0) + { + SalMessageBox(parent, LoadStr(userMenuAdvancedData->ListOfSelNamesIsEmpty ? IDS_EMPTYLISTOFSELNAMES : IDS_TOOLONGLISTOFSELNAMES), + LoadStr(IDS_USERMENUERROR), MB_OK | MB_ICONEXCLAMATION); + ok = FALSE; + } + if (ok && userMenuValidationData.UsesListOfSelFullNames && userMenuAdvancedData->ListOfSelFullNames[0] == 0) + { + SalMessageBox(parent, LoadStr(userMenuAdvancedData->ListOfSelFullNamesIsEmpty ? IDS_EMPTYLISTOFSELFULLNAMES : IDS_TOOLONGLISTOFSELFULLNAMES), + LoadStr(IDS_USERMENUERROR), MB_OK | MB_ICONEXCLAMATION); + ok = FALSE; + } + if (ok && userMenuValidationData.UsesFullPathLeft && userMenuAdvancedData->FullPathLeft[0] == 0) + { + SalMessageBox(parent, LoadStr(IDS_NOTDEFFULLPATHLEFT), + LoadStr(IDS_USERMENUERROR), MB_OK | MB_ICONEXCLAMATION); + ok = FALSE; + } + if (ok && userMenuValidationData.UsesFullPathRight && userMenuAdvancedData->FullPathRight[0] == 0) + { + SalMessageBox(parent, LoadStr(IDS_NOTDEFFULLPATHRIGHT), + LoadStr(IDS_USERMENUERROR), MB_OK | MB_ICONEXCLAMATION); + ok = FALSE; + } + if (ok && userMenuValidationData.UsesFullPathInactive && userMenuAdvancedData->FullPathInactive[0] == 0) + { + SalMessageBox(parent, LoadStr(IDS_NOTDEFFULLPATHINACTIVE), + LoadStr(IDS_USERMENUERROR), MB_OK | MB_ICONEXCLAMATION); + ok = FALSE; + } + if (ok && userMenuValidationData.UsedCompareType != 0) + { + if ((userMenuValidationData.UsedCompareType == 6 /* file-or-dir-left-right */ || + userMenuValidationData.UsedCompareType == 7 /* file-or-dir-active-inactive */) && + userMenuAdvancedData->CompareName1[0] == 0 && + userMenuAdvancedData->CompareName2[0] == 0) + { // we don't know if files or directories should be compared, ask the user (the name selection dialog differs for files/directories) + MSGBOXEX_PARAMS params; + memset(¶ms, 0, sizeof(params)); + params.HParent = parent; + params.Flags = MB_YESNO | MB_ICONQUESTION | MSGBOXEX_SILENT; + params.Caption = LoadStr(IDS_QUESTION); + params.Text = LoadStr(IDS_COMPAREFILESORDIRS); + char aliasBtnNames[200]; + /* used by the export_mnu.py script that generates salmenu.mnu for the Translator + we let the message box buttons resolve hotkey collisions by pretending it's a menu +MENU_TEMPLATE_ITEM MsgBoxButtons[] = +{ + {MNTT_PB, 0 + {MNTT_IT, IDS_MSGBOXBTN_FILES + {MNTT_IT, IDS_MSGBOXBTN_DIRS + {MNTT_PE, 0 +}; +*/ + sprintf(aliasBtnNames, "%d\t%s\t%d\t%s", + DIALOG_YES, LoadStr(IDS_MSGBOXBTN_FILES), + DIALOG_NO, LoadStr(IDS_MSGBOXBTN_DIRS)); + params.AliasBtnNames = aliasBtnNames; + userMenuAdvancedData->CompareNamesAreDirs = (SalMessageBoxEx(¶ms) == DIALOG_NO); + } + + BOOL swapNames = FALSE; + BOOL clearNames = FALSE; + BOOL comparingFiles = TRUE; + switch (userMenuValidationData.UsedCompareType) + { + case 1: // file-left-right + { + if (userMenuAdvancedData->CompareNamesReversed) + swapNames = TRUE; + if (userMenuAdvancedData->CompareNamesAreDirs) + clearNames = TRUE; + break; + } + + case 2: // file-active-inactive + { + if (userMenuAdvancedData->CompareNamesAreDirs) + clearNames = TRUE; + break; + } + + case 3: // dir-left-right + { + comparingFiles = FALSE; + if (userMenuAdvancedData->CompareNamesReversed) + swapNames = TRUE; + if (!userMenuAdvancedData->CompareNamesAreDirs) + clearNames = TRUE; + break; + } + + case 4: // dir-active-inactive + { + comparingFiles = FALSE; + if (!userMenuAdvancedData->CompareNamesAreDirs) + clearNames = TRUE; + break; + } + + case 6: // file-or-dir-left-right + { + comparingFiles = !userMenuAdvancedData->CompareNamesAreDirs; + if (userMenuAdvancedData->CompareNamesReversed) + swapNames = TRUE; + break; + } + + case 7: // file-or-dir-active-inactive + { + comparingFiles = !userMenuAdvancedData->CompareNamesAreDirs; + break; + } + } + if (clearNames) + { + userMenuAdvancedData->CompareName1[0] = 0; + userMenuAdvancedData->CompareName2[0] = 0; + } + else + { + if (swapNames) + { + char swap[MAX_PATH]; + lstrcpyn(swap, userMenuAdvancedData->CompareName1, MAX_PATH); + lstrcpyn(userMenuAdvancedData->CompareName1, userMenuAdvancedData->CompareName2, MAX_PATH); + lstrcpyn(userMenuAdvancedData->CompareName2, swap, MAX_PATH); + } + } + if (Configuration.CnfrmShowNamesToCompare || + userMenuAdvancedData->CompareName1[0] == 0 || + userMenuAdvancedData->CompareName2[0] == 0) + { + CCompareArgsDlg dlg(parent, comparingFiles, userMenuAdvancedData->CompareName1, + userMenuAdvancedData->CompareName2, &Configuration.CnfrmShowNamesToCompare); + if (dlg.Execute() != IDOK) + ok = FALSE; + } + } + } + else + ok = FALSE; + if (ok) + { + BOOL buildBat = UserMenuItems->At(itemIndex)->ThroughShell; + BOOL batNotEmpty = FALSE; + char* batName; + HANDLE file; + char batUniqueName[50]; // we need a unique name for the batch file in the cache + DWORD lastErr; + + _TRY_AGAIN: + + sprintf(batUniqueName, "Usermenu %X", GetTickCount()); + if (buildBat) + { + BOOL exists; + batName = (char*)DiskCache.GetName(batUniqueName, "usermenu.bat", &exists, TRUE, NULL, FALSE, NULL, NULL); + if (batName == NULL) // error (if 'exists' is TRUE -> fatal, otherwise "file already exists") + { + if (!exists) // file exists -> almost impossible, handle anyway + { + Sleep(100); + goto _TRY_AGAIN; + } + return; // fatal error + } + file = HANDLES_Q(CreateFileUtf8(batName, GENERIC_WRITE, 0, NULL, CREATE_NEW, + FILE_ATTRIBUTE_TEMPORARY, NULL)); + if (file == INVALID_HANDLE_VALUE) + { + lastErr = GetLastError(); + goto ERROR_LABEL; + } + } + + // build the .bat file + int index; + index = 0; + char cmdLine[USRMNUCMDLINE_MAXLEN]; + char arguments[USRMNUARGS_MAXLEN]; + char initDir[MAX_PATH]; + char prevInitDir[MAX_PATH]; + initDir[0] = 0; + arguments[0] = 0; + char path[MAX_PATH], name[MAX_PATH]; + BOOL error; + error = FALSE; + BOOL skipErrorMessage; + skipErrorMessage = FALSE; + DWORD written; + BOOL fileNameUsed; + BOOL firstRound; + firstRound = TRUE; + while (getNextFile(index, path, name, data)) + { + strcpy(prevInitDir, initDir); + BOOL expandOK = ExpandCommand2(parent, + cmdLine, USRMNUCMDLINE_MAXLEN, + arguments, USRMNUARGS_MAXLEN, buildBat, // if we are running via a batch file, allow + initDir, MAX_PATH, // arguments will be inserted into cmdLine + UserMenuItems->At(itemIndex), + path, name, &fileNameUsed, + userMenuAdvancedData, + !firstRound); + if (!expandOK) + { + error = TRUE; + skipErrorMessage = TRUE; + break; + } + if (expandOK && (firstRound || fileNameUsed || strcmp(initDir, prevInitDir) != 0)) // block running the same command for all items (a user mistake that happens often) + { + if (buildBat) // building a .bat file + { + char initDirOEM[MAX_PATH]; + char cmdLineOEM[USRMNUCMDLINE_MAXLEN]; + CharToOem(initDir, initDirOEM); + CharToOem(cmdLine, cmdLineOEM); + batNotEmpty = TRUE; + if ((initDirOEM[0] != 0 && + (initDirOEM[1] == ':' && // "@C:" + (!WriteFile(file, "@", 1, &written, NULL) || + !WriteFile(file, initDirOEM, 2, &written, NULL) || + !WriteFile(file, "\r\n", 2, &written, NULL))) || + (initDirOEM[1] == ':' && // "@cd C:\\path" + (!WriteFile(file, "@cd \"", 5, &written, NULL) || + !WriteFile(file, initDirOEM, (DWORD)strlen(initDirOEM), &written, NULL) || + !WriteFile(file, "\"\r\n", 3, &written, NULL)))) || + !WriteFile(file, "call ", 5, &written, NULL) || + !WriteFile(file, cmdLineOEM, (DWORD)strlen(cmdLineOEM), &written, NULL) || + !WriteFile(file, "\r\n", 2, &written, NULL)) + { + error = TRUE; + break; + } + } + else // direct execution + { + // the original launching via CreateProcess couldn't run screen savers (*.SCR) + // or Control Panel items (*.cpl) and people kept complaining + // + // try executing it via ShellExecuteEx - it appears to work ;-) + // additionally, launch restrictions will be handled + + // set correct default directories for individual drives + MainWindow->SetDefaultDirectories((initDir[0] != 0) ? initDir : NULL); + + // to work with old configurations, remove the " character from the start and end of cmdLine + int cmdLen = (int)strlen(cmdLine); + if (cmdLen > 1 && cmdLine[0] == '\"' && cmdLine[cmdLen - 1] == '\"') + { + memmove(cmdLine, cmdLine + 1, cmdLen - 2); + cmdLine[cmdLen - 2] = 0; + } + // better not swallow backslashes so that we don't destroy some OLE paths + //RemoveRedundantBackslahes(cmdLine); // ShellExecuteEx dislikes multiple backslashes, "$(SalDir)\salamand.exe" + + CShellExecuteWnd shellExecuteWnd; + SHELLEXECUTEINFO sei; + memset(&sei, 0, sizeof(SHELLEXECUTEINFO)); + sei.cbSize = sizeof(SHELLEXECUTEINFO); + sei.hwnd = shellExecuteWnd.Create(parent, "SEW: CMainWindow::UserMenu"); // handle to any message boxes that the system might produce while executing + sei.lpFile = cmdLine; + sei.lpParameters = arguments; + sei.lpDirectory = (initDir[0] != 0) ? initDir : NULL; + sei.nShow = SW_SHOWNORMAL; + + if (!ShellExecuteEx(&sei)) + { + DWORD err = GetLastError(); + char buff[4 * MAX_PATH]; + if (strlen(cmdLine) > 2 * MAX_PATH) // "always false" (arguments are in 'arguments'): shorten overly long command lines for error display + strcpy(cmdLine + 2 * MAX_PATH - 4, "..."); + sprintf(buff, LoadStr(IDS_EXECERROR), cmdLine, GetErrorText(err)); + SalMessageBox(parent, buff, LoadStr(IDS_ERRORTITLE), MB_OK | MB_ICONEXCLAMATION); + break; + } + } + } + index++; + firstRound = FALSE; + if (userMenuValidationData.MustHandleItemsAsGroup) + break; // in this mode, only one command is executed for all selected items + } + + if (buildBat) + { + lastErr = GetLastError(); + DWORD size; + size = GetFileSize(file, NULL); + HANDLES(CloseHandle(file)); + + DiskCache.NamePrepared(batUniqueName, CQuadWord(size, 0)); + + if (!error) // run the .bat + { + if (batNotEmpty) + { + MainWindow->SetDefaultDirectories((initDir[0] != 0) ? initDir : NULL); + + STARTUPINFO si; + memset(&si, 0, sizeof(STARTUPINFO)); + si.cb = sizeof(STARTUPINFO); + si.lpTitle = LoadStr(IDS_COMMANDSHELL); + si.dwFlags = STARTF_USESHOWWINDOW; + POINT p; + if (UserMenuItems->At(itemIndex)->UseWindow && + MultiMonGetDefaultWindowPos(MainWindow->HWindow, &p)) + { + // if the main window is on another monitor, we should open + // the new window there, preferably at the default position (as on the primary monitor) + si.dwFlags |= STARTF_USEPOSITION; + si.dwX = p.x; + si.dwY = p.y; + } + si.wShowWindow = (UserMenuItems->At(itemIndex)->UseWindow ? SW_SHOWNORMAL : SW_HIDE); + + PROCESS_INFORMATION pi; + + GetEnvironmentVariable("COMSPEC", cmdLine, USRMNUCMDLINE_MAXLEN - 20); + AddDoubleQuotesIfNeeded(cmdLine, USRMNUCMDLINE_MAXLEN - 10); // CreateProcess requires the name with spaces in quotes (otherwise it tries various options, see help) + if (!UserMenuItems->At(itemIndex)->UseWindow || UserMenuItems->At(itemIndex)->CloseShell) + strcat(cmdLine, " /C "); // run command and close immediately after it finishes + else + strcat(cmdLine, " /K "); // run command and keep the shell open after it finishes + + char* s = cmdLine + strlen(cmdLine); + if ((s - cmdLine) + strlen(batName) < USRMNUCMDLINE_MAXLEN - 2) + sprintf(s, "\"%s\"", batName); + else + strcpy(cmdLine, batName); + + if (!HANDLES(CreateProcess(NULL, cmdLine, NULL, NULL, FALSE, + CREATE_DEFAULT_ERROR_MODE | NORMAL_PRIORITY_CLASS, + NULL, NULL, &si, &pi))) + { + DWORD err = GetLastError(); + char buff[4 * MAX_PATH]; + if (strlen(cmdLine) > 2 * MAX_PATH) + strcpy(cmdLine + 2 * MAX_PATH, "..."); // shorten just in case (probably never needed) + sprintf(buff, LoadStr(IDS_EXECERROR), cmdLine, GetErrorText(err)); + DiskCache.ReleaseName(batUniqueName, FALSE); + SalMessageBox(parent, buff, LoadStr(IDS_ERRORTITLE), MB_OK | MB_ICONEXCLAMATION); + } + else + { + DiskCache.AssignName(batUniqueName, pi.hProcess, TRUE, crtDirect); + // HANDLES(CloseHandle(pi.hProcess)); // handled by DiskCache + HANDLES(CloseHandle(pi.hThread)); + } + } + else // an empty .BAT is not worth running (in case of low memory or other crazy errors) + { + DiskCache.ReleaseName(batUniqueName, FALSE); + } + } + else + { + ERROR_LABEL: + + DiskCache.ReleaseName(batUniqueName, FALSE); + if (!skipErrorMessage) + { + SalMessageBox(parent, GetErrorText(lastErr), LoadStr(IDS_ERRORTITLE), + MB_OK | MB_ICONEXCLAMATION); + } + } + } + } + } + UpdateWindow(parent); +} + +void CMainWindow::SetDefaultDirectories(const char* curPath) +{ + CALL_STACK_MESSAGE2("CMainWindow::SetDefaultDirectories(%s)", curPath); + //--- restore DefaultDir + MainWindow->UpdateDefaultDir(TRUE); + //--- set environment variables + char name[4] = "= :"; + const char* dir; + char d; + for (d = 'a'; d <= 'z'; d++) + { + name[1] = d; + if (curPath != NULL && d == LowerCase[curPath[0]]) // UNC paths are ignored + dir = curPath; + else + dir = DefaultDir[d - 'a']; + + if (dir[1] == ':' && dir[2] == '\\' && dir[3] == 0) + SetEnvironmentVariable(name, NULL); + else + SetEnvironmentVariable(name, dir); + } +} + +BOOL CMainWindow::HandleCtrlLetter(char c) +{ + CALL_STACK_MESSAGE2("CMainWindow::HandleCtrlLetter(%u)", c); + if ((GetKeyState(VK_SHIFT) & 0x8000) != 0) + { // change drive via Shift+letter + GetActivePanel()->ChangeDrive(c); + } + else // NC + Windows Ctrl+? hotkeys + { + WPARAM cmd; + switch (c) // only upper-case characters reach here + { + case 'A': + cmd = CM_ACTIVESELECTALL; + break; + + case 'C': // copy + case 'X': // cut + { + BOOL files = FALSE; + if (GetActivePanel() != NULL) + { + if (GetActivePanel()->GetCaretIndex() == 0) + { + if (0 == GetActivePanel()->Dirs->Count || + strcmp(GetActivePanel()->Dirs->At(0).Name, "..") != 0) + { + files = GetActivePanel()->Dirs->Count + GetActivePanel()->Files->Count > 0; + } + else + { + int count = GetActivePanel()->GetSelCount(); + if (count == 1) + { + int index; + GetActivePanel()->GetSelItems(1, &index); + files = index != 0; + } + else + files = count > 0; + } + } + else + files = GetActivePanel()->GetCaretIndex() > 0; + } + if (!files) + return FALSE; // cut and copy cannot be performed + cmd = (c == 'C') ? CM_CLIPCOPY : CM_CLIPCUT; + break; + } + + case 'D': + cmd = CM_ACTIVEUNSELECTALL; + break; + case 'E': + cmd = CM_EMAILFILES; + break; + case 'F': + cmd = CM_FINDFILE; + break; + case 'G': + cmd = CM_ACTIVE_CHANGEDIR; + break; + case 'H': + cmd = CM_TOGGLEHIDDENFILES; + break; + case 'I': + cmd = CM_LAST_PLUGIN_CMD; + break; + case 'K': + cmd = CM_CONVERTFILES; + break; + case 'L': + cmd = CM_DRIVEINFO; + break; + case 'M': + cmd = CM_FILELIST; + break; + case 'N': + cmd = CM_TOGGLEELASTICSMART; + break; + case 'P': + cmd = CM_SEC_PERMISSIONS; + break; + case 'Q': + cmd = CM_OCCUPIEDSPACE; + break; + case 'R': + cmd = CM_ACTIVEREFRESH; + break; + case 'S': + cmd = CM_CLIPPASTELINKS; + break; + case 'T': + cmd = CM_AFOCUSSHORTCUT; + break; + case 'U': + cmd = CM_SWAPPANELS; + break; + case 'V': + cmd = CM_CLIPPASTE; + break; + case 'W': + cmd = CM_RESELECT; + break; + + default: + return FALSE; + } + SendMessage(HWindow, WM_COMMAND, cmd, 0); + } + return TRUE; +} + +void CMainWindow::ChangePanel(BOOL force) +{ + CALL_STACK_MESSAGE1("CMainWindow::ChangePanel()"); + + MainWindow->CancelPanelsUI(); // cancel QuickSearch and QuickEdit + if (IsIconic(HWindow)) + return; + + CFilesWindow* p1 = GetActivePanel(); + CFilesWindow* p2 = GetNonActivePanel(); + + BOOL change = FALSE; + if (force || p2->CanBeFocused()) + change = TRUE; + else + { + // if a panel is ZOOMed, minimize it and ZOOM the other one + if (IsPanelZoomed(TRUE) || IsPanelZoomed(FALSE)) + { + if (IsPanelZoomed(TRUE)) + SplitPosition = 0.0; + else + SplitPosition = 1.0; + LayoutWindows(); + change = TRUE; + } + } + + if (change) + { + SetActivePanel(p2); + + // ensure the active panel header is redrawn + if (p1->DirectoryLine != NULL) + p1->DirectoryLine->InvalidateAndUpdate(FALSE); + if (p2->DirectoryLine != NULL) + p2->DirectoryLine->InvalidateAndUpdate(FALSE); + + UpdateDriveBars(); // press the correct drive in the drive bar + + // ReleaseMenuNew(); + if (EditMode) + { + p1->RedrawIndex(p1->FocusedIndex); + int i = p2->GetCaretIndex(); + i = max(i, 0); + p2->SetCaretIndex(i, TRUE); + p2->RedrawIndex(i); + } + else + { + if (GetFocus() != p2->GetListBoxHWND()) + SetFocus(p2->GetListBoxHWND()); + int i = p2->GetCaretIndex(); + i = max(i, 0); + p2->SetCaretIndex(i, FALSE); + } + EditWindowSetDirectory(); + IdleRefreshStates = TRUE; // on the next Idle, force checking of state variables + MainWindow->UpdateDefaultDir(TRUE); + + // broadcast this news to all loaded plugins + Plugins.Event(PLUGINEVENT_PANELACTIVATED, p2 == LeftPanel ? PANEL_LEFT : PANEL_RIGHT); + } +} + +void CMainWindow::FocusPanel(CFilesWindow* focus, BOOL testIfMainWndActive) +{ + CALL_STACK_MESSAGE2("CMainWindow::FocusPanel(, %d)", testIfMainWndActive); + MainWindow->CancelPanelsUI(); // cancel QuickSearch and QuickEdit + + if (!IsIconic(HWindow) && !focus->CanBeFocused()) + focus = ((focus == LeftPanel) ? RightPanel : LeftPanel); + + if (GetFocus() != focus->GetListBoxHWND()) + { + if (!testIfMainWndActive || GetForegroundWindow() == HWindow) // focus only if main window is active (FTP plugin with non-modal Welcome Message window could focus the panel on command line shutdown -> deactivate Welcome Message) + SetFocus(focus->GetListBoxHWND()); + else + focus->OnSetFocus(FALSE); // simulate focus in the panel + } + + CFilesWindow* old = GetActivePanel(); + SetActivePanel(focus); + + UpdateDriveBars(); // press the correct drive in the drive bar + + // ensure the active panel header is redrawn + if (old != focus) + { + // activated a different panel, let it set its enablers + RefreshCommandStates(); + // fixes a bug (present in 2.5b10) when users had one panel active with focus on UpDir + // and then right-clicked a file in the passive panel and chose DELETE from the context menu + // nothing happened because the EnablerFilesDelete enabler was FALSE (not updated for the new panel) + // see /viewtopic.php?t=181 + + // repaint the directory line of both panels + if (old->DirectoryLine != NULL) + old->DirectoryLine->InvalidateAndUpdate(FALSE); + if (focus->DirectoryLine != NULL) + focus->DirectoryLine->InvalidateAndUpdate(FALSE); + // ReleaseMenuNew(); + EditWindowSetDirectory(); + IdleRefreshStates = TRUE; // on the next Idle, force checking of state variables + // broadcast this news to loaded plugins + Plugins.Event(PLUGINEVENT_PANELACTIVATED, focus == LeftPanel ? PANEL_LEFT : PANEL_RIGHT); + } + //--- restore DefaultDir + MainWindow->UpdateDefaultDir(TRUE); +} + +void CMainWindow::ShowCommandLine() +{ + CALL_STACK_MESSAGE1("CMainWindow::ShowCommandLine()"); + if (EditWindow == NULL || EditWindow->HWindow != NULL) + return; + + if (!EditWindow->Create(HWindow, IDC_EDITWINDOW)) + TRACE_E("Unable to create EditWindow."); + else + { + LayoutWindows(); + EditWindow->RestoreContent(); + ShowWindow(EditWindow->HWindow, SW_SHOW); + if (EditWindow->IsEnabled()) + SetFocus(EditWindow->HWindow); + IdleRefreshStates = TRUE; // on the next Idle, force checking of state variables + } +} + +void CMainWindow::HideCommandLine(BOOL storeContent, BOOL focusPanel) +{ + if (EditWindow == NULL || EditWindow->HWindow == NULL) + return; + + if (storeContent) + EditWindow->StoreContent(); + + DestroyWindow(EditWindow->HWindow); + if (focusPanel) + FocusPanel(GetActivePanel()); + LayoutWindows(); + IdleRefreshStates = TRUE; // on the next Idle, force checking of state variables +} + +//**************************************************************************** +// +// Image Drag functions +// + +void ImageDragBegin(int width, int height, int dxHotspot, int dyHotspot) +{ + if (ImageDragging) + TRACE_E("ImageDragging == TRUE - this should never happen"); + ImageDragW = width; + ImageDragH = height; + ImageDragDxHotspot = dxHotspot; + ImageDragDyHotspot = dyHotspot; + ImageDragging = TRUE; +} + +void ImageDragEnd() +{ + if (!ImageDragging) + TRACE_E("ImageDragging == FALSE - this should never happen"); + ImageDragX = INT_MAX; + ImageDragY = INT_MAX; + ImageDragW = INT_MAX; + ImageDragH = INT_MAX; + ImageDragging = FALSE; +} + +BOOL ImageDragInterfereRect(const RECT* rect) +{ + if (!ImageDraggingVisible) + return FALSE; + if (ImageDragX == INT_MAX || ImageDragY == INT_MAX) + { + TRACE_E("ImageDragX == INT_MAX || ImageDragY == INT_MAX"); + return TRUE; // just to be safe + } + if (ImageDragW == INT_MAX || ImageDragH == INT_MAX) + { + TRACE_E("ImageDragW == INT_MAX || ImageDragH == INT_MAX"); + return TRUE; // just to be safe + } + RECT r; + r.left = ImageDragX - ImageDragDxHotspot; + r.top = ImageDragY - ImageDragDyHotspot; + r.right = r.left + ImageDragW; + r.bottom = r.top + ImageDragH; + RECT dstR; + IntersectRect(&dstR, rect, &r); + return !IsRectEmpty(&dstR); +} + +void ImageDragEnter(int x, int y) +{ + CALL_STACK_MESSAGE3("ImageDragEnter(%d, %d)", x, y); + if (ImageDraggingVisible) + TRACE_E("ImageDraggingVisible == TRUE - this should never happen"); + if (!ImageDragging) + TRACE_E("ImageDragging == FALSE - this should never happen"); + ImageDragX = x; + ImageDragY = y; + ShowCaretAfterDrop = MainWindow->EditWindow->HideCaret(); + ImageList_DragEnter(MainWindow->HWindow, x - MainWindow->WindowRect.left, y - MainWindow->WindowRect.top); + ImageDraggingVisible = TRUE; + ImageDraggingVisibleLevel = 1; +} + +void ImageDragMove(int x, int y) +{ + CALL_STACK_MESSAGE3("ImageDragMove(%d, %d)", x, y); + if (!ImageDragging) + TRACE_E("ImageDragging == FALSE - this should never happen"); + ImageDragX = x; + ImageDragY = y; + ImageList_DragMove(x - MainWindow->WindowRect.left, y - MainWindow->WindowRect.top); +} + +void ImageDragLeave() +{ + CALL_STACK_MESSAGE1("ImageDragLeave()"); + if (!ImageDragging) + TRACE_E("ImageDragging == FALSE - this should never happen"); + ImageList_DragLeave(MainWindow->HWindow); + ImageDraggingVisible = FALSE; + ImageDraggingVisibleLevel = 0; + ImageDragX = INT_MAX; + ImageDragY = INT_MAX; + if (ShowCaretAfterDrop) + { + MainWindow->EditWindow->ShowCaret(); + ShowCaretAfterDrop = FALSE; + } +} + +void ImageDragShow(BOOL show) +{ + CALL_STACK_MESSAGE2("ImageDragShow(%d)", show); + if (!ImageDragging) + TRACE_E("ImageDragging == FALSE - this should never happen"); + ImageDraggingVisibleLevel += show ? 1 : -1; + + if (show && ImageDraggingVisibleLevel == 1) + { + ImageList_DragShowNolock(TRUE); + ImageDraggingVisible = TRUE; + } + if (!show && ImageDraggingVisibleLevel == 0) + { + ImageList_DragShowNolock(FALSE); + ImageDraggingVisible = FALSE; + } +} + +//**************************************************************************** +// +// Context Help (Shift+F1) support +// + +///////////////////////////////////////////////////////////////////////////// +// useful message ranges + +#define WM_SYSKEYFIRST WM_SYSKEYDOWN +#define WM_SYSKEYLAST WM_SYSDEADCHAR + +#define WM_NCMOUSEFIRST WM_NCMOUSEMOVE +#define WM_NCMOUSELAST WM_NCMBUTTONDBLCLK + +HWND GetParentOwner(HWND hWnd) +{ + CALL_STACK_MESSAGE_NONE + // return parent in the Windows sense + return (GetWindowLongPtr(hWnd, GWL_STYLE) & WS_CHILD) ? GetParent(hWnd) : GetWindow(hWnd, GW_OWNER); +} + +HWND GetTopLevelParent(HWND hWindow) +{ + CALL_STACK_MESSAGE_NONE + HWND hWndParent = hWindow; + HWND hWndT; + while ((hWndT = GetParentOwner(hWndParent)) != NULL) + hWndParent = hWndT; + + return hWndParent; +} + +BOOL IsDescendant(HWND hWndParent, HWND hWndChild) +{ + CALL_STACK_MESSAGE_NONE + // helper for detecting whether child descendent of parent + // (works with owned popups as well) + if (!IsWindow(hWndParent)) + { + TRACE_E("hWndParent is not window"); + return FALSE; + } + if (!IsWindow(hWndChild)) + { + TRACE_E("hWndChild is not window"); + return FALSE; + } + + do + { + if (hWndParent == hWndChild) + return TRUE; + + hWndChild = GetParentOwner(hWndChild); + } while (hWndChild != NULL); + + return FALSE; +} + +DWORD +CMainWindow::MapClientArea(POINT point) +{ + DWORD dwContext = 0; + + CMainWindowsHitTestEnum hit = HitTest(point.x, point.y); + CToolBar* toolbar = NULL; + + switch (hit) + { + case mwhteMenu: + dwContext = IDH_MENUBAR; + break; + + case mwhteTopToolbar: + { + dwContext = IDH_TOPTOOLBAR; + toolbar = TopToolBar; + break; + } + + case mwhtePluginsBar: + dwContext = IDH_PLUGINSBAR; + break; + + case mwhteMiddleToolbar: + { + dwContext = IDH_MIDDLETOOLBAR; + toolbar = MiddleToolBar; + break; + } + + case mwhteUMToolbar: + dwContext = IDH_UMTOOLBAR; + break; + + case mwhteHPToolbar: + dwContext = IDH_HPTOOLBAR; + break; + + case mwhteDriveBar: + dwContext = IDH_DRIVEBAR; + break; + + case mwhteCmdLine: + dwContext = IDH_COMMANDLINE; + break; + + case mwhteBottomToolbar: + { + dwContext = IDH_BOTTOMTOOLBAR; + toolbar = BottomToolBar; + break; + } + + case mwhteSplitLine: + dwContext = IDH_SPLITBAR; + break; + + case mwhteLeftDirLine: + { + dwContext = IDH_DIRECTORYLINE; + + if (LeftPanel->DirectoryLine->ToolBar != NULL && + LeftPanel->DirectoryLine->ToolBar->HWindow != NULL) + toolbar = LeftPanel->DirectoryLine->ToolBar; + break; + } + + case mwhteRightDirLine: + { + dwContext = IDH_DIRECTORYLINE; + + if (RightPanel->DirectoryLine->ToolBar != NULL && + RightPanel->DirectoryLine->ToolBar->HWindow != NULL) + toolbar = RightPanel->DirectoryLine->ToolBar; + break; + } + + case mwhteLeftHeaderLine: + case mwhteRightHeaderLine: + dwContext = IDH_HEADERLINE; + break; + + case mwhteLeftStatusLine: + case mwhteRightStatusLine: + dwContext = IDH_INFOLINE; + break; + + case mwhteLeftWorkingArea: + case mwhteRightWorkingArea: + dwContext = IDH_WORKINGAREA; + break; + } + + // get the ID of the button the user clicked + if (toolbar != NULL) + { + POINT p; + p = point; + ScreenToClient(toolbar->HWindow, &p); + int index = toolbar->HitTest(p.x, p.y); + if (index != -1) + { + TLBI_ITEM_INFO2 tii; + tii.Mask = TLBI_MASK_ID; + if (toolbar->GetItemInfo2(index, TRUE, &tii)) + dwContext = tii.ID; + } + } + return dwContext; +} + +DWORD +CMainWindow::MapNonClientArea(int iHit) +{ + DWORD dwContext = 0; + switch (iHit) + { + /* + case HTBORDER: + case HTBOTTOM: + case HTBOTTOMLEFT: + case HTBOTTOMRIGHT: + case HTLEFT: + case HTRIGHT: + case HTTOP: + case HTTOPLEFT: + case HTTOPRIGHT: + case HTCAPTION: + case HTREDUCE: + case HTZOOM: +*/ + + case HTMINBUTTON: + case HTMAXBUTTON: + case HTCLOSE: + dwContext = IDH_MINMAXCLOSEBTNS; + break; + } + return dwContext; +} + +BOOL CMainWindow::CanEnterHelpMode() +{ + CALL_STACK_MESSAGE1("CMainWindow::CanEnterHelpMode()"); + if (HelpMode == HELP_ACTIVE) // already in help mode? + return FALSE; + + if (HHelpCursor == NULL) + { + HHelpCursor = LoadCursor(NULL, IDC_HELP); + if (HHelpCursor == NULL) + return FALSE; + } + + return TRUE; +} + +void CMainWindow::OnContextHelp() +{ + CALL_STACK_MESSAGE1("CMainWindow::OnContextHelp()"); + // don't enter twice, and don't enter if initialization fails + if (HelpMode == HELP_ACTIVE || !CanEnterHelpMode()) + return; + + // don't enter help mode with pending WM_USER_EXITHELPMODE message + MSG msg; + if (PeekMessage(&msg, HWindow, WM_USER_EXITHELPMODE, WM_USER_EXITHELPMODE, PM_REMOVE | PM_NOYIELD)) + return; + + BOOL bHelpMode = HelpMode; + if (HelpMode != HELP_INACTIVE && HelpMode != HELP_ENTERING) + return; + HelpMode = HELP_ACTIVE; + + if (bHelpMode == HELP_INACTIVE) + { + // need to delay help startup until later + PostMessage(HWindow, WM_COMMAND, CM_HELP_CONTEXT, 0); + HelpMode = HELP_ENTERING; + return; + } + + IdleRefreshStates = TRUE; // trigger idle update + OnEnterIdle(); // redraw the toolbar + + if (HelpMode != HELP_ACTIVE) + return; + + MenuBar->SetHelpMode(TRUE); + + // reset the bottom toolbar to its normal state + BottomToolBar->SetState(btbsNormal); + BottomToolBar->UpdateItemsState(); + + // if someone is monitoring the mouse, stop monitoring + TRACKMOUSEEVENT tme; + tme.cbSize = sizeof(tme); + tme.dwFlags = TME_QUERY; + if (TrackMouseEvent(&tme) && tme.hwndTrack != NULL) + SendMessage(tme.hwndTrack, WM_MOUSELEAVE, 0, 0); + + DWORD dwContext = 0; + POINT point; + + GetCursorPos(&point); + SetHelpCapture(point, NULL); + LONG lIdleCount = 0; + + BOOL first = TRUE; + + HWND hDirtyWindow = NULL; + while (HelpMode) + { + if (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) + { + if (!ProcessHelpMsg(msg, &dwContext, &hDirtyWindow)) + break; + if (dwContext != 0) + return; + } + else + { + if (first) + { + // buffer a mouse move to fall through to the toolbar when the cursor is over disabled buttons + POINT p; + GetCursorPos(&p); + ScreenToClient(HWindow, &p); + PostMessage(HWindow, WM_MOUSEMOVE, 0, MAKELPARAM(point.x, point.y)); + // originally this buffering was before the loop, but that misbehaved + // if a tooltip for a disabled button was shown and I pressed Shift+F1: + // when the tooltip vanished, WM_MOUSELEAVE was delivered (PeekMessage distributes messages, see MSDN). + // The button under the cursor then drew as enabled and then immediately as disabled again. + // With this trick I wait until all messages are processed and MOUSEMOVE is definitely handled + + first = FALSE; + } + else + { + WaitMessage(); + } + } + } + if (hDirtyWindow != NULL) + SendMessage(hDirtyWindow, WM_USER_HELP_MOUSELEAVE, 0, 0); + + MenuBar->SetHelpMode(FALSE); + HelpMode = HELP_INACTIVE; + ReleaseCapture(); + + // make sure the cursor is set appropriately + SetCapture(HWindow); + ReleaseCapture(); + + if (dwContext != 0) + OpenHtmlHelp(NULL, HWindow, HHCDisplayContext, dwContext, FALSE); + + IdleRefreshStates = TRUE; // trigger idle update +} + +HWND CMainWindow::SetHelpCapture(POINT point, BOOL* pbDescendant) +// set or release capture, depending on where the mouse is +// also assign the proper cursor to be displayed. +{ + CALL_STACK_MESSAGE1("CMainWindow::SetHelpCapture(,)"); + if (!HelpMode) + return NULL; + + HWND hWndCapture = GetCapture(); + HWND hWndHit = WindowFromPoint(point); + HWND hTopHit = GetTopLevelParent(hWndHit); + HWND hTopActive = GetTopLevelParent(GetActiveWindow()); + BOOL bDescendant = FALSE; + DWORD hCurTask = GetCurrentThreadId(); + DWORD hTaskHit = hWndHit != NULL ? GetWindowThreadProcessId(hWndHit, NULL) : NULL; + + if (hTopActive == NULL || hWndHit == GetDesktopWindow()) + { + if (hWndCapture == HWindow) + ReleaseCapture(); + SetCursor(HHelpCursor); + } + else if (hTopActive == NULL || + hWndHit == NULL || hCurTask != hTaskHit || + !IsDescendant(HWindow, hWndHit)) + { + if (hCurTask != hTaskHit) + hWndHit = NULL; + if (hWndCapture == HWindow) + ReleaseCapture(); + } + else + { + bDescendant = TRUE; + if (hTopActive != hTopHit) + hWndHit = NULL; + else + { + if (hWndCapture != HWindow) + SetCapture(HWindow); + SetCursor(HHelpCursor); + } + } + if (pbDescendant != NULL) + *pbDescendant = bDescendant; + return hWndHit; +} + +BOOL CMainWindow::ProcessHelpMsg(MSG& msg, DWORD* pContext, HWND* hDirtyWindow) +{ + CALL_STACK_MESSAGE4("CMainWindow::ProcessHelpMsg(0x%X, 0x%IX, 0x%IX,)", msg.message, msg.wParam, msg.lParam); + + if (pContext == NULL) + { + TRACE_E("pContext == NULL"); + return FALSE; + } + if (msg.message == WM_USER_EXITHELPMODE || + (msg.message == WM_KEYDOWN && msg.wParam == VK_ESCAPE)) + { + PeekMessage(&msg, NULL, msg.message, msg.message, PM_REMOVE); + return FALSE; + } + + POINT point; + if ((msg.message >= WM_MOUSEFIRST && msg.message <= WM_MOUSELAST) || + (msg.message >= WM_NCMOUSEFIRST && msg.message <= WM_NCMOUSELAST)) + { + BOOL bDescendant; + HWND hWndHit = SetHelpCapture(msg.pt, &bDescendant); + if (hWndHit == NULL) + { + PeekMessage(&msg, NULL, msg.message, msg.message, PM_REMOVE); // eat the message + return TRUE; + } + + if (bDescendant) + { + if (msg.message != WM_LBUTTONDOWN) + { + // Hit one of our owned windows -- eat the message. + PeekMessage(&msg, NULL, msg.message, msg.message, PM_REMOVE); + + // notify windows that wish to highlight items during Shift+F1 mode + if (msg.message == WM_MOUSEMOVE) + { + if (*hDirtyWindow != NULL && *hDirtyWindow != hWndHit) + SendMessage(*hDirtyWindow, WM_USER_HELP_MOUSELEAVE, 0, 0); + *hDirtyWindow = hWndHit; // this window will need to receive a LEAVE message + + POINT p = msg.pt; + ScreenToClient(hWndHit, &p); + SendMessage(hWndHit, WM_USER_HELP_MOUSEMOVE, 0, MAKELPARAM(p.x, p.y)); + } + return TRUE; + } + int iHit = (int)SendMessage(hWndHit, WM_NCHITTEST, 0, + MAKELONG(msg.pt.x, msg.pt.y)); + if (iHit == HTSYSMENU) + { + if (GetCapture() != HWindow) + { + TRACE_E("GetCapture() != HWindow"); + return FALSE; + } + ReleaseCapture(); + // the message we peeked changes into a non-client because + // of the release capture. + GetMessage(&msg, NULL, WM_NCLBUTTONDOWN, WM_NCLBUTTONDOWN); + DispatchMessage(&msg); + GetCursorPos(&point); + SetHelpCapture(point, NULL); + } + else if (iHit == HTCLIENT) + { + if (hWndHit == MenuBar->HWindow) + { + PeekMessage(&msg, NULL, msg.message, msg.message, PM_REMOVE); + ReleaseCapture(); + POINT p = msg.pt; + ScreenToClient(hWndHit, &p); + msg.lParam = MAKELPARAM(p.x, p.y); + msg.hwnd = hWndHit; + msg.message = WM_MOUSEMOVE; + DispatchMessage(&msg); + msg.message = WM_LBUTTONDOWN; + DispatchMessage(&msg); + GetCursorPos(&point); + SetHelpCapture(point, NULL); + } + else + { + *pContext = MapClientArea(msg.pt); + PeekMessage(&msg, NULL, msg.message, msg.message, PM_REMOVE); + return FALSE; + } + } + else + { + *pContext = MapNonClientArea(iHit); + PeekMessage(&msg, NULL, msg.message, msg.message, PM_REMOVE); + return FALSE; + } + } + else + { + // Hit one of our apps windows (or desktop) -- dispatch the message. + PeekMessage(&msg, NULL, msg.message, msg.message, PM_REMOVE); + + // Dispatch mouse messages that hit the desktop! + DispatchMessage(&msg); + } + } + else if (msg.message == WM_SYSCOMMAND || + (msg.message >= WM_KEYFIRST && msg.message <= WM_KEYLAST)) + { + if (GetCapture() != NULL) + { + ReleaseCapture(); + MSG msg2; + while (PeekMessage(&msg2, NULL, WM_MOUSEFIRST, WM_MOUSELAST, PM_REMOVE | PM_NOYIELD)) + ; + } + if (PeekMessage(&msg, NULL, msg.message, msg.message, PM_NOREMOVE)) + { + GetMessage(&msg, NULL, msg.message, msg.message); + + // ensure sending messages to our menu (avoiding the need for a keyboard hook) + // this supports entering the menu via Alt/F10/Alt+letter during help mode + if (MenuBar == NULL || !MenuBar->IsMenuBarMessage(&msg)) + { + TranslateMessage(&msg); + if (msg.message == WM_SYSCOMMAND || + (msg.message >= WM_SYSKEYFIRST && + msg.message <= WM_SYSKEYLAST)) + { + // only dispatch system keys and system commands + DispatchMessage(&msg); + } + } + } + GetCursorPos(&point); + SetHelpCapture(point, NULL); + } + else + { + // allow all other messages to go through (capture still set) + if (PeekMessage(&msg, NULL, msg.message, msg.message, PM_REMOVE)) + DispatchMessage(&msg); + } + + return TRUE; +} + +void CMainWindow::ExitHelpMode() +{ + CALL_STACK_MESSAGE1("CMainWindow::ExitHelpMode()"); + // if not in help mode currently, this is a no-op + if (!HelpMode) + return; + + // only post new WM_EXITHELPMODE message if one doesn't already exist + // in the queue. + MSG msg; + if (!PeekMessage(&msg, HWindow, WM_USER_EXITHELPMODE, WM_USER_EXITHELPMODE, PM_REMOVE | PM_NOYIELD)) + PostMessage(HWindow, WM_USER_EXITHELPMODE, 0, 0); + + // release capture if this window has it + if (GetCapture() == HWindow) + ReleaseCapture(); + + HelpMode = HELP_INACTIVE; + IdleRefreshStates = TRUE; // trigger idle update +} + +void CMainWindow::UpdateDriveBars() +{ + if (DriveBar == NULL || DriveBar2 == NULL) + return; + + if (DriveBar->HWindow == NULL) + return; + + if (DriveBar2->HWindow == NULL) + { + // when there is only one drive bar it belongs to the active panel + DriveBar->SetCheckedDrive(GetActivePanel()); + } + else + { + DriveBar->SetCheckedDrive(LeftPanel); + DriveBar2->SetCheckedDrive(RightPanel); + } +} + +void CMainWindow::CancelPanelsUI() +{ + LeftPanel->CancelUI(); + RightPanel->CancelUI(); +} + +BOOL CMainWindow::QuickRenameWindowActive() +{ + return (LeftPanel->IsQuickRenameActive() || RightPanel->IsQuickRenameActive()); +} + +BOOL CMainWindow::DoQuickRename() +{ + if (LeftPanel->IsQuickRenameActive()) + return LeftPanel->HandeQuickRenameWindowKey(VK_RETURN); + if (RightPanel->IsQuickRenameActive()) + return RightPanel->HandeQuickRenameWindowKey(VK_RETURN); + return TRUE; // OK +} + +// +// **************************************************************************** +// LockUI +// + +void CMainWindow::LockUI(BOOL lock, HWND hToolWnd, const char* lockReason) +{ + if (LockedUI && lock) + { + TRACE_E("CMainWindow::LockUI(): main window is already locked! Ignoring this request..."); + return; + } + if (!LockedUI && !lock) + { + TRACE_E("CMainWindow::LockUI(): main window is not locked! Ignoring this request..."); + return; + } + + LockedUI = lock; + if (lock) + { + LockedUIToolWnd = hToolWnd; + if (lockReason != NULL) + LockedUIReason = DupStr(lockReason); + } + else + { + LockedUIToolWnd = NULL; + if (LockedUIReason != NULL) + free(LockedUIReason); + } + + if (HTopRebar != NULL) + EnableWindow(HTopRebar, !lock); + if (MiddleToolBar != NULL && MiddleToolBar->HWindow != NULL) + EnableWindow(MiddleToolBar->HWindow, !lock); + if (BottomToolBar != NULL && BottomToolBar->HWindow != NULL) + EnableWindow(BottomToolBar->HWindow, !lock); + LeftPanel->LockUI(lock); + RightPanel->LockUI(lock); +} + +void CMainWindow::BringLockedUIToolWnd() +{ + if (LockedUIToolWnd != NULL) + SetWindowPos(LockedUIToolWnd, HWindow, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOSENDCHANGING | SWP_NOREDRAW); +} + +// **************************************************************************** + +CFilesWindow* +CMainWindow::GetPanel(int panel) +{ + switch (panel) + { + case PANEL_SOURCE: + return GetActivePanel(); + case PANEL_TARGET: + return GetNonActivePanel(); + case PANEL_LEFT: + return LeftPanel; + case PANEL_RIGHT: + return RightPanel; + default: + TRACE_E("Invalid panel (PANEL_XXX) constant: " << panel); + return NULL; + } +} + +void CMainWindow::PostFocusNameInPanel(int panel, const char* path, const char* name) +{ + CALL_STACK_MESSAGE4("CMainWindow::FocusNameInPanel(%d, %s, %s)", panel, path, name); + CFilesWindow* p = GetPanel(panel); + if (p != NULL) + { + static char pathBackup[MAX_PATH + 200]; + static char nameBackup[MAX_PATH + 200]; + lstrcpyn(pathBackup, path, MAX_PATH + 200); + lstrcpyn(nameBackup, name, MAX_PATH + 200); + PostMessage(p->HWindow, WM_USER_FOCUSFILE, (WPARAM)nameBackup, (LPARAM)pathBackup); + } +} + +//**************************************************************************** +// + +//**************************************************************************** +// +// CMainWindow::HandleWmCommand +// +// Dispatches all WM_COMMAND menu and toolbar commands. +// Extracted from the WindowProc switch in mainwnd_messages.cpp. +// + +#include "snooper.h" +#include "shellib.h" +#include "pack.h" +#include "filesbox.h" +#include "drivelst.h" +#include "worker.h" +#include "find.h" +#include "viewer.h" +#include "gui.h" +#include "tasklist.h" +#include "jumplist.h" +#include "execlog.h" +#include "htmlhelp.h" + +// Helper: invokes a shell context-menu command with temporary thread-priority reduction +// to prevent misbehaving shell extensions from hogging the CPU. +static void CMainWindowWindowProcAux(IContextMenu* menu2, CMINVOKECOMMANDINFO& ici) +{ + CALL_STACK_MESSAGE_NONE + + // temporarily lower the thread priority so a misbehaving shell extension does not hog the CPU + HANDLE hThread = GetCurrentThread(); // pseudo-handle, no need to release + int oldThreadPriority = GetThreadPriority(hThread); + SetThreadPriority(hThread, THREAD_PRIORITY_NORMAL); + + __try + { + menu2->InvokeCommand(&ici); + } + __except (CCallStack::HandleException(GetExceptionInformation(), 12)) + { + ICExceptionHasOccured++; + } + + SetThreadPriority(hThread, oldThreadPriority); +} + +LRESULT CMainWindow::HandleWmCommand(WPARAM wParam, LPARAM lParam) +{ + if (HelpMode && (HWND)lParam == NULL && LOWORD(wParam) != CM_HELP_CONTEXT) + { + DWORD id = LOWORD(wParam); + + if (id >= CM_PLUGINCMD_MIN && id <= CM_PLUGINCMD_MAX) + { // command of a plugin (submenu of Plugins menu) + if (Plugins.HelpForMenuItem(HWindow, LOWORD(wParam))) + return 0; + else + id = CM_LAST_PLUGIN_CMD; // if the plugin has no help, show Salamander's help "Using Plugins" + } + + // adjust ranges to their first value + if (id > CM_USERMENU_MIN && id <= CM_USERMENU_MAX) + id = CM_USERMENU_MIN; + if (id > CM_DRIVEBAR_MIN && id <= CM_DRIVEBAR_MAX) + id = CM_DRIVEBAR_MIN; + if (id > CM_DRIVEBAR2_MIN && id <= CM_DRIVEBAR2_MAX) + id = CM_DRIVEBAR2_MIN; + if (id > CM_PLUGINCFG_MIN && id <= CM_PLUGINCFG_MAX) + id = CM_PLUGINCFG_MIN; + if (id > CM_PLUGINABOUT_MIN && id <= CM_PLUGINABOUT_MAX) + id = CM_PLUGINABOUT_MIN; + + if (id > CM_ACTIVEMODE_1 && id <= CM_ACTIVEMODE_10) + id = CM_ACTIVEMODE_1; + if (id > CM_LEFTMODE_1 && id <= CM_LEFTMODE_10) + id = CM_LEFTMODE_1; + if (id > CM_RIGHTMODE_1 && id <= CM_RIGHTMODE_10) + id = CM_RIGHTMODE_1; + + if (id > CM_LEFTSORTBY_MIN && id <= CM_LEFTSORTBY_MAX) + id = CM_LEFTSORTBY_MIN; + if (id > CM_RIGHTSORTBY_MIN && id <= CM_RIGHTSORTBY_MAX) + id = CM_RIGHTSORTBY_MIN; + + if (id > CM_LEFTHOTPATH_MIN && id <= CM_LEFTHOTPATH_MAX) + id = CM_LEFTHOTPATH_MIN; + if (id > CM_RIGHTHOTPATH_MIN && id <= CM_RIGHTHOTPATH_MAX) + id = CM_RIGHTHOTPATH_MIN; + + if (id > CM_LEFTHISTORYPATH_MIN && id <= CM_LEFTHISTORYPATH_MAX) + id = CM_LEFTHISTORYPATH_MIN; + if (id > CM_RIGHTHISTORYPATH_MIN && id <= CM_RIGHTHISTORYPATH_MAX) + id = CM_RIGHTHISTORYPATH_MIN; + + if (id > CM_CODING_MIN && id <= CM_CODING_MAX) + id = CM_CODING_MIN; + + if (id > CM_NEWMENU_MIN && id <= CM_NEWMENU_MAX) + id = CM_NEWMENU_MIN; + + OpenHtmlHelp(NULL, HWindow, HHCDisplayContext, id, FALSE); + + return 0; + } + CFilesWindow* activePanel = GetActivePanel(); + if (activePanel == NULL || LeftPanel == NULL || RightPanel == NULL) + { + TRACE_E("activePanel == NULL || LeftPanel == NULL || RightPanel == NULL"); + return 0; + } + + // exit quick-search mode + if (LOWORD(wParam) != CM_ACTIVEREFRESH && // except refresh in the active panel + LOWORD(wParam) != CM_LEFTREFRESH && // except refresh in the left panel + LOWORD(wParam) != CM_RIGHTREFRESH && // except refresh in the right panel + (HIWORD(wParam) == 0 || HIWORD(wParam) == 1)) // only from menu or accelerator + { + CancelPanelsUI(); // cancel QuickSearch and QuickEdit + } + + if (LOWORD(wParam) >= CM_NEWMENU_MIN && LOWORD(wParam) <= CM_NEWMENU_MAX) + { // command from the New menu + if (ContextMenuNew->MenuIsAssigned() && activePanel->CheckPath(TRUE) == ERROR_SUCCESS) + { + activePanel->UserWorkedOnThisPath = TRUE; + { + char newMenuDetail[64]; + _snprintf_s(newMenuDetail, _TRUNCATE, "id=%u", (unsigned)LOWORD(wParam)); + ExecLogFeatureStart("new item", newMenuDetail); + CALL_STACK_MESSAGE1("CMainWindow::WindowProc::menu_new"); + IContextMenu* menu2 = ContextMenuNew->GetMenu2(); + menu2->AddRef(); // just in case ContextMenuNew vanishes asynchronously (message loop) + CShellExecuteWnd shellExecuteWnd; + CMINVOKECOMMANDINFO ici; + ici.cbSize = sizeof(CMINVOKECOMMANDINFO); + ici.fMask = 0; + ici.hwnd = shellExecuteWnd.Create(HWindow, "SEW: CMainWindow::WindowProc cmd=%d", LOWORD(wParam) - CM_NEWMENU_MIN); + ici.lpVerb = MAKEINTRESOURCE((LOWORD(wParam) - CM_NEWMENU_MIN)); + ici.lpParameters = NULL; + ici.lpDirectory = activePanel->GetPath(); + ici.nShow = SW_SHOWNORMAL; + ici.dwHotKey = 0; + ici.hIcon = 0; + activePanel->FocusFirstNewItem = TRUE; // select the newly generated file/directory + + CMainWindowWindowProcAux(menu2, ici); + ExecLogFeatureResult("new item", newMenuDetail, TRUE); + + menu2->Release(); + } + //--- refresh directories that are not automatically refreshed + // announce a change in the current directory (a new file or directory is most likely created there) + MainWindow->PostChangeOnPathNotification(activePanel->GetPath(), FALSE); + } + else + { + ExecLogFeatureResult("new item", "menu unavailable", FALSE); + TRACE_E("ContextMenuNew is not valid anymore, it is not posible to invoke menu New command."); + } + return 0; + } + + if (LOWORD(wParam) >= CM_PLUGINABOUT_MIN && LOWORD(wParam) <= CM_PLUGINABOUT_MAX) + { + Plugins.OnPluginAbout(HWindow, LOWORD(wParam) - CM_PLUGINABOUT_MIN); + return 0; + } + + if (LOWORD(wParam) >= CM_PLUGINCFG_MIN && LOWORD(wParam) <= CM_PLUGINCFG_MAX) + { + Plugins.OnPluginConfiguration(HWindow, LOWORD(wParam) - CM_PLUGINCFG_MIN); + return 0; + } + + if (LOWORD(wParam) >= CM_PLUGINCMD_MIN && LOWORD(wParam) <= CM_PLUGINCMD_MAX) + { // command from a plugin menu + // lower the thread priority to "normal" (so operations don't burden the system) + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL); + + char pluginCmdDetail[64]; + _snprintf_s(pluginCmdDetail, _TRUNCATE, "id=%u", (unsigned)LOWORD(wParam)); + ExecLogFeatureStart("plugin command", pluginCmdDetail); + BOOL pluginCmdResult = Plugins.ExecuteMenuItem(activePanel, HWindow, LOWORD(wParam)); + ExecLogFeatureResult("plugin command", pluginCmdDetail, pluginCmdResult); + if (pluginCmdResult) + { + activePanel->StoreSelection(); // save selection for Restore Selection command + activePanel->SetSel(FALSE, -1, TRUE); // explicit redraw + PostMessage(activePanel->HWindow, WM_USER_SELCHANGED, 0, 0); // sel-change notify + } + + // raise the thread priority again, the operation has finished + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL); + + // restoring the contents of non-automatic panels is up to plugins + + UpdateWindow(HWindow); + return 0; + } + + if (LOWORD(wParam) == CM_LAST_PLUGIN_CMD) + { // Plugins/Last Command action + // lower the thread priority to "normal" (so operations don't burden the system) + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL); + + ExecLogFeatureStart("plugin last command", ""); + BOOL lastCmdResult = Plugins.OnLastCommand(activePanel, HWindow); + ExecLogFeatureResult("plugin last command", "", lastCmdResult); + if (lastCmdResult) + { + activePanel->StoreSelection(); // save selection for Restore Selection command + activePanel->SetSel(FALSE, -1, TRUE); // explicit redraw + PostMessage(activePanel->HWindow, WM_USER_SELCHANGED, 0, 0); // sel-change notify + } + + // raise the thread priority again, the operation has finished + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL); + + // restoring the contents of non-automatic panels is up to plugins + + UpdateWindow(HWindow); + return 0; + } + + if (LOWORD(wParam) >= CM_USERMENU_MIN && LOWORD(wParam) <= CM_USERMENU_MAX) + { + if (activePanel->Is(ptDisk)) + { + activePanel->UserWorkedOnThisPath = TRUE; + activePanel->StoreSelection(); // save selection for Restore Selection command + + CUserMenuAdvancedData userMenuAdvancedData; + + char* list = userMenuAdvancedData.ListOfSelNames; + char* listEnd = list + USRMNUARGS_MAXLEN - 1; + BOOL smallBuf = FALSE; + if (activePanel->SelectedCount > 0) + { + int count = activePanel->Files->Count + activePanel->Dirs->Count; + int i; + for (i = 0; i < count; i++) + { + CFileData* file = (i < activePanel->Dirs->Count) ? &activePanel->Dirs->At(i) : &activePanel->Files->At(i - activePanel->Dirs->Count); + if (file->Selected) + { + if (list > userMenuAdvancedData.ListOfSelNames) + { + if (list < listEnd) + *list++ = ' '; + else + break; + } + if (!AddToListOfNames(&list, listEnd, file->Name, file->NameLen)) + break; + } + } + if (i < count) + smallBuf = TRUE; + } + else // take the focused item + { + BOOL subDir; + if (activePanel->Dirs->Count > 0) + subDir = (strcmp(activePanel->Dirs->At(0).Name, "..") == 0); + else + subDir = FALSE; + int index = activePanel->GetCaretIndex(); + if (index >= 0 && index < activePanel->Files->Count + activePanel->Dirs->Count && + (index != 0 || !subDir)) + { + CFileData* file = (index < activePanel->Dirs->Count) ? &activePanel->Dirs->At(index) : &activePanel->Files->At(index - activePanel->Dirs->Count); + if (!AddToListOfNames(&list, listEnd, file->Name, file->NameLen)) + smallBuf = TRUE; + } + } + if (smallBuf) + { + userMenuAdvancedData.ListOfSelNames[0] = 0; // small buffer for the list of selected names + userMenuAdvancedData.ListOfSelNamesIsEmpty = FALSE; + } + else + { + *list = 0; + userMenuAdvancedData.ListOfSelNamesIsEmpty = userMenuAdvancedData.ListOfSelNames[0] == 0; + } + + char* listFull = userMenuAdvancedData.ListOfSelFullNames; + char* listFullEnd = listFull + USRMNUARGS_MAXLEN - 1; + smallBuf = FALSE; + char fullName[MAX_PATH]; + if (activePanel->SelectedCount > 0) + { + int count = activePanel->Files->Count + activePanel->Dirs->Count; + int i; + for (i = 0; i < count; i++) + { + CFileData* file = (i < activePanel->Dirs->Count) ? &activePanel->Dirs->At(i) : &activePanel->Files->At(i - activePanel->Dirs->Count); + if (file->Selected) + { + if (listFull > userMenuAdvancedData.ListOfSelFullNames) + { + if (listFull < listFullEnd) + *listFull++ = ' '; + else + break; + } + lstrcpyn(fullName, activePanel->GetPath(), MAX_PATH); + if (!SalPathAppend(fullName, file->Name, MAX_PATH) || + !AddToListOfNames(&listFull, listFullEnd, fullName, (int)strlen(fullName))) + break; + } + } + if (i < count) + smallBuf = TRUE; + } + else // take the focused item + { + BOOL subDir; + if (activePanel->Dirs->Count > 0) + subDir = (strcmp(activePanel->Dirs->At(0).Name, "..") == 0); + else + subDir = FALSE; + int index = activePanel->GetCaretIndex(); + if (index >= 0 && index < activePanel->Files->Count + activePanel->Dirs->Count && + (index != 0 || !subDir)) + { + CFileData* file = (index < activePanel->Dirs->Count) ? &activePanel->Dirs->At(index) : &activePanel->Files->At(index - activePanel->Dirs->Count); + lstrcpyn(fullName, activePanel->GetPath(), MAX_PATH); + if (!SalPathAppend(fullName, file->Name, MAX_PATH) || + !AddToListOfNames(&listFull, listFullEnd, fullName, (int)strlen(fullName))) + { + smallBuf = TRUE; + } + } + } + if (smallBuf) + { + userMenuAdvancedData.ListOfSelFullNames[0] = 0; // small buffer for the list of selected full names + userMenuAdvancedData.ListOfSelFullNamesIsEmpty = FALSE; + } + else + { + *listFull = 0; + userMenuAdvancedData.ListOfSelFullNamesIsEmpty = userMenuAdvancedData.ListOfSelFullNames[0] == 0; + } + + if (LeftPanel->Is(ptDisk)) + { + lstrcpyn(userMenuAdvancedData.FullPathLeft, LeftPanel->GetPath(), MAX_PATH); + if (!SalPathAddBackslash(userMenuAdvancedData.FullPathLeft, MAX_PATH)) + userMenuAdvancedData.FullPathLeft[0] = 0; + } + else + userMenuAdvancedData.FullPathLeft[0] = 0; + if (RightPanel->Is(ptDisk)) + { + lstrcpyn(userMenuAdvancedData.FullPathRight, RightPanel->GetPath(), MAX_PATH); + if (!SalPathAddBackslash(userMenuAdvancedData.FullPathRight, MAX_PATH)) + userMenuAdvancedData.FullPathRight[0] = 0; + } + else + userMenuAdvancedData.FullPathRight[0] = 0; + userMenuAdvancedData.FullPathInactive = (activePanel == LeftPanel) ? userMenuAdvancedData.FullPathRight : userMenuAdvancedData.FullPathLeft; + + userMenuAdvancedData.CompareName1[0] = 0; + userMenuAdvancedData.CompareName2[0] = 0; + userMenuAdvancedData.CompareNamesAreDirs = FALSE; + userMenuAdvancedData.CompareNamesReversed = FALSE; + CFilesWindow* inactivePanel = (activePanel == LeftPanel) ? RightPanel : LeftPanel; + CFileData* f1 = NULL; + CFileData* f2 = NULL; + BOOL f2FromInactPanel = FALSE; + int focus = activePanel->GetCaretIndex(); + BOOL focusOnUpDir = (focus == 0 && activePanel->Dirs->Count > 0 && + strcmp(activePanel->Dirs->At(0).Name, "..") == 0); + int indexes[3]; + int selCount = activePanel->GetSelItems(3, indexes); // interested in: 0-2=number selected, 3=more than two + int tgtIndexes[2]; + int tgtSelCount = inactivePanel->Is(ptDisk) ? inactivePanel->GetSelItems(2, tgtIndexes) : 0; // interested in: 0-1=number selected, 2=more than one + if (selCount == 2) // two selected items in the source panel + { + if ((indexes[0] < activePanel->Dirs->Count) == (indexes[1] < activePanel->Dirs->Count)) // both items are files/directories + { + f1 = (indexes[0] < activePanel->Dirs->Count) ? &activePanel->Dirs->At(indexes[0]) : &activePanel->Files->At(indexes[0] - activePanel->Dirs->Count); + f2 = (indexes[1] < activePanel->Dirs->Count) ? &activePanel->Dirs->At(indexes[1]) : &activePanel->Files->At(indexes[1] - activePanel->Dirs->Count); + userMenuAdvancedData.CompareNamesAreDirs = (indexes[0] < activePanel->Dirs->Count); + } + } + else + { + if (selCount == 1) // one selected item in the source panel + { + f1 = (indexes[0] < activePanel->Dirs->Count) ? &activePanel->Dirs->At(indexes[0]) : &activePanel->Files->At(indexes[0] - activePanel->Dirs->Count); + userMenuAdvancedData.CompareNamesAreDirs = (indexes[0] < activePanel->Dirs->Count); + if (!focusOnUpDir && focus != indexes[0] && tgtSelCount != 1) + { + if ((focus < activePanel->Dirs->Count) == userMenuAdvancedData.CompareNamesAreDirs) // both items are files/directories + { + f2 = (focus < activePanel->Dirs->Count) ? &activePanel->Dirs->At(focus) : &activePanel->Files->At(focus - activePanel->Dirs->Count); + } + } + } + else + { + if (selCount == 0) // no selected item in the source panel, take the focus + { + if (!focusOnUpDir) + { + if (focus >= 0 && focus < activePanel->Dirs->Count + activePanel->Files->Count) + { + f1 = (focus < activePanel->Dirs->Count) ? &activePanel->Dirs->At(focus) : &activePanel->Files->At(focus - activePanel->Dirs->Count); + userMenuAdvancedData.CompareNamesAreDirs = (focus < activePanel->Dirs->Count); + } + } + } + } + } + if (f1 != NULL && f2 == NULL) + { + if (tgtSelCount == 1 && + (tgtIndexes[0] < inactivePanel->Dirs->Count) == userMenuAdvancedData.CompareNamesAreDirs) // both items are files/directories + { + f2 = (tgtIndexes[0] < inactivePanel->Dirs->Count) ? &inactivePanel->Dirs->At(tgtIndexes[0]) : &inactivePanel->Files->At(tgtIndexes[0] - inactivePanel->Dirs->Count); + f2FromInactPanel = TRUE; + } + else + { + if (inactivePanel->Is(ptDisk)) + { + int c = inactivePanel->Dirs->Count + inactivePanel->Files->Count; + int i; + for (i = 0; i < c; i++) + { + CFileData* f = (i < inactivePanel->Dirs->Count) ? &inactivePanel->Dirs->At(i) : &inactivePanel->Files->At(i - inactivePanel->Dirs->Count); + if (f->NameLen == f1->NameLen && + StrICmp(f->Name, f1->Name) == 0) + { + if ((i < inactivePanel->Dirs->Count) == userMenuAdvancedData.CompareNamesAreDirs) // both items are files/directories + { + f2 = f; + f2FromInactPanel = TRUE; + } + break; + } + } + } + } + } + if (f1 != NULL) + { + lstrcpyn(userMenuAdvancedData.CompareName1, activePanel->GetPath(), MAX_PATH); + if (!SalPathAppend(userMenuAdvancedData.CompareName1, f1->Name, MAX_PATH)) + userMenuAdvancedData.CompareName1[0] = 0; + } + if (f2 != NULL) + { + lstrcpyn(userMenuAdvancedData.CompareName2, + (f2FromInactPanel ? inactivePanel : activePanel)->GetPath(), MAX_PATH); + if (!SalPathAppend(userMenuAdvancedData.CompareName2, f2->Name, MAX_PATH)) + userMenuAdvancedData.CompareName2[0] = 0; + else + { + if (f2FromInactPanel && inactivePanel == LeftPanel) + userMenuAdvancedData.CompareNamesReversed = TRUE; + } + } + if (userMenuAdvancedData.CompareName1[0] != 0 && + userMenuAdvancedData.CompareName2[0] == 0 && activePanel == RightPanel) + { + userMenuAdvancedData.CompareNamesReversed = TRUE; + } + + CUMDataFromPanel data(activePanel); + SetCurrentDirectory(activePanel->GetPath()); + UserMenu(HWindow, LOWORD(wParam) - CM_USERMENU_MIN, GetNextFileFromPanel, + &data, &userMenuAdvancedData); + SetCurrentDirectoryToSystem(); + } + return 0; + } + + if (LOWORD(wParam) >= CM_VIEWWITH_MIN && LOWORD(wParam) <= CM_VIEWWITH_MAX) + { + activePanel->UserWorkedOnThisPath = TRUE; + activePanel->OnViewFileWith(LOWORD(wParam) - CM_VIEWWITH_MIN); + return 0; + } + + if (LOWORD(wParam) >= CM_EDITWITH_MIN && LOWORD(wParam) <= CM_EDITWITH_MAX) + { + activePanel->UserWorkedOnThisPath = TRUE; + activePanel->OnEditFileWith(LOWORD(wParam) - CM_EDITWITH_MIN); + return 0; + } + + if (LOWORD(wParam) >= CM_DRIVEBAR_MIN && LOWORD(wParam) <= CM_DRIVEBAR_MAX) + { + DriveBar->Execute(LOWORD(wParam)); + return 0; + } + + if (LOWORD(wParam) >= CM_DRIVEBAR2_MIN && LOWORD(wParam) <= CM_DRIVEBAR2_MAX) + { + DriveBar2->Execute(LOWORD(wParam)); + return 0; + } + + if (LOWORD(wParam) >= CM_ACTIVEHOTPATH_MIN && LOWORD(wParam) < CM_ACTIVEHOTPATH_MIN + HOT_PATHS_COUNT) + { + activePanel->GotoHotPath(LOWORD(wParam) - CM_ACTIVEHOTPATH_MIN); + return 0; + } + + if (LOWORD(wParam) >= CM_LEFTHOTPATH_MIN && LOWORD(wParam) < CM_LEFTHOTPATH_MIN + HOT_PATHS_COUNT) + { + LeftPanel->GotoHotPath(LOWORD(wParam) - CM_LEFTHOTPATH_MIN); + return 0; + } + + if (LOWORD(wParam) >= CM_RIGHTHOTPATH_MIN && LOWORD(wParam) < CM_RIGHTHOTPATH_MIN + HOT_PATHS_COUNT) + { + RightPanel->GotoHotPath(LOWORD(wParam) - CM_RIGHTHOTPATH_MIN); + return 0; + } + + if (LOWORD(wParam) >= CM_LEFTHISTORYPATH_MIN && LOWORD(wParam) <= CM_LEFTHISTORYPATH_MAX) + { + DirHistory->Execute(LOWORD(wParam) - CM_LEFTHISTORYPATH_MIN + 1, FALSE, LeftPanel, TRUE, FALSE); + return 0; + } + + if (LOWORD(wParam) >= CM_RIGHTHISTORYPATH_MIN && LOWORD(wParam) <= CM_RIGHTHISTORYPATH_MAX) + { + DirHistory->Execute(LOWORD(wParam) - CM_RIGHTHISTORYPATH_MIN + 1, FALSE, RightPanel, TRUE, FALSE); + return 0; + } + + if (LOWORD(wParam) >= CM_ACTIVEMODE_1 && LOWORD(wParam) <= CM_ACTIVEMODE_10) + { + int index = LOWORD(wParam) - CM_ACTIVEMODE_1; + if (activePanel->IsViewTemplateValid(index)) + activePanel->SelectViewTemplate(index, TRUE, FALSE); + return 0; + } + + if (LOWORD(wParam) >= CM_LEFTMODE_1 && LOWORD(wParam) <= CM_LEFTMODE_10) + { + int index = LOWORD(wParam) - CM_LEFTMODE_1; + if (LeftPanel->IsViewTemplateValid(index)) + LeftPanel->SelectViewTemplate(index, TRUE, FALSE); + return 0; + } + + if (LOWORD(wParam) >= CM_RIGHTMODE_1 && LOWORD(wParam) <= CM_RIGHTMODE_10) + { + int index = LOWORD(wParam) - CM_RIGHTMODE_1; + if (RightPanel->IsViewTemplateValid(index)) + RightPanel->SelectViewTemplate(index, TRUE, FALSE); + return 0; + } + + if (LOWORD(wParam) >= CM_ACTIVEHOTPATH_MIN && LOWORD(wParam) < CM_ACTIVEHOTPATH_MIN + HOT_PATHS_COUNT) + { + activePanel->GotoHotPath(LOWORD(wParam) - CM_ACTIVEHOTPATH_MIN); + return 0; + } + + switch (LOWORD(wParam)) + { + case CM_HELP_CONTEXT: + { + OnContextHelp(); + return 0; + } + + /* + case CM_HELP_KEYBOARD: + { + ShellExecute(HWindow, "open", "https://www.taskscape.com/salam_en/features/keyboard.html", NULL, NULL, SW_SHOWNORMAL); + return 0; + } +*/ + case CM_FORUM: + { + ShellExecute(HWindow, "open", "/", NULL, NULL, SW_SHOWNORMAL); + return 0; + } + + case CM_HELP_CONTENTS: + case CM_HELP_SEARCH: + case CM_HELP_INDEX: + case CM_HELP_KEYBOARD: + { + CHtmlHelpCommand command; + DWORD_PTR dwData = 0; + switch (LOWORD(wParam)) + { + case CM_HELP_CONTENTS: + { + OpenHtmlHelp(NULL, HWindow, HHCDisplayTOC, 0, TRUE); // we don't want two message boxes in a row + command = HHCDisplayContext; + dwData = IDH_INTRODUCTION; + break; + } + + case CM_HELP_INDEX: + { + command = HHCDisplayIndex; + break; + } + + case CM_HELP_SEARCH: + { + command = HHCDisplaySearch; + break; + } + + case CM_HELP_KEYBOARD: + { + command = HHCDisplayContext; + dwData = CM_HELP_KEYBOARD; + break; + } + } + + OpenHtmlHelp(NULL, HWindow, command, dwData, FALSE); + + return 0; + } + + case CM_HELP_ABOUT: + { + CAboutDialog dlg(HWindow); + dlg.Execute(); + return 0; + } + + /* + case CM_HELP_TIP: + { + BOOL openQuiet = lParam == 0xffffffff; + if (TipOfTheDayDialog != NULL) + { + TipOfTheDayDialog->IncrementTipIndex(); + TipOfTheDayDialog->InvalidateTipWindow(); + SetForegroundWindow(TipOfTheDayDialog->HWindow); + } + else + { + TipOfTheDayDialog = new CTipOfTheDayDialog(openQuiet); + if (TipOfTheDayDialog != NULL) + { + if (TipOfTheDayDialog->IsGood()) + { + TipOfTheDayDialog->Create(); + } + else + { + delete TipOfTheDayDialog; + TipOfTheDayDialog = NULL; + // the file probably does not exist - next time we won't even try at startup + if (openQuiet) + Configuration.ShowTipOfTheDay = FALSE; + } + } + } + return 0; + } +*/ + case CM_ALWAYSONTOP: + { + if (!Configuration.AlwaysOnTop && Configuration.CnfrmAlwaysOnTop) + { + BOOL dontShow = !Configuration.CnfrmAlwaysOnTop; + + MSGBOXEX_PARAMS params; + memset(¶ms, 0, sizeof(params)); + params.HParent = HWindow; + params.Flags = MSGBOXEX_OKCANCEL | MSGBOXEX_ICONINFORMATION | MSGBOXEX_SILENT | MSGBOXEX_HINT; + params.Caption = LoadStr(IDS_INFOTITLE); + params.Text = LoadStr(IDS_WANTALWAYSONTOP); + params.CheckBoxText = LoadStr(IDS_DONTSHOWAGAINAT); + params.CheckBoxValue = &dontShow; + int ret = SalMessageBoxEx(¶ms); + Configuration.CnfrmAlwaysOnTop = !dontShow; + if (ret == IDCANCEL) + return 0; + } + + Configuration.AlwaysOnTop = !Configuration.AlwaysOnTop; + HMENU h = GetSystemMenu(HWindow, FALSE); + if (h != NULL) + { + CheckMenuItem(h, CM_ALWAYSONTOP, MF_BYCOMMAND | (Configuration.AlwaysOnTop ? MF_CHECKED : MF_UNCHECKED)); + } + + SetWindowPos(HWindow, + Configuration.AlwaysOnTop ? HWND_TOPMOST : HWND_NOTOPMOST, + 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); + + return 0; + } + + case CM_MINIMIZE: + MinimizeApp(MainWindow->HWindow); + return 0; + + case CM_TASKLIST: + { + CTaskListDialog(HWindow).Execute(); + return 0; + } + + case CM_CLIPCOPYFULLNAME: + { + activePanel->UserWorkedOnThisPath = TRUE; + activePanel->CopyFocusedNameToClipboard(cfnmFull); + return 0; + } + + case CM_CLIPCOPYNAME: + { + activePanel->UserWorkedOnThisPath = TRUE; + activePanel->CopyFocusedNameToClipboard(cfnmShort); + return 0; + } + + case CM_CLIPCOPYFULLPATH: + { + activePanel->UserWorkedOnThisPath = TRUE; + activePanel->CopyCurrentPathToClipboard(); + return 0; + } + + case CM_CLIPCOPYUNCNAME: + { + activePanel->UserWorkedOnThisPath = TRUE; + activePanel->CopyFocusedNameToClipboard(cfnmUNC); + return 0; + } + + case CM_OPEN_IN_OTHER_PANEL: + case CM_OPEN_IN_OTHER_PANEL_ACT: + { + activePanel->OpenFocusedInOtherPanel(LOWORD(wParam) == CM_OPEN_IN_OTHER_PANEL_ACT); + return 0; + } + + case CM_PLUGINS: + { + BeginStopRefresh(); // snooper takes a break + + CPluginsDlg dlg(HWindow); + dlg.Execute(); + if (dlg.GetRefreshPanels()) + { + UpdateWindow(HWindow); + + if ((LeftPanel->Is(ptDisk) || LeftPanel->Is(ptZIPArchive)) && + IsUNCPath(LeftPanel->GetPath()) && + LeftPanel->DirectoryLine != NULL) + { + LeftPanel->DirectoryLine->BuildHotTrackItems(); + } + if ((RightPanel->Is(ptDisk) || RightPanel->Is(ptZIPArchive)) && + IsUNCPath(RightPanel->GetPath()) && + RightPanel->DirectoryLine != NULL) + { + RightPanel->DirectoryLine->BuildHotTrackItems(); + } + + HANDLES(EnterCriticalSection(&TimeCounterSection)); + int t1 = MyTimeCounter++; + int t2 = MyTimeCounter++; + HANDLES(LeaveCriticalSection(&TimeCounterSection)); + SendMessage(LeftPanel->HWindow, WM_USER_REFRESH_DIR, 0, t1); + SendMessage(RightPanel->HWindow, WM_USER_REFRESH_DIR, 0, t2); + } + if (dlg.GetRefreshPanels() || // also refresh drive bars because of the Nethood plugin (Network Neighborhood icon appears/disappears) + dlg.GetDrivesBarChange()) // change in visibility of the FS item in the Drive bars + { + PostMessage(HWindow, WM_USER_DRIVES_CHANGE, 0, 0); + } + + const char* focusPlugin = dlg.GetFocusPlugin(); + if (focusPlugin[0] != 0) + { + char newPath[MAX_PATH]; + lstrcpyn(newPath, focusPlugin, MAX_PATH); + const char* newName; + char* p = strrchr(newPath, '\\'); + if (p != NULL) + { + p++; + *p = 0; + newName = focusPlugin + int(p - newPath); + } + else + newName = ""; + SendMessage(GetActivePanel()->HWindow, WM_USER_FOCUSFILE, (WPARAM)newName, (LPARAM)newPath); + } + + EndStopRefresh(); // snooper starts again now + return 0; + } + + case CM_SAVECONFIG: + { + // if an exported configuration already exists, show a warning + if (FileExists(ConfigurationName)) + { + char buff[3000]; + _snprintf_s(buff, _TRUNCATE, LoadStr(IDS_SAVECFG_EXPFILEEXISTS), ConfigurationName); + int ret = SalMessageBox(HWindow, buff, LoadStr(IDS_INFOTITLE), + MB_ICONINFORMATION | MB_OKCANCEL); + if (ret == IDCANCEL) + { + // navigate the user to the correct directory and focus the configuration file to make it easier + char path[MAX_PATH]; + char* s = strrchr(ConfigurationName, '\\'); + if (s != NULL) + { + memcpy(path, ConfigurationName, s - ConfigurationName); + path[s - ConfigurationName] = 0; + SendMessage(activePanel->HWindow, WM_USER_FOCUSFILE, (WPARAM)(s + 1), (LPARAM)path); + } + return 0; + } + } + SaveConfig(); + return 0; + } + + case CM_EXPORTCONFIG: + { + int ret = SalMessageBox(HWindow, LoadStr(IDS_PREDCONFIGEXPORT), + LoadStr(IDS_QUESTION), MB_YESNOCANCEL | MB_ICONQUESTION); + if (ret == IDCANCEL) + return 0; + + if (ret == IDYES) + { + SaveConfig(); + } + + char file[MAX_PATH]; + char defDir[MAX_PATH]; + strcpy(file, "config_.reg"); + + BOOL clearKeyBeforeImport = TRUE; + + MSGBOXEX_PARAMS params; + memset(¶ms, 0, sizeof(params)); + params.HParent = HWindow; + params.Flags = MSGBOXEX_OK | MSGBOXEX_ICONINFORMATION | MSGBOXEX_SILENT; + params.Caption = LoadStr(IDS_INFOTITLE); + params.Text = LoadStr(WindowsVistaAndLater ? IDS_CONFIGEXPVISTA : IDS_CONFIGEXPUPTOXP); + params.CheckBoxText = LoadStr(IDS_CONFIGEXPCLEARKEY); + params.CheckBoxValue = &clearKeyBeforeImport; + SalMessageBoxEx(¶ms); + + if (WindowsVistaAndLater) + { + if (!CreateOurPathInRoamingAPPDATA(defDir, _countof(defDir))) + { + TRACE_E("CM_EXPORTCONFIG: unexpected situation: unable to get our directory under CSIDL_APPDATA"); + return 0; + } + } + else + { + GetModuleFileName(HInstance, defDir, MAX_PATH); + *strrchr(defDir, '\\') = 0; + } + OPENFILENAME ofn; + memset(&ofn, 0, sizeof(OPENFILENAME)); + ofn.lStructSize = sizeof(OPENFILENAME); + ofn.hwndOwner = HWindow; + char* s = LoadStr(IDS_REGFILTER); + ofn.lpstrFilter = s; + while (*s != 0) // create a double-null-terminated list + { + if (*s == '|') + *s = 0; + s++; + } + ofn.nFilterIndex = 1; + ofn.lpstrFile = file; + ofn.nMaxFile = MAX_PATH; + ofn.lpstrInitialDir = defDir; + + ofn.Flags = OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY; + ofn.lpstrDefExt = "reg"; + + if (SafeGetSaveFileName(&ofn)) + { + if (SalGetFullName(file)) + { + // perform the export + if (ExportConfiguration(HWindow, file, clearKeyBeforeImport)) + { + SalMessageBox(HWindow, LoadStr(IDS_CONFIGEXPORTED), LoadStr(IDS_INFOTITLE), + MB_OK | MB_ICONINFORMATION); + } + else + DeleteFileUtf8(file); + } + } + return 0; + } + + case CM_IMPORTCONFIG: + { + SalMessageBox(HWindow, LoadStr(IDS_CONFIGHOWTOIMPORT), LoadStr(IDS_INFOTITLE), + MB_OK | MB_ICONINFORMATION); + return 0; + } + + case CM_SHARES: + { + CSharesDialog dlg(HWindow); + if (dlg.Execute() == IDOK) + { + // user chose Focus + const char* path = dlg.GetFocusedPath(); + if (path != NULL) + { + char newPath[MAX_PATH]; + lstrcpyn(newPath, path, MAX_PATH); + const char* newName; + char* p = strrchr(newPath, '\\'); + if (p != NULL) + { + p++; + *p = 0; + newName = path + int(p - newPath); + } + else + newName = ""; + SendMessage(GetActivePanel()->HWindow, WM_USER_FOCUSFILE, (WPARAM)newName, (LPARAM)newPath); + } + } + break; + } + + case CM_SKILLLEVEL: + { + CSkillLevelDialog dlg(HWindow, &Configuration.SkillLevel); + if (dlg.Execute() == IDOK) + MainMenu.SetSkillLevel(CfgSkillLevelToMenu(Configuration.SkillLevel)); + break; + } + + case CM_CONFIGURATION: + { + PostMessage(HWindow, WM_USER_CONFIGURATION, 0, 0); // standard configuration + break; + } + + case CM_AUTOCONFIG: + { + PostMessage(HWindow, WM_USER_AUTOCONFIG, 0, 0); + break; + } + + case CM_LCUSTOMIZEVIEW: + { + PostMessage(HWindow, WM_USER_CONFIGURATION, 4, LeftPanel->GetViewTemplateIndex()); + return 0; + } + + case CM_RCUSTOMIZEVIEW: + { + PostMessage(HWindow, WM_USER_CONFIGURATION, 4, RightPanel->GetViewTemplateIndex()); + return 0; + } + + case CM_LEFTNAME: + { + LeftPanel->ChangeSortType(stName, TRUE); + return 0; + } + + case CM_LEFTEXT: + { + LeftPanel->ChangeSortType(stExtension, TRUE); + return 0; + } + + case CM_LEFTTIME: + { + LeftPanel->ChangeSortType(stTime, TRUE); + return 0; + } + + case CM_LEFTSIZE: + { + LeftPanel->ChangeSortType(stSize, TRUE); + return 0; + } + + case CM_LEFTATTR: + { + LeftPanel->ChangeSortType(stAttr, TRUE); + return 0; + } + // change sorting in the right panel + case CM_RIGHTNAME: + { + RightPanel->ChangeSortType(stName, TRUE); + return 0; + } + + case CM_RIGHTEXT: + { + RightPanel->ChangeSortType(stExtension, TRUE); + return 0; + } + + case CM_RIGHTTIME: + { + RightPanel->ChangeSortType(stTime, TRUE); + return 0; + } + + case CM_RIGHTSIZE: + { + RightPanel->ChangeSortType(stSize, TRUE); + return 0; + } + + case CM_RIGHTATTR: + { + RightPanel->ChangeSortType(stAttr, TRUE); + return 0; + } + // change sorting in the current panel + case CM_ACTIVENAME: + activePanel->ChangeSortType(stName, TRUE); + return 0; + case CM_ACTIVEEXT: + activePanel->ChangeSortType(stExtension, TRUE); + return 0; + case CM_ACTIVETIME: + activePanel->ChangeSortType(stTime, TRUE); + return 0; + case CM_ACTIVESIZE: + activePanel->ChangeSortType(stSize, TRUE); + return 0; + case CM_ACTIVEATTR: + activePanel->ChangeSortType(stAttr, TRUE); + return 0; + + case CM_SORTOPTIONS: + { + PostMessage(HWindow, WM_USER_CONFIGURATION, 5, 0); + return 0; + } + + // toggle Smart Mode (Ctrl+N) + case CM_ACTIVE_SMARTMODE: + ToggleSmartColumnMode(activePanel); + return 0; + case CM_LEFT_SMARTMODE: + ToggleSmartColumnMode(LeftPanel); + return 0; + case CM_RIGHT_SMARTMODE: + ToggleSmartColumnMode(RightPanel); + return 0; + + // change the current drive in the left panel + case CM_LCHANGEDRIVE: + { + if (activePanel != LeftPanel) + { + ChangePanel(); + if (GetActivePanel() != LeftPanel) + return 0; // the panel cannot be activated + UpdateWindow(HWindow); // render the focus before the menu appears + } + if (LeftPanel->DirectoryLine != NULL) + LeftPanel->DirectoryLine->SetDrivePressed(TRUE); + LeftPanel->ChangeDrive(); + if (LeftPanel->DirectoryLine != NULL) + LeftPanel->DirectoryLine->SetDrivePressed(FALSE); + return 0; + } + // change of the current drive in the right panel + case CM_RCHANGEDRIVE: + { + if (activePanel != RightPanel) + { + ChangePanel(); + if (GetActivePanel() != RightPanel) + return 0; // the panel cannot be activated + UpdateWindow(HWindow); // render the focus before the menu appears + } + if (RightPanel->DirectoryLine != NULL) + RightPanel->DirectoryLine->SetDrivePressed(TRUE); + RightPanel->ChangeDrive(); + if (RightPanel->DirectoryLine != NULL) + RightPanel->DirectoryLine->SetDrivePressed(FALSE); + return 0; + } + // change the file filter + case CM_LCHANGEFILTER: + { + LeftPanel->ChangeFilter(); + return 0; + } + + case CM_RCHANGEFILTER: + { + RightPanel->ChangeFilter(); + return 0; + } + + case CM_CHANGEFILTER: + activePanel->ChangeFilter(); + return 0; + + case CM_ACTIVEPARENTDIR: + { + activePanel->CtrlPageUpOrBackspace(); + return 0; + } + + case CM_LPARENTDIR: + { + LeftPanel->CtrlPageUpOrBackspace(); + return 0; + } + + case CM_RPARENTDIR: + { + RightPanel->CtrlPageUpOrBackspace(); + return 0; + } + + case CM_ACTIVEROOTDIR: + { + activePanel->GotoRoot(); + return 0; + } + + case CM_LROOTDIR: + { + LeftPanel->GotoRoot(); + return 0; + } + + case CM_RROOTDIR: + { + RightPanel->GotoRoot(); + return 0; + } + // enabling/diabling the left panel status line + case CM_LEFTSTATUS: + { + LeftPanel->ToggleStatusLine(); + IdleRefreshStates = TRUE; // on the next Idle, force a check of status variables + return 0; + } + // enabling/disabling the right panel status line + case CM_RIGHTSTATUS: + { + RightPanel->ToggleStatusLine(); + IdleRefreshStates = TRUE; // on the next Idle, force a check of status variables + return 0; + } + // enabling/disabling the left panel directory line + case CM_LEFTDIRLINE: + { + LeftPanel->ToggleDirectoryLine(); + IdleRefreshStates = TRUE; // on the next Idle, force a check of status variables + return 0; + } + // enabling/disabling the right panel directory line + case CM_RIGHTDIRLINE: + { + RightPanel->ToggleDirectoryLine(); + IdleRefreshStates = TRUE; // on the next Idle, force a check of status variables + return 0; + } + + case CM_LEFTHEADER: + { + LeftPanel->ToggleHeaderLine(); + LeftPanel->HeaderLineVisible = !LeftPanel->HeaderLineVisible; + return 0; + } + + case CM_RIGHTHEADER: + { + RightPanel->ToggleHeaderLine(); + RightPanel->HeaderLineVisible = !RightPanel->HeaderLineVisible; + return 0; + } + + case CM_LEFTREFRESH: // refresh the left panel + { + LeftPanel->NextFocusName[0] = 0; + while (SnooperSuspended) + EndSuspendMode(); // safety catch to resume refreshing + while (StopRefresh) + EndStopRefresh(FALSE); // safety catch to resume refreshing + while (StopIconRepaint) + EndStopIconRepaint(FALSE); // safety catch to resume refreshing + HANDLES(EnterCriticalSection(&TimeCounterSection)); + int t1 = MyTimeCounter++; + HANDLES(LeaveCriticalSection(&TimeCounterSection)); + SendMessage(LeftPanel->HWindow, WM_USER_REFRESH_DIR, 0, t1); + RebuildDriveBarsIfNeeded(FALSE, 0, FALSE, 0); // maybe the user refreshed to update the drives list? + return 0; + } + + case CM_RIGHTREFRESH: // refresh the right panel + { + RightPanel->NextFocusName[0] = 0; + while (SnooperSuspended) + EndSuspendMode(); // safety catch to resume refreshing + while (StopRefresh) + EndStopRefresh(FALSE); // safety catch to resume refreshing + while (StopIconRepaint) + EndStopIconRepaint(FALSE); // safety catch to resume refreshing + HANDLES(EnterCriticalSection(&TimeCounterSection)); + int t1 = MyTimeCounter++; + HANDLES(LeaveCriticalSection(&TimeCounterSection)); + SendMessage(RightPanel->HWindow, WM_USER_REFRESH_DIR, 0, t1); + RebuildDriveBarsIfNeeded(FALSE, 0, FALSE, 0); // maybe the user refreshed to update the drives list? + return 0; + } + + case CM_ACTIVEREFRESH: // refresh the right panel + { + activePanel->NextFocusName[0] = 0; + while (SnooperSuspended) + EndSuspendMode(); // safety catch to resume refreshing + while (StopRefresh) + EndStopRefresh(FALSE); // safety catch to resume refreshing + while (StopIconRepaint) + EndStopIconRepaint(FALSE); // safety catch to resume refreshing + HANDLES(EnterCriticalSection(&TimeCounterSection)); + int t1 = MyTimeCounter++; + HANDLES(LeaveCriticalSection(&TimeCounterSection)); + SendMessage(activePanel->HWindow, WM_USER_REFRESH_DIR, 0, t1); + RebuildDriveBarsIfNeeded(FALSE, 0, FALSE, 0); // maybe the user refreshed to update the drives list? + return 0; + } + + case CM_ACTIVEFORWARD: + { + activePanel->PathHistory->Execute(1, TRUE, activePanel); + return 0; + } + + case CM_ACTIVEBACK: + { + activePanel->PathHistory->Execute(2, FALSE, activePanel); + return 0; + } + + case CM_LFORWARD: + { + LeftPanel->PathHistory->Execute(1, TRUE, LeftPanel); + return 0; + } + + case CM_LBACK: + { + LeftPanel->PathHistory->Execute(2, FALSE, LeftPanel); + return 0; + } + + case CM_RFORWARD: + { + RightPanel->PathHistory->Execute(1, TRUE, RightPanel); + return 0; + } + + case CM_RBACK: + { + RightPanel->PathHistory->Execute(2, FALSE, RightPanel); + return 0; + } + + case CM_REFRESHASSOC: // reload associations from the Registry + { + OnAssociationsChangedNotification(TRUE); + return 0; + } + + case CM_EMAILFILES: // emailing files and directories + { + if (!EnablerFilesOnDisk) + return 0; + activePanel->UserWorkedOnThisPath = TRUE; + activePanel->StoreSelection(); // save selection for Restore Selection command + + // if no item is selected, select the focused one and store its name + char temporarySelected[MAX_PATH]; + activePanel->SelectFocusedItemAndGetName(temporarySelected, MAX_PATH); + + activePanel->EmailFiles(); + + // if we selected an item, deselect it again + activePanel->UnselectItemWithName(temporarySelected); + + return 0; + } + + case CM_COPYFILES: // copy files and directories + if (!EnablerFilesCopy) + return 0; + case CM_MOVEFILES: // move/rename files and directories + if (LOWORD(wParam) == CM_MOVEFILES && !EnablerFilesMove) + return 0; + case CM_DELETEFILES: // delete files and directories + if (LOWORD(wParam) == CM_DELETEFILES && !EnablerFilesDelete) + return 0; + case CM_OCCUPIEDSPACE: // calculate occupied disk space + if (LOWORD(wParam) == CM_OCCUPIEDSPACE && !EnablerOccupiedSpace) + return 0; + case CM_CHANGECASE: // change case in names + { + if (LOWORD(wParam) == CM_CHANGECASE && !EnablerFilesOnDisk) + return 0; + activePanel->UserWorkedOnThisPath = TRUE; + activePanel->StoreSelection(); // save selection for Restore Selection command + + // if no item is selected, select the focused one and store its name + char temporarySelected[MAX_PATH]; + activePanel->SelectFocusedItemAndGetName(temporarySelected, MAX_PATH); + + if (activePanel->Is(ptDisk)) // source is disk - all operations go here + { + CActionType type; + switch (LOWORD(wParam)) + { + case CM_COPYFILES: + type = atCopy; + break; + case CM_MOVEFILES: + type = atMove; + break; + case CM_DELETEFILES: + type = atDelete; + break; + case CM_OCCUPIEDSPACE: + type = atCountSize; + break; + case CM_CHANGECASE: + type = atChangeCase; + break; + } + + // perform the action + activePanel->FilesAction(type, GetNonActivePanel()); + } + else + { + if (activePanel->Is(ptZIPArchive)) // source is an archive - all operations go here + { + BOOL archMaybeUpdated; + activePanel->OfferArchiveUpdateIfNeeded(HWindow, IDS_ARCHIVECLOSEEDIT2, &archMaybeUpdated); + if (!archMaybeUpdated) + { + switch (LOWORD(wParam)) + { + case CM_OCCUPIEDSPACE: + activePanel->CalculateOccupiedZIPSpace(); + break; + case CM_COPYFILES: + activePanel->UnpackZIPArchive(GetNonActivePanel()); + break; + case CM_DELETEFILES: + activePanel->DeleteFromZIPArchive(); + break; + } + } + } + else + { + if (activePanel->Is(ptPluginFS)) // source is a FS - all operations go here + { + CPluginFSActionType type; + switch (LOWORD(wParam)) + { + case CM_COPYFILES: + type = fsatCopy; + break; + case CM_MOVEFILES: + type = fsatMove; + break; + case CM_DELETEFILES: + type = fsatDelete; + break; + case CM_OCCUPIEDSPACE: + type = fsatCountSize; + break; + } + activePanel->PluginFSFilesAction(type); + } + } + } + + // if we selected an item temporarily, deselect it again + activePanel->UnselectItemWithName(temporarySelected); + + return 0; + } + + case CM_MENU: + { + MenuBar->EnterMenu(); + return 0; + } + + case CM_DIRMENU: + { + ShellAction(activePanel, saContextMenu, FALSE, FALSE); + return 0; + } + + case CM_CONTEXTMENU: + { // panel type checks are done later in ShellAction + activePanel->UserWorkedOnThisPath = TRUE; + activePanel->StoreSelection(); // save selection for Restore Selection command + ShellAction(activePanel, saContextMenu, TRUE, FALSE); + return 0; + } + + case CM_CALCDIRSIZES: + { + activePanel->UserWorkedOnThisPath = TRUE; + activePanel->CalculateDirSizes(); + return 0; + } + + case CM_RENAMEFILE: + { + activePanel->UserWorkedOnThisPath = TRUE; + activePanel->RenameFile(); + return 0; + } + + case CM_CHANGEATTR: + { + if (EnablerChangeAttrs) + { + activePanel->UserWorkedOnThisPath = TRUE; + activePanel->StoreSelection(); // save selection for Restore Selection command + activePanel->ChangeAttr(); + } + return 0; + } + + case CM_CONVERTFILES: + { + if (activePanel->Is(ptDisk)) + { + activePanel->UserWorkedOnThisPath = TRUE; + activePanel->StoreSelection(); // save selection for Restore Selection command + + // if no item is selected, choose the one under the focus and store its name + char temporarySelected[MAX_PATH]; + activePanel->SelectFocusedItemAndGetName(temporarySelected, MAX_PATH); + + activePanel->Convert(); + + // if we selected an item temporarily, deselect it again + activePanel->UnselectItemWithName(temporarySelected); + } + return 0; + } + + case CM_COMPRESS: + { + if (activePanel->Is(ptDisk)) + { + activePanel->UserWorkedOnThisPath = TRUE; + activePanel->StoreSelection(); // save selection for Restore Selection command + activePanel->ChangeAttr(TRUE, TRUE); + } + return 0; + } + + case CM_UNCOMPRESS: + { + if (activePanel->Is(ptDisk)) + { + activePanel->UserWorkedOnThisPath = TRUE; + activePanel->StoreSelection(); // save selection for Restore Selection command + activePanel->ChangeAttr(TRUE, FALSE); + } + return 0; + } + + case CM_ENCRYPT: + { + if (activePanel->Is(ptDisk)) + { + ExecLogFeatureStart("encrypt", activePanel->GetPath()); + activePanel->UserWorkedOnThisPath = TRUE; + activePanel->StoreSelection(); // save selection for Restore Selection command + activePanel->ChangeAttr(FALSE, FALSE, TRUE, TRUE); + } + return 0; + } + + case CM_DECRYPT: + { + if (activePanel->Is(ptDisk)) + { + ExecLogFeatureStart("decrypt", activePanel->GetPath()); + activePanel->UserWorkedOnThisPath = TRUE; + activePanel->StoreSelection(); // save selection for Restore Selection command + activePanel->ChangeAttr(FALSE, FALSE, TRUE, FALSE); + } + return 0; + } + + case CM_PACK: + { + if (activePanel->Is(ptDisk)) + { + ExecLogFeatureStart("pack", activePanel->GetPath()); + activePanel->UserWorkedOnThisPath = TRUE; + activePanel->StoreSelection(); // save selection for Restore Selection command + activePanel->Pack(GetNonActivePanel()); + } + return 0; + } + + case CM_UNPACK: + { + if (activePanel->Is(ptDisk)) + { + ExecLogFeatureStart("unpack", activePanel->GetPath()); + activePanel->UserWorkedOnThisPath = TRUE; + activePanel->StoreSelection(); // save selection for Restore Selection command + activePanel->Unpack(GetNonActivePanel()); + } + return 0; + } + + case CM_AFOCUSSHORTCUT: + { + if (EnablerFileOrDirLinkOnDisk) // enabler for activePanel + { + // activePanel->UserWorkedOnThisPath = TRUE; // it's just navigation, don't mark the path dirty + activePanel->FocusShortcutTarget(activePanel); + } + return 0; + } + + case CM_PROPERTIES: + { + if (EnablerShowProperties) + { + ExecLogFeatureStart("properties", activePanel->GetPath()); + activePanel->UserWorkedOnThisPath = TRUE; + activePanel->StoreSelection(); // save selection for Restore Selection command + ShellAction(activePanel, saProperties, TRUE, FALSE); + } + return 0; + } + + case CM_OPEN: + { + ExecLogFeatureStart("open", activePanel->GetPath()); + activePanel->UserWorkedOnThisPath = TRUE; + activePanel->CtrlPageDnOrEnter(VK_RETURN); + return 0; + } + + case CM_VIEW: + { + ExecLogFeatureStart("view", activePanel->GetPath()); + activePanel->UserWorkedOnThisPath = TRUE; + activePanel->ViewFile(NULL, FALSE, 0xFFFFFFFF, activePanel->Is(ptDisk) ? activePanel->EnumFileNamesSourceUID : -1, -1); + return 0; + } + + case CM_ALTVIEW: + { + ExecLogFeatureStart("alt view", activePanel->GetPath()); + activePanel->UserWorkedOnThisPath = TRUE; + activePanel->ViewFile(NULL, TRUE, 0xFFFFFFFF, activePanel->Is(ptDisk) ? activePanel->EnumFileNamesSourceUID : -1, -1); + return 0; + } + + case CM_VIEW_WITH: + { + POINT menuPos; + ExecLogFeatureStart("view with", activePanel->GetPath()); + activePanel->UserWorkedOnThisPath = TRUE; + activePanel->GetContextMenuPos(&menuPos); + activePanel->ViewFileWith(NULL, HWindow, &menuPos, NULL, + activePanel->Is(ptDisk) ? activePanel->EnumFileNamesSourceUID : -1, -1); + return 0; + } + + case CM_EDIT: + { + if (EnablerFileOnDiskOrArchive) + { + ExecLogFeatureStart("edit", activePanel->GetPath()); + activePanel->UserWorkedOnThisPath = TRUE; + if (activePanel->Is(ptZIPArchive)) + { + int index = activePanel->GetCaretIndex(); + if (index >= activePanel->Dirs->Count && + index < activePanel->Dirs->Count + activePanel->Files->Count) + { + activePanel->ExecuteFromArchive(index, TRUE); + } + } + else + activePanel->EditFile(NULL); + } + return 0; + } + + case CM_EDITNEW: + { + if (activePanel->Is(ptDisk)) + { + ExecLogFeatureStart("edit new", activePanel->GetPath()); + activePanel->UserWorkedOnThisPath = TRUE; + activePanel->EditNewFile(); + } + return 0; + } + + case CM_EDIT_WITH: + { + activePanel->UserWorkedOnThisPath = TRUE; + POINT menuPos; + ExecLogFeatureStart("edit with", activePanel->GetPath()); + activePanel->GetContextMenuPos(&menuPos); + if (activePanel->Is(ptDisk)) + { + activePanel->EditFileWith(NULL, HWindow, &menuPos); + } + else + { + if (activePanel->Is(ptZIPArchive)) + { + int index = activePanel->GetCaretIndex(); + if (index >= activePanel->Dirs->Count && + index < activePanel->Dirs->Count + activePanel->Files->Count) + { + activePanel->ExecuteFromArchive(index, TRUE, HWindow, &menuPos); + } + } + } + return 0; + } + + case CM_FINDFILE: + { + ExecLogFeatureStart("find file", activePanel->GetPath()); + if (activePanel->Is(ptDisk)) // does Find relate to the current path? (archives and FS not yet) + { + activePanel->UserWorkedOnThisPath = TRUE; + } + + activePanel->FindFile(); + return 0; + } + + case CM_DRIVEINFO: + { + activePanel->DriveInfo(); + return 0; + } + + case CM_CREATEDIR: + { + ExecLogFeatureStart("create dir", activePanel->GetPath()); + activePanel->UserWorkedOnThisPath = TRUE; + activePanel->CreateDir(GetNonActivePanel()); + return 0; + } + + case CM_ACTIVE_CHANGEDIR: + { + ExecLogFeatureStart("change dir", activePanel->GetPath()); + activePanel->ChangeDir(); + return 0; + } + + case CM_LEFT_CHANGEDIR: + { + ExecLogFeatureStart("change dir", LeftPanel->GetPath()); + LeftPanel->ChangeDir(); + return 0; + } + + case CM_RIGHT_CHANGEDIR: + { + ExecLogFeatureStart("change dir", RightPanel->GetPath()); + RightPanel->ChangeDir(); + return 0; + } + + case CM_ACTIVE_AS_OTHER: + { + ExecLogFeatureStart("sync path", "active to other"); + activePanel->ChangePathToOtherPanelPath(); + return 0; + } + + case CM_LEFT_AS_OTHER: + { + ExecLogFeatureStart("sync path", "left to other"); + LeftPanel->ChangePathToOtherPanelPath(); + return 0; + } + + case CM_RIGHT_AS_OTHER: + { + ExecLogFeatureStart("sync path", "right to other"); + RightPanel->ChangePathToOtherPanelPath(); + return 0; + } + + case CM_ACTIVESELECTALL: + { + activePanel->SelectUnselect(TRUE, TRUE, FALSE); + return 0; + } + + case CM_ACTIVEUNSELECTALL: + { + activePanel->SelectUnselect(TRUE, FALSE, FALSE); + return 0; + } + + case CM_ACTIVESELECT: + { + activePanel->SelectUnselect(FALSE, TRUE, TRUE); + return 0; + } + + case CM_ACTIVEUNSELECT: + { + activePanel->SelectUnselect(FALSE, FALSE, TRUE); + return 0; + } + + case CM_ACTIVEINVERTSEL: + { + activePanel->InvertSelection(FALSE); + return 0; + } + + case CM_ACTIVEINVERTSELALL: + { + activePanel->InvertSelection(TRUE); + return 0; + } + + case CM_RESELECT: + { + activePanel->Reselect(); + return 0; + } + + case CM_SELECTBYFOCUSEDNAME: + { + activePanel->SelectUnselectByFocusedItem(TRUE, TRUE); + return 0; + } + + case CM_UNSELECTBYFOCUSEDNAME: + { + activePanel->SelectUnselectByFocusedItem(FALSE, TRUE); + return 0; + } + + case CM_SELECTBYFOCUSEDEXT: + { + activePanel->SelectUnselectByFocusedItem(TRUE, FALSE); + return 0; + } + + case CM_UNSELECTBYFOCUSEDEXT: + { + activePanel->SelectUnselectByFocusedItem(FALSE, FALSE); + return 0; + } + + case CM_HIDE_SELECTED_NAMES: + { + activePanel->ShowHideNames(1); // hide selected + return 0; + } + + case CM_HIDE_UNSELECTED_NAMES: + { + activePanel->ShowHideNames(2); // hide unselected + return 0; + } + + case CM_SHOW_ALL_NAME: + { + activePanel->ShowHideNames(0); // show all + return 0; + } + + case CM_STORESEL: + { + activePanel->StoreGlobalSelection(); + return 0; + } + + case CM_RESTORESEL: + { + activePanel->RestoreGlobalSelection(); + return 0; + } + + case CM_GOTO_PREV_SEL: + case CM_GOTO_NEXT_SEL: + { + activePanel->GotoSelectedItem(LOWORD(wParam) == CM_GOTO_NEXT_SEL); + return 0; + } + + case CM_COMPAREDIRS: + { + // currently we support only ptDisk<->ptDisk, ptDisk<->ptZIPArchive and ptZIPArchive<->ptZIPArchive + //if (LeftPanel->Is(ptPluginFS) || RightPanel->Is(ptPluginFS)) + //{ + // SalMessageBox(HWindow, LoadStr(IDS_COMPARE_FS), LoadStr(IDS_COMPAREDIRSTITLE), MB_OK | MB_ICONINFORMATION); + // return 0; + //} + + // if both panels point to the same path, exit + char leftPath[2 * MAX_PATH]; + char rightPath[2 * MAX_PATH]; + LeftPanel->GetGeneralPath(leftPath, 2 * MAX_PATH); + RightPanel->GetGeneralPath(rightPath, 2 * MAX_PATH); + char compareDetail[4 * MAX_PATH]; + _snprintf_s(compareDetail, _TRUNCATE, "left=%s, right=%s", leftPath, rightPath); + ExecLogFeatureStart("compare dirs", compareDetail); + if (strcmp(leftPath, rightPath) == 0) // case sensitive; if this condition fails, it's fine + { + ExecLogFeatureResult("compare dirs", compareDetail, FALSE); + SalMessageBox(HWindow, LoadStr(IDS_COMPARE_SAMEPATH), LoadStr(IDS_COMPAREDIRSTITLE), MB_OK | MB_ICONINFORMATION); + return 0; + } + + BOOL enableByDateAndTime = (LeftPanel->ValidFileData & (VALID_DATA_DATE | VALID_DATA_PL_DATE)) && + (RightPanel->ValidFileData & (VALID_DATA_DATE | VALID_DATA_PL_DATE)); + BOOL enableBySize = (LeftPanel->ValidFileData & (VALID_DATA_SIZE | VALID_DATA_PL_SIZE)) && + (RightPanel->ValidFileData & (VALID_DATA_SIZE | VALID_DATA_PL_SIZE)); + BOOL enableByAttrs = (LeftPanel->ValidFileData & VALID_DATA_ATTRIBUTES) && + (RightPanel->ValidFileData & VALID_DATA_ATTRIBUTES); + BOOL enableByContent = LeftPanel->Is(ptDisk) && RightPanel->Is(ptDisk); + BOOL enableSubdirs = !LeftPanel->Is(ptPluginFS) && !RightPanel->Is(ptPluginFS); + BOOL enableCompAttrsOfSubdirs = enableSubdirs && enableByAttrs; + CCompareDirsDialog dlg(HWindow, enableByDateAndTime, enableBySize, enableByAttrs, + enableByContent, enableSubdirs, enableCompAttrsOfSubdirs, + LeftPanel, RightPanel); + if (dlg.Execute() == IDOK) + { + activePanel->UserWorkedOnThisPath = TRUE; + DWORD flags = 0; + if (enableByDateAndTime && Configuration.CompareByTime) + flags |= COMPARE_DIRECTORIES_BYTIME; + if (enableBySize && Configuration.CompareBySize) + flags |= COMPARE_DIRECTORIES_BYSIZE; + if (enableByContent && Configuration.CompareByContent) + flags |= COMPARE_DIRECTORIES_BYCONTENT; + if (enableByAttrs && Configuration.CompareByAttr) + flags |= COMPARE_DIRECTORIES_BYATTR; + if (enableSubdirs && Configuration.CompareSubdirs) + flags |= COMPARE_DIRECTORIES_SUBDIRS; + else + { + if (Configuration.CompareOnePanelDirs) + { + flags |= COMPARE_DIRECTORIES_ONEPANELDIRS; + Configuration.CompareSubdirs = FALSE; // handles case when CompareSubdirs is enabled and a compare is run for FS and the user toggles CompareOnePanelDirs - without this line, on the next open of the disk dialog, CompareSubdirs would take precedence over CompareOnePanelDirs, which isnÔÇÖt quite right... + } + } + if (enableCompAttrsOfSubdirs && Configuration.CompareSubdirsAttr) + flags |= COMPARE_DIRECTORIES_SUBDIRS_ATTR; + if (Configuration.CompareIgnoreFiles) + flags |= COMPARE_DIRECTORIES_IGNFILENAMES; + if ((enableSubdirs && Configuration.CompareSubdirs || Configuration.CompareOnePanelDirs) && + Configuration.CompareIgnoreDirs) + flags |= COMPARE_DIRECTORIES_IGNDIRNAMES; + CompareDirectories(flags); + ExecLogFeatureResult("compare dirs", compareDetail, TRUE); + } + else + { + ExecLogFeatureResult("compare dirs", compareDetail, FALSE); + } + return 0; + } + + case CM_EXIT: + { + PostMessage(HWindow, WM_USER_CLOSE_MAINWND, 0, 0); + return 0; + } + + case CM_CONNECTNET: + { + activePanel->ConnectNet(FALSE); + return 0; + } + + case CM_DISCONNECTNET: + { + activePanel->DisconnectNet(); + return 0; + } + + case CM_FILEHISTORY: + { + if (!FileHistory->HasItem()) + return 0; + MainWindow->CancelPanelsUI(); // cancel QuickSearch and QuickEdit + + BeginStopRefresh(); // snooper takes a break + + RECT r; + GetWindowRect(HWindow, &r); + int x = r.left + (r.right - r.left) / 2; + int y = r.top + (r.bottom - r.top) / 2; + + CMenuPopup menu; + FileHistory->FillPopupMenu(&menu); + DWORD cmd = menu.Track(MENU_TRACK_RETURNCMD | MENU_TRACK_CENTERALIGN | MENU_TRACK_VCENTERALIGN, + x, y, HWindow, NULL); + if (cmd != 0) + FileHistory->Execute(cmd); + + EndStopRefresh(); // snooper starts again now + + return 0; + } + + case CM_DIRHISTORY: + { + activePanel->OpenDirHistory(); + return 0; + } + + case CM_USERMENU: + { + if (activePanel->Is(ptDisk)) + { + BeginStopRefresh(); // no refreshes needed + + MainWindow->CancelPanelsUI(); // cancel QuickSearch and QuickEdit + + UserMenuIconBkgndReader.BeginUserMenuIconsInUse(); + CMenuPopup menu; + FillUserMenu(&menu); + POINT p; + activePanel->GetContextMenuPos(&p); + // another lock/unlock cycle (BeginUserMenuIconsInUse + EndUserMenuIconsInUse) will occur + // in WM_USER_ENTERMENULOOP + WM_USER_LEAVEMENULOOP, but it is nested and lightweight, + // so we ignore it and do not fight it + menu.Track(0, p.x, p.y, HWindow, NULL); + UserMenuIconBkgndReader.EndUserMenuIconsInUse(); + + EndStopRefresh(); + } + return 0; + } + + case CM_OPENHOTPATHS: + { + BeginStopRefresh(); // no refreshes needed + + MainWindow->CancelPanelsUI(); // cancel QuickSearch and QuickEdit + + RECT r; + GetWindowRect(GetActivePanelHWND(), &r); + int dirHeight = GetDirectoryLineHeight(); + + CMenuPopup menu; + HotPaths.FillHotPathsMenu(&menu, CM_ACTIVEHOTPATH_MIN); + menu.Track(0, r.left, r.top + dirHeight, HWindow, NULL); + + EndStopRefresh(); + return 0; + } + + case CM_CUSTOMIZE_HOTPATHS: + { + PostMessage(HWindow, WM_USER_CONFIGURATION, 1, -1); + return 0; + } + + case CM_CUSTOMIZE_USERMENU: + { + PostMessage(HWindow, WM_USER_CONFIGURATION, 2, 0); + return 0; + } + + case CM_EDITLINE: + { + if (SystemPolicies.GetNoRun()) + { + MSGBOXEX_PARAMS params; + memset(¶ms, 0, sizeof(params)); + params.HParent = HWindow; + params.Flags = MSGBOXEX_OK | MSGBOXEX_HELP | MSGBOXEX_ICONEXCLAMATION; + params.Caption = LoadStr(IDS_POLICIESRESTRICTION_TITLE); + params.Text = LoadStr(IDS_POLICIESRESTRICTION); + params.ContextHelpId = IDH_GROUPPOLICY; + params.HelpCallback = MessageBoxHelpCallback; + SalMessageBoxEx(¶ms); + return 0; + } + if (EditWindow->HWindow != NULL) + { + if (EditWindow->IsEnabled()) + SetFocus(EditWindow->HWindow); + } + else + { + if (EditPermanentVisible || EditWindow->IsEnabled()) // there may be an archive in the panel + ShowCommandLine(); + } + return 0; + } + + case CM_TOGGLEEDITLINE: + { + if (SystemPolicies.GetNoRun()) + { + MSGBOXEX_PARAMS params; + memset(¶ms, 0, sizeof(params)); + params.HParent = HWindow; + params.Flags = MSGBOXEX_OK | MSGBOXEX_HELP | MSGBOXEX_ICONEXCLAMATION; + params.Caption = LoadStr(IDS_POLICIESRESTRICTION_TITLE); + params.Text = LoadStr(IDS_POLICIESRESTRICTION); + params.ContextHelpId = IDH_GROUPPOLICY; + params.HelpCallback = MessageBoxHelpCallback; + SalMessageBoxEx(¶ms); + return 0; + } + EditPermanentVisible = !EditPermanentVisible; + if (EditWindow->HWindow != NULL && !EditPermanentVisible) + HideCommandLine(); + else if (EditWindow->HWindow == NULL) + { + if (EditPermanentVisible) + { + ShowCommandLine(); + if (lParam == 0) + SetFocus(EditWindow->HWindow); + } + } + return 0; + } + + case CM_TOGGLETOPTOOLBAR: + { + ToggleTopToolBar(); + // LayoutWindows(); + break; + } + + case CM_TOGGLEPLUGINSBAR: + { + TogglePluginsBar(); + break; + } + + case CM_TOGGLEMIDDLETOOLBAR: + { + ToggleMiddleToolBar(); + InvalidateRect(HWindow, NULL, FALSE); + LayoutWindows(); + break; + } + + case CM_TOGGLEUSERMENUTOOLBAR: + { + ToggleUserMenuToolBar(); + IdleRefreshStates = TRUE; // on the next Idle, force a check of status variables + // LayoutWindows(); + break; + } + + case CM_TOGGLEHOTPATHSBAR: + { + ToggleHotPathsBar(); + IdleRefreshStates = TRUE; // on the next Idle, force a check of status variables + // LayoutWindows(); + break; + } + + case CM_TOGGLEDRIVEBAR: + case CM_TOGGLEDRIVEBAR2: + { + ToggleDriveBar(LOWORD(wParam) == CM_TOGGLEDRIVEBAR2); + // LayoutWindows(); + break; + } + + case CM_TOGGLEBOTTOMTOOLBAR: + { + ToggleBottomToolBar(); + IdleRefreshStates = TRUE; // on the next Idle, force a check of status variables + LayoutWindows(); + break; + } + + case CM_TOGGLE_UMLABELS: + { + UMToolBar->ToggleLabels(); + break; + } + + // case CM_TOGGLE_HPLABELS: + // { + // HPToolBar->ToggleLabels(); + // break; + // } + + case CM_TOGGLE_GRIPS: + { + ToggleToolBarGrips(); + break; + } + + case CM_CUSTOMIZETOP: + { + if (TopToolBar->HWindow == NULL) + { + ToggleTopToolBar(); + IdleRefreshStates = TRUE; // on the next Idle, force a check of status variables + LayoutWindows(); + } + TopToolBar->Customize(); + break; + } + + case CM_CUSTOMIZEPLUGINS: + { + if (PluginsBar->HWindow == NULL) + { + TogglePluginsBar(); + LayoutWindows(); + } + // let the Plugins Manager open + PostMessage(MainWindow->HWindow, WM_COMMAND, CM_PLUGINS, 0); + break; + } + + case CM_CUSTOMIZEMIDDLE: + { + if (MiddleToolBar->HWindow == NULL) + { + ToggleMiddleToolBar(); + IdleRefreshStates = TRUE; // on the next Idle, force a check of status variables + LayoutWindows(); + } + MiddleToolBar->Customize(); + break; + } + + case CM_CUSTOMIZEUM: + { + if (UMToolBar->HWindow == NULL) + { + ToggleUserMenuToolBar(); + IdleRefreshStates = TRUE; // on the next Idle, force a check of status variables + LayoutWindows(); + } + // expand the UserMenu page and edit the item at the given index + PostMessage(HWindow, WM_USER_CONFIGURATION, 2, 0); + break; + } + + case CM_CUSTOMIZEHP: + { + if (HPToolBar->HWindow == NULL) + { + ToggleHotPathsBar(); + IdleRefreshStates = TRUE; // on the next Idle, force a check of status variables + LayoutWindows(); + } + // let the HotPaths page expand + PostMessage(HWindow, WM_USER_CONFIGURATION, 1, -1); + break; + } + + case CM_CUSTOMIZELEFT: + { + if (LeftPanel->DirectoryLine->HWindow == NULL) + LeftPanel->ToggleDirectoryLine(); + if (LeftPanel->DirectoryLine->ToolBar != NULL) + LeftPanel->DirectoryLine->ToolBar->Customize(); + break; + } + + case CM_CUSTOMIZERIGHT: + { + if (RightPanel->DirectoryLine->HWindow == NULL) + RightPanel->ToggleDirectoryLine(); + if (RightPanel->DirectoryLine->ToolBar != NULL) + RightPanel->DirectoryLine->ToolBar->Customize(); + break; + } + + case CM_DOSSHELL: + { + activePanel->UserWorkedOnThisPath = TRUE; + + char cmd[MAX_PATH]; + if (!GetEnvironmentVariable("COMSPEC", cmd, MAX_PATH)) + cmd[0] = 0; + + if (SystemPolicies.GetNoRun() || + (SystemPolicies.GetMyRunRestricted() && !SystemPolicies.GetMyCanRun(cmd))) + { + MSGBOXEX_PARAMS params; + memset(¶ms, 0, sizeof(params)); + params.HParent = HWindow; + params.Flags = MSGBOXEX_OK | MSGBOXEX_HELP | MSGBOXEX_ICONEXCLAMATION; + params.Caption = LoadStr(IDS_POLICIESRESTRICTION_TITLE); + params.Text = LoadStr(IDS_POLICIESRESTRICTION); + params.ContextHelpId = IDH_GROUPPOLICY; + params.HelpCallback = MessageBoxHelpCallback; + SalMessageBoxEx(¶ms); + return 0; + } + + AddDoubleQuotesIfNeeded(cmd, MAX_PATH); // CreateProcess requires the name with spaces in quotes (otherwise it tries various options; see help) + ExecLogFeatureStart("command shell", cmd); + + SetDefaultDirectories(); + + STARTUPINFO si; + memset(&si, 0, sizeof(STARTUPINFO)); + si.cb = sizeof(STARTUPINFO); + si.lpTitle = LoadStr(IDS_COMMANDSHELL); + // There is an undocumented flag 0x400 where we can pass the monitor handle into si.hStdOutput + // Unfortunately it works with SOL.EXE but not with CMD.EXE, so we use the old method + // with a dummy window + // On W2K the flag appears as #define STARTF_HASHMONITOR 0x00000400 // same as HASSHELLDATA + // STARTF_MONITOR was mentioned online in an article about undocumented features + si.dwFlags = STARTF_USESHOWWINDOW; + POINT p; + if (MultiMonGetDefaultWindowPos(MainWindow->HWindow, &p)) + { + // if the main window is on another monitor we should open + // the new window there as well, preferably at the default position (same as on the primary) + si.dwFlags |= STARTF_USEPOSITION; + si.dwX = p.x; + si.dwY = p.y; + // TRACE_I("MultiMonGetDefaultWindowPos(): x = " << p.x << ", y = " << p.y); + } + si.wShowWindow = SW_SHOWNORMAL; + + PROCESS_INFORMATION pi; + + BOOL createProcessOk = HANDLES(CreateProcess(NULL, cmd, NULL, NULL, FALSE, + CREATE_DEFAULT_ERROR_MODE | NORMAL_PRIORITY_CLASS, NULL, + (activePanel->Is(ptDisk) || activePanel->Is(ptZIPArchive)) ? activePanel->GetPath() : NULL, &si, &pi)); + ExecLogFeatureResult("command shell", cmd, createProcessOk); + if (!createProcessOk) + { + DWORD err = GetLastError(); + SalMessageBox(HWindow, GetErrorText(err), + LoadStr(IDS_ERROREXECPROMPT), MB_OK | MB_ICONEXCLAMATION); + } + else + { + HANDLES(CloseHandle(pi.hProcess)); + HANDLES(CloseHandle(pi.hThread)); + } + + return 0; + } + + case CM_FILELIST: + { + ExecLogFeatureStart("file list", activePanel->GetPath()); + activePanel->UserWorkedOnThisPath = TRUE; + activePanel->StoreSelection(); // save selection for Restore Selection command + MakeFileList(); + return 0; + } + + case CM_OPENACTUALFOLDER: + { + ExecLogFeatureStart("open active folder", activePanel->GetPath()); + activePanel->OpenActiveFolder(); + return 0; + } + + case CM_SWAPPANELS: + { + // swap panels + CFilesWindow* swap = LeftPanel; + LeftPanel = RightPanel; + RightPanel = swap; + // swap toolbar records + char buff[1024]; + lstrcpy(buff, Configuration.LeftToolBar); + lstrcpy(Configuration.LeftToolBar, Configuration.RightToolBar); + lstrcpy(Configuration.RightToolBar, buff); + // set panel variables and load the toolbars + LeftPanel->DirectoryLine->SetLeftPanel(TRUE); + RightPanel->DirectoryLine->SetLeftPanel(FALSE); + // the icon must be changed in the image list + LeftPanel->UpdateDriveIcon(FALSE); + RightPanel->UpdateDriveIcon(FALSE); + + // if the active panel was ZOOMed, after Ctrl+U, the minimized panel would remain active + if (GetActivePanel() == LeftPanel && IsPanelZoomed(FALSE) || + GetActivePanel() == RightPanel && IsPanelZoomed(TRUE)) + { + // so activate the visible one + ChangePanel(TRUE); + } + + LockWindowUpdate(HWindow); + LayoutWindows(); + LockWindowUpdate(NULL); + + // reload columns again (column widths are not swapped) + LeftPanel->SelectViewTemplate(LeftPanel->GetViewTemplateIndex(), TRUE, FALSE); + RightPanel->SelectViewTemplate(RightPanel->GetViewTemplateIndex(), TRUE, FALSE); + + // distribute this news among plugins as well + Plugins.Event(PLUGINEVENT_PANELSSWAPPED, 0); + + return 0; + } + + case CM_OPENRECYCLEBIN: + { + OpenSpecFolder(HWindow, CSIDL_BITBUCKET); + return 0; + } + + case CM_OPENCONROLPANEL: + { + OpenSpecFolder(HWindow, CSIDL_CONTROLS); + return 0; + } + + case CM_OPENDESKTOP: + { + OpenSpecFolder(HWindow, CSIDL_DESKTOP); + return 0; + } + + case CM_OPENMYCOMP: + { + OpenSpecFolder(HWindow, CSIDL_DRIVES); + return 0; + } + + case CM_OPENFONTS: + { + OpenSpecFolder(HWindow, CSIDL_FONTS); + return 0; + } + + case CM_OPENNETNEIGHBOR: + { + OpenSpecFolder(HWindow, CSIDL_NETWORK); + return 0; + } + + case CM_OPENPRINTERS: + { + OpenSpecFolder(HWindow, CSIDL_PRINTERS); + return 0; + } + + case CM_OPENDESKTOPDIR: + { + OpenSpecFolder(HWindow, CSIDL_DESKTOPDIRECTORY); + return 0; + } + + case CM_OPENPERSONAL: + { + OpenSpecFolder(HWindow, CSIDL_PERSONAL); + return 0; + } + + case CM_OPENPROGRAMS: + { + OpenSpecFolder(HWindow, CSIDL_PROGRAMS); + return 0; + } + + case CM_OPENRECENT: + { + OpenSpecFolder(HWindow, CSIDL_RECENT); + return 0; + } + + case CM_OPENSENDTO: + { + OpenSpecFolder(HWindow, CSIDL_SENDTO); + return 0; + } + + case CM_OPENSTARTMENU: + { + OpenSpecFolder(HWindow, CSIDL_STARTMENU); + return 0; + } + + case CM_OPENSTARTUP: + { + OpenSpecFolder(HWindow, CSIDL_STARTUP); + return 0; + } + + case CM_OPENTEMPLATES: + { + OpenSpecFolder(HWindow, CSIDL_TEMPLATES); + return 0; + } + + case CM_CLIPCOPY: + { + if (activePanel->Is(ptDisk) || activePanel->Is(ptZIPArchive)) + { + activePanel->UserWorkedOnThisPath = TRUE; + activePanel->StoreSelection(); // save selection for Restore Selection command + activePanel->ClipboardCopy(); + } + return 0; + } + + case CM_CLIPCUT: + { + if (activePanel->Is(ptDisk)) + { + activePanel->UserWorkedOnThisPath = TRUE; + activePanel->StoreSelection(); // save selection for Restore Selection command + activePanel->ClipboardCut(); + } + return 0; + } + + case CM_CLIPPASTE: + { + activePanel->UserWorkedOnThisPath = TRUE; + if (!activePanel->Is(ptDisk) || !activePanel->ClipboardPaste()) // attempt to paste files to disk + { + if (!activePanel->Is(ptZIPArchive) && !activePanel->Is(ptPluginFS) || + !activePanel->ClipboardPasteToArcOrFS(FALSE, NULL)) // attempt to paste files into an archive or the file system + { + activePanel->ClipboardPastePath(); // or change the current path + } + } + return 0; + } + + case CM_CLIPPASTELINKS: + { + if (activePanel->Is(ptDisk)) + { + activePanel->UserWorkedOnThisPath = TRUE; + activePanel->ClipboardPasteLinks(); + } + return 0; + } + + case CM_TOGGLEELASTICSMART: + { + ToggleSmartColumnMode(activePanel); + return 0; + } + + case CM_TOGGLEHIDDENFILES: + { + Configuration.NotHiddenSystemFiles = !Configuration.NotHiddenSystemFiles; + HANDLES(EnterCriticalSection(&TimeCounterSection)); + int t1 = MyTimeCounter++; + int t2 = MyTimeCounter++; + HANDLES(LeaveCriticalSection(&TimeCounterSection)); + SendMessage(LeftPanel->HWindow, WM_USER_REFRESH_DIR, 0, t1); + SendMessage(RightPanel->HWindow, WM_USER_REFRESH_DIR, 0, t2); + + // distribute this news among plug-ins as well + Plugins.Event(PLUGINEVENT_CONFIGURATIONCHANGED, 0); + return 0; + } + + case CM_SEC_PERMISSIONS: + { + if (EnablerPermissions) + { + activePanel->UserWorkedOnThisPath = TRUE; + activePanel->StoreSelection(); // save selection for Restore Selection command + ShellAction(activePanel, saPermissions, TRUE, FALSE); + } + return 0; + } + + case CM_ACTIVEZOOMPANEL: + case CM_LEFTZOOMPANEL: + case CM_RIGHTZOOMPANEL: + { + if (IsPanelZoomed(TRUE) || IsPanelZoomed(FALSE)) + { + SplitPosition = BeforeZoomSplitPosition; + // better protect ourselves against a bad value in BeforeZoomSplitPosition + if (IsPanelZoomed(TRUE) || IsPanelZoomed(FALSE)) + SplitPosition = 0.5; + } + else + { + BeforeZoomSplitPosition = SplitPosition; + if (LOWORD(wParam) == CM_ACTIVEZOOMPANEL) + { + if (activePanel == LeftPanel) + SplitPosition = 1.0; + else + SplitPosition = 0.0; + } + else + { + if (LOWORD(wParam) == CM_LEFTZOOMPANEL) + SplitPosition = 1.0; + else + SplitPosition = 0.0; + } + } + LayoutWindows(); + FocusPanel(GetActivePanel()); + return 0; + } + + case CM_FULLSCREEN: + { + if (IsZoomed(HWindow)) + ShowWindow(HWindow, SW_RESTORE); + else + ShowWindow(HWindow, SW_MAXIMIZE); + return 0; + } + } + return 0; +} diff --git a/src/mainwnd2.cpp b/src/mainwnd_config.cpp similarity index 100% rename from src/mainwnd2.cpp rename to src/mainwnd_config.cpp diff --git a/src/mainwnd_help.cpp b/src/mainwnd_help.cpp new file mode 100644 index 00000000..d856d441 --- /dev/null +++ b/src/mainwnd_help.cpp @@ -0,0 +1,282 @@ +// SPDX-FileCopyrightText: 2023 Taskscape Ltd +// SPDX-License-Identifier: GPL-2.0-or-later +// CommentsTranslationProject: TRANSLATED + +// HtmlHelp support: MessageBoxHelpCallback, CSalamanderHelp, OpenHtmlHelp + +#include "precomp.h" + +#include +#undef PathIsPrefix // otherwise conflicts with CSalamanderGeneral::PathIsPrefix + +#include "htmlhelp.h" +#include "stswnd.h" +#include "editwnd.h" +#include "usermenu.h" +#include "execute.h" +#include "plugins.h" +#include "fileswnd.h" +#include "toolbar.h" +#include "mainwnd.h" +#include "cfgdlg.h" +#include "dialogs.h" +#include "execlog.h" +#include "snooper.h" +#include "shellib.h" +#include "menu.h" +#include "pack.h" +#include "filesbox.h" +#include "drivelst.h" +#include "cache.h" +#include "gui.h" +#include +#include "zip.h" +#include "tasklist.h" +#include "jumplist.h" +extern "C" +{ +#include "shexreg.h" +} +#include "salshlib.h" +#include "worker.h" +#include "find.h" +#include "viewer.h" + +//**************************************************************************** +// +// HtmlHelp support +// + +// universal callback for our MessageBox when the user clicks the HELP button +// should be called, for example, like this: +// MSGBOXEX_PARAMS params; +// params.Flags = MSGBOXEX_OK | MSGBOXEX_HELP | MSGBOXEX_ICONEXCLAMATION; +// params.ContextHelpId = IDH_LICENSE; +// params.HelpCallback = MessageBoxHelpCallback; +void CALLBACK MessageBoxHelpCallback(LPHELPINFO helpInfo) +{ + OpenHtmlHelp(NULL, MainWindow->HWindow, HHCDisplayContext, (UINT)helpInfo->dwContextId, FALSE); // MSGBOXEX_PARAMS::ContextHelpId +} + +CSalamanderHelp SalamanderHelp; + +void CSalamanderHelp::OnHelp(HWND hWindow, UINT helpID, HELPINFO* helpInfo, + BOOL ctrlPressed, BOOL shiftPressed) +{ + if (!ctrlPressed && !shiftPressed) + { + OpenHtmlHelp(NULL, hWindow, HHCDisplayContext, helpID, FALSE); + } +} + +void CSalamanderHelp::OnContextMenu(HWND hWindow, WORD xPos, WORD yPos) +{ +} + +typedef struct tagHH_LAST_ERROR +{ + int cbStruct; + HRESULT hr; + BSTR description; +} HH_LAST_ERROR; + +BOOL OpenHtmlHelp(char* helpFileName, HWND parent, CHtmlHelpCommand command, DWORD_PTR dwData, BOOL quiet) +{ + // SalMessageBox(parent, "This beta version doesn't contain help.\nPlease wait for the next beta version.", + // "Open Salamander Help", MB_OK | MB_ICONINFORMATION); + + HANDLES(EnterCriticalSection(&OpenHtmlHelpCS)); + + char helpPath[MAX_PATH + 50]; + if (CurrentHelpDir[0] == 0) + { + char helpSubdir[MAX_PATH]; + helpSubdir[0] = 0; + CLanguage language; + if (language.Init(Configuration.LoadedSLGName, NULL)) + { + lstrcpyn(helpSubdir, language.HelpDir, MAX_PATH); + language.Free(); + } + if (helpSubdir[0] == 0) + { + TRACE_E("OpenHtmlHelp(): unable to get (or empty) SLGHelpDir!"); + strcpy(helpSubdir, "english"); + } + BOOL ok = FALSE; + if (GetModuleFileName(HInstance, CurrentHelpDir, MAX_PATH) != 0 && + CutDirectory(CurrentHelpDir) && + SalPathAppend(CurrentHelpDir, "help", MAX_PATH) && + DirExists(CurrentHelpDir)) + { + lstrcpyn(helpPath, CurrentHelpDir, MAX_PATH); + if (!SalPathAppend(helpPath, helpSubdir, MAX_PATH) || + !DirExists(helpPath)) + { // the directory from the current .slg file does not exist + lstrcpyn(helpPath, CurrentHelpDir, MAX_PATH); + if (_stricmp(helpSubdir, "english") == 0 || // we already tested "english" and it does not exist so no point in trying again + !SalPathAppend(helpPath, "english", MAX_PATH) || + !DirExists(helpPath)) + { // the ENGLISH directory does not exist + lstrcpyn(helpPath, CurrentHelpDir, MAX_PATH); + if (SalPathAppend(helpPath, "*", MAX_PATH)) + { // try to find at least some other directory + WIN32_FIND_DATAW dataW; + WIN32_FIND_DATA data; + CStrP helpPathW(ConvertAllocUtf8ToWide(helpPath, -1)); + HANDLE find = helpPathW != NULL ? HANDLES_Q(FindFirstFileW(helpPathW, &dataW)) : INVALID_HANDLE_VALUE; + if (find != INVALID_HANDLE_VALUE) + { + do + { + ConvertFindDataWToUtf8(dataW, &data); + if (strcmp(data.cFileName, ".") != 0 && strcmp(data.cFileName, "..") != 0 && + (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) // only if it is a directory + { + lstrcpyn(helpPath, CurrentHelpDir, MAX_PATH); + if (SalPathAppend(helpPath, data.cFileName, MAX_PATH)) + { + ok = TRUE; + break; + } + } + } while (FindNextFileW(find, &dataW)); + HANDLES(FindClose(find)); + } + } + } + else + ok = TRUE; + } + else + ok = TRUE; + if (ok) + lstrcpyn(CurrentHelpDir, helpPath, MAX_PATH); + } + if (!ok) + { + CurrentHelpDir[0] = 0; + + HANDLES(LeaveCriticalSection(&OpenHtmlHelpCS)); + + if (!quiet) + { + SalMessageBox(parent, LoadStr(IDS_FAILED_TO_FIND_HELP), + LoadStr(IDS_HELPERROR), MB_OK | MB_ICONEXCLAMATION); + } + return FALSE; + } + } + + HANDLES(LeaveCriticalSection(&OpenHtmlHelpCS)); + + HH_FTS_QUERY query; + DWORD uCommand = 0; + switch (command) + { + case HHCDisplayTOC: + { + uCommand = HH_DISPLAY_TOC; + break; + } + + case HHCDisplayIndex: + { + uCommand = HH_DISPLAY_INDEX; + if (dwData == 0) + dwData = 0; + break; + } + + case HHCDisplaySearch: + { + uCommand = HH_DISPLAY_SEARCH; + if (dwData == 0) + { + ZeroMemory(&query, sizeof(query)); + query.cbStruct = sizeof(query); + dwData = (DWORD_PTR)&query; + } + break; + } + + case HHCDisplayContext: + { + uCommand = HH_HELP_CONTEXT; + break; + } + + default: + { + TRACE_E("OpenHtmlHelp(): unknown command = " << command); + return FALSE; + } + } + + if (helpFileName != NULL) // plugin help: to open the window in the right position + { // with remembered Favorites, we must open "salamand.chm" first (then + // the plugin help opens in this same window) + lstrcpyn(helpPath, CurrentHelpDir, MAX_PATH); + if (SalPathAppend(helpPath, "salamand.chm", MAX_PATH) && + FileExists(helpPath)) + { + HtmlHelp(NULL, helpPath, HH_DISPLAY_TOC, 0); // ignore potential error + } + } + + BOOL ret = FALSE; + + lstrcpyn(helpPath, CurrentHelpDir, MAX_PATH); + if (SalPathAppend(helpPath, helpFileName == NULL ? "salamand.chm" : helpFileName, MAX_PATH) && + FileExists(helpPath)) + { + if (HtmlHelp(NULL, helpPath, uCommand, dwData) == NULL) + { + BOOL errorHandled = FALSE; + HH_LAST_ERROR lasterror; + lasterror.cbStruct = sizeof(lasterror); + if (HtmlHelp(NULL, NULL, HH_GET_LAST_ERROR, (DWORD_PTR)&lasterror) != NULL) + { + // Only report an error if we found one: + if (FAILED(lasterror.hr)) + { + // Is there a text message to display... + if (lasterror.description) + { + if (!quiet) + { + char buff[5000]; + // Convert the String to ANSI + WideCharToMultiByte(CP_ACP, 0, lasterror.description, -1, buff, 5000, NULL, NULL); + buff[5000 - 1] = 0; + SysFreeString(lasterror.description); + + // Display + SalMessageBox(parent, buff, LoadStr(IDS_HELPERROR), MB_OK); + } + errorHandled = TRUE; + } + } + } + if (!errorHandled && !quiet) + { + SalMessageBox(parent, LoadStr(IDS_FAILED_TO_LAUNCH_HELP), + LoadStr(IDS_HELPERROR), MB_OK | MB_ICONEXCLAMATION); + } + } + else + { + ret = TRUE; + } + } + else + { + if (!quiet) + { + SalMessageBox(parent, LoadStr(IDS_FAILED_TO_FIND_HELP), + LoadStr(IDS_HELPERROR), MB_OK | MB_ICONEXCLAMATION); + } + } + return ret; +} + diff --git a/src/mainwnd1.cpp b/src/mainwnd_init.cpp similarity index 99% rename from src/mainwnd1.cpp rename to src/mainwnd_init.cpp index d5b5e3e7..1e4a9940 100644 --- a/src/mainwnd1.cpp +++ b/src/mainwnd_init.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 Taskscape Ltd +// SPDX-FileCopyrightText: 2023 Taskscape Ltd // SPDX-License-Identifier: GPL-2.0-or-later // CommentsTranslationProject: TRANSLATED @@ -2340,7 +2340,7 @@ MENU_TEMPLATE_ITEM InfoLineMenu[] = if (hit == mwhteLeftHeaderLine || hit == mwhteRightHeaderLine) { - CHeaderLine* hdrLine = panel->GetHeaderLine(); + CFileListHeader* hdrLine = panel->GetHeaderLine(); if (hdrLine != NULL) { // find out over which item of the header line the point is located diff --git a/src/mainwnd_messages.cpp b/src/mainwnd_messages.cpp new file mode 100644 index 00000000..a36d4ec3 --- /dev/null +++ b/src/mainwnd_messages.cpp @@ -0,0 +1,3639 @@ +// SPDX-FileCopyrightText: 2023 Taskscape Ltd +// SPDX-License-Identifier: GPL-2.0-or-later +// CommentsTranslationProject: TRANSLATED + +#include "precomp.h" + +#include +#undef PathIsPrefix // otherwise conflicts with CSalamanderGeneral::PathIsPrefix + +#include "stswnd.h" +#include "editwnd.h" +#include "usermenu.h" +#include "execute.h" +#include "plugins.h" +#include "fileswnd.h" +#include "toolbar.h" +#include "mainwnd.h" +#include "cfgdlg.h" +#include "dialogs.h" +#include "execlog.h" +#include "snooper.h" +#include "shellib.h" +#include "menu.h" +#include "pack.h" +#include "filesbox.h" +#include "drivelst.h" +#include "cache.h" +#include "gui.h" +#include +#include "zip.h" +#include "tasklist.h" +#include "jumplist.h" +extern "C" +{ +#include "shexreg.h" +} +#include "salshlib.h" +#include "worker.h" +#include "find.h" +#include "viewer.h" + +// variables used when saving configuration during shutdown, log-off or restart +// we must pump messages so the system does not kill us as "not responding" +CWaitWindow* GlobalSaveWaitWindow = NULL; // if a global wait window for Save exists, it's here (otherwise NULL) +int GlobalSaveWaitWindowProgress = 0; // current progress value of the global wait window for Save + +// borrow constants from a newer SDK +#define WM_APPCOMMAND 0x0319 +#define FAPPCOMMAND_MOUSE 0x8000 +#define FAPPCOMMAND_KEY 0 +#define FAPPCOMMAND_OEM 0x1000 +#define FAPPCOMMAND_MASK 0xF000 +#define GET_APPCOMMAND_LPARAM(lParam) ((short)(HIWORD(lParam) & ~FAPPCOMMAND_MASK)) +#define APPCOMMAND_BROWSER_BACKWARD 1 +#define APPCOMMAND_BROWSER_FORWARD 2 +/* not supported yet +#define APPCOMMAND_BROWSER_SEARCH 5 +#define APPCOMMAND_HELP 27 +#define APPCOMMAND_BROWSER_REFRESH 3 +#define APPCOMMAND_FIND 28 +#define APPCOMMAND_COPY 36 +#define APPCOMMAND_CUT 37 +#define APPCOMMAND_PASTE 38 +*/ + +const int SPLIT_LINE_WIDTH = 3; // width of the split line in points +// if the middle toolbar is visible, the composition will be SPLIT_LINE_WIDTH + toolbar + SPLIT_LINE_WIDTH + +const int MIN_WIN_WIDTH = 2; // minimal panel width + +extern BOOL CacheNextSetFocus; + +BOOL MainFrameIsActive = FALSE; + +// code for testing time losses +/* + const char *s1 = "aj hjka sakjSJKAHS AJKSH JKDSHFJSDH FJS HDFJSD HFJS"; + const char *s2 = "Aj hjka sakjSJKAHS AJKSH JKDSHFJSDH FJS HDFJSD HFJS"; + + LARGE_INTEGER t1, t2, t3, f; + + int len1 = strlen(s1); + int count = 100000; + QueryPerformanceCounter(&t1); + int c = 0; + int i; + for (i = 0; i < count; i++) + c += MemICmp(s1, s2, len1); + QueryPerformanceCounter(&t2); + c = 0; + for (i = 0; i < count; i++) + c += StrICmp(s1, len1, s2, len1); + QueryPerformanceCounter(&t3); + + QueryPerformanceFrequency(&f); + + char buff[200]; + double a = (double)(t2.QuadPart - t1.QuadPart) / f.QuadPart; + double b = (double)(t3.QuadPart - t2.QuadPart) / f.QuadPart; + sprintf(buff, "t1=%1.4lg\nt2=%1.4lg", a, b); + MessageBox(HWindow, buff, "Results", MB_OK); +*/ + + +// HtmlHelp support (MessageBoxHelpCallback, CSalamanderHelp, OpenHtmlHelp) +// has been moved to mainwnd_help.cpp. + +//**************************************************************************** +// +// CMWDropTarget +// +// used only for moving dragged images +// + +class CMWDropTarget : public IDropTarget +{ +private: + long RefCount; // object lifetime + +public: + CMWDropTarget() + { + RefCount = 1; + } + + virtual ~CMWDropTarget() + { + if (RefCount != 0) + TRACE_E("Preliminary destruction of object"); + } + + STDMETHOD(QueryInterface) + (REFIID refiid, void FAR* FAR* ppv) + { + if (refiid == IID_IUnknown || refiid == IID_IDropTarget) + { + *ppv = this; + AddRef(); + return NOERROR; + } + else + { + *ppv = NULL; + return E_NOINTERFACE; + } + } + + STDMETHOD_(ULONG, AddRef) + (void) { return ++RefCount; } + STDMETHOD_(ULONG, Release) + (void) + { + if (--RefCount == 0) + { + delete this; + return 0; // cannot touch the object anymore, it no longer exists + } + return RefCount; + } + + STDMETHOD(DragEnter) + (IDataObject* pDataObject, DWORD grfKeyState, + POINTL pt, DWORD* pdwEffect) + { + if (ImageDragging) + ImageDragEnter(pt.x, pt.y); + *pdwEffect = DROPEFFECT_NONE; + return S_OK; + } + + STDMETHOD(DragOver) + (DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) + { + if (ImageDragging) + ImageDragMove(pt.x, pt.y); + *pdwEffect = DROPEFFECT_NONE; + return S_OK; + } + + STDMETHOD(DragLeave) + () + { + if (ImageDragging) + ImageDragLeave(); + return E_UNEXPECTED; + } + + STDMETHOD(Drop) + (IDataObject* pDataObject, DWORD grfKeyState, POINTL pt, + DWORD* pdwEffect) + { + *pdwEffect = DROPEFFECT_NONE; + return E_UNEXPECTED; + } +}; + +// +// **************************************************************************** +// MyShutdownBlockReasonCreate a MyShutdownBlockReasonDestroy +// +// Vista+: dynamically obtain functions for setting/clearing shutdown block reasons +// + + +// MyShutdownBlockReasonCreate/Destroy have been moved to mainwnd_shutdown.cpp. + + +// +// **************************************************************************** +// CMainWindow +// + +VOID CALLBACK SkipOneARTimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime) +{ + SkipOneActivateRefresh = FALSE; + KillTimer(hwnd, idEvent); +} + +void CMainWindow::SafeHandleMenuNewMsg2(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT* plResult) +{ + __try + { + IContextMenu3* contextMenu3 = NULL; + *plResult = 0; + if (uMsg == WM_MENUCHAR) + { + if (SUCCEEDED(ContextMenuNew->GetMenu2()->QueryInterface(IID_IContextMenu3, (void**)&contextMenu3))) + { + contextMenu3->HandleMenuMsg2(uMsg, wParam, lParam, plResult); + contextMenu3->Release(); + return; + } + } + // the menu is destroyed directly from the menu it was attached to + ContextMenuNew->GetMenu2()->HandleMenuMsg(uMsg, wParam, lParam); // this call occasionally crashes + } + __except (CCallStack::HandleException(GetExceptionInformation(), 11)) + { + MenuNewExceptionHasOccured++; + if (ContextMenuNew != NULL) + ContextMenuNew->Release(); // substitute for calling ReleaseMenuNew + // ReleaseMenuNew(); + } +} + +void CMainWindow::PostChangeOnPathNotification(const char* path, BOOL includingSubdirs) +{ + CALL_STACK_MESSAGE3("CMainWindow::PostChangeOnPathNotification(%s, %d)", path, includingSubdirs); + + HANDLES(EnterCriticalSection(&DispachChangeNotifCS)); + + // add this notification to the array (for later processing) + CChangeNotifData data; + lstrcpyn(data.Path, path, MAX_PATH); + data.IncludingSubdirs = includingSubdirs; + ChangeNotifArray.Add(data); + if (!ChangeNotifArray.IsGood()) + ChangeNotifArray.ResetState(); // ignore errors (at worst we won't refresh) + + // post a request to distribute path change notifications + HANDLES(EnterCriticalSection(&TimeCounterSection)); + int t1 = MyTimeCounter++; + HANDLES(LeaveCriticalSection(&TimeCounterSection)); + PostMessage(HWindow, WM_USER_DISPACHCHANGENOTIF, 0, t1); + + HANDLES(LeaveCriticalSection(&DispachChangeNotifCS)); +} + + +void BroadcastConfigChanged() +{ + // Internal Viewer and Find: refresh all windows (for example after global font change) + ViewerWindowQueue.BroadcastMessage(WM_USER_CFGCHANGED, 0, 0); + FindDialogQueue.BroadcastMessage(WM_USER_CFGCHANGED, 0, 0); +} + +void CMainWindow::FillViewModeMenu(CMenuPopup* popup, int firstIndex, int type) +{ + char buff[VIEW_NAME_MAX + 10]; + + DWORD fistCMID; + CFilesWindow* panel; + + switch (type) + { + case 0: + { + fistCMID = CM_ACTIVEMODE_1; + panel = GetActivePanel(); + break; + } + + case 1: + { + fistCMID = CM_LEFTMODE_1; + panel = LeftPanel; + break; + } + + case 2: + { + fistCMID = CM_RIGHTMODE_1; + panel = RightPanel; + break; + } + + default: + { + TRACE_E("Uknown type=" << type); + return; + } + } + + MENU_ITEM_INFO mii; + mii.Mask = MENU_MASK_TYPE | MENU_MASK_STRING | MENU_MASK_STATE | + MENU_MASK_ID /*| MENU_MASK_SKILLLEVEL*/; + mii.Type = MENU_TYPE_STRING | MENU_TYPE_RADIOCHECK; + mii.String = buff; + int i; + for (i = 0; i < VIEW_TEMPLATES_COUNT; i++) + { + if (i == 0) // tree view is not shown yet + continue; + + CViewTemplate* tmpl = &ViewTemplates.Items[i]; + if (tmpl->Name[0] != 0) + { + sprintf(buff, "%s\tAlt+%d", tmpl->Name, i < VIEW_TEMPLATES_COUNT - 1 ? i + 1 : 0); + + mii.ID = fistCMID + i; + + // mii.SkillLevel = MENU_LEVEL_INTERMEDIATE | MENU_LEVEL_ADVANCED; + // if (i > 2) + // mii.SkillLevel |= MENU_LEVEL_BEGINNER; + + mii.State = panel->ViewTemplate == tmpl ? MENU_STATE_CHECKED : 0; + + popup->InsertItem(firstIndex, TRUE, &mii); + firstIndex++; + } + } +} + +void CMainWindow::SetDoNotLoadAnyPlugins(BOOL doNotLoad) +{ + if (doNotLoad) + { + DoNotLoadAnyPlugins = TRUE; + } + else + { + DoNotLoadAnyPlugins = FALSE; + if (!CriticalShutdown) + { + HANDLES(EnterCriticalSection(&TimeCounterSection)); + int t1 = MyTimeCounter++; + int t2 = MyTimeCounter++; + HANDLES(LeaveCriticalSection(&TimeCounterSection)); + + if (LeftPanel->GetViewMode() == vmThumbnails) + { + PostMessage(LeftPanel->HWindow, WM_USER_REFRESH_DIR, 0, t1); // ensure the icon cache is refilled (thumbnails can be shown again) + } + if (RightPanel->GetViewMode() == vmThumbnails) + { + PostMessage(RightPanel->HWindow, WM_USER_REFRESH_DIR, 0, t2); // ensure the icon cache is refilled (thumbnails can be shown again) + } + } + } +} + +void CMainWindow::ShowHideTwoDriveBarsInternal(BOOL show) +{ + LockWindowUpdate(HWindow); + + if (show) + { + REBARBANDINFO rbi; + rbi.cbSize = sizeof(REBARBANDINFO); + + int count = (int)SendMessage(HTopRebar, RB_GETBANDCOUNT, 0, 0); + // drive bar 1 + int index = (int)SendMessage(HTopRebar, RB_IDTOINDEX, BANDID_DRIVEBAR, 0); + SendMessage(HTopRebar, RB_MOVEBAND, (WPARAM)index, (LPARAM)count - 1); + rbi.fMask = RBBIM_STYLE; + rbi.fStyle = RBBS_NOGRIPPER | RBBS_BREAK; + SendMessage(HTopRebar, RB_SETBANDINFO, count - 1, (LPARAM)&rbi); + + // drive bar 2 + index = (int)SendMessage(HTopRebar, RB_IDTOINDEX, BANDID_DRIVEBAR2, 0); + SendMessage(HTopRebar, RB_MOVEBAND, (WPARAM)index, (LPARAM)count - 1); + rbi.fMask = RBBIM_STYLE; + rbi.fStyle = RBBS_NOGRIPPER; + SendMessage(HTopRebar, RB_SETBANDINFO, count - 1, (LPARAM)&rbi); + } + else + { + int index = (int)SendMessage(HTopRebar, RB_IDTOINDEX, BANDID_DRIVEBAR, 0); + SendMessage(HTopRebar, RB_SHOWBAND, index, FALSE); + + index = (int)SendMessage(HTopRebar, RB_IDTOINDEX, BANDID_DRIVEBAR2, 0); + SendMessage(HTopRebar, RB_SHOWBAND, index, FALSE); + } + + LockWindowUpdate(NULL); +} + +int CMainWindow::GetSplitBarWidth() +{ + if (MiddleToolBar != NULL && MiddleToolBar->HWindow != NULL) + return 2 * SPLIT_LINE_WIDTH + MiddleToolBar->GetNeededWidth(); + else + return SPLIT_LINE_WIDTH; +} + +BOOL CMainWindow::IsPanelZoomed(BOOL leftPanel) +{ + if (leftPanel) + return SplitPosition >= 0.99; + else + return SplitPosition <= 0.01; +} + +void CMainWindow::ToggleSmartColumnMode(CFilesWindow* panel) +{ + if (panel->GetViewMode() == vmDetailed) // the panel must be running in detailed mode + { + if (panel->Columns.Count < 1) + return; + CColumn* column = &panel->Columns[0]; + BOOL leftPanel = (panel == LeftPanel); + BOOL smartMode = !(!column->FixedWidth && + (leftPanel && panel->ViewTemplate->LeftSmartMode || + !leftPanel && panel->ViewTemplate->RightSmartMode)); + if (smartMode && column->FixedWidth) + { // smart mode works only for elastic columns (must be changed in the view template) + if (leftPanel) + panel->ViewTemplate->Columns[0].LeftFixedWidth = 0; + else + panel->ViewTemplate->Columns[0].RightFixedWidth = 0; + } + if (leftPanel) + { + panel->ViewTemplate->LeftSmartMode = smartMode; + LeftPanel->SelectViewTemplate(LeftPanel->GetViewTemplateIndex(), TRUE, FALSE, VALID_DATA_ALL, TRUE); + } + else + { + panel->ViewTemplate->RightSmartMode = smartMode; + RightPanel->SelectViewTemplate(RightPanel->GetViewTemplateIndex(), TRUE, FALSE, VALID_DATA_ALL, TRUE); + } + } +} + +BOOL CMainWindow::GetSmartColumnMode(CFilesWindow* panel) +{ + if (panel->Columns.Count < 1) + return FALSE; + CColumn* column = &panel->Columns[0]; + BOOL smartMode = (!column->FixedWidth && + (panel == LeftPanel && panel->ViewTemplate->LeftSmartMode || + panel == RightPanel && panel->ViewTemplate->RightSmartMode)); + return smartMode; +} + +void CMainWindow::SafeHandleMenuChngDrvMsg2(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT* plResult) +{ + CALL_STACK_MESSAGE_NONE + __try + { + IContextMenu3* contextMenu3 = NULL; + *plResult = 0; + if (uMsg == WM_MENUCHAR) + { + if (SUCCEEDED(ContextMenuChngDrv->QueryInterface(IID_IContextMenu3, (void**)&contextMenu3))) + { + contextMenu3->HandleMenuMsg2(uMsg, wParam, lParam, plResult); + contextMenu3->Release(); + return; + } + } + ContextMenuChngDrv->HandleMenuMsg(uMsg, wParam, lParam); + } + __except (CCallStack::HandleException(GetExceptionInformation(), 3)) + { + } +} + +void CMainWindow::ApplyCommandLineParams(const CCommandLineParams* cmdLineParams, BOOL setActivePanelAndPanelPaths) +{ + if (setActivePanelAndPanelPaths) + { + // first set the active panel + if (cmdLineParams->ActivatePanel == 1 && GetActivePanel() == RightPanel || + cmdLineParams->ActivatePanel == 2 && GetActivePanel() == LeftPanel) + { + ChangePanel(FALSE); + } + // then we can set the path in the active panel + if (cmdLineParams->LeftPath[0] == 0 && cmdLineParams->RightPath[0] == 0 && cmdLineParams->ActivePath[0] != 0) + GetActivePanel()->ChangeDir(cmdLineParams->ActivePath); // makes no sense to combine with setting the left/right panel + else + { + if (cmdLineParams->LeftPath[0] != 0) + LeftPanel->ChangeDir(cmdLineParams->LeftPath); + if (cmdLineParams->RightPath[0] != 0) + RightPanel->ChangeDir(cmdLineParams->RightPath); + } + } + + if (cmdLineParams->SetMainWindowIconIndex) + { + Configuration.MainWindowIconIndexForced = cmdLineParams->MainWindowIconIndex; + SetWindowIcon(); + } + if (cmdLineParams->SetTitlePrefix) + { + Configuration.UseTitleBarPrefixForced = TRUE; + lstrcpyn(Configuration.TitleBarPrefixForced, cmdLineParams->TitlePrefix, TITLE_PREFIX_MAX); + SetWindowTitle(); + } +} + +BOOL CMainWindow::SHChangeNotifyInitialize() +{ + if (SHChangeNotifyRegisterID != 0) + { + TRACE_E("SHChangeNotifyRegisterID != 0"); + return FALSE; + } + + LPITEMIDLIST pidl; + if (!SUCCEEDED(SHGetSpecialFolderLocation(HWindow, CSIDL_DESKTOP, &pidl))) + { + TRACE_E("SHGetSpecialFolderLocation failed on CSIDL_DESKTOP"); + return FALSE; + } + + SHChangeNotifyEntry entry; + entry.pidl = pidl; + entry.fRecursive = TRUE; + + // message WM_USER_SHCHANGENOTIFY, which will be delivered to us on notifications, crosses process boundaries + // by using the constant SHCNRF_NewDelivery (also known as SHCNF_NO_PROXY) we assume responsibility + // for accessing the memory passed with the message (via SHChangeNotification_Lock) and tell the OS not to + // create proxy windows (note: a bug has been reported on XP where the proxy window is created but not destroyed): + // http://groups.google.com/groups?selm=3CDFD449.6BA0CDB4%40ic.ac.uk&output=gplain + // + // through SHCNE_ASSOCCHANGED we receive notifications about association changes + SHChangeNotifyRegisterID = SHChangeNotifyRegister(HWindow, SHCNRF_ShellLevel | SHCNRF_NewDelivery, + SHCNE_MEDIAINSERTED | SHCNE_MEDIAREMOVED | SHCNE_DRIVEREMOVED | + SHCNE_DRIVEADD | SHCNE_NETSHARE | SHCNE_NETUNSHARE | + SHCNE_DRIVEADDGUI | SHCNE_ASSOCCHANGED | SHCNE_UPDATEITEM, + WM_USER_SHCHANGENOTIFY, + 1, &entry); + + // dealokace pidl + IMalloc* alloc; + if (SUCCEEDED(CoGetMalloc(1, &alloc))) + { + alloc->Free(pidl); + alloc->Release(); + } + + return TRUE; +} + +BOOL CMainWindow::SHChangeNotifyRelease() +{ + if (SHChangeNotifyRegisterID != 0) + { + SHChangeNotifyDeregister(SHChangeNotifyRegisterID); + SHChangeNotifyRegisterID = 0; + } + return TRUE; +} + +typedef WINSHELLAPI BOOL(WINAPI* FT_FileIconInit)( + BOOL bFullInit); + +BOOL CMainWindow::OnAssociationsChangedNotification(BOOL showWaitWnd) +{ + // tweak the icon size + + LoadSaveToRegistryMutex.Enter(); // users reported shrunken icons, see /viewtopic.php?t=638 + // this synchronization ensures that two Salamanders do not interfere with each other + // unfortunately the trick with changing "Shell Icon Size" to rebuild the cache is used by many tools (including Tweak UI), + // so if they refresh at the same time as Salamander, conflicts occur + // we try to avoid this by postponing the following mess using IDT_ASSOCIATIONSCHNG + + HKEY hKey; + if (HANDLES(RegOpenKeyEx(HKEY_CURRENT_USER, "Control Panel\\Desktop\\WindowMetrics", 0, KEY_READ | KEY_WRITE, &hKey)) == ERROR_SUCCESS) + { + // older SHELL32.DLL versions may not export this, fileIconInit will be NULL + FT_FileIconInit fileIconInit = NULL; + fileIconInit = (FT_FileIconInit)GetProcAddress(Shell32DLL, MAKEINTRESOURCE(660)); // no header available + + char size[50]; + BOOL deleteVal = FALSE; + if (!GetValueAux(NULL, hKey, "Shell Icon Size", REG_SZ, size, 50)) + { + // The values for the icon size are Shell Icon Size and + // Shell Small Icon Size (both are stored as strings - not + // DWORDs). You only need to change one of them to cause + // the refresh to happen (typically the large icon size). If those + // values don't exist, the shell uses the SM_CXICON metric + // (GetSystemMetrics) as the default size for large icons, and + // half of that for the small icon size. If you're trying to cause + // a refresh and the registry entry doesn't exist, you can just + // assume that the size is set to SM_CXICON. + sprintf(size, "%d", GetSystemMetrics(SM_CXICON)); + deleteVal = TRUE; + } + int val = atoi(size); + if (val > 0) // unfortunately (according to net) users set icon sizes randomly (72, 96, 128, etc.) so we cannot filter out "strange" sizes + { + IgnoreWM_SETTINGCHANGE = TRUE; + + sprintf(size, "%d", val - 1); + SetValueAux(NULL, hKey, "Shell Icon Size", REG_SZ, size, -1); + SendMessage(MainWindow->HWindow, WM_SETTINGCHANGE, SPI_SETICONMETRICS, (LPARAM) "WindowMetrics"); + if (fileIconInit != NULL) + fileIconInit(FALSE); + sprintf(size, "%d", val); + SetValueAux(NULL, hKey, "Shell Icon Size", REG_SZ, size, -1); + SendMessage(MainWindow->HWindow, WM_SETTINGCHANGE, SPI_SETICONMETRICS, (LPARAM) "WindowMetrics"); + if (fileIconInit != NULL) + fileIconInit(TRUE); + if (deleteVal) + RegDeleteValue(hKey, "Shell Icon Size"); // clean up after ourselves + HANDLES(RegCloseKey(hKey)); + + IgnoreWM_SETTINGCHANGE = FALSE; + } + } + + LoadSaveToRegistryMutex.Leave(); + + /* + if (fileIconInit != NULL) + fileIconInit(TRUE); + + // debug icon display + SHFILEINFO shi; + HIMAGELIST systemIL = (HIMAGELIST)SHGetFileInfo("C:\\TEST.QWE", 0, &shi, sizeof(shi), + SHGFI_SYSICONINDEX | SHGFI_SMALLICON | SHGFI_SHELLICONSIZE); + TRACE_I("systemIL="<RebuildDrives(); + copyDrivesListFrom = DriveBar; + } + } + if (DriveBar2 != NULL && DriveBar2->HWindow != NULL) + { + if (DriveBar2->GetCachedDrivesMask() != drivesMask || + checkCloudStorages && DriveBar2->GetCachedCloudStoragesMask() != cloudStoragesMask) + { + // notifications about drive changes or cloud storage availability do not work; rebuild the drive bar manually + TRACE_I("Forced drives rebuild for DriveBar2!"); + DriveBar2->RebuildDrives(copyDrivesListFrom); + } + } + } +} + +LRESULT +CMainWindow::WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + SLOW_CALL_STACK_MESSAGE4("CMainWindow::WindowProc(0x%X, 0x%IX, 0x%IX)", uMsg, wParam, lParam); + switch (uMsg) + { + case WM_CREATE: + { + SHChangeNotifyInitialize(); // request receiving Shell Notifications + ExecLogStartupPhase("main window create"); + + SetTimer(HWindow, IDT_ADDNEWMODULES, 15000, NULL); // timer after 15 seconds for AddNewlyLoadedModulesToGlobalModulesStore() + + CMWDropTarget* dropTarget = new CMWDropTarget(); + if (dropTarget != NULL) + { + HANDLES(RegisterDragDrop(HWindow, dropTarget)); + dropTarget->Release(); // RegisterDragDrop called AddRef() + } + + HMENU h = GetSystemMenu(HWindow, FALSE); + if (h != NULL) + { + int items = GetMenuItemCount(h); + int pos = items; // append new items at the end of the menu + + // if the last two menu items are a separator and Close, insert above them + // (users have long complained they accidentally click our AOT instead of the intended Close) + if (items > 2) + { + UINT predLastCmd = GetMenuItemID(h, items - 2); + UINT lastCmd = GetMenuItemID(h, items - 1); + if (predLastCmd == 0 && lastCmd == SC_CLOSE) + pos = items - 2; + } + + /* used by the export_mnu.py script which generates salmenu.mnu for Translator. + Keep this synchronized with the InsertMenu() call below... +MENU_TEMPLATE_ITEM AddToSystemMenu[] = +{ + {MNTT_PB, 0 + {MNTT_IT, IDS_ALWAYSONTOP + {MNTT_PE, 0 +}; +*/ + InsertMenu(h, pos, MF_BYPOSITION | MF_SEPARATOR, 0, NULL); + InsertMenu(h, pos + 1, MF_BYPOSITION | MF_STRING | MF_ENABLED | (Configuration.AlwaysOnTop ? MF_CHECKED : MF_UNCHECKED), + CM_ALWAYSONTOP, LoadStr(IDS_ALWAYSONTOP)); + } + SetWindowPos(HWindow, + Configuration.AlwaysOnTop ? HWND_TOPMOST : HWND_NOTOPMOST, + 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); + + HTopRebar = CreateWindowEx(WS_EX_TOOLWINDOW, REBARCLASSNAME, "", + WS_VISIBLE | WS_BORDER | WS_CHILD | + WS_CLIPCHILDREN | WS_CLIPSIBLINGS | + RBS_VARHEIGHT | CCS_NODIVIDER | + RBS_BANDBORDERS | CCS_NOPARENTALIGN | + RBS_AUTOSIZE, + 0, 0, 0, 0, // dummy + HWindow, (HMENU)0, HInstance, NULL); + if (HTopRebar == NULL) + { + TRACE_E("CreateWindowEx on " << REBARCLASSNAME); + return -1; + } + + // we do not want visual styles for the rebar + // disable them + SetWindowTheme(HTopRebar, (L" "), (L" ")); + + // enforce WS_BORDER which somehow "disappeared" + DWORD style = (DWORD)GetWindowLongPtr(HTopRebar, GWL_STYLE); + style |= WS_BORDER; + SetWindowLongPtr(HTopRebar, GWL_STYLE, style); + + MenuBar = new CMenuBar(&MainMenu, HWindow); + if (MenuBar == NULL) + { + TRACE_E(LOW_MEMORY); + return -1; + } + if (!MenuBar->CreateWnd(HTopRebar)) + return -1; + + LeftPanel = new CFilesWindow(this); + if (LeftPanel == NULL) + { + TRACE_E(LOW_MEMORY); + return -1; + } + if (!LeftPanel->Create(CWINDOW_CLASSNAME2, "", + WS_VISIBLE | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, + 0, 0, 0, 0, + HWindow, + NULL, + HInstance, + LeftPanel)) + { + TRACE_E("LeftPanel->Create failed"); + return -1; + } + SetActivePanel(LeftPanel); + // ReleaseMenuNew(); + RightPanel = new CFilesWindow(this); + if (RightPanel == NULL) + { + TRACE_E(LOW_MEMORY); + return -1; + } + if (!RightPanel->Create(CWINDOW_CLASSNAME2, "", + WS_VISIBLE | WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, + 0, 0, 0, 0, + HWindow, + NULL, + HInstance, + RightPanel)) + { + TRACE_E("RightPanel->Create failed"); + return -1; + } + + EditWindow = new CEditWindow; + if (EditWindow == NULL || !EditWindow->IsGood()) + { + TRACE_E(LOW_MEMORY); + return -1; + } + + TopToolBar = new CMainToolBar(HWindow, mtbtTop); + if (TopToolBar == NULL) + { + TRACE_E(LOW_MEMORY); + return -1; + } + TopToolBar->SetImageList(HGrayToolBarImageList); + TopToolBar->SetHotImageList(HHotToolBarImageList); + TopToolBar->SetStyle(TLB_STYLE_IMAGE | TLB_STYLE_ADJUSTABLE); + TOOLBAR_PADDING padding; + TopToolBar->GetPadding(&padding); + padding.ToolBarVertical = 1; + padding.IconLeft = 2; + padding.IconRight = 3; + TopToolBar->SetPadding(&padding); + + MiddleToolBar = new CMainToolBar(HWindow, mtbtMiddle); + if (MiddleToolBar == NULL) + { + TRACE_E(LOW_MEMORY); + return -1; + } + MiddleToolBar->SetImageList(HGrayToolBarImageList); + MiddleToolBar->SetHotImageList(HHotToolBarImageList); + MiddleToolBar->SetStyle(TLB_STYLE_IMAGE | TLB_STYLE_ADJUSTABLE | TLB_STYLE_VERTICAL); + MiddleToolBar->GetPadding(&padding); + padding.ToolBarVertical = 1; + padding.IconLeft = 2; + padding.IconRight = 3; + MiddleToolBar->SetPadding(&padding); + + PluginsBar = new CPluginsBar(HWindow); + if (PluginsBar == NULL) + { + TRACE_E(LOW_MEMORY); + return -1; + } + + // AnimateBar = new CAnimate(HWorkerBitmap, 50, 0, RGB(255, 255, 255)); // 50 frames total, loop from 0, white background + // AnimateBar = new CAnimate(HWorkerBitmap, 43, 3, RGB(0, 0, 0)); // 43 frames total, loop from 3, black background + // if (AnimateBar == NULL) + // { + // TRACE_E(LOW_MEMORY); + // return -1; + // } + // if (!AnimateBar->IsGood()) + // return -1; + + // User Menu Bar + UMToolBar = new CUserMenuBar(HWindow); + if (UMToolBar == NULL) + { + TRACE_E(LOW_MEMORY); + return -1; + } + UMToolBar->GetPadding(&padding); + padding.IconLeft = 2; + padding.IconRight = 3; + padding.ButtonIconText = 2; + padding.TextRight = 4; + UMToolBar->SetPadding(&padding); + + // Hot Path Bar + HPToolBar = new CHotPathsBar(HWindow); + if (HPToolBar == NULL) + { + TRACE_E(LOW_MEMORY); + return -1; + } + HPToolBar->GetPadding(&padding); + padding.IconLeft = 2; + padding.IconRight = 3; + padding.ButtonIconText = 2; + padding.TextRight = 4; + HPToolBar->SetPadding(&padding); + + // Drive Bar + DriveBar = new CDriveBar(HWindow); + if (DriveBar == NULL) + { + TRACE_E(LOW_MEMORY); + return -1; + } + DriveBar2 = new CDriveBar(HWindow); + if (DriveBar2 == NULL) + { + TRACE_E(LOW_MEMORY); + return -1; + } + + BottomToolBar = new CBottomToolBar(HWindow); + if (BottomToolBar == NULL) + { + TRACE_E(LOW_MEMORY); + return -1; + } + BottomToolBar->SetImageList(HBottomTBImageList); + BottomToolBar->SetHotImageList(HHotBottomTBImageList); + + TaskbarRestartMsg = RegisterWindowMessage(TEXT("TaskbarCreated")); + + Created = TRUE; + ExecLogStartupComplete(); + return 0; + } + + // case WM_CHANGEUISTATE: // it seems both messages always arrive + case WM_UPDATEUISTATE: + { + if (MenuBar != NULL && MenuBar->HWindow != NULL) + SendMessage(MenuBar->HWindow, WM_UPDATEUISTATE, wParam, lParam); + // TRACE_I("KeyboardCuesAlwaysVisible="<RefreshListBox(-1, -1, LeftPanel->FocusedIndex, FALSE, FALSE); + RightPanel->RefreshListBox(-1, -1, RightPanel->FocusedIndex, FALSE, FALSE); + RefreshDiskFreeSpace(); + + // the font changed; notify plugins so their toolbars and menu bars call SetFont() + Plugins.Event(PLUGINEVENT_SETTINGCHANGE, 0); + + return 0; + } + + case WM_USER_SHCHANGENOTIFY: // received thanks to SHChangeNotifyRegister + { + LONG wEventId; + HANDLE hLock = NULL; + + // TRACE_E("WM_USER_SHCHANGENOTIFY lParam="<IconOverlaysChangedOnPath(szPath); + RightPanel->IconOverlaysChangedOnPath(szPath); + if (CutDirectory(szPath)) + { + LeftPanel->IconOverlaysChangedOnPath(szPath); + RightPanel->IconOverlaysChangedOnPath(szPath); + } + } + } + else + { + if (wEventId == SHCNE_ASSOCCHANGED) + { + // change in associations + // delay one second so we don't collide with other software using the icon size change trick to reset the icon cache + if (!SetTimer(HWindow, IDT_ASSOCIATIONSCHNG, 1000, NULL)) + OnAssociationsChangedNotification(FALSE); + } + else + { + // change in media or drives + + // after media insertion, automatically perform Retry in the "drive not ready" message box + // (if it is displayed for the drive with inserted media) + if (wEventId == SHCNE_MEDIAINSERTED) + { + if (CheckPathRootWithRetryMsgBox[0] != 0 && + HasTheSameRootPath(CheckPathRootWithRetryMsgBox, szPath)) + { + if (LastDriveSelectErrDlgHWnd != NULL) + PostMessage(LastDriveSelectErrDlgHWnd, WM_COMMAND, IDRETRY, 0); + } + } + + // if the Alt+F1/F2 menu is open, refresh (read the volume name) + CFilesWindow* panel = GetActivePanel(); + if (panel != NULL) + PostMessage(MainWindow->HWindow, WM_USER_DRIVES_CHANGE, 0, 0); + + // if the panels show CD-ROM or removable media, refresh them + while (1) + { + if ((panel->Is(ptDisk) || panel->Is(ptZIPArchive)) && !IsUNCPath(panel->GetPath())) + { + UINT type = MyGetDriveType(panel->GetPath()); + if (type == DRIVE_CDROM || type == DRIVE_REMOVABLE) + { + HANDLES(EnterCriticalSection(&TimeCounterSection)); // capture the time when a refresh is needed + int t1 = MyTimeCounter++; + HANDLES(LeaveCriticalSection(&TimeCounterSection)); + PostMessage(panel->HWindow, WM_USER_REFRESH_DIR, 0, t1); + } + if (type == DRIVE_NO_ROOT_DIR) // device disappeared (the drive is invalid) + { + if (LeftPanel == panel) + { + if (!ChangeLeftPanelToFixedWhenIdleInProgress) + ChangeLeftPanelToFixedWhenIdle = TRUE; + } + else + { + if (!ChangeRightPanelToFixedWhenIdleInProgress) + ChangeRightPanelToFixedWhenIdle = TRUE; + } + } + } + if (panel != GetNonActivePanel()) + panel = GetNonActivePanel(); + else + break; + } + } + } + break; + } + + /* + // WM_DEVICECHANGE didn't work well, for example under Win XP when connecting the DSC F707 camera. + // A notification about device connection arrived, but the subsequent device name detection + // (if the Alt+F1/2 menu was displayed) via SHGetFileInfo returned an empty string. + // I found a thread on Google where someone complains about the same problem + // + // http://groups.google.com/groups?hl=en&lr=&ie=UTF-8&oe=UTF-8&threadm=99a435fa.0203280715.69a286a8%40posting. + // google.com&rnum=1&prev=/groups%3Fhl%3Den%26lr%3D%26ie%3DUTF-8%26oe%3DUTF-8%26q%3Ddevice%2Bname%2Bshgetfileinfo + // + // and he solved it with a wait. People recommended abandoning WM_DEVICECHANGE and switching to + // the undocumented function SHChangeNotifyRegister... + // (http://www.geocities.com/SiliconValley/4942/notify.html) + case WM_DEVICECHANGE: + { + if (wParam == DBT_DEVICEARRIVAL || wParam == DBT_DEVICEREMOVECOMPLETE || + wParam == DBT_CONFIGCHANGED) // CD-ROM media change + { + // if the Alt+F1/F2 menu is open, refresh (read volume name) + CFilesWindow *panel = GetActivePanel(); + if (panel != NULL) + PostMessage(MainWindow->HWindow, WM_USER_DRIVES_CHANGE, 0, 0); + + // if the panels show CD-ROM or removable media, refresh them + while (1) + { + if (panel->Is(ptDisk) || panel->Is(ptZIPArchive)) + { + UINT type = MyGetDriveType(panel->GetPath()); + if (type == DRIVE_CDROM || type == DRIVE_REMOVABLE) + { + HANDLES(EnterCriticalSection(&TimeCounterSection)); // capture the time when a refresh is needed + int t1 = MyTimeCounter++; + HANDLES(LeaveCriticalSection(&TimeCounterSection)); + PostMessage(panel->HWindow, WM_USER_REFRESH_DIR, 0, t1); + } + } + if (panel != GetNonActivePanel()) panel = GetNonActivePanel(); + else break; + } + } + break; + } + */ + + case WM_USER_PROCESSDELETEMAN: + { + // delay data processing due to the main window activation after ESC from the viewer on WinXP; + // without this hack, it somehow did not catch up - the main window stayed inactive and the safe-wait window never appeared + if (!SetTimer(HWindow, IDT_DELETEMNGR_PROCESS, 200, NULL)) + DeleteManager.ProcessData(); // if the timer fails, run immediately; forget about WinXP + return 0; + } + + case WM_USER_DRIVES_CHANGE: + { + CFilesWindow* panel = GetActivePanel(); + if (panel->OpenedDrivesList != NULL) + { + // rebuild the menu + panel->OpenedDrivesList->RebuildMenu(); + } + CDriveBar* copyDrivesListFrom = NULL; + if (DriveBar != NULL && DriveBar->HWindow != NULL) + { + DriveBar->RebuildDrives(); + copyDrivesListFrom = DriveBar; + } + if (DriveBar2 != NULL && DriveBar2->HWindow != NULL) + DriveBar2->RebuildDrives(copyDrivesListFrom); + return 0; + } + + case WM_USER_ENTERMENULOOP: + case WM_USER_LEAVEMENULOOP: + { + // turn off any tooltip + SetCurrentToolTip(NULL, 0); + + // if someone is monitoring the mouse, end the monitoring + TRACKMOUSEEVENT tme; + tme.cbSize = sizeof(tme); + tme.dwFlags = TME_QUERY; + if (TrackMouseEvent(&tme) && tme.hwndTrack != NULL) + SendMessage(tme.hwndTrack, WM_MOUSELEAVE, 0, 0); + + // let the existing caret hide (or show again) so it does not distract the user + CancelPanelsUI(); // cancel QuickSearch and QuickEdit + if (EditMode) + { + if (uMsg == WM_USER_ENTERMENULOOP) + EditWindow->HideCaret(); + else + EditWindow->ShowCaret(); + } + + if (uMsg == WM_USER_ENTERMENULOOP) + UserMenuIconBkgndReader.BeginUserMenuIconsInUse(); + else + UserMenuIconBkgndReader.EndUserMenuIconsInUse(); + + // Ensure the enablers are set correctly so enabled items in the menu reflect + // the real state. Also update the bottom toolbar status. + OnEnterIdle(); + return 0; + } + + case WM_USER_TBDROPDOWN: + { + CToolBar* tlb = (CToolBar*)WindowsManager.GetWindowPtr((HWND)wParam); + if (tlb == NULL) + return 0; + int index = (int)lParam; + TLBI_ITEM_INFO2 tii; + tii.Mask = TLBI_MASK_ID; + if (!tlb->GetItemInfo2(index, TRUE, &tii)) + return 0; + + DWORD id = tii.ID; + + RECT r; + tlb->GetItemRect(index, r); + + switch (id) + { + case CM_LCHANGEDRIVE: + case CM_RCHANGEDRIVE: + { + SendMessage(HWindow, WM_COMMAND, id, 0); + break; + } + + case CM_OPENHOTPATHSDROP: + { + CMenuPopup menu; + HotPaths.FillHotPathsMenu(&menu, CM_ACTIVEHOTPATH_MIN); + menu.Track(0, r.left, r.bottom, HWindow, &r); + break; + } + + case CM_USERMENUDROP: + { + UserMenuIconBkgndReader.BeginUserMenuIconsInUse(); + CMenuPopup menu; + FillUserMenu(&menu); + // another lock/unlock cycle (BeginUserMenuIconsInUse + EndUserMenuIconsInUse) + // will occur in WM_USER_ENTERMENULOOP + WM_USER_LEAVEMENULOOP, but + // it is nested and lightweight, so we ignore it and do not fight it + menu.Track(0, r.left, r.bottom, HWindow, &r); + UserMenuIconBkgndReader.EndUserMenuIconsInUse(); + break; + } + + case CM_NEWDROP: + { + CMenuPopup menu(CML_FILES_NEW); + menu.Track(0, r.left, r.bottom, HWindow, &r); + break; + } + + case CM_OPEN_FOLDER_DROP: + { + CMenuPopup menu; + + CGUIMenuPopupAbstract* popup = MainMenu.GetSubMenu(CML_COMMANDS, FALSE); + if (popup != NULL) + { + popup = popup->GetSubMenu(CML_COMMANDS_FOLDERS, FALSE); + if (popup != NULL) + popup->Track(0, r.left, r.bottom, HWindow, &r); + } + break; + } + + case CM_ACTIVEBACK: + case CM_ACTIVEFORWARD: + case CM_LBACK: + case CM_LFORWARD: + case CM_RBACK: + case CM_RFORWARD: + { + BOOL forward = id == CM_ACTIVEFORWARD || id == CM_LFORWARD || id == CM_RFORWARD; + + CMenuPopup menu; + CFilesWindow* panel = GetActivePanel(); + if (id == CM_LBACK || id == CM_LFORWARD) + panel = LeftPanel; + if (id == CM_RBACK || id == CM_RFORWARD) + panel = RightPanel; + panel->PathHistory->FillBackForwardPopupMenu(&menu, forward); + DWORD cmd = menu.Track(MENU_TRACK_RETURNCMD, + r.left, r.bottom, + HWindow, &r); + if (cmd != 0) + panel->PathHistory->Execute(cmd, forward, panel); + break; + } + + case CM_ACTIVEVIEWMODE: + case CM_LEFTVIEWMODE: + case CM_RIGHTVIEWMODE: + { + CMenuPopup menu; + int type = 0; + if (id == CM_LEFTVIEWMODE) + type = 1; + else if (id == CM_RIGHTVIEWMODE) + type = 2; + FillViewModeMenu(&menu, 0, type); + menu.Track(0, r.left, r.bottom, HWindow, &r); + break; + } + + case CM_VIEW: + case CM_EDIT: + { + CFilesWindow* activePanel = GetActivePanel(); + if (activePanel == NULL) + break; + + CMenuPopup popup(id == CM_VIEW ? CML_FILES_VIEWWITH : 0); + + if (id == CM_VIEW) + activePanel->FillViewWithMenu(&popup); + else + activePanel->FillEditWithMenu(&popup); + + popup.Track(0, r.left, r.bottom, HWindow, &r); + break; + } + } + + if (id >= CM_USERMENU_MIN && id <= CM_USERMENU_MAX) + { + // user clicked a group in the User Menu Toolbar + int iterator = id - CM_USERMENU_MIN; + int endIndex = UserMenuItems->GetSubmenuEndIndex(iterator); + if (endIndex != -1) + { + UserMenuIconBkgndReader.BeginUserMenuIconsInUse(); + iterator++; + CMenuPopup menu; + FillUserMenu2(&menu, &iterator, endIndex); + // another lock/unlock cycle (BeginUserMenuIconsInUse + EndUserMenuIconsInUse) + // will occur in WM_USER_ENTERMENULOOP + WM_USER_LEAVEMENULOOP, + // but it is nested and lightweight, so we ignore it + menu.Track(0, r.left, r.bottom, HWindow, &r); + UserMenuIconBkgndReader.EndUserMenuIconsInUse(); + } + } + + if (id >= CM_PLUGINCMD_MIN && id <= CM_PLUGINCMD_MAX) + { + // user clicked on the plugin icon in the PluginsBar; + int index2 = id - CM_PLUGINCMD_MIN; // index of the plugin in CPlugions::Data + CMenuPopup menu(CML_PLUGINS_SUBMENU); + if (Plugins.InitPluginMenuItemsForBar(HWindow, index2, &menu)) + menu.Track(0, r.left, r.bottom, HWindow, &r); + } + + if (id >= CM_DRIVEBAR_MIN && id <= CM_DRIVEBAR_MAX) + DriveBar->Execute(id); + if (id >= CM_DRIVEBAR2_MIN && id <= CM_DRIVEBAR2_MAX) + DriveBar2->Execute(id); + return 0; + } + + case WM_USER_REPAINTALLICONS: + { + if (LeftPanel != NULL) + LeftPanel->RepaintIconOnly(-1); // all + if (RightPanel != NULL) + RightPanel->RepaintIconOnly(-1); // all + return 0; + } + + case WM_USER_REPAINTSTATUSBARS: + { + if (LeftPanel != NULL && LeftPanel->DirectoryLine != NULL) + LeftPanel->DirectoryLine->InvalidateAndUpdate(FALSE); + if (RightPanel != NULL && RightPanel->DirectoryLine != NULL) + RightPanel->DirectoryLine->InvalidateAndUpdate(FALSE); + return 0; + } + + case WM_USER_SHOWWINDOW: + { + if (!SalamanderBusy) + { + SalamanderBusy = TRUE; // now BUSY + LastSalamanderIdleTime = GetTickCount(); + BringWindowToTop(HWindow); // probably not important, but I saw it in a sample so I am adding it here too + if (IsIconic(HWindow)) + { + // SetForegroundWindow: this is crucial. If we don't call it and + // "only one instance" with the tray is active, Salamander sometimes + // appears in the background and only later moves to the front. + SetForegroundWindow(HWindow); + ShowWindow(HWindow, SW_RESTORE); + } + else + SetForegroundWindow(HWindow); + } + return 0; + } + + case WM_USER_SKIPONEREFRESH: + { + if (!SetTimer(NULL, 0, 500, SkipOneARTimerProc)) + { + SkipOneActivateRefresh = FALSE; + } + return 0; + } + + /* + case WM_USER_SETPATHS: + { + if (!SalamanderBusy && MainWindow != NULL && MainWindow->CanClose) // not BUSY and already started, otherwise ignore requests from other processes + { + SalamanderBusy = TRUE; // now BUSY + LastSalamanderIdleTime = GetTickCount(); + CSetPathsParams params; + ZeroMemory(¶ms, sizeof(params)); // default values + HANDLE sendingProcess = HANDLES_Q(OpenProcess(PROCESS_DUP_HANDLE, FALSE, wParam)); + HANDLE sendingFM = (HGLOBAL)lParam; + + HANDLE fm; + BOOL alreadyDone = FALSE; + if (sendingProcess != NULL && + HANDLES(DuplicateHandle(sendingProcess, sendingFM, // sending-process file-mapping + GetCurrentProcess(), &fm, // this process file-mapping + 0, FALSE, DUPLICATE_SAME_ACCESS))) + { + CSetPathsParams *unsafe = (CSetPathsParams *)HANDLES(MapViewOfFile(fm, FILE_MAP_WRITE, 0, 0, sizeof(CSetPathsParams))); // FIXME_X64 are we passing x86/x64 incompatible data? + if (unsafe != NULL) + { + alreadyDone = unsafe->Received; + if (!alreadyDone) + { + lstrcpyn(params.LeftPath, unsafe->LeftPath, MAX_PATH); + lstrcpyn(params.RightPath, unsafe->RightPath, MAX_PATH - 1); + + if (unsafe->MagicSignature1 == 0x07f2ab13 && unsafe->MagicSignature2 == 0x471e0901) + { + // new features since 2.52 + // WORD version = unsafe->StructVersion; // not used yet, the first version is recognized by the presence of signatures + lstrcpyn(params.ActivePath, unsafe->ActivePath, MAX_PATH); + params.ActivatePanel = unsafe->ActivatePanel; + } + // we return the result value having taken over the data + unsafe->Received = TRUE; + } + HANDLES(UnmapViewOfFile(unsafe)); + } + HANDLES(CloseHandle(fm)); + } + if (sendingProcess != NULL) HANDLES(CloseHandle(sendingProcess)); + + if (!alreadyDone) + ApplyCommandLineParams(¶ms); + } + return 0; + } +*/ + + case WM_USER_AUTOCONFIG: + { + PackAutoconfig(HWindow); + return 0; + } + + case WM_USER_VIEWERCONFIG: + { + if (GetForegroundWindow() != HWindow) + SetForegroundWindow(HWindow); // so we rise above the viewer + WindowProc(WM_USER_CONFIGURATION, 3, 0); + HWND hCaller = (HWND)wParam; + if (IsWindow(hCaller)) + { + // If the window that invoked us still exists, try to bring it to + // the foreground. This is a bit dirty because if it opens a modal + // dialog in the meantime, it won't get activation. But I don't care, + // the viewer will (hopefully) end up inside Salamander - in the plugin ;-) + SetForegroundWindow(hCaller); + } + return 0; + } + + case WM_USER_CONFIGURATION: + { + if (!SalamanderBusy) + { + SalamanderBusy = TRUE; // now BUSY + LastSalamanderIdleTime = GetTickCount(); + } + + BeginStopRefresh(); // snooper takes a break + + BOOL oldStatusArea = Configuration.StatusArea; + BOOL oldPanelCaption = Configuration.ShowPanelCaption; + BOOL oldPanelZoom = Configuration.ShowPanelZoom; + + UserMenuIconBkgndReader.ResetSysColorsChanged(); // now, we start watching system color changes (icon reload required) + BOOL readingUMIcons = UserMenuIconBkgndReader.IsReadingIcons(); + if (readingUMIcons) // new icons are on their way to the user menu; show them after configuration is done (on OK reload icons again so newly added ones are read as well) + UserMenuIconBkgndReader.BeginUserMenuIconsInUse(); + BOOL oldUseCustomPanelFont = UseCustomPanelFont; + LOGFONT oldLogFont = LogFont; + CConfigurationDlg dlg(HWindow, UserMenuItems, (int)wParam, (int)lParam); + int res = dlg.Execute(LoadStr(IDS_BUTTON_OK), LoadStr(IDS_BUTTON_CANCEL), + LoadStr(IDS_BUTTON_HELP)); + if (readingUMIcons) + UserMenuIconBkgndReader.EndUserMenuIconsInUse(); + + // dialog closed - the user could have changed the clipboard, check it + IdleRefreshStates = TRUE; // force status variable check on next Idle + IdleCheckClipboard = TRUE; // also check the clipboard + + if (res == IDOK) // values changed -> refresh everything possible + { + if (dlg.PageView.IsDirty()) + { + // user changed something in the view configuration - rebuild the columns + LeftPanel->SelectViewTemplate(LeftPanel->GetViewTemplateIndex(), TRUE, FALSE); + RightPanel->SelectViewTemplate(RightPanel->GetViewTemplateIndex(), TRUE, FALSE); + } + if (memcmp(&oldLogFont, &LogFont, sizeof(LogFont)) != 0 || + oldUseCustomPanelFont != UseCustomPanelFont) + { + SetFont(); + // if the header line is shown, we must set its correct size + LeftPanel->LayoutListBoxChilds(); + RightPanel->LayoutListBoxChilds(); + } + + if (Configuration.ThumbnailSize != LeftPanel->GetThumbnailSize() || + Configuration.ThumbnailSize != RightPanel->GetThumbnailSize()) + { + // if the thumbnail size changed, it must be propagated to the panels + LeftPanel->SetThumbnailSize(Configuration.ThumbnailSize); + RightPanel->SetThumbnailSize(Configuration.ThumbnailSize); + } + + if (oldStatusArea != Configuration.StatusArea) + { + if (Configuration.StatusArea) + AddTrayIcon(); + else + RemoveTrayIcon(); + } + + if (UMToolBar != NULL && UMToolBar->HWindow != NULL) + UMToolBar->CreateButtons(); + + if (HPToolBar != NULL && HPToolBar->HWindow != NULL) + HPToolBar->CreateButtons(); + + if (Windows7AndLater) + CreateJumpList(); + + // the user could have enabled/disabled Documents + CDriveBar* copyDrivesListFrom = NULL; + if (DriveBar != NULL && DriveBar->HWindow != NULL) + { + DriveBar->RebuildDrives(DriveBar); // we don't need slow drive enumeration + copyDrivesListFrom = DriveBar; + } + if (DriveBar2 != NULL && DriveBar2->HWindow != NULL) + DriveBar2->RebuildDrives(copyDrivesListFrom); + + if (oldPanelCaption != Configuration.ShowPanelCaption || oldPanelZoom != Configuration.ShowPanelZoom) + { + if (LeftPanel->DirectoryLine != NULL && LeftPanel->DirectoryLine->HWindow != NULL) + LeftPanel->DirectoryLine->Repaint(); + if (RightPanel->DirectoryLine != NULL && RightPanel->DirectoryLine->HWindow != NULL) + RightPanel->DirectoryLine->Repaint(); + } + + // main window icon + SetWindowIcon(); + // icon in progress windows + ProgressDlgArray.PostIconChange(); + + // tell both panels they need to refresh + LeftPanel->RefreshForConfig(); + RightPanel->RefreshForConfig(); + + // clear stored data in SalShExtPastedData (the archiver may have changed) + SalShExtPastedData.ReleaseStoredArchiveData(); + + // Internal Viewer and Find: refresh all windows (font already changed) + BroadcastConfigChanged(); + + // distribute this news among plugins as well + Plugins.Event(PLUGINEVENT_CONFIGURATIONCHANGED, 0); + } + + EndStopRefresh(); // snooper starts again now + return 0; + } + + case WM_SYSCOMMAND: + { + if (HasLockedUI()) + break; + + // if the user pressed the Alt button while the initial splash window was shown, + // the system menu could be entered before MainWindow appeared and the splash + // window remained open until the user pressed Escape + // if MainWindow is not yet visible, disable entering the Window menu + if (wParam == SC_KEYMENU && !IsWindowVisible(HWindow)) + return 0; + + // set status bar as appropriate + UINT nItemID = wParam != CM_ALWAYSONTOP ? ((UINT)wParam & 0xFFF0) : (UINT)wParam; + + // don't interfere with system commands if not in help mode + if (HelpMode) + { + switch (nItemID) + { + case SC_SIZE: + case SC_MOVE: + case SC_MINIMIZE: + case SC_MAXIMIZE: + case SC_NEXTWINDOW: + case SC_PREVWINDOW: + case SC_CLOSE: + case SC_RESTORE: + case SC_TASKLIST: + { + OpenHtmlHelp(NULL, HWindow, HHCDisplayContext, IDH_SYSMENUCMDS, FALSE); + return 0; + } + + case CM_ALWAYSONTOP: + { + OpenHtmlHelp(NULL, HWindow, HHCDisplayContext, nItemID, FALSE); + return 0; + } + } + } + + if (wParam == CM_ALWAYSONTOP) + WindowProc(WM_COMMAND, wParam, lParam); // pass it on + + if (Configuration.StatusArea && wParam == SC_MINIMIZE) + { + ShowWindow(HWindow, SW_MINIMIZE); + ShowWindow(HWindow, SW_HIDE); + return 0; + } + break; + } + + case WM_USER_FLASHWINDOW: + { + FlashWindow(HWindow, TRUE); + Sleep(100); + FlashWindow(HWindow, FALSE); + return 0; + } + + case WM_APPCOMMAND: + { + // we catch messages coming especially from newer mice (4th button and above) + // and multimedia keyboards + // viz /viewtopic.php?t=192 + DWORD cmd = GET_APPCOMMAND_LPARAM(lParam); + switch (cmd) + { + case APPCOMMAND_BROWSER_BACKWARD: + { + SendMessage(HWindow, WM_COMMAND, CM_ACTIVEBACK, 0); + return TRUE; + } + + case APPCOMMAND_BROWSER_FORWARD: + { + SendMessage(HWindow, WM_COMMAND, CM_ACTIVEFORWARD, 0); + return TRUE; + } + } + break; + } + + case WM_COMMAND: + // Command dispatch extracted to HandleWmCommand() in mainwnd_commands.cpp. + return HandleWmCommand(wParam, lParam); + + + case WM_USER_DISPACHCHANGENOTIF: + { + if (LastDispachChangeNotifTime < lParam) // not an outdated message + { + if (AlreadyInPlugin || StopRefresh > 0) + NeedToResentDispachChangeNotif = TRUE; + else + { + char path[MAX_PATH]; + BOOL includingSubdirs; + BOOL ok = TRUE; + while (1) + { + HANDLES(EnterCriticalSection(&DispachChangeNotifCS)); + if (ChangeNotifArray.Count > 0) + { + CChangeNotifData* item = &ChangeNotifArray[ChangeNotifArray.Count - 1]; + strcpy(path, item->Path); + includingSubdirs = item->IncludingSubdirs; + ChangeNotifArray.Delete(ChangeNotifArray.Count - 1); + if (!ChangeNotifArray.IsGood()) + { + ChangeNotifArray.ResetState(); + ChangeNotifArray.DestroyMembers(); + ChangeNotifArray.ResetState(); + ok = FALSE; + } + } + else + ok = FALSE; + if (!ok) // store the time of the last refresh (still in the critical section) + { + HANDLES(EnterCriticalSection(&TimeCounterSection)); + LastDispachChangeNotifTime = MyTimeCounter++; + HANDLES(LeaveCriticalSection(&TimeCounterSection)); + } + HANDLES(LeaveCriticalSection(&DispachChangeNotifCS)); + + if (ok) // distribute a notification about the change on 'path' with 'includingSubdirs' + { + // send the message to all loaded plugins + Plugins.AcceptChangeOnPathNotification(path, includingSubdirs); + + if (GetNonActivePanel() != NULL) // non-active panel first (due to timestamps of subdirectory changes on NTFS) + { + GetNonActivePanel()->AcceptChangeOnPathNotification(path, includingSubdirs); + } + if (GetActivePanel() != NULL) // then the active panel + { + GetActivePanel()->AcceptChangeOnPathNotification(path, includingSubdirs); + } + + if (DetachedFSList->Count > 0) + { + // for better input/output optimization with plugins, the EnterPlugin/LeavePlugin section + // is exported here (not inside the interface encapsulation) + EnterPlugin(); + int i; + for (i = 0; i < DetachedFSList->Count; i++) + { + CPluginFSInterfaceEncapsulation* fs = DetachedFSList->At(i); + fs->AcceptChangeOnPathNotification(fs->GetPluginFSName(), path, includingSubdirs); + } + LeavePlugin(); + } + } + else + break; // end of loop + } + } + } + return 0; + } + + case WM_USER_DISPACHCFGCHANGE: + { + // broadcast a message about configuration changes to the plugins + Plugins.Event(PLUGINEVENT_CONFIGURATIONCHANGED, 0); + return 0; + } + + case WM_USER_TBCHANGED: + { + HWND hToolBar = (HWND)wParam; + if (TopToolBar != NULL && hToolBar == TopToolBar->HWindow) + { + TopToolBar->Save(Configuration.TopToolBar); + } + if (MiddleToolBar != NULL && hToolBar == MiddleToolBar->HWindow) + { + MiddleToolBar->Save(Configuration.MiddleToolBar); + } + if (LeftPanel->DirectoryLine->ToolBar != NULL && hToolBar == LeftPanel->DirectoryLine->ToolBar->HWindow) + { + LeftPanel->DirectoryLine->LayoutWindow(); + LeftPanel->DirectoryLine->ToolBar->Save(Configuration.LeftToolBar); + } + if (RightPanel->DirectoryLine->ToolBar != NULL && hToolBar == RightPanel->DirectoryLine->ToolBar->HWindow) + { + RightPanel->DirectoryLine->LayoutWindow(); + RightPanel->DirectoryLine->ToolBar->Save(Configuration.RightToolBar); + } + return FALSE; // we have no buttons + } + + case WM_USER_TBENUMBUTTON2: + { + HWND hToolBar = (HWND)wParam; + // we forward it to our toolbar + if (TopToolBar != NULL && hToolBar == TopToolBar->HWindow) + return TopToolBar->OnEnumButton(lParam); + if (MiddleToolBar != NULL && hToolBar == MiddleToolBar->HWindow) + return MiddleToolBar->OnEnumButton(lParam); + if (LeftPanel->DirectoryLine->ToolBar != NULL && hToolBar == LeftPanel->DirectoryLine->ToolBar->HWindow) + return LeftPanel->DirectoryLine->ToolBar->OnEnumButton(lParam); + if (RightPanel->DirectoryLine->ToolBar != NULL && hToolBar == RightPanel->DirectoryLine->ToolBar->HWindow) + return RightPanel->DirectoryLine->ToolBar->OnEnumButton(lParam); + return FALSE; // we have no buttons + } + + case WM_USER_TBRESET: + { + HWND hToolBar = (HWND)wParam; + // forward to our toolbar + if (TopToolBar != NULL && hToolBar == TopToolBar->HWindow) + TopToolBar->OnReset(); + if (MiddleToolBar != NULL && hToolBar == MiddleToolBar->HWindow) + MiddleToolBar->OnReset(); + if (LeftPanel->DirectoryLine->ToolBar != NULL && hToolBar == LeftPanel->DirectoryLine->ToolBar->HWindow) + LeftPanel->DirectoryLine->ToolBar->OnReset(); + if (RightPanel->DirectoryLine->ToolBar != NULL && hToolBar == RightPanel->DirectoryLine->ToolBar->HWindow) + RightPanel->DirectoryLine->ToolBar->OnReset(); + return FALSE; // we have no buttons + } + + case WM_USER_TBGETTOOLTIP: + { + HWND hToolBar = (HWND)wParam; + // we forward it to our toolbar + if (TopToolBar != NULL && hToolBar == TopToolBar->HWindow) + TopToolBar->OnGetToolTip(lParam); + if (MiddleToolBar != NULL && hToolBar == MiddleToolBar->HWindow) + MiddleToolBar->OnGetToolTip(lParam); + if (PluginsBar != NULL && hToolBar == PluginsBar->HWindow) + PluginsBar->OnGetToolTip(lParam); + if (UMToolBar != NULL && hToolBar == UMToolBar->HWindow) + UMToolBar->OnGetToolTip(lParam); + if (HPToolBar != NULL && hToolBar == HPToolBar->HWindow) + HPToolBar->OnGetToolTip(lParam); + if (DriveBar != NULL && hToolBar == DriveBar->HWindow) + DriveBar->OnGetToolTip(lParam); + if (DriveBar2 != NULL && hToolBar == DriveBar2->HWindow) + DriveBar2->OnGetToolTip(lParam); + if (LeftPanel->DirectoryLine->ToolBar != NULL && hToolBar == LeftPanel->DirectoryLine->ToolBar->HWindow) + LeftPanel->DirectoryLine->ToolBar->OnGetToolTip(lParam); + if (RightPanel->DirectoryLine->ToolBar != NULL && hToolBar == RightPanel->DirectoryLine->ToolBar->HWindow) + RightPanel->DirectoryLine->ToolBar->OnGetToolTip(lParam); + if (BottomToolBar != NULL && hToolBar == BottomToolBar->HWindow) + BottomToolBar->OnGetToolTip(lParam); + return FALSE; // we have no buttons + } + + case WM_USER_TBENDADJUST: + { + // some toolbar was configured - force an update + IdleForceRefresh = TRUE; + IdleRefreshStates = TRUE; + return 0; + } + + case WM_USER_LEAVEMENULOOP2: + { + // this message arrives after the command, so any New menu command has already been processed + if (ContextMenuNew != NULL) + ContextMenuNew->Release(); + return 0; + } + + case WM_USER_UNINITMENUPOPUP: + { + CMenuPopup* popup = (CMenuPopup*)(CGUIMenuPopupAbstract*)wParam; + WORD popupID = HIWORD(lParam); + + switch (popupID) + { + case CML_OPTIONS_PLUGINS: + case CML_HELP_ABOUTPLUGINS: + case CML_PLUGINS: + case CML_PLUGINS_SUBMENU: + case CML_FILES_VIEWWITH: + { + HIMAGELIST hIcons = popup->GetImageList(); + if (hIcons != NULL) + { + popup->SetImageList(NULL); // just to be safe, so the popup doesn't own an invalid handle + ImageList_Destroy(hIcons); + } + hIcons = popup->GetHotImageList(); + if (hIcons != NULL) + { + popup->SetHotImageList(NULL); // just to be safe, so the popup doesn't own an invalid handle + ImageList_Destroy(hIcons); + } + if (popupID == CML_PLUGINS) // closing the Plugins menu; dynamic icons can be freed (they are rebuilt before each next menu opening) + Plugins.ReleasePluginDynMenuIcons(); + break; + } + + case CML_FILES_NEW: + { + popup->SetTemplateMenu(NULL); + EndStopRefresh(); // closed in WM_USER_UNINITMENUPOPUP/WM_USER_INITMENUPOPUP + break; + } + } + return 0; + } + + case WM_USER_INITMENUPOPUP: + { + CMenuPopup* popup = (CMenuPopup*)(CGUIMenuPopupAbstract*)wParam; + WORD popupID = HIWORD(lParam); + + switch (popupID) + { + case CML_LEFT: + case CML_RIGHT: + { + BOOL left = popupID == CML_LEFT; + + popup->CheckItem(left ? CM_LCHANGEFILTER : CM_RCHANGEFILTER, FALSE, + (left ? LeftPanel : RightPanel)->FilterEnabled); + + DWORD firstID = left ? CML_LEFT_VIEWS1 : CML_RIGHT_VIEWS1; + DWORD lastID = left ? CML_LEFT_VIEWS2 : CML_RIGHT_VIEWS2; + // find the separator above and below the views + int firstIndex = popup->FindItemPosition(firstID); + int lastIndex = popup->FindItemPosition(lastID); + if (firstIndex == -1 || lastIndex == -1) + { + TRACE_E("Requested items were not found"); + } + else + { + // remove the current contents + if (firstIndex + 1 < lastIndex - 1) + popup->RemoveItemsRange(firstIndex + 1, lastIndex - 1); + + // populate the list of views + FillViewModeMenu(popup, firstIndex + 1, left ? 1 : 2); + } + break; + } + + case CML_LEFT_GO: + case CML_RIGHT_GO: + { + static int GO_ITEMS_COUNT = -1; + + int count = popup->GetItemCount(); + if (GO_ITEMS_COUNT == -1) + GO_ITEMS_COUNT = count; + + if (count > GO_ITEMS_COUNT) + { + // remove the existing contents + popup->RemoveItemsRange(GO_ITEMS_COUNT, count - 1); + } + + // append hot paths, if any exist + DWORD firstID = popupID == CML_LEFT_GO ? CM_LEFTHOTPATH_MIN : CM_RIGHTHOTPATH_MIN; + HotPaths.FillHotPathsMenu(popup, firstID, FALSE, FALSE, FALSE, TRUE); + + // append directory history, at most 10 items + firstID = popupID == CML_LEFT_GO ? CM_LEFTHISTORYPATH_MIN : CM_RIGHTHISTORYPATH_MIN; + DirHistory->FillHistoryPopupMenu(popup, firstID, 10, TRUE); + break; + } + + case CML_LEFT_VISIBLE: + { + popup->CheckItem(CM_LEFTDIRLINE, FALSE, LeftPanel->DirectoryLine->HWindow != NULL); + popup->EnableItem(CM_LEFTHEADER, FALSE, LeftPanel->GetViewMode() == vmDetailed); + popup->CheckItem(CM_LEFTHEADER, FALSE, LeftPanel->GetViewMode() == vmDetailed && LeftPanel->HeaderLineVisible); + popup->CheckItem(CM_LEFTSTATUS, FALSE, LeftPanel->StatusLine->HWindow != NULL); + break; + } + + case CML_RIGHT_VISIBLE: + { + popup->CheckItem(CM_RIGHTDIRLINE, FALSE, RightPanel->DirectoryLine->HWindow != NULL); + popup->EnableItem(CM_RIGHTHEADER, FALSE, RightPanel->GetViewMode() == vmDetailed); + popup->CheckItem(CM_RIGHTHEADER, FALSE, RightPanel->GetViewMode() == vmDetailed && RightPanel->HeaderLineVisible); + popup->CheckItem(CM_RIGHTSTATUS, FALSE, RightPanel->StatusLine->HWindow != NULL); + break; + } + + case CML_LEFT_SORTBY: + case CML_RIGHT_SORTBY: + { + BOOL left = popupID == CML_LEFT_SORTBY; + (left ? LeftPanel : RightPanel)->FillSortByMenu(popup); + break; + } + + case CML_FILES: + { + break; + } + + case CML_EDIT: + { + // If this is a "change directory" paste operation, show it in the Paste item + char text[220]; + char tail[50]; + tail[0] = 0; + + strcpy(text, LoadStr(IDS_MENU_EDIT_PASTE)); + + CFilesWindow* activePanel = GetActivePanel(); + BOOL activePanelIsDisk = (activePanel != NULL && activePanel->Is(ptDisk)); + if (EnablerPastePath && + (!activePanelIsDisk || !EnablerPasteFiles) && // PasteFiles has higher priority + !EnablerPasteFilesToArcOrFS) // PasteFilesToArcOrFS has higher priority + { + char* p = strrchr(text, '\t'); + if (p != NULL) + strcpy(tail, p); + else + p = text + strlen(text); + + sprintf(p, " (%s)%s", LoadStr(IDS_PASTE_CHANGE_DIRECTORY), tail); + } + + MENU_ITEM_INFO mii; + mii.Mask = MENU_MASK_STRING; + mii.String = text; + popup->SetItemInfo(CM_CLIPPASTE, FALSE, &mii); + break; + } + + case CML_FILES_NEW: + { + CFilesWindow* activePanel = GetActivePanel(); + if (activePanel == NULL) + break; + BeginStopRefresh(); // we close in WM_USER_UNINITMENUPOPUP/CML_FILES_NEW, + // which is guaranteed to pair with this entry + + // if the menu does not exist, let it be created + if ((!ContextMenuNew->MenuIsAssigned()) && activePanel->Is(ptDisk) && + activePanel->CheckPath(FALSE) == ERROR_SUCCESS) + GetNewOrBackgroundMenu(HWindow, activePanel->GetPath(), ContextMenuNew, CM_NEWMENU_MIN, CM_NEWMENU_MAX, FALSE); + + // if the menu exists, build our menu based on it + if (ContextMenuNew->MenuIsAssigned()) + popup->SetTemplateMenu(ContextMenuNew->GetMenu()); + else + { + // otherwise insert a message that the New menu is unavailable + popup->RemoveAllItems(); + MENU_ITEM_INFO mii; + mii.Mask = MENU_MASK_TYPE | MENU_MASK_STRING | MENU_MASK_STATE; + mii.Type = MENU_TYPE_STRING; + mii.String = LoadStr(IDS_NEWISNOTAVAILABLE); + mii.State = MENU_STATE_GRAYED; + popup->InsertItem(0, TRUE, &mii); + } + break; + } + + case CML_FILES_VIEWWITH: + { + CFilesWindow* activePanel = GetActivePanel(); + if (activePanel == NULL) + break; + + HIMAGELIST hIcons = Plugins.CreateIconsList(FALSE); // the image list will be destroyed in WM_USER_UNINITMENUPOPUP + HIMAGELIST hIconsGray = Plugins.CreateIconsList(TRUE); + popup->SetImageList(hIconsGray); + popup->SetHotImageList(hIcons); + + activePanel->FillViewWithMenu(popup); + break; + } + + case CML_FILES_EDITWITH: + { + CFilesWindow* activePanel = GetActivePanel(); + if (activePanel == NULL) + break; + activePanel->FillEditWithMenu(popup); + break; + } + + case CML_COMMANDS_USERMENU: + { + popup->RemoveAllItems(); + FillUserMenu(popup); // expanding the user menu here is handled via WM_USER_ENTERMENULOOP/WM_USER_LEAVEMENULOOP (UserMenuIconBkgndReader.BeginUserMenuIconsInUse / EndUserMenuIconsInUse) + break; + } + + case CML_PLUGINS: + { + // initialize the Plugins menu + HIMAGELIST hIcons = Plugins.CreateIconsList(FALSE); // the image list will be destroyed in WM_USER_UNINITMENUPOPUP + HIMAGELIST hIconsGray = Plugins.CreateIconsList(TRUE); + popup->SetImageList(hIconsGray); + popup->SetHotImageList(hIcons); + + Plugins.InitMenuItems(HWindow, popup); + popup->AssignHotKeys(); + break; + } + + case CML_PLUGINS_SUBMENU: + { + // initialize a submenu of one of the plugins + Plugins.InitSubMenuItems(HWindow, popup); + break; + } + + case CML_OPTIONS: + { + popup->CheckItem(CM_ALWAYSONTOP, FALSE, Configuration.AlwaysOnTop); + break; + } + + case CML_OPTIONS_PLUGINS: + { + popup->RemoveAllItems(); + + HIMAGELIST hIcons = Plugins.CreateIconsList(FALSE); // the image list will be destroyed in WM_USER_UNINITMENUPOPUP + HIMAGELIST hIconsGray = Plugins.CreateIconsList(TRUE); + popup->SetImageList(hIconsGray); + popup->SetHotImageList(hIcons); + // we want only plugins with configuration options + if (Plugins.AddNamesToMenu(popup, CM_PLUGINCFG_MIN, CM_PLUGINCFG_MAX - CM_PLUGINCFG_MIN, TRUE)) + popup->AssignHotKeys(); + break; + } + + case CML_OPTIONS_VISIBLE: + { + popup->CheckItem(CM_TOGGLETOPTOOLBAR, FALSE, TopToolBar->HWindow != NULL); + popup->CheckItem(CM_TOGGLEPLUGINSBAR, FALSE, PluginsBar->HWindow != NULL); + popup->CheckItem(CM_TOGGLEMIDDLETOOLBAR, FALSE, MiddleToolBar->HWindow != NULL); + popup->CheckItem(CM_TOGGLEUSERMENUTOOLBAR, FALSE, UMToolBar->HWindow != NULL); + popup->CheckItem(CM_TOGGLEHOTPATHSBAR, FALSE, HPToolBar->HWindow != NULL); + popup->CheckItem(CM_TOGGLEDRIVEBAR, FALSE, DriveBar->HWindow != NULL && DriveBar2->HWindow == NULL); + popup->CheckItem(CM_TOGGLEDRIVEBAR2, FALSE, DriveBar2->HWindow != NULL); + popup->CheckItem(CM_TOGGLEEDITLINE, FALSE, EditPermanentVisible); + popup->CheckItem(CM_TOGGLEBOTTOMTOOLBAR, FALSE, BottomToolBar->HWindow != NULL); + popup->CheckItem(CM_TOGGLE_UMLABELS, FALSE, Configuration.UserMenuToolbarLabels); + popup->CheckItem(CM_TOGGLE_GRIPS, FALSE, !Configuration.GripsVisible); + break; + } + + case CML_HELP_ABOUTPLUGINS: + { + popup->RemoveAllItems(); + + HIMAGELIST hIcons = Plugins.CreateIconsList(FALSE); // the image list will be destroyed in WM_USER_UNINITMENUPOPUP + HIMAGELIST hIconsGray = Plugins.CreateIconsList(TRUE); + popup->SetImageList(hIconsGray); + popup->SetHotImageList(hIcons); + // we want all plugins + if (Plugins.AddNamesToMenu(popup, CM_PLUGINABOUT_MIN, CM_PLUGINABOUT_MAX - CM_PLUGINABOUT_MIN, FALSE)) + popup->AssignHotKeys(); + break; + } + } + return 0; + } + + case WM_INITMENUPOPUP: // note: similar code is also in CFileListBox + case WM_DRAWITEM: + case WM_MEASUREITEM: + case WM_MENUCHAR: + { + LRESULT plResult = 0; + if (ContextMenuChngDrv != NULL) + { + // if the user right-clicks HotPath in the ChangeDrive menu, it comes here + CALL_STACK_MESSAGE1("CMainWindow::WindowProc::ContextMenuChngDrv"); + SafeHandleMenuChngDrvMsg2(uMsg, wParam, lParam, &plResult); + } + if (ContextMenuNew != NULL && ContextMenuNew->MenuIsAssigned()) + { + CALL_STACK_MESSAGE1("CMainWindow::WindowProc::SafeHandleMenuMsg2"); + SafeHandleMenuNewMsg2(uMsg, wParam, lParam, &plResult); + } + return plResult; + } + + case WM_SETCURSOR: + { + if (HasLockedUI()) + break; + if (HelpMode) + { + SetCursor(HHelpCursor); + return TRUE; + } + POINT p, p2; + GetCursorPos(&p); + p2 = p; + ScreenToClient(HWindow, &p); + RECT r; + GetSplitRect(r); + if (IsWindowEnabled(HWindow) && PtInRect(&r, p) && GetCapture() == NULL) + { + BOOL aboveMiddle = FALSE; + if (MiddleToolBar != NULL && MiddleToolBar->HWindow != NULL) + { + GetWindowRect(MiddleToolBar->HWindow, &r); + aboveMiddle = PtInRect(&r, p2); + } + if (!aboveMiddle) + { + SetCursor(LoadCursor(NULL, IDC_SIZEWE)); + return TRUE; + } + } + break; + } + + case WM_CONTEXTMENU: + { + if (HasLockedUI()) + break; + if (!DragMode) + { + OnWmContextMenu((HWND)wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); + } + break; + } + + case WM_LBUTTONDOWN: + case WM_LBUTTONDBLCLK: + { + if (HasLockedUI()) + break; + + POINT p; + p.x = (short)LOWORD(lParam); + p.y = (short)HIWORD(lParam); + + RECT r; + GetSplitRect(r); + + if (PtInRect(&r, p)) + { + if (uMsg == WM_LBUTTONDOWN) // click -> start dragging + { + UpdateWindow(HWindow); // if Salamander is underneath, repaint all windows + MainWindow->CancelPanelsUI(); // cancel QuickSearch and QuickEdit + BeginStopIconRepaint(); // we do not want any icon repaints + if (!DragFullWindows) + BeginStopStatusbarRepaint(); // skip throbber repaints when dragging the XOR split bar + + DragMode = TRUE; + DragAnchorX = p.x - r.left; + SetCapture(HWindow); + + HWND toolTip = CreateWindowEx(0, + TOOLTIPS_CLASS, + NULL, + TTS_ALWAYSTIP | TTS_NOPREFIX, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + NULL, + NULL, + HInstance, + NULL); + ToolTipWindow.AttachToWindow(toolTip); + ToolTipWindow.SetToolWindow(HWindow); + TOOLINFO ti; + ti.cbSize = sizeof(TOOLINFO); + ti.uFlags = TTF_SUBCLASS | TTF_ABSOLUTE | TTF_TRACK; + ti.hwnd = HWindow; + ti.uId = 1; + GetClientRect(HWindow, &ti.rect); + ti.hinst = HInstance; + ti.lpszText = LPSTR_TEXTCALLBACK; + SendMessage(ToolTipWindow.HWindow, TTM_ADDTOOL, 0, (LPARAM)&ti); + + int splitWidth = MainWindow->GetSplitBarWidth(); + DragSplitPosition = SplitPosition; + POINT mp; + GetCursorPos(&mp); + POINT p2; + p2.x = r.left; + p2.y = 0; + ClientToScreen(HWindow, &p2); + mp.x = p2.x; + SendMessage(ToolTipWindow.HWindow, TTM_TRACKPOSITION, 0, (LPARAM)(DWORD)MAKELONG(mp.x + splitWidth + 2, mp.y + 10)); + SendMessage(ToolTipWindow.HWindow, TTM_TRACKACTIVATE, TRUE, (LPARAM)&ti); + + GetWindowSplitRect(r); + DragSplitX = p.x - DragAnchorX; + DrawSplitLine(HWindow, DragSplitX, -1, r); + return 0; + } + if (uMsg == WM_LBUTTONDBLCLK) + { + if (SplitPosition != 0.5) + { + SplitPosition = 0.5; + LayoutWindows(); + FocusPanel(GetActivePanel()); + } + return 0; + } + } + break; + } + + case WM_MOUSEMOVE: + { + if (HasLockedUI()) + break; + if (DragMode && (wParam & MK_LBUTTON)) + { + int x = (short)LOWORD(lParam); + RECT r; + GetWindowSplitRect(r); + + int splitWidth = MainWindow->GetSplitBarWidth(); + + // stopper at the center + double splitPosition = (double)(x - DragAnchorX) / (WindowWidth - splitWidth); + + if (splitPosition >= 0.49 && splitPosition <= 0.51) + { + x = (WindowWidth - splitWidth) / 2 + DragAnchorX; + splitPosition = 0.5; + } + + if (splitPosition < 0) + splitPosition = 0; + if (splitPosition > 1) + splitPosition = 1; + + int leftWidth = x - DragAnchorX; + if (leftWidth < MIN_WIN_WIDTH + 1) + leftWidth = MIN_WIN_WIDTH + 1; + int rightWidth = WindowWidth - 2 - leftWidth - splitWidth; + if (rightWidth < MIN_WIN_WIDTH - 1) + { + rightWidth = MIN_WIN_WIDTH - 1; + leftWidth = WindowWidth - 2 - splitWidth - rightWidth; + } + + TOOLINFO ti; + ti.cbSize = sizeof(TOOLINFO); + ti.uFlags = 0; + ti.hwnd = HWindow; + ti.uId = 1; + GetClientRect(HWindow, &ti.rect); + + DragSplitPosition = splitPosition; + + POINT p; + GetCursorPos(&p); + POINT p2; + p2.x = leftWidth; + p2.y = 0; + ClientToScreen(HWindow, &p2); + p.x = p2.x; + SendMessage(ToolTipWindow.HWindow, TTM_TRACKPOSITION, 0, (LPARAM)(DWORD)MAKELONG(p.x + splitWidth + 2, p.y + 10)); + UpdateWindow(HWindow); + + if (DragFullWindows) + { + if (DragSplitX != leftWidth) + { + DragSplitX = leftWidth; + SplitPosition = DragSplitPosition; + LayoutWindows(); + } + } + else + { + DrawSplitLine(HWindow, leftWidth, DragSplitX, r); + DragSplitX = leftWidth; + } + + // ti.hinst = HInstance; + // ti.lpszText = LPSTR_TEXTCALLBACK; + // SendMessage(ToolTipWindow.HWindow, TTM_UPDATETIPTEXT, 0, (LPARAM)&ti); + } + break; + } + + case WM_CANCELMODE: + case WM_LBUTTONUP: + { + if (HasLockedUI()) + break; + if (DragMode) + { + RECT r; + GetClientRect(HWindow, &r); + RECT r2; + GetSplitRect(r2); + r2.left = r.left; + r2.right = r.right; + DrawSplitLine(HWindow, -1, DragSplitX, r2); + SendMessage(ToolTipWindow.HWindow, TTM_ACTIVATE, FALSE, 0); + DestroyWindow(ToolTipWindow.HWindow); // just detaches the tooltip from the control + if (uMsg == WM_LBUTTONUP) + { + // accept the position only when the drag finishes legally + // int splitWidth = MainWindow->GetSplitBarWidth(); + // SplitPosition = (double)DragSplitX / (WindowWidth - splitWidth); + SplitPosition = DragSplitPosition; + LayoutWindows(); + } + DragMode = FALSE; + ReleaseCapture(); + FocusPanel(GetActivePanel()); + EndStopIconRepaint(TRUE); // resume icon repainting and repaint them now + if (!DragFullWindows) + EndStopStatusbarRepaint(); // resume throbber repaints when dragging the XOR split bar + return 0; + } + break; + } + + case WM_NOTIFY: + { + if (!Created) + break; + if (HasLockedUI()) + break; + LPNMHDR lphdr = (LPNMHDR)lParam; + if (lphdr->code == TTN_NEEDTEXT && lphdr->hwndFrom == ToolTipWindow.HWindow) + { + char* text = ((LPTOOLTIPTEXT)lParam)->szText; + sprintf(text, "%.1lf %%", DragSplitPosition * 100); + PointToLocalDecimalSeparator(text, 15); + return 0; + } + + if (lphdr->code == NM_RCLICK && + (LeftPanel->DirectoryLine->ToolBar != NULL && + lphdr->hwndFrom == LeftPanel->DirectoryLine->ToolBar->HWindow)) + { + CToolBar* toolBar = LeftPanel->DirectoryLine->ToolBar; + DWORD pos = GetMessagePos(); + POINT p; + p.x = GET_X_LPARAM(pos); + p.y = GET_Y_LPARAM(pos); + ScreenToClient(toolBar->HWindow, &p); + int index = toolBar->HitTest(p.x, p.y); + if (index >= 0) + { + TLBI_ITEM_INFO2 tii; + tii.Mask = TLBI_MASK_ID; + if (toolBar->GetItemInfo2(index, TRUE, &tii)) + { + if (tii.ID == CM_LCHANGEDRIVE) + { + LeftPanel->UserWorkedOnThisPath = TRUE; + ShellAction(LeftPanel, saContextMenu, FALSE); + return 1; + } + } + } + break; + } + + if (lphdr->code == NM_RCLICK && + (RightPanel->DirectoryLine->ToolBar != NULL && + lphdr->hwndFrom == RightPanel->DirectoryLine->ToolBar->HWindow)) + { + CToolBar* toolBar = RightPanel->DirectoryLine->ToolBar; + DWORD pos = GetMessagePos(); + POINT p; + p.x = GET_X_LPARAM(pos); + p.y = GET_Y_LPARAM(pos); + ScreenToClient(toolBar->HWindow, &p); + int index = toolBar->HitTest(p.x, p.y); + if (index >= 0) + { + TLBI_ITEM_INFO2 tii; + tii.Mask = TLBI_MASK_ID; + if (toolBar->GetItemInfo2(index, TRUE, &tii)) + { + if (tii.ID == CM_RCHANGEDRIVE) + { + RightPanel->UserWorkedOnThisPath = TRUE; + ShellAction(RightPanel, saContextMenu, FALSE); + return 1; + } + } + } + break; + } + + if (lphdr->code == NM_RCLICK && + (DriveBar != NULL && DriveBar->HWindow != NULL && + lphdr->hwndFrom == DriveBar->HWindow)) + { + if (DriveBar->OnContextMenu()) + return 1; + break; + } + + if (lphdr->code == NM_RCLICK && + (DriveBar2 != NULL && DriveBar2->HWindow != NULL && + lphdr->hwndFrom == DriveBar2->HWindow)) + { + if (DriveBar2->OnContextMenu()) + return 1; + break; + } + + if (lphdr->code == TBN_TOOLBARCHANGE) + { + if (LeftPanel->DirectoryLine->ToolBar != NULL && + lphdr->hwndFrom == LeftPanel->DirectoryLine->ToolBar->HWindow) + LeftPanel->DirectoryLine->LayoutWindow(); + if (RightPanel->DirectoryLine->ToolBar != NULL && + lphdr->hwndFrom == RightPanel->DirectoryLine->ToolBar->HWindow) + RightPanel->DirectoryLine->LayoutWindow(); + IdleRefreshStates = TRUE; // on the next Idle, force a check of status variables + return 0; + } + if (lphdr->code == RBN_AUTOSIZE) + { + LPNMRBAUTOSIZE lpnmas = (LPNMRBAUTOSIZE)lParam; + LayoutWindows(); + return 0; + } + if (lphdr->code == RBN_LAYOUTCHANGED) + { + StoreBandsPos(); + return 0; + } + + if (lphdr->code == RBN_BEGINDRAG && DriveBar2->HWindow != NULL) + { + // hide the drive bars while dragging bands + ShowHideTwoDriveBarsInternal(FALSE); + return 0; + } + + if (lphdr->code == RBN_ENDDRAG && DriveBar2->HWindow != NULL) + { + // after dragging, show our two bands again and move them to the end + ShowHideTwoDriveBarsInternal(TRUE); + return 0; + } + + break; + } + + case WM_WINDOWPOSCHANGED: + { + GetWindowRect(HWindow, &WindowRect); + break; + } + + case WM_SIZE: // panel size adjustment + { + // at Tonda's, WM_SIZE arrives before WM_CREATE finishes + // (bug report execution address = 0x004743C3) + if (!Created) + { + PostMessage(HWindow, uMsg, wParam, lParam); + break; + } + + WindowWidth = LOWORD(lParam); + WindowHeight = HIWORD(lParam); + + if (SplitPosition < 0) + SplitPosition = 0; + if (SplitPosition > 1) + SplitPosition = 1; + + int splitWidth = GetSplitBarWidth(); + int middleToolbarWidth = 0; + if (MiddleToolBar->HWindow != NULL) + middleToolbarWidth = MiddleToolBar->GetNeededWidth(); + + int leftWidth = (int)((WindowWidth - splitWidth) * SplitPosition) - 1; + if (leftWidth < MIN_WIN_WIDTH) + leftWidth = MIN_WIN_WIDTH; + int rightWidth = WindowWidth - 2 - leftWidth - splitWidth; + if (rightWidth < MIN_WIN_WIDTH) + { + rightWidth = MIN_WIN_WIDTH; + leftWidth = WindowWidth - 2 - rightWidth - splitWidth; + } + SplitPositionPix = 1 + leftWidth; + + TopRebarHeight = 0; + BottomToolBarHeight = 0; + EditHeight = 0; + PanelsHeight = WindowHeight - 1; + + int windowsCount = 3; + + RECT rebRect; + GetWindowRect(HTopRebar, &rebRect); + TopRebarHeight = rebRect.bottom - rebRect.top; + + if (MiddleToolBar->HWindow != NULL) + { + windowsCount++; + } + if (BottomToolBar->HWindow != NULL) + { + windowsCount++; + BottomToolBarHeight = BottomToolBar->GetNeededHeight(); + } + if (EditWindow->HWindow != NULL) + { + windowsCount++; + EditHeight = EditWindow->GetNeededHeight() + 1; + } + + PanelsHeight -= TopRebarHeight + BottomToolBarHeight + EditHeight; + if (PanelsHeight < 0) + PanelsHeight = 0; + + HDWP hdwp = HANDLES(BeginDeferWindowPos(windowsCount)); + if (hdwp != NULL) + { + hdwp = HANDLES(DeferWindowPos(hdwp, HTopRebar, NULL, + 0, 0, WindowWidth, TopRebarHeight, + SWP_NOACTIVATE | SWP_NOZORDER)); + + hdwp = HANDLES(DeferWindowPos(hdwp, LeftPanel->HWindow, NULL, + 1, TopRebarHeight, leftWidth, PanelsHeight, + SWP_NOACTIVATE | SWP_NOZORDER)); + hdwp = HANDLES(DeferWindowPos(hdwp, RightPanel->HWindow, NULL, + SplitPositionPix + splitWidth, TopRebarHeight, rightWidth, PanelsHeight, + SWP_NOACTIVATE | SWP_NOZORDER)); + + if (MiddleToolBar->HWindow != NULL) + { + // move the toolbar down if any panel has a directory line + int offset1 = 0; + int offset2 = 0; + if (LeftPanel->DirectoryLine != NULL && LeftPanel->DirectoryLine->HWindow != NULL) + offset1 = LeftPanel->DirectoryLine->GetNeededHeight(); + if (RightPanel->DirectoryLine != NULL && RightPanel->DirectoryLine->HWindow != NULL) + offset2 = RightPanel->DirectoryLine->GetNeededHeight(); + int offset = max(offset1, offset2); + hdwp = HANDLES(DeferWindowPos(hdwp, MiddleToolBar->HWindow, NULL, + SplitPositionPix + SPLIT_LINE_WIDTH, TopRebarHeight + offset, + middleToolbarWidth, PanelsHeight - offset, + SWP_NOACTIVATE | SWP_NOZORDER)); + } + + // HWND_BOTTOM - prevents flickering during window resize + // if the bottom toolbar ends up down there, it flickers when resizing + if (EditWindow->HWindow != NULL) + hdwp = HANDLES(DeferWindowPos(hdwp, EditWindow->HWindow, HWND_BOTTOM, + 0, TopRebarHeight + PanelsHeight + 2, WindowWidth, EditHeight + 150, + SWP_NOACTIVATE /*| SWP_NOZORDER*/)); + + if (BottomToolBar->HWindow != NULL) + hdwp = HANDLES(DeferWindowPos(hdwp, BottomToolBar->HWindow, NULL, + 1, TopRebarHeight + PanelsHeight + EditHeight + 1, WindowWidth - 2, BottomToolBarHeight, + SWP_NOACTIVATE | SWP_NOZORDER)); + HANDLES(EndDeferWindowPos(hdwp)); + } + if (DriveBar2->HWindow != NULL) + { + REBARBANDINFO rbi; + rbi.cbSize = sizeof(REBARBANDINFO); + rbi.fMask = RBBIM_SIZE; + + RECT r; + // at Tomas Jelinek the second band strip could stick to the right side after maximizing the main window + // and refused to move; this might solve the problem + GetClientRect(RightPanel->HWindow, &r); + rbi.cx = r.right; + int index = (int)SendMessage(HTopRebar, RB_IDTOINDEX, BANDID_DRIVEBAR2, 0); + SendMessage(HTopRebar, RB_SETBANDINFO, index, (LPARAM)&rbi); + + GetClientRect(LeftPanel->HWindow, &r); + rbi.cx = r.right + MainWindow->GetSplitBarWidth() / 2 - 1; + index = (int)SendMessage(HTopRebar, RB_IDTOINDEX, BANDID_DRIVEBAR, 0); + SendMessage(HTopRebar, RB_SETBANDINFO, index, (LPARAM)&rbi); + } + break; + } + + case WM_NCACTIVATE: + { + // set the global variable indicating the main window frame state + CaptionIsActive = (BOOL)wParam; + + // repaint the directory line of the active window + // if selection is being lost, request an update quickly so we don't + // destroy the buffer of the opening window with CS_SAVEBITS + CFilesWindow* panel = GetActivePanel(); + if (panel != NULL && panel->DirectoryLine != NULL) + panel->DirectoryLine->InvalidateAndUpdate(!CaptionIsActive); + + if (!CaptionIsActive) + { + // let the bottom toolbar reset to its default position + UpdateBottomToolBar(); + } + break; + } + + case WM_ENABLE: + { + if (WindowsVistaAndLater) + { + // Windows Vista UAC patch: when starting a file from the panels caused the UAC elevation prompt to appear + // and then was closed using Cancel, Salamander would lose focus from the panel. + // The main window is disabled at the time messages like WM_ACTIVATE or WM_SETFOCUS arrive, and the focus is received by Microsoft IME-supported popups. + BOOL enabled = (BOOL)wParam; + if (enabled) + { + HWND hFocused = GetFocus(); + HWND hPanelListbox = NULL; + CFilesWindow* activePanel = GetActivePanel(); + if (activePanel != NULL && !EditMode) + { + hPanelListbox = activePanel->GetListBoxHWND(); + if (hFocused == NULL || hFocused != hPanelListbox) + FocusPanel(activePanel); + } + } + } + break; + } + + case WM_ACTIVATE: + { + int active = LOWORD(wParam); + if (active == WA_INACTIVE) + CacheNextSetFocus = TRUE; // for a smooth switch to Salamander; otherwise focus would be drawn aggressively (like old versions) + else + SuppressToolTipOnCurrentMousePos(); // suppress an unwanted tooltip when switching to the window + ExitHelpMode(); + + // ensure hiding/showing the Wait window if it exists + ShowSafeWaitWindow(active != WA_INACTIVE); + + if (active != WA_INACTIVE) + BringLockedUIToolWnd(); + + if (active == WA_ACTIVE || active == WA_CLICKACTIVE) + { + if (!EditMode) + { + if (GetActivePanel() != NULL) + { + FocusPanel(GetActivePanel()); + return 0; + } + } + else + { + if (EditWindow->HWindow != NULL) + { + SetFocus(EditWindow->HWindow); + return 0; + } + } + } + break; + } + + case WM_USER_POSTCMDORUNLOADPLUGIN: + { + CPluginData* data = Plugins.GetPluginData((CPluginInterfaceAbstract*)wParam); + if (data != NULL && data->GetLoaded()) + { + if (lParam == 0) + data->ShouldUnload = TRUE; // set the flag to unload the plugin + else + { + if (lParam == 1) + data->ShouldRebuildMenu = TRUE; // set the flag to rebuild the plugin menu + else + data->Commands.Add(LOWORD(lParam - 2)); // add salCmd/menuCmd + } + ExecCmdsOrUnloadMarkedPlugins = TRUE; // inform Salamander to scan all plugin data + } + else + { + // may occur while waiting for Release(force==TRUE) method of the plugin to finish + // TRACE_E("Unexpected situation in WM_USER_POSTCMDORUNLOADPLUGIN."); + } + return 0; + } + + case WM_USER_POSTMENUEXTCMD: + { + CPluginData* data = Plugins.GetPluginData((CPluginInterfaceAbstract*)wParam); + if (data != NULL && data->GetLoaded()) + { + if (data->GetPluginInterfaceForMenuExt()->NotEmpty()) + { + CALL_STACK_MESSAGE4("CPluginInterfaceForMenuExt::ExecuteMenuItem(, , %d,) (%s v. %s)", + (int)lParam, data->DLLName, data->Version); + + // lower the thread priority to "normal" (so operations don't burden the system) + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL); + + data->GetPluginInterfaceForMenuExt()->ExecuteMenuItem(NULL, HWindow, (int)lParam, 0); + + // raise the thread priority again, the operation has finished + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL); + } + else + { + TRACE_E("Plugin must have PluginInterfaceForMenuExt when " + "calling CSalamanderGeneral::PostMenuExtCommand()!"); + } + } + else + { + // it must be loaded because post-menu-ext-cmd was invoked from a loaded plugin... + // post-unload runs during "idle", so the unload couldn't have happened yet... + TRACE_E("Unexpected situation in WM_USER_POSTMENUEXTCMD."); + } + return 0; + } + + case WM_USER_SALSHEXT_TRYRELDATA: + { + // TRACE_I("WM_USER_SALSHEXT_TRYRELDATA: begin"); + if (SalShExtSharedMemView != NULL) // shared memory is available (we cannot handle cut/copy&paste errors) + { + WaitForSingleObject(SalShExtSharedMemMutex, INFINITE); + BOOL needRelease = TRUE; + if (!SalShExtSharedMemView->BlockPasteDataRelease) + { + if (!SalShExtPastedData.IsLocked()) + { + BOOL isOnClipboard = FALSE; + if (SalShExtSharedMemView->DoPasteFromSalamander && + SalShExtSharedMemView->SalamanderMainWndPID == GetCurrentProcessId() && + SalShExtSharedMemView->SalamanderMainWndTID == GetCurrentThreadId() && + SalShExtSharedMemView->PastedDataID == SalShExtPastedData.GetDataID()) + { + ReleaseMutex(SalShExtSharedMemMutex); + needRelease = FALSE; + + IDataObject* dataObj; + if (OleGetClipboard(&dataObj) == S_OK && dataObj != NULL) + { + if (IsFakeDataObject(dataObj, NULL, NULL, 0)) + { + isOnClipboard = TRUE; + } + dataObj->Release(); + } + } + + if (!isOnClipboard) + { + if (needRelease) + ReleaseMutex(SalShExtSharedMemMutex); + needRelease = FALSE; + + //TRACE_I("WM_USER_SALSHEXT_TRYRELDATA: clearing paste-data!"); + SalShExtPastedData.Clear(); + } + // else TRACE_I("WM_USER_SALSHEXT_TRYRELDATA: fake-data-object is still on clipboard"); + } + // else TRACE_I("WM_USER_SALSHEXT_TRYRELDATA: paste-data is locked"); + } + // else TRACE_I("WM_USER_SALSHEXT_TRYRELDATA: release of paste-data is blocked"); + if (needRelease) + ReleaseMutex(SalShExtSharedMemMutex); + } + // TRACE_I("WM_USER_SALSHEXT_TRYRELDATA: end"); + return 0; + } + + case WM_USER_SALSHEXT_PASTE: + { + // TRACE_I("WM_USER_SALSHEXT_PASTE: begin"); + if (SalShExtSharedMemView != NULL) // shared memory is available (we cannot handle cut/copy&paste errors) + { + BOOL tmpPasteDone = FALSE; + char tgtPath[MAX_PATH]; + tgtPath[0] = 0; + int operation = 0; + DWORD dataID = -1; + WaitForSingleObject(SalShExtSharedMemMutex, INFINITE); + if (SalShExtSharedMemView->PostMsgIndex == (int)wParam) // process only the "current" messages + { + if (SalamanderBusy) + SalShExtSharedMemView->SalBusyState = 2 /* Salamander is busy, postpone paste for later */; + else + { + SalamanderBusy = TRUE; + SalShExtPastedData.SetLock(TRUE); + LastSalamanderIdleTime = GetTickCount(); + SalShExtSharedMemView->SalBusyState = 1 /* Salamander is not busy and now is waiting for a paste operation */; + SalShExtSharedMemView->PasteDone = FALSE; + + int count = 0; + while (count++ < 50) // wait no longer than 5 seconds + { + ReleaseMutex(SalShExtSharedMemMutex); + Sleep(100); // give the copy hook 100 ms to respond + WaitForSingleObject(SalShExtSharedMemMutex, INFINITE); + if (SalShExtSharedMemView->PasteDone) // copy hook supplied the target path for Paste and other data + { + // TRACE_I("WM_USER_SALSHEXT_PASTE: copy hook returned: paste done!"); + lstrcpyn(tgtPath, SalShExtSharedMemView->TargetPath, MAX_PATH); + operation = SalShExtSharedMemView->Operation; + dataID = SalShExtSharedMemView->PastedDataID; + tmpPasteDone = TRUE; + break; + } + } + SalamanderBusy = FALSE; + } + } + ReleaseMutex(SalShExtSharedMemMutex); + + if (tmpPasteDone && operation == SALSHEXT_COPY && SalShExtPastedData.GetDataID() == dataID) // perform the Paste operation + { + SalamanderBusy = TRUE; + LastSalamanderIdleTime = GetTickCount(); + // TRACE_I("WM_USER_SALSHEXT_PASTE: calling SalShExtPastedData.DoPasteOperation"); + ProgressDialogActivateDrop = LastWndFromPasteGetData; + SalShExtPastedData.DoPasteOperation(operation == SALSHEXT_COPY, tgtPath); + ProgressDialogActivateDrop = NULL; // clear global variable for next use of the progress dialog + LastWndFromPasteGetData = NULL; // reset for the next Paste operation here + SHChangeNotify(SHCNE_UPDATEDIR, SHCNF_PATH, tgtPath, NULL); + SalamanderBusy = FALSE; + } + SalShExtPastedData.SetLock(FALSE); + PostMessage(HWindow, WM_USER_SALSHEXT_TRYRELDATA, 0, 0); // after unlocking, optionally release the data + } + // TRACE_I("WM_USER_SALSHEXT_PASTE: end"); + return 0; + } + + case WM_USER_REFRESH_SHARES: + { + Shares.Refresh(); + HANDLES(EnterCriticalSection(&TimeCounterSection)); + int t1 = MyTimeCounter++; + int t2 = MyTimeCounter++; + HANDLES(LeaveCriticalSection(&TimeCounterSection)); + if (LeftPanel != NULL && LeftPanel->Is(ptDisk) && !LeftPanel->GetNetworkDrive()) + { + PostMessage(LeftPanel->HWindow, WM_USER_REFRESH_DIR, 0, t1); + } + if (RightPanel != NULL && RightPanel->Is(ptDisk) && !RightPanel->GetNetworkDrive()) + { + PostMessage(RightPanel->HWindow, WM_USER_REFRESH_DIR, 0, t1); + } + return 0; + } + + case WM_USER_END_SUSPMODE: + { + // if the main window is minimized (slow restore or opening a context menu), + // postpone panel content check ("retry" may occur when removing a disk, etc.) + if (IsIconic(HWindow)) + { + SetTimer(HWindow, IDT_POSTENDSUSPMODE, 500, NULL); + // originally instead of using a timer: PostMessage(HWindow, WM_USER_END_SUSPMODE, 0, 0); + return 0; + } + + if (--ActivateSuspMode < 0) + { + ActivateSuspMode = 0; + // TRACE_E("WM_USER_END_SUSPMODE: problem 2"); // opening a message box with a NULL parent resends WM_ACTIVATEAPP "activate" (Salamander is already active) + return 0; // the message was already cancelled + } + HCURSOR oldCur = SetCursor(LoadCursor(NULL, IDC_WAIT)); + + // first we must finish activating the window + static BOOL recursion = FALSE; + if (!recursion) + { + recursion = TRUE; + MSG msg; + CanCloseButInEndSuspendMode = CanClose; + BOOL oldCanClose = CanClose; + CanClose = FALSE; // don't let ourselves be closed; we are inside the method + BOOL postWM_USER_CLOSE_MAINWND = FALSE; + BOOL postWM_USER_FORCECLOSE_MAINWND = FALSE; + while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) + { + if (msg.message == WM_USER_CLOSE_MAINWND && msg.hwnd == HWindow) + postWM_USER_CLOSE_MAINWND = TRUE; + else + { + if (msg.message == WM_USER_FORCECLOSE_MAINWND && msg.hwnd == HWindow) + postWM_USER_FORCECLOSE_MAINWND = TRUE; + else + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + } + CanClose = oldCanClose; + CanCloseButInEndSuspendMode = FALSE; + + if (postWM_USER_CLOSE_MAINWND) + PostMessage(HWindow, WM_USER_CLOSE_MAINWND, 0, 0); + if (postWM_USER_FORCECLOSE_MAINWND) + PostMessage(HWindow, WM_USER_FORCECLOSE_MAINWND, 0, 0); + + recursion = FALSE; + } + // else + // { + //#pragma message (__FILE__ " (2120): remove") + // SalMessageBox(HWindow, "problem3", "problem3", MB_OK); // debug message + // } + + // window is activated, perform a refresh + // EndSuspendMode(); // removed, we want to refresh even when the main window is inactive + + LeftPanel->Activate(FALSE); + RightPanel->Activate(FALSE); + + // if OneDrive Personal/Business was connected or disconnected, refresh the Drive bars + // so the icon or drop down menu disappears or appears + BOOL oneDrivePersonal = OneDrivePath[0] != 0; + int oneDriveBusinessStoragesCount = OneDriveBusinessStorages.Count; + InitOneDrivePath(); + if (oneDrivePersonal != (OneDrivePath[0] != 0) || + oneDriveBusinessStoragesCount != OneDriveBusinessStorages.Count) + { + PostMessage(HWindow, WM_USER_DRIVES_CHANGE, 0, 0); + } + + SetCursor(oldCur); + return 0; + } + + case WM_TIMER: + { + switch (wParam) + { + case IDT_DELETEMNGR_PROCESS: + { + KillTimer(HWindow, IDT_DELETEMNGR_PROCESS); + DeleteManager.ProcessData(); + break; + } + + case IDT_POSTENDSUSPMODE: + { + KillTimer(HWindow, IDT_POSTENDSUSPMODE); + PostMessage(HWindow, WM_USER_END_SUSPMODE, 0, 0); // if ActivateSuspMode < 1, nothing happens + break; + } + + case IDT_ADDNEWMODULES: + { + AddNewlyLoadedModulesToGlobalModulesStore(); + break; + } + + case IDT_PLUGINFSTIMERS: + { + Plugins.HandlePluginFSTimers(); + break; + } + + case IDT_ASSOCIATIONSCHNG: + { + KillTimer(HWindow, IDT_ASSOCIATIONSCHNG); + OnAssociationsChangedNotification(FALSE); + break; + } + + default: + { + TRACE_E("Unknown WM_TIMER wParam=" << wParam); + break; + } + } + break; + } + + case WM_USER_SLGINCOMPLETE: + { + char buff[1000]; + sprintf(buff, "%s\n", LoadStr(IDS_SLGINCOMPLETE_TEXT)); + Configuration.ShowSLGIncomplete = FALSE; + CMessageBox(HWindow, MSGBOXEX_OK | MSGBOXEX_ESCAPEENABLED | MSGBOXEX_SILENT | MSGBOXEX_ICONINFORMATION, + LoadStr(IDS_SLGINCOMPLETE_TITLE), buff, NULL, + NULL, NULL, 0, NULL, NULL, IsSLGIncomplete, NULL) + .Execute(); + break; + } + + case WM_USER_USERMENUICONS_READY: + { + CUserMenuIconDataArr* bkgndReaderData = (CUserMenuIconDataArr*)wParam; + DWORD threadID = (DWORD)lParam; + if (bkgndReaderData != NULL && // "always true" + UserMenuIconBkgndReader.EnterCSIfCanUpdateUMIcons(&bkgndReaderData, threadID)) + { // if the user menu still wants these icons: + // if icons can be updated immediately, lock user menu access from Find and update them; otherwise + // postpone the update until the menu with icons closes (we cannot pull the rug from under it) or after closing + // configuration dialog: after OK the newly loaded icons would be overwritten and reloading wouldn't start, leaving icons unloaded + for (int i = 0; i < UserMenuItems->Count; i++) + UserMenuItems->At(i)->GetIconHandle(bkgndReaderData, TRUE); + UserMenuIconBkgndReader.LeaveCSAfterUMIconsUpdate(); + if (UMToolBar != NULL && UMToolBar->HWindow != NULL) // refresh the user menu toolbar + UMToolBar->CreateButtons(); + } + if (bkgndReaderData != NULL) + delete bkgndReaderData; + break; + } + + case WM_ACTIVATEAPP: + { + // TRACE_I("WM_ACTIVATEAPP: " << (wParam == TRUE ? "activate" : "deactivate")); + if (FirstActivateApp) + { + if (IsWindowVisible(HWindow)) + FirstActivateApp = FALSE; + else + break; + } + + // do the work for lost and undelivered messages + int actSusMode = (wParam == TRUE) ? 1 : 0; // ActivateSuspMode should be 1 when activating, otherwise 0 + if (ActivateSuspMode < 0) + { + ActivateSuspMode = 0; + TRACE_E("WM_USER_END_SUSPMODE: problem 6"); + } + else + { + if (ActivateSuspMode != actSusMode) // e.g. two deactivations in a row or missed activation + { + KillTimer(HWindow, IDT_POSTENDSUSPMODE); // if activation hasn't happened yet, cancel (it may start again) + + MSG msg; // pump WM_USER_END_SUSPMODE from the queue, otherwise suspend mode ends shortly (e.g. opening File Comparator triggers activation+deactivation after 10ms) + while (PeekMessage(&msg, HWindow, WM_USER_END_SUSPMODE, WM_USER_END_SUSPMODE, PM_REMOVE)) + ; + + while (ActivateSuspMode > actSusMode) + { + // EndSuspendMode(); // removed, we want to refresh even when the main window is inactive + ActivateSuspMode--; + } + } + } + + // if (IsWindowVisible(HWindow)) // now handled by FirstActivateApp + // { + if (wParam == TRUE) // activating the app + { + if (!LeftPanel->DontClearNextFocusName) + LeftPanel->NextFocusName[0] = 0; + else + LeftPanel->DontClearNextFocusName = FALSE; + if (!RightPanel->DontClearNextFocusName) + RightPanel->NextFocusName[0] = 0; + else + RightPanel->DontClearNextFocusName = FALSE; + if (Windows7AndLater && IsIconic(HWindow)) + { + SetTimer(HWindow, IDT_POSTENDSUSPMODE, 200, NULL); // hopefully we'll never find out why this timer existed; commented out because it delays directory refresh by 200 ms after operations (e.g. moving a file into a subdirectory, it is visible on a local disk) + } + else + { + // until 2.53b1 only this branch existed and the timer version was commented out + // on Windows 7 users reported activation issues when icon grouping was enabled + // and Salamander was minimized; sometimes clicking its preview (or Alt+Tab) + // would not restore Salamander, only a beep; see /viewtopic.php?f=6&t=3791 + // + // so we enable the delayed variant (200ms) again, but only on W7 and only if the window is minimized + PostMessage(HWindow, WM_USER_END_SUSPMODE, 0, 0); // if ActivateSuspMode is not >= 1, nothing happens + } + IdleRefreshStates = TRUE; // on the next Idle, force a check of status variables + IdleCheckClipboard = TRUE; // also let it check the clipboard + } + else // deactivating the app + { + // when the main window deactivates, cancel quick search and quick rename modes + CancelPanelsUI(); + + // BeginSuspendMode(); // removed, we want refresh even with inactive main window + ActivateSuspMode++; + // } + // } + // if (wParam == FALSE) // when deactivating, leave directories displayed in panels + // { // so other software can delete or disconnect them + if (CanChangeDirectory()) + { + SetCurrentDirectoryToSystem(); + } + } + break; + } + + case WM_CLOSE: + case WM_ENDSESSION: + case WM_QUERYENDSESSION: + case WM_USER_CLOSE_MAINWND: + case WM_USER_FORCECLOSE_MAINWND: + // Shutdown handling extracted to HandleShutdown() in mainwnd_shutdown.cpp. + return HandleShutdown(uMsg, wParam, lParam); + + + case WM_ERASEBKGND: + { + /* + HDC dc = (HDC)wParam; + HPEN oldPen = (HPEN)SelectObject(dc, BtnFacePen); + MoveToEx(dc, 0, 0, NULL); + LineTo(dc, 0, WindowHeight - 1); + LineTo(dc, WindowWidth - 1, WindowHeight - 1); + LineTo(dc, WindowWidth - 1, 0); + SelectObject(dc, oldPen); +*/ + return TRUE; + } + + case WM_PAINT: + { + PAINTSTRUCT ps; + + HDC dc = HANDLES(BeginPaint(HWindow, &ps)); + HPEN oldPen = (HPEN)SelectObject(dc, BtnShadowPen); + + RECT r; + if (TopToolBar->HWindow != NULL) + { + MoveToEx(dc, 0, 0, NULL); + LineTo(dc, WindowWidth + 1, 0); + SelectObject(dc, BtnHilightPen); + MoveToEx(dc, 0, 1, NULL); + LineTo(dc, WindowWidth + 1, 1); + } + + if (PanelsHeight > 0) + { + r.left = SplitPositionPix; + r.top = TopRebarHeight; + r.right = SplitPositionPix + MainWindow->GetSplitBarWidth(); + // SelectObject(dc, shadowPen); + // MoveToEx(dc, r.left, r.top, NULL); + // LineTo(dc, r.right, r.top); + // SelectObject(dc, lightPen); + // MoveToEx(dc, r.left, r.top + 1, NULL); + // LineTo(dc, r.right, r.top + 1); + r.bottom = r.top + PanelsHeight; + FillRect(dc, &r, HDialogBrush); + + SelectObject(dc, BtnFacePen); + MoveToEx(dc, 0, 0, NULL); + LineTo(dc, 0, WindowHeight - 1); + LineTo(dc, WindowWidth - 1, WindowHeight - 1); + LineTo(dc, WindowWidth - 1, 0); + } + + if (EditWindow->HWindow != NULL) + { + r.left = 0; + r.top = TopRebarHeight + PanelsHeight; + r.right = WindowWidth; + r.bottom = r.top + 2; + FillRect(dc, &r, HDialogBrush); + } + + if (BottomToolBar->HWindow != NULL) + { + r.left = 0; + r.top = TopRebarHeight + PanelsHeight + EditHeight; + r.right = WindowWidth; + r.bottom = r.top + 2; + FillRect(dc, &r, HDialogBrush); + } + + SelectObject(dc, oldPen); + HANDLES(EndPaint(HWindow, &ps)); + return 0; + } + + case WM_DESTROY: + { + if (!CanDestroyMainWindow) + { + // some crazy shell extension has just called DestroyWindow on Salamander's main window + + MSG msg; // flush the message queue (WMP9 buffered Enter and dismissed our OK) + // while (PeekMessage(&msg, HWindow, 0, 0, PM_REMOVE)); // Petr: I replaced it by discarding key messages only; without TranslateMessage and DispatchMessage we risk an endless loop (discovered during unloading Automation with memory leaks; before showing the leak message box, an infinite loop occurred because WM_PAINT kept being added to the queue and we kept discarding it) + while (PeekMessage(&msg, NULL, WM_KEYFIRST, WM_KEYLAST, PM_REMOVE)) + ; + + // ask the user to send us a break report + SalMessageBox(HWindow, LoadStr(IDS_SHELLEXTBREAK), SALAMANDER_TEXT_VERSION, + MB_OK | MB_ICONSTOP); + + // and break here + strcpy(BugReportReasonBreak, "Some faulty shell extension destroyed our main window."); + TaskList.FireEvent(TASKLIST_TODO_BREAK, GetCurrentProcessId()); + // freeze this thread + // MainWindow no longer exists anyway; we would crash at the next opportunity + while (1) + Sleep(1000); + } + + // notify the task list that we are exiting + TaskList.SetProcessState(PROCESS_STATE_ENDING, NULL); + + UserMenuIconBkgndReader.EndProcessing(); + + SHChangeNotifyRelease(); // we no longer accept Shell Notifications + KillTimer(HWindow, IDT_ADDNEWMODULES); + HANDLES(RevokeDragDrop(HWindow)); + if (Configuration.StatusArea) + RemoveTrayIcon(); + //--- destroy child windows + if (EditWindow != NULL) + { + if (EditWindow->HWindow != NULL) + DestroyWindow(EditWindow->HWindow); + delete EditWindow; + EditWindow = NULL; + } + if (TopToolBar != NULL) + { + if (TopToolBar->HWindow != NULL) + DestroyWindow(TopToolBar->HWindow); + delete TopToolBar; + TopToolBar = NULL; + } + if (PluginsBar != NULL) + { + if (PluginsBar->HWindow != NULL) + DestroyWindow(PluginsBar->HWindow); + delete PluginsBar; + PluginsBar = NULL; + } + if (MiddleToolBar != NULL) + { + if (MiddleToolBar->HWindow != NULL) + DestroyWindow(MiddleToolBar->HWindow); + delete MiddleToolBar; + MiddleToolBar = NULL; + } + if (UMToolBar != NULL) + { + if (UMToolBar->HWindow != NULL) + DestroyWindow(UMToolBar->HWindow); + delete UMToolBar; + UMToolBar = NULL; + } + if (HPToolBar != NULL) + { + if (HPToolBar->HWindow != NULL) + DestroyWindow(HPToolBar->HWindow); + delete HPToolBar; + HPToolBar = NULL; + } + if (DriveBar != NULL) + { + if (DriveBar->HWindow != NULL) + DestroyWindow(DriveBar->HWindow); + delete DriveBar; + DriveBar = NULL; + } + if (DriveBar2 != NULL) + { + if (DriveBar2->HWindow != NULL) + DestroyWindow(DriveBar2->HWindow); + delete DriveBar2; + DriveBar2 = NULL; + } + if (BottomToolBar != NULL) + { + if (BottomToolBar->HWindow != NULL) + DestroyWindow(BottomToolBar->HWindow); + delete BottomToolBar; + BottomToolBar = NULL; + } + if (MenuBar != NULL) + { + if (MenuBar->HWindow != NULL) + DestroyWindow(MenuBar->HWindow); + delete MenuBar; + MenuBar = NULL; + } + SetMessagesParent(NULL); + PostQuitMessage(0); + break; + } + + case WM_USER_ICON_NOTIFY: + { + UINT uID = (UINT)wParam; + if (uID != TASKBAR_ICON_ID) + break; + UINT uMouseMsg = (UINT)lParam; + if (uMouseMsg == WM_LBUTTONDOWN) + { + if (!IsWindowVisible(HWindow)) + { + ShowWindow(HWindow, SW_SHOW); + if (IsIconic(HWindow)) + ShowWindow(HWindow, SW_RESTORE); + } + else + { + SetForegroundWindow(GetLastActivePopup(HWindow)); + } + } + if (uMouseMsg == WM_LBUTTONDBLCLK) + { + if (GetActiveWindow() == HWindow) + { + ShowWindow(HWindow, SW_MINIMIZE); + ShowWindow(HWindow, SW_HIDE); + } + } + if (uMouseMsg == WM_RBUTTONDOWN) + { + /* used by the export_mnu.py script which generates salmenu.mnu for the Translator; + keep synchronized with the InsertMenu() call below... +MENU_TEMPLATE_ITEM TaskBarIconMenu[] = +{ + {MNTT_PB, 0 + {MNTT_IT, IDS_CONTEXTMENU_EXIT + {MNTT_PE, 0 +}; +*/ + HMENU hMenu = CreatePopupMenu(); + InsertMenu(hMenu, 0, MF_BYPOSITION | MF_STRING, CM_EXIT, LoadStr(IDS_CONTEXTMENU_EXIT)); + + POINT p; + GetCursorPos(&p); + + DWORD cmd = TrackPopupMenu(hMenu, TPM_RETURNCMD | TPM_LEFTBUTTON | TPM_RIGHTBUTTON, + p.x, p.y, 0, HWindow, NULL); + DestroyMenu(hMenu); + if (cmd != 0) + PostMessage(HWindow, WM_COMMAND, CM_EXIT, 0); + } + break; + } + +#if (_MSC_VER < 1700) + // handle messages sent from the file manager extension + case FM_GETDRIVEINFOW: + { + TRACE_E("FM_GETDRIVEINFOW not implemented"); + break; + } + + case FM_GETFILESELW: + { + TRACE_E("FM_GETFILESELW not implemented"); + break; + } + + case FM_GETFILESELLFNW: + { + if (!GetActivePanel()->Is(ptDisk)) + return 0; // we operate only on the disk + + int index = (int)wParam; + FMS_GETFILESELW* fs = (FMS_GETFILESELW*)lParam; + CFilesWindow* activePanel = GetActivePanel(); + + int count = activePanel->GetSelCount(); + if (count != 0) + { + // determine the index of the nth (index) selected item + int totalCount = activePanel->Dirs->Count + activePanel->Files->Count; + if (totalCount == 0 || index >= totalCount) + return 0; + int selectedCount = 0; + int i; + for (i = 0; i < totalCount; i++) + { + CFileData* f = (i < activePanel->Dirs->Count) ? &activePanel->Dirs->At(i) : &activePanel->Files->At(i - activePanel->Dirs->Count); + if (f->Selected == 1) + { + if (index == selectedCount) + { + index = i; + break; + } + selectedCount++; + } + } + } + else + { + index = GetActivePanel()->GetCaretIndex(); + } + + CFileData* f; + f = (index < GetActivePanel()->Dirs->Count) ? &GetActivePanel()->Dirs->At(index) : &GetActivePanel()->Files->At(index - GetActivePanel()->Dirs->Count); + + char buff[MAX_PATH]; + strcpy(buff, GetActivePanel()->GetPath()); + if (buff[strlen(buff) - 1] != '\\') + strcat(buff, "\\"); + strcat(buff, f->Name); + MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, buff, -1, fs->szName, sizeof(fs->szName) / 2); + fs->szName[sizeof(fs->szName) / 2 - 1] = 0; + fs->ftTime = f->LastWrite; + fs->dwSize = f->Size.LoDWord; + fs->bAttr = (BYTE)f->Attr; + return 0; + } + + case FM_GETFOCUS: + { + return FMFOCUS_DIR; + } + + case FM_GETSELCOUNT: + { + TRACE_E("FM_GETSELCOUNT not implemented"); + return 0; + } + + case FM_GETSELCOUNTLFN: + { + if (!GetActivePanel()->Is(ptDisk)) + return 0; // we operate only on the disk + + CFilesWindow* activePanel = GetActivePanel(); + + if (activePanel->Dirs->Count + activePanel->Files->Count == 0) + return 0; + int count = GetActivePanel()->GetSelCount(); + if (count == 0) + { + int index = GetActivePanel()->GetCaretIndex(); + if (index == 0 && GetActivePanel()->Dirs->Count > 0 && + strcmp(GetActivePanel()->Dirs->At(0).Name, "..") == 0) + count = 0; + else + count = 1; + } + return count; + } + + case FM_REFRESH_WINDOWS: + { + CFilesWindow* panel = GetActivePanel(); + if (panel != NULL && panel->Is(ptDisk)) + { + //--- refresh directories that are not automatically refreshed + // a change in the directory shown in the panel and preferably its subdirectories (who knows what the system does) + PostChangeOnPathNotification(panel->GetPath(), TRUE); + } + break; + } + + case FM_RELOAD_EXTENSIONS: + { + break; + } +#endif // _MSC_VER < 1700 + + default: + { + if (uMsg == TaskbarRestartMsg && Configuration.StatusArea) + AddTrayIcon(); + if (TaskbarBtnCreatedMsg != 0 && uMsg == TaskbarBtnCreatedMsg) + TaskBarList3.Init(HWindow); + break; + } + } + return CWindow::WindowProc(uMsg, wParam, lParam); +} diff --git a/src/mainwnd5.cpp b/src/mainwnd_panels.cpp similarity index 100% rename from src/mainwnd5.cpp rename to src/mainwnd_panels.cpp diff --git a/src/mainwnd_shutdown.cpp b/src/mainwnd_shutdown.cpp new file mode 100644 index 00000000..95f3018c --- /dev/null +++ b/src/mainwnd_shutdown.cpp @@ -0,0 +1,834 @@ +// SPDX-FileCopyrightText: 2023 Taskscape Ltd +// SPDX-License-Identifier: GPL-2.0-or-later +// CommentsTranslationProject: TRANSLATED + +// Shutdown and close handlers: WM_CLOSE, WM_ENDSESSION, WM_QUERYENDSESSION, +// WM_USER_CLOSE_MAINWND, WM_USER_FORCECLOSE_MAINWND + +#include "precomp.h" + +#include +#undef PathIsPrefix // otherwise conflicts with CSalamanderGeneral::PathIsPrefix + +#include "htmlhelp.h" +#include "stswnd.h" +#include "editwnd.h" +#include "usermenu.h" +#include "execute.h" +#include "plugins.h" +#include "fileswnd.h" +#include "toolbar.h" +#include "mainwnd.h" +#include "cfgdlg.h" +#include "dialogs.h" +#include "execlog.h" +#include "snooper.h" +#include "shellib.h" +#include "menu.h" +#include "pack.h" +#include "filesbox.h" +#include "drivelst.h" +#include "cache.h" +#include "gui.h" +#include +#include "zip.h" +#include "tasklist.h" +#include "jumplist.h" +extern "C" +{ +#include "shexreg.h" +} +#include "salshlib.h" +#include "worker.h" +#include "find.h" +#include "viewer.h" + +// Maximum time allowed in WM_QUERYENDSESSION before Windows kills the process. +#define QUERYENDSESSION_TIMEOUT 4500 + +// Globals defined in mainwnd_messages.cpp, updated during shutdown save sequence. +extern CWaitWindow* GlobalSaveWaitWindow; +extern int GlobalSaveWaitWindowProgress; + +//**************************************************************************** +// +// Vista+: ShutdownBlockReason helpers (used only during shutdown) +// +BOOL MyShutdownBlockReasonCreate(HWND hWnd, LPCWSTR pwszReason) +{ + typedef BOOL(WINAPI * FT_ShutdownBlockReasonCreate)(HWND hWnd, LPCWSTR pwszReason); + static FT_ShutdownBlockReasonCreate shutdownBlockReasonCreate = NULL; + if (shutdownBlockReasonCreate == NULL && User32DLL != NULL && WindowsVistaAndLater) + { + shutdownBlockReasonCreate = (FT_ShutdownBlockReasonCreate)GetProcAddress(User32DLL, + "ShutdownBlockReasonCreate"); // Min: Vista + } + if (shutdownBlockReasonCreate != NULL) + return shutdownBlockReasonCreate(hWnd, pwszReason); + SetLastError(ERROR_CALL_NOT_IMPLEMENTED); + return FALSE; +} + +BOOL MyShutdownBlockReasonDestroy(HWND hWnd) +{ + typedef BOOL(WINAPI * FT_ShutdownBlockReasonDestroy)(HWND hWnd); + static FT_ShutdownBlockReasonDestroy shutdownBlockReasonDestroy = NULL; + if (shutdownBlockReasonDestroy == NULL && User32DLL != NULL && WindowsVistaAndLater) + { + shutdownBlockReasonDestroy = (FT_ShutdownBlockReasonDestroy)GetProcAddress(User32DLL, + "ShutdownBlockReasonDestroy"); // Min: Vista + } + if (shutdownBlockReasonDestroy != NULL) + return shutdownBlockReasonDestroy(hWnd); + SetLastError(ERROR_CALL_NOT_IMPLEMENTED); + return FALSE; +} + +//**************************************************************************** +// +// CMainWindow::HandleShutdown +// +// Handles WM_CLOSE, WM_ENDSESSION (with deliberate fall-through to the shared +// WM_QUERYENDSESSION / WM_USER_CLOSE_MAINWND / WM_USER_FORCECLOSE_MAINWND case), +// and those three combined cases. +// + +LRESULT CMainWindow::HandleShutdown(UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) + { + case WM_CLOSE: + { + PostMessage(HWindow, WM_USER_CLOSE_MAINWND, 0, 0); + return 0; + } + + case WM_ENDSESSION: + { + if (!wParam) + return 0; // no shutdown or log off requested, nothing to handle + + // normal shutdown/log off should not come here at all; it is handled when + // WM_QUERYENDSESSION arrives, at its end the main window closes and Salamander + // is killed. Theoretically, TRUE should be returned to call WM_ENDSESSION so this + // could arrive, everything is already done, just return 0 according to MSDN, but so far all Windows versions prefer killing the app. + // + // here we handle so-called "critical shutdown" (including log off) which + // has the ENDSESSION_CRITICAL flag in lParam; it can be triggered using calling (EWX_FORCE is crucial): + // ExitWindowsEx(EWX_LOGOFF | EWX_FORCE, SHTDN_REASON_MAJOR_OPERATINGSYSTEM | + // SHTDN_REASON_MINOR_UPGRADE | SHTDN_REASON_FLAG_PLANNED); + // the code is here (search for SE_SHUTDOWN_NAME): https://msdn.microsoft.com/en-us/library/windows/desktop/aa376871%28v=vs.85%29.aspx + // + // Vista+ only: we also handle shutdown with EWX_FORCEIFHUNG flag here; it doesn't have + // the ENDSESSION_CRITICAL flag set but forces the application to exit regardless of the return value + // WM_QUERYENDSESSION is followed by WM_ENDSESSION and after it completes the app is killed + // (unless the user interrupts the action with Cancel from the system dialog shown after 5s): + // - I call this mode "forced shutdown" + // - the system won't kill our process, no timeout is running, we won't forcibly terminate anything + // - we must notify the user if they want to interrupt the shutdown; if they cancel, after processing this WM_ENDSESSION, the software will continue running normally + // + // during a "critical shutdown" (including log off): + // - on W2K the system kills our process without warning, nothing to handle + // - on XP it's annoying that WM_QUERYENDSESSION doesn't reveal it's "critical shutdown", + // so unless something stops it we'll start saving the configuration and if we don't finish within 5s + // Windows kills the process and the configuration is lost; theoretically, making a copy each during every shutdown would solve it, + // but XP rarely loses configuration, + // on XP, regardless of WM_QUERYENDSESSION's return value (even if no response comes within 5s, + // e.g. a prompt asking to cancel ongoing disk operations), WM_ENDSESSION is still sent, + // so we don't save configuration in WM_ENDSESSION, only perform the worst-case cleanup + // (stop ongoing disk operations) + // - on Vista+ we first back up the configuration registry key in WM_QUERYENDSESSION (5s limit), + // then return TRUE to continue shutdown, and the system gives us another 5s to finish in WM_ENDSESSION, which we dedicate to saving the configuration, + // it might not finish in time, then we get killed and the configuration is left broken; + // on the next start we delete it and copy the last configuration from the backup created in WM_QUERYENDSESSION; + // on Vista+ when closing without saving configuration, we first wait 5s in WM_QUERYENDSESSION + // for disk operations to finish and then another 5s here in WM_ENDSESSION + + // Experimentally determined behavior during three types of shutdowns: + // + // EWX_FORCE: (since Vista the ENDSESSION_CRITICAL flag is set) + // W2K: kill without anything + // XP: WM_QUERYENDSESSION, without a reply: WM_ENDSESSION arrives after 5s and after another 5s a kill + // WM_QUERYENDSESSION returns TRUE/FALSE: WM_ENDSESSION follows, kill after 5s + // Win7-10: WM_QUERYENDSESSION, if there is no response: in 5s -> kill + // +Vista WM_QUERYENDSESSION returns TRUE/FALSE: WM_ENDSESSION will follow, then a kill after 5s + // + // EWX_FORCEIFHUNG: (when the ENDSESSION_CRITICAL flag is not set) + // W2K: behaves like Log Off from the Start menu + // XP: same as W2K: Log Off from the Start menu + // Win7-10: WM_QUERYENDSESSION, if there is no response in 5s: a black screen and Kill/Cancel (Cancel aborts the shutdown) + // +Vista WARNING: if the main window is not disabled (no parent message box or wait window) it kills after 5s, + // WM_QUERYENDSESSION returns TRUE/FALSE: WM_ENDSESSION follows, + // after 5s a black screen Kill/Cancel (Cancel aborts the shutdown) + // + // Log Off from the Start menu: + // W2K: WM_QUERYENDSESSION, after 5s a message box Kill/Cancel (Cancel aborts the shutdown), + // WM_QUERYENDSESSION returns TRUE -> WM_ENDSESSION arrives, after 5s a Kill/Cancel message box (Cancel acts like Kill) + // WM_QUERYENDSESSION returns FALSE - aborts shutdown + // XP: same as W2K + // Win7-10: WM_QUERYENDSESSION, if there is no response: after 5s a black screen with Kill/Cancel (Cancel aborts the shutdown), + // +Vista WM_QUERYENDSESSION returns TRUE: WM_ENDSESSION arrives, + // after 5s a black screen with Kill/Cancel (Cancel aborts the shutdown) + // WM_QUERYENDSESSION returns FALSE: immediately shows a black screen with Kill/Cancel (Cancel aborts the shutdown) + + // see above for "forced shutdown" description; give the user a chance to stop the shutdown manually, + // if they refuse, they can at least cancel running disk operations and will only lose configuration saving + if (!SaveCfgInEndSession && !WaitInEndSession && WindowsVistaAndLater && + (lParam & ENDSESSION_CRITICAL) == 0) + { + if (ProgressDlgArray.RemoveFinishedDlgs() > 0) + { + WCHAR blockReason[MAX_STR_BLOCKREASON]; + if (HLanguage != NULL && + LoadStringW(HLanguage, IDS_BLOCKSHUTDOWNDISKOPER, blockReason, _countof(blockReason))) + { + MyShutdownBlockReasonCreate(HWindow, blockReason); + } + + if (SalMessageBox(HWindow, LoadStr(IDS_FORCEDSHUTDOWNDISKOPER), + SALAMANDER_TEXT_VERSION, MB_YESNO | MB_ICONQUESTION) == IDYES) + { + ProgressDlgArray.PostCancelToAllDlgs(); // dialogs and workers run in their own threads, they may exit + while (ProgressDlgArray.RemoveFinishedDlgs() > 0) + Sleep(200); // wait until disk operations cancel + } + + MyShutdownBlockReasonDestroy(HWindow); + } + else + SalMessageBox(HWindow, LoadStr(IDS_FORCEDSHUTDOWN), SALAMANDER_TEXT_VERSION, MB_OK | MB_ICONINFORMATION); + // unfortunately there's no way to tell whether shutdown is still running or the user + // has cancelled it (black full screen window on Win7). If not, the OS kills the app; we + // already warned the user, nothing more to do. + // Disk operations may still be running; if they don't get canceled, files remain in an + // incomplete state, e.g. during copying the full file size is allocated but the content is not + // copied, just filled with zeros, and the configuration won't be saved. + return 0; + } + + if (!SaveCfgInEndSession) // configuration should not be saved (handled later) + { // WaitInEndSession or XP "critical shutdown": wait for disk operations to complete (if any are running) + if (!WindowsVistaAndLater && ProgressDlgArray.RemoveFinishedDlgs() > 0) + ProgressDlgArray.PostCancelToAllDlgs(); // dialogs and workers run in their own threads; there is a chance that they might exit + + while (ProgressDlgArray.RemoveFinishedDlgs() > 0) + Sleep(200); + return 0; // let the software close + } + + if ((lParam & ENDSESSION_CRITICAL) == 0) // theoretically cannot happen (SaveCfgInEndSession is always TRUE) + { + TRACE_E("WM_ENDSESSION: unexpected SaveCfgInEndSession: it is not ENDSESSION_CRITICAL!"); + return 0; + } + + // break; // this break is not missing! only "critical shutdown" (including log off) continues below to save configuration + } + case WM_QUERYENDSESSION: + case WM_USER_CLOSE_MAINWND: + case WM_USER_FORCECLOSE_MAINWND: + { + CALL_STACK_MESSAGE1("WM_USER_CLOSE_MAINWND::1"); + + DWORD msgArrivalTime = GetTickCount(); // critical shutdown lasts 5s + 5s; if exceeded, we are killed, so we measure the time + + if (uMsg == WM_QUERYENDSESSION) + { + TRACE_I("WM_QUERYENDSESSION: message received"); + SaveCfgInEndSession = FALSE; + WaitInEndSession = FALSE; + + if ((lParam & ENDSESSION_CRITICAL) != 0) + { + // precaution against WM_ENDSESSION being triggered from the code handling IdleCheckClipboard + // when OnEnterIdle() and CannotCloseSalMainWnd were TRUE (WM_ENDSESSION would refuse to run) + // the program is about to terminate; handling IDLE makes no sense here, and it only causes delays + DisableIdleProcessing = TRUE; + } + } + + // Windows XP: during critical shutdown they don't set ENDSESSION_CRITICAL, W2K: during critical + // shutdown they don't even send WM_QUERYENDSESSION, see above at WM_ENDSESSION + + // Vista+: endAfterCleanup: TRUE = critical shutdown (killed within 5s) cannot be refused, the app + // will 100% terminate, so perform at least the worst-case cleanup: cancel ongoing disk operations + // (stopping searches and closing Find windows and viewers is pointless, just read) + BOOL endAfterCleanup = FALSE; + + if (!CanClose) + { + if (CanCloseButInEndSuspendMode && + (uMsg == WM_QUERYENDSESSION || uMsg == WM_ENDSESSION)) + { // CanClose is FALSE only because of window activation; it doesn't prevent shutdown + } + else // "startup not completed" or "window close postponed until activation", exit now + { + if (uMsg == WM_QUERYENDSESSION) + TRACE_I("WM_QUERYENDSESSION: cancelling shutdown: CanClose is FALSE"); + if (uMsg == WM_QUERYENDSESSION && (lParam & ENDSESSION_CRITICAL) != 0) + endAfterCleanup = TRUE; // cannot be refused -> perform minimal cleanup + else + return 0; // refuse close/shutdown/logoff; a forced shutdown will be detected in WM_ENDSESSION + } + } + + if (!endAfterCleanup && CannotCloseSalMainWnd) + { + TRACE_E("WM_USER_CLOSE_MAINWND: CannotCloseSalMainWnd == TRUE!"); + if (uMsg == WM_QUERYENDSESSION) + TRACE_I("WM_QUERYENDSESSION: cancelling shutdown: CannotCloseSalMainWnd is TRUE"); + if (uMsg == WM_QUERYENDSESSION && (lParam & ENDSESSION_CRITICAL) != 0) + endAfterCleanup = TRUE; // cannot be refused -> perform minimal cleanup + else + return 0; // refuse close/shutdown/logoff; a forced shutdown will be detected in WM_ENDSESSION + } + + if (!endAfterCleanup && uMsg != WM_ENDSESSION) + { // with WM_ENDSESSION the busy state was set in WM_QUERYENDSESSION, skip the test + if (!SalamanderBusy) + { + SalamanderBusy = TRUE; // already BUSY, continue processing WM_USER_CLOSE_MAINWND + LastSalamanderIdleTime = GetTickCount(); + } + else + { + if (uMsg == WM_QUERYENDSESSION && (lParam & ENDSESSION_CRITICAL) != 0) + endAfterCleanup = TRUE; // cannot be refused -> perform minimal cleanup + else + { + if (LockedUIReason != NULL && HasLockedUI()) + SalMessageBox(HWindow, LockedUIReason, SALAMANDER_TEXT_VERSION, MB_OK | MB_ICONINFORMATION); + else + TRACE_E("WM_USER_CLOSE_MAINWND: SalamanderBusy == TRUE!"); + if (uMsg == WM_QUERYENDSESSION) + TRACE_I("WM_QUERYENDSESSION: cancelling shutdown: SalamanderBusy is TRUE"); + return 0; // refuse close/shutdown/logoff; a forced shutdown will be detected in WM_ENDSESSION + } + } + } + + if (!endAfterCleanup && AlreadyInPlugin > 0) + { + TRACE_E("WM_USER_CLOSE_MAINWND: AlreadyInPlugin > 0!"); + if (uMsg == WM_QUERYENDSESSION) + TRACE_I("WM_QUERYENDSESSION: cancelling shutdown: AlreadyInPlugin > 0"); + if (uMsg == WM_QUERYENDSESSION && (lParam & ENDSESSION_CRITICAL) != 0) + endAfterCleanup = TRUE; // cannot be refused -> perform minimal cleanup + else // cannot unload the plugin while we are in it! + return 0; // refuse close/shutdown/logoff; a forced shutdown will be detected in WM_ENDSESSION + } + + // if OnClose confirmation is enabled, ask the user to confirm closing the program + if (uMsg == WM_USER_CLOSE_MAINWND && Configuration.CnfrmOnSalClose) + { + MSGBOXEX_PARAMS params; + memset(¶ms, 0, sizeof(params)); + params.HParent = HWindow; + params.Flags = MSGBOXEX_YESNO | MSGBOXEX_ESCAPEENABLED | MSGBOXEX_ICONQUESTION | MSGBOXEX_SILENT | MSGBOXEX_HINT; + params.Caption = LoadStr(IDS_QUESTION); + params.Text = LoadStr(IDS_CANCLOSESALAMANDER); + params.CheckBoxText = LoadStr(IDS_DONTSHOWAGAINCS); + BOOL dontShow = !Configuration.CnfrmOnSalClose; + params.CheckBoxValue = &dontShow; + int ret = SalMessageBoxEx(¶ms); + Configuration.CnfrmOnSalClose = !dontShow; + + if (ret != IDYES) + return 0; + } + + // we have some dialogs with disk operations running + WCHAR blockReason[MAX_STR_BLOCKREASON]; + if (ProgressDlgArray.RemoveFinishedDlgs() > 0) + { + if ((uMsg == WM_QUERYENDSESSION || uMsg == WM_ENDSESSION) && (lParam & ENDSESSION_CRITICAL) != 0) + { // "critical shutdown" (including log off) = no time to discuss, cancel everything so + // no "unfinished" mess remains on disk + if (uMsg == WM_QUERYENDSESSION) // cancel only upon the first critical shutdown message + ProgressDlgArray.PostCancelToAllDlgs(); // dialogs and workers run in their own threads, there is a chance that they may exit + } + else // report it in a window and wait for everything to finish; WM_ENDSESSION cannot arrive here + { + if (uMsg == WM_QUERYENDSESSION && HLanguage != NULL && + LoadStringW(HLanguage, IDS_BLOCKSHUTDOWNDISKOPER, blockReason, _countof(blockReason))) + { + MyShutdownBlockReasonCreate(HWindow, blockReason); + } + CExitingOpenSal dlg(HWindow); + INT_PTR res = dlg.Execute(); + if (uMsg == WM_QUERYENDSESSION) + MyShutdownBlockReasonDestroy(HWindow); + if (res == IDCANCEL) + { + if (uMsg == WM_QUERYENDSESSION) + TRACE_I("WM_QUERYENDSESSION: cancelling shutdown: user rejects to close all disk operation progress dialogs"); + // the user does not want to exit yet + return 0; // refuse closing/shutdown/logoff; any "forced shutdown" will be detected later in WM_ENDSESSION + } + UpdateWindow(HWindow); + } + } + + // critical shutdown cannot be refused; alternative solution: don't save the configuration, do only + // the bare minimum cleanup and then terminate the app (the system may kill us sooner, current mode: kill within 5s), + // for simplicity we do not proceed with closing Find and viewer windows, the first is unnecessary, + // the second would be nice (temporary files in TEMP would vanish) + if (endAfterCleanup) + { // always true: uMsg == WM_QUERYENDSESSION && (lParam & ENDSESSION_CRITICAL) != 0 + // wait up to five seconds from receiving WM_QUERYENDSESSION for disk operations to finish + while (ProgressDlgArray.RemoveFinishedDlgs() > 0 && + GetTickCount() - msgArrivalTime <= QUERYENDSESSION_TIMEOUT - 200) + Sleep(200); + WaitInEndSession = TRUE; + return TRUE; // continue to WM_ENDSESSION where we will either finish or be killed while waiting + } + + int i = 0; + TDirectArray destroyArray(10, 5); // array of windows to destroy + if (uMsg != WM_ENDSESSION) + { + BeginStopRefresh(); // we no longer want any panel refreshes + + // gather all Find windows + FindDialogQueue.AddToArray(destroyArray); + } + + CALL_STACK_MESSAGE1("WM_USER_CLOSE_MAINWND::2"); + + if (uMsg != WM_ENDSESSION) + { + HCURSOR hOldCursor = NULL; + CWaitWindow closingFindWin(HWindow, IDS_CLOSINGFINDWINDOWS, FALSE, ooStatic); + BOOL showCloseFindWin = destroyArray.Count > 0; + if (showCloseFindWin) + { + if (uMsg == WM_QUERYENDSESSION && HLanguage != NULL && + LoadStringW(HLanguage, IDS_BLOCKSHUTDOWNFINDFILES, blockReason, _countof(blockReason))) + { + MyShutdownBlockReasonCreate(HWindow, blockReason); + } + + hOldCursor = SetCursor(LoadCursor(NULL, IDC_WAIT)); + closingFindWin.Create(); + EnableWindow(HWindow, FALSE); + } + + // ask whether Find windows can be closed; running searches will be stopped if requested + BOOL endProcessing = FALSE; + for (i = 0; i < destroyArray.Count; i++) + { + if (IsWindow(destroyArray[i])) // if the window still exists + { + BOOL canclose = TRUE; // in case the upcoming SendMessage fails + + WindowsManager.CS.Enter(); // we do not want any changes to WindowsManager + CFindDialog* findDlg = (CFindDialog*)WindowsManager.GetWindowPtr(destroyArray[i]); + if (findDlg != NULL) // if the window still exists, we send it a close query (otherwise it is pointless) + { + BOOL myPost = findDlg->StateOfFindCloseQuery == sofcqNotUsed; + if (myPost) // if this is not nesting (maybe possible, not verified but unlikely) + { + findDlg->StateOfFindCloseQuery = sofcqSentToFind; + PostMessage(destroyArray[i], WM_USER_QUERYCLOSEFIND, 0, + uMsg == WM_QUERYENDSESSION && (lParam & ENDSESSION_CRITICAL) != 0); // during critical shutdown we don't ask, we just cancel + } + BOOL cont = TRUE; + while (cont) + { + cont = FALSE; + WindowsManager.CS.Leave(); + // pretend we are responding software by pumping messages + MSG msg; + while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + // give the Find thread some time to react + Sleep(50); + // time to check whether our query has been answered + WindowsManager.CS.Enter(); // no changes to WindowsManager allowed + findDlg = (CFindDialog*)WindowsManager.GetWindowPtr(destroyArray[i]); + if (findDlg != NULL) // handle only if the window still exists (otherwise it is pointless) + { + if (findDlg->StateOfFindCloseQuery == sofcqCanClose || + findDlg->StateOfFindCloseQuery == sofcqCannotClose) + { // decision made, we are done + if (findDlg->StateOfFindCloseQuery == sofcqCannotClose) + canclose = FALSE; + if (myPost) + findDlg->StateOfFindCloseQuery = sofcqNotUsed; + } + else + cont = TRUE; // keep waiting for a response from the Find thread + } + } + } + WindowsManager.CS.Leave(); + + if (!canclose) + { + if (uMsg == WM_QUERYENDSESSION) + { + TRACE_I("WM_QUERYENDSESSION: cancelling shutdown: unable to close all Find windows"); + MyShutdownBlockReasonDestroy(HWindow); + } + if (uMsg == WM_QUERYENDSESSION && (lParam & ENDSESSION_CRITICAL) != 0) + { + // EndStopRefresh(); // during critical shutdown we don't end stop-refresh (refreshes are sent to panels) + endAfterCleanup = TRUE; // cannot be refused -> perform minimal cleanup + } + else + { + EndStopRefresh(); + endProcessing = TRUE; + } + break; + } + } + } + + if (showCloseFindWin) + { + EnableWindow(HWindow, TRUE); + DestroyWindow(closingFindWin.HWindow); + SetCursor(hOldCursor); + } + + if (endProcessing) + return 0; // refuse close/shutdown/logoff; a forced shutdown will be detected later in WM_ENDSESSION + + // let Find windows close in their own thread + // not done during critical shutdown: closing Find windows is pointless (column widths, + // window size and a few other minor things won't be saved, but we ignore that) + // note: the !endAfterCleanup check here is unnecessary because outside critical shutdown + // endAfterCleanup is always FALSE + if (uMsg != WM_QUERYENDSESSION || (lParam & ENDSESSION_CRITICAL) == 0) // mimo criticky shutdown + { + for (i = 0; i < destroyArray.Count; i++) + { + if (IsWindow(destroyArray[i])) // if the window still exists + SendMessage(destroyArray[i], WM_USER_CLOSEFIND, 0, 0); + } + } + + if (showCloseFindWin && !endAfterCleanup && uMsg == WM_QUERYENDSESSION) + MyShutdownBlockReasonDestroy(HWindow); + + if (!endAfterCleanup) + { + // close viewer windows (they are not child windows -> WM_DESTROY is not sent automatically) + // we also do this during critical shutdown so TEMP files get cleaned up, + // which might otherwise be harmful (e.g. a viewer with a decrypted file starts + // shredding the temporary file after closing and the system kills us during shredding). + // A better approach is to shred properly after system restart, handled in DeleteTmpCopy() method; + // shredding does not happen during critical shutdown + ViewerWindowQueue.BroadcastMessage(WM_CLOSE, 0, 0); + + // add a delay before calling plugin unload - if there are Find windows or the internal viewer + // they have time to close here (they might hold Encrypt files) + int winsCount = ViewerWindowQueue.GetWindowCount() + FindDialogQueue.GetWindowCount(); + int timeOut = 3; + while (winsCount > 0 && timeOut--) + { + Sleep(100); + int c = ViewerWindowQueue.GetWindowCount() + FindDialogQueue.GetWindowCount(); + if (winsCount > c) // windows are still closing; wait at least another 300 ms + { + winsCount = c; + timeOut = 3; + } + } + } + } + + // a critical shutdown cannot be refused; workaround: skip saving the configuration, + // perform the bare minimum cleanup and then exit the software (the system may kill us earlier, current mode: kill within 5s), + if (endAfterCleanup) + { + // always true: uMsg == WM_QUERYENDSESSION && (lParam & ENDSESSION_CRITICAL) != 0 + // wait up to five seconds from WM_QUERYENDSESSION for disk operations to finish + while (ProgressDlgArray.RemoveFinishedDlgs() > 0 && + GetTickCount() - msgArrivalTime <= QUERYENDSESSION_TIMEOUT - 200) + Sleep(200); + + WaitInEndSession = TRUE; + return TRUE; // continue to WM_ENDSESSION where we finish or are killed if we wait longer + } + + if (uMsg == WM_QUERYENDSESSION && (lParam & ENDSESSION_CRITICAL) != 0) // this applies to Vista+ + { + BOOL cfgOK = FALSE; + if (SALAMANDER_ROOT_REG != NULL) + { + // ensure exclusive access to the configuration in the registry + LoadSaveToRegistryMutex.Enter(); + + HKEY salamander; + if (OpenKeyAux(NULL, HKEY_CURRENT_USER, SALAMANDER_ROOT_REG, salamander)) + { + DWORD saveInProgress; + if (!GetValueAux(NULL, salamander, SALAMANDER_SAVE_IN_PROGRESS, REG_DWORD, &saveInProgress, sizeof(DWORD))) + { // configuration is not corrupted + cfgOK = TRUE; + } + CloseKeyAux(salamander); + } + if (!cfgOK) + LoadSaveToRegistryMutex.Leave(); // done with the configuration; exit the section + // NOTE: LoadSaveToRegistryMutex.Leave() is called again in WM_ENDSESSION after saving the config (see below) + } + + BOOL backupOK = FALSE; + if (cfgOK) // old configuration seems OK; back it up in case saving the new configuration fails + { + char backup[200]; + sprintf_s(backup, "%s.backup.63A7CD13", SALAMANDER_ROOT_REG); // "63A7CD13" prevents the key name from matching a user key + SHDeleteKey(HKEY_CURRENT_USER, backup); // delete the old backup if one exists + HKEY salBackup; + if (!OpenKeyAux(NULL, HKEY_CURRENT_USER, backup, salBackup)) // check that no backup exists + { + if (CreateKeyAux(NULL, HKEY_CURRENT_USER, backup, salBackup)) // create a key for the backup + { + // I tried RegCopyTree (without KEY_ALL_ACCESS it failed) and it was as fast as SHCopyKey + if (SHCopyKey(HKEY_CURRENT_USER, SALAMANDER_ROOT_REG, salBackup, 0) == ERROR_SUCCESS) + { // creating the backup + DWORD copyIsOK = 1; + if (SetValueAux(NULL, salBackup, SALAMANDER_COPY_IS_OK, REG_DWORD, ©IsOK, sizeof(DWORD))) + backupOK = TRUE; + } + CloseKeyAux(salBackup); + } + } + else + CloseKeyAux(salBackup); + if (!backupOK) + LoadSaveToRegistryMutex.Leave(); // done with the configuration; exit the section + } + + // wait up to five seconds from WM_QUERYENDSESSION for disk operations to finish + while (ProgressDlgArray.RemoveFinishedDlgs() > 0 && + GetTickCount() - msgArrivalTime <= QUERYENDSESSION_TIMEOUT - 200) + Sleep(200); + + if (backupOK) // backup done, configuration will be saved in WM_ENDSESSION, + SaveCfgInEndSession = TRUE; // if we get killed during it, the configuration will load from the backup + else + { + // EndStopRefresh(); // during critical shutdown we don't end stop-refresh (refreshes are sent to panels) + WaitInEndSession = TRUE; // backup failed, we won't risk saving the configuration + } + return TRUE; // we want 5s in WM_ENDSESSION, so return TRUE + } + + if ((uMsg == WM_QUERYENDSESSION || uMsg == WM_ENDSESSION) && HLanguage != NULL && + LoadStringW(HLanguage, IDS_BLOCKSHUTDOWNSAVECFG, blockReason, _countof(blockReason))) + { + MyShutdownBlockReasonCreate(HWindow, blockReason); + } + + HCURSOR hOldCursor = NULL; + CWaitWindow analysing(HWindow, IDS_SAVINGCONFIGURATION, FALSE, ooStatic, TRUE); + HWND oldPluginMsgBoxParent = PluginMsgBoxParent; + BOOL shutdown = uMsg == WM_QUERYENDSESSION || uMsg == WM_ENDSESSION; + if (shutdown) // during shutdown/log-off/restart show a wait window for all Saves (including plugins) and process the message loop (so we aren't marked as "not responding" and killed early) + { + // start a thread that will handle registry work while saving the configuration; + // meanwhile this (main) thread will pump messages in the message loop + RegistryWorkerThread.StartThread(); + + hOldCursor = SetCursor(LoadCursor(NULL, IDC_WAIT)); + analysing.SetProgressMax(7 /* number from CMainWindow::SaveConfig() -- MUST stay in sync! */ + + Plugins.GetPluginSaveCount()); // minus one so they can enjoy a viewing 100% + analysing.Create(); + GlobalSaveWaitWindow = &analysing; + GlobalSaveWaitWindowProgress = 0; + EnableWindow(HWindow, FALSE); + + // SaveConfiguration of plugins will be called too -> parent must be set for their message boxes + PluginMsgBoxParent = analysing.HWindow; + } + + // declare a "critical shutdown" so all routines should respect it and terminate everything as quickly as possible + CriticalShutdown = uMsg == WM_ENDSESSION && (lParam & ENDSESSION_CRITICAL) != 0; + + // unload all plugins (paths in panels may point to fixed drives) + SetDoNotLoadAnyPlugins(TRUE); // for now due to thumbnails + if (!Plugins.UnloadAll(shutdown ? analysing.HWindow : HWindow)) + { + SetDoNotLoadAnyPlugins(FALSE); + + if (uMsg == WM_QUERYENDSESSION) + TRACE_I("WM_QUERYENDSESSION: cancelling shutdown: unable to unload all plugins"); + + EXIT_WM_USER_CLOSE_MAINWND: + if (shutdown) + { + GlobalSaveWaitWindow = NULL; + GlobalSaveWaitWindowProgress = 0; + EnableWindow(HWindow, TRUE); + PluginMsgBoxParent = oldPluginMsgBoxParent; + DestroyWindow(analysing.HWindow); + SetCursor(hOldCursor); + + // stop the thread that handled registry work during configuration saving... + RegistryWorkerThread.StopThread(); + } + if (uMsg == WM_QUERYENDSESSION || uMsg == WM_ENDSESSION) + MyShutdownBlockReasonDestroy(HWindow); + + if (uMsg != WM_ENDSESSION) // during critical shutdown we don't end stop-refresh (refreshes are sent to the panels) + { + EndStopRefresh(); + return 0; // refuse close/shutdown/logoff; any "forced shutdown" will be detected in WM_ENDSESSION + } + else + { + // wait for disk operations to finish; the drive system might kill our process before that + while (ProgressDlgArray.RemoveFinishedDlgs() > 0) + Sleep(200); + CriticalShutdown = FALSE; // just to be safe + return 0; // application exit + } + } + + // if CShellExecuteWnd windows exist, offer to abort closing or send a bug report and terminate + char reason[BUG_REPORT_REASON_MAX]; // problem reason + list of windows (multiline) + strcpy(reason, "Some faulty shell extension has locked our main window."); + if (EnumCShellExecuteWnd(shutdown ? analysing.HWindow : HWindow, + reason + (int)strlen(reason), BUG_REPORT_REASON_MAX - ((int)strlen(reason) + 1)) > 0) + { + // ask whether Salamander should continue or generate a bug report + if (CriticalShutdown || // during critical shutdown there's no point in asking anything, let the system terminate us quietly + SalMessageBox(shutdown ? analysing.HWindow : HWindow, + LoadStr(IDS_SHELLEXTBREAK3), SALAMANDER_TEXT_VERSION, + MSGBOXEX_CONTINUEABORT | MB_ICONINFORMATION) != IDABORT) + { + if (uMsg == WM_QUERYENDSESSION) + TRACE_I("WM_QUERYENDSESSION: cancelling shutdown: some faulty shell extension has locked our main window"); + goto EXIT_WM_USER_CLOSE_MAINWND; // we should continue + } + + // and break here + strcpy(BugReportReasonBreak, reason); + TaskList.FireEvent(TASKLIST_TODO_BREAK, GetCurrentProcessId()); + // freeze this thread + while (1) + Sleep(1000); + } + + CALL_STACK_MESSAGE1("WM_USER_CLOSE_MAINWND::3"); + + // ask the panels whether we can exit + if (LeftPanel != NULL && RightPanel != NULL) + { + BOOL canClose = FALSE; + BOOL detachFS1, detachFS2; + if (LeftPanel->PrepareCloseCurrentPath(shutdown ? analysing.HWindow : LeftPanel->HWindow, TRUE, FALSE, detachFS1, FSTRYCLOSE_UNLOADCLOSEFS /* unnecessary - plugins (including FS) already unloaded */)) + { + if (RightPanel->PrepareCloseCurrentPath(shutdown ? analysing.HWindow : RightPanel->HWindow, TRUE, FALSE, detachFS2, FSTRYCLOSE_UNLOADCLOSEFS /* unnecessary - plugins (including FS) already unloaded */)) + { + canClose = TRUE; // close both panels at the same time or none + if (RightPanel->UseSystemIcons || RightPanel->UseThumbnails) + RightPanel->SleepIconCacheThread(); + RightPanel->CloseCurrentPath(shutdown ? analysing.HWindow : RightPanel->HWindow, FALSE, detachFS2, FALSE, FALSE, TRUE); // close the right panel + + // protect the list box from errors caused by redraw requests (we just cut its data) + RightPanel->ListBox->SetItemsCount(0, 0, 0, TRUE); + RightPanel->SelectedCount = 0; + // If WM_USER_UPDATEPANEL is delivered, the panel contents are redrawn + // and scroll bars adjusted. The message loop may deliver it when creating + // a message box. Otherwise the panel would appear unchanged and the message + // would be removed from the queue. + PostMessage(RightPanel->HWindow, WM_USER_UPDATEPANEL, 0, 0); + } + if (canClose) + { + if (LeftPanel->UseSystemIcons || LeftPanel->UseThumbnails) + LeftPanel->SleepIconCacheThread(); + LeftPanel->CloseCurrentPath(shutdown ? analysing.HWindow : LeftPanel->HWindow, FALSE, detachFS1, FALSE, FALSE, TRUE); // close the left panel + + // Protect the list box from errors caused by redraw requests (after we just cut the data) + LeftPanel->ListBox->SetItemsCount(0, 0, 0, TRUE); + LeftPanel->SelectedCount = 0; + // If WM_USER_UPDATEPANEL is delivered, the panel contents are redrawn + // and scroll bars adjusted. The message loop may deliver it when creating + // a message box. Otherwise the panel would appear unchanged and the message + // would be removed from the queue. + PostMessage(LeftPanel->HWindow, WM_USER_UPDATEPANEL, 0, 0); + } + else + LeftPanel->CloseCurrentPath(shutdown ? analysing.HWindow : LeftPanel->HWindow, TRUE, detachFS1, FALSE, FALSE, TRUE); // cancel closing the left panel + } + if (!canClose) + { + SetDoNotLoadAnyPlugins(FALSE); + if (uMsg == WM_QUERYENDSESSION) + TRACE_I("WM_QUERYENDSESSION: cancelling shutdown: unable to close paths in panels"); + goto EXIT_WM_USER_CLOSE_MAINWND; // panels cannot be closed + } + } + + CALL_STACK_MESSAGE1("WM_USER_CLOSE_MAINWND::4"); + + // !!! WARNING: from this point (until DestroyWindow) no interruption must occur, + // if the window opens up, the user would find both panels empty (listing released). + // This is already violated during Shutdown / Log Off / Restart because we must distribute + // messages, otherwise we are considered "not responding" and the system kills us prematurely. + + if (StrICmp(Configuration.SLGName, Configuration.LoadedSLGName) != 0) // if the user changed Salamander's language + { + Plugins.ClearLastSLGNames(); // so that a new fallback language will be selected for all plugins if needed + Configuration.UseAsAltSLGInOtherPlugins = FALSE; + Configuration.AltPluginSLGName[0] = 0; + } + + if (Configuration.AutoSave) + SaveConfig(); + + if (uMsg == WM_ENDSESSION) + LoadSaveToRegistryMutex.Leave(); // pairs with Enter() called when WM_QUERYENDSESSION was received + + if (shutdown) + { + GlobalSaveWaitWindow = NULL; + GlobalSaveWaitWindowProgress = 0; + EnableWindow(HWindow, TRUE); + PluginMsgBoxParent = oldPluginMsgBoxParent; + DestroyWindow(analysing.HWindow); + SetCursor(hOldCursor); + + // stop the thread that handled registry work during configuration saving... + RegistryWorkerThread.StopThread(); + } + + CALL_STACK_MESSAGE1("WM_USER_CLOSE_MAINWND::5"); + + DiskCache.PrepareForShutdown(); // clean any empty tmp directories from disk + + // if (TipOfTheDayDialog != NULL) + // DestroyWindow(TipOfTheDayDialog->HWindow); // the dialog already saved its data (transfer happens there at runtime) + + MainWindowCS.SetClosed(); + + CanDestroyMainWindow = TRUE; // it's now safe to call DestroyWindow on MainWindow + + DestroyWindow(HWindow); + + // WM_QUERYENDSESSION and WM_ENDSESSION: all Windows versions kill the process as soon as + // the main window is destroyed during shutdown, so the following code is dead code in that case + + CriticalShutdown = FALSE; // just to be safe + + if (uMsg == WM_QUERYENDSESSION) + { + TRACE_I("WM_QUERYENDSESSION: allowing shutdown..."); + // main window already closed - nobody to deliver WM_ENDSESSION to, neither WaitInEndSession + // and SaveCfgInEndSession needs to be set + return TRUE; // if it gets this far, allow the shutdown + } + return 0; // return value for WM_USER_CLOSE_MAINWND, WM_USER_FORCECLOSE_MAINWND and WM_ENDSESSION + } + } + return 0; +} diff --git a/src/menu2.cpp b/src/menu_popup.cpp similarity index 100% rename from src/menu2.cpp rename to src/menu_popup.cpp diff --git a/src/menu3.cpp b/src/menu_shared_resources.cpp similarity index 100% rename from src/menu3.cpp rename to src/menu_shared_resources.cpp diff --git a/src/menu4.cpp b/src/menu_templates.cpp similarity index 100% rename from src/menu4.cpp rename to src/menu_templates.cpp diff --git a/src/menu1.cpp b/src/menu_window_queue.cpp similarity index 100% rename from src/menu1.cpp rename to src/menu_window_queue.cpp diff --git a/src/operations_core.cpp b/src/operations_core.cpp new file mode 100644 index 00000000..4db26810 --- /dev/null +++ b/src/operations_core.cpp @@ -0,0 +1,1606 @@ +// SPDX-FileCopyrightText: 2023 Taskscape Ltd +// SPDX-License-Identifier: GPL-2.0-or-later +// CommentsTranslationProject: TRANSLATED + +#include "precomp.h" + +#include "cfgdlg.h" +#include "worker.h" +#include "execlog.h" + +#include +#include + +// CreateFileUtf8 / DeleteFileUtf8 / SetFileAttributesUtf8 / RemoveDirectoryUtf8 +// are globally declared in common/strutils.h and defined in common/strutils.cpp. +extern NTQUERYINFORMATIONFILE DynNtQueryInformationFile; +extern NTFSCONTROLFILE DynNtFsControlFile; + +// Forward declarations of helpers defined in async_copy.cpp +void GainWriteOwnerAccess(); +DWORD CompressFile(char* fileName, DWORD attrs); +DWORD UncompressFile(char* fileName, DWORD attrs); +DWORD MyEncryptFile(HWND hProgressDlg, char* fileName, DWORD attrs, DWORD finalAttrs, + CProgressDlgData& dlgData, BOOL& cancelOper, BOOL preserveDate); +DWORD MyDecryptFile(char* fileName, DWORD attrs, BOOL preserveDate); +BOOL DoCopyFile(COperation* op, HWND hProgressDlg, void* buffer, + COperations* script, CQuadWord& totalDone, + DWORD clearReadonlyMask, BOOL* skip, BOOL lantasticCheck, + int& mustDeleteFileBeforeOverwrite, int& allocWholeFileOnStart, + CProgressDlgData& dlgData, BOOL copyADS, BOOL copyAsEncrypted, + BOOL isMove, CAsyncCopyParams*& asyncPar); + +struct TMN_REPARSE_DATA_BUFFER +{ + DWORD ReparseTag; + WORD ReparseDataLength; + WORD Reserved; + WORD SubstituteNameOffset; + WORD SubstituteNameLength; + WORD PrintNameOffset; + WORD PrintNameLength; + WCHAR PathBuffer[1]; +}; + +#define IO_REPARSE_TAG_VALID_VALUES 0xE000FFFF +#define IsReparseTagValid(x) (!((x)&~IO_REPARSE_TAG_VALID_VALUES)&&((x)>IO_REPARSE_TAG_RESERVED_RANGE)) +#define MAXIMUM_REPARSE_DATA_BUFFER_SIZE ( 16 * 1024 ) + + +/* + HANDLE srcDir = HANDLES_Q(CreateFileUtf8(name, GENERIC_READ, 0, 0, OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, NULL)); + if (srcDir != INVALID_HANDLE_VALUE) + { + HANDLE tgtDir = HANDLES_Q(CreateFileUtf8("D:\\ZUMPA\\link", GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, NULL)); + if (tgtDir != INVALID_HANDLE_VALUE) + { + char szBuff[MAXIMUM_REPARSE_DATA_BUFFER_SIZE]; + TMN_REPARSE_DATA_BUFFER& rdb = *(TMN_REPARSE_DATA_BUFFER*)szBuff; + + DWORD dwBytesReturned; + if (DeviceIoControl(srcDir, FSCTL_GET_REPARSE_POINT, NULL, 0, (LPVOID)&rdb, + MAXIMUM_REPARSE_DATA_BUFFER_SIZE, &dwBytesReturned, 0) && + IsReparseTagValid(rdb.ReparseTag)) + { + DWORD dwBytesReturnedDummy; + if (DeviceIoControl(tgtDir, FSCTL_SET_REPARSE_POINT, (LPVOID)&rdb, dwBytesReturned, + NULL, 0, &dwBytesReturnedDummy, 0)) + { + TRACE_I("eureka?"); + } + } + HANDLES(CloseHandle(tgtDir)); + } + HANDLES(CloseHandle(srcDir)); + } + return FALSE; +*/ + +BOOL DoDeleteDirLinkAux(const char* nameDelLink, DWORD* err) +{ + // remove the reparse point from directory 'nameDelLink' + if (err != NULL) + *err = ERROR_SUCCESS; + BOOL ok = FALSE; + DWORD attr = GetFileAttributesUtf8(nameDelLink); + if (attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_REPARSE_POINT)) + { + HANDLE dir = HANDLES_Q(CreateFileUtf8(nameDelLink, GENERIC_WRITE /* | GENERIC_READ */, 0, 0, OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, NULL)); + if (dir != INVALID_HANDLE_VALUE) + { + DWORD dummy; + char buf[MAXIMUM_REPARSE_DATA_BUFFER_SIZE]; + REPARSE_GUID_DATA_BUFFER* juncData = (REPARSE_GUID_DATA_BUFFER*)buf; + if (DeviceIoControl(dir, FSCTL_GET_REPARSE_POINT, NULL, 0, juncData, + MAXIMUM_REPARSE_DATA_BUFFER_SIZE, &dummy, NULL) == 0) + { + if (err != NULL) + *err = GetLastError(); + } + else + { + if (juncData->ReparseTag != IO_REPARSE_TAG_MOUNT_POINT && + juncData->ReparseTag != IO_REPARSE_TAG_SYMLINK) + { // if this is not a volume mount point, junction, or symlink, report an error (we could probably delete it, but better refuse than break something...) + TRACE_E("DoDeleteDirLinkAux(): Unknown type of reparse point (tag is 0x" << std::hex << juncData->ReparseTag << std::dec << "): " << nameDelLink); + if (err != NULL) + *err = 4394 /* ERROR_REPARSE_TAG_MISMATCH */; + } + else + { + REPARSE_GUID_DATA_BUFFER rgdb = {0}; + rgdb.ReparseTag = juncData->ReparseTag; + + DWORD dwBytes; + if (DeviceIoControl(dir, FSCTL_DELETE_REPARSE_POINT, &rgdb, REPARSE_GUID_DATA_BUFFER_HEADER_SIZE, + NULL, 0, &dwBytes, 0) != 0) + { + ok = TRUE; + } + else + { + if (err != NULL) + *err = GetLastError(); + } + } + } + HANDLES(CloseHandle(dir)); + } + else + { + if (err != NULL) + *err = GetLastError(); + } + } + else + ok = TRUE; // the reparse point is apparently gone; all that remains is to delete the empty directory... + // remove the empty directory (that remained after deleting the reparse point) + if (ok) + ClearReadOnlyAttr(nameDelLink, attr); // ensure it can be deleted even with the read-only attribute + if (ok && !RemoveDirectoryUtf8(nameDelLink)) + { + ok = FALSE; + if (err != NULL) + *err = GetLastError(); + } + return ok; +} + +BOOL DeleteDirLink(const char* name, DWORD* err) +{ + // if the path ends with a space/dot, we must append '\\'; otherwise CreateFile + // and RemoveDirectory trim the spaces/dots and operate on a different path + const char* nameDelLink = name; + char nameDelLinkCopy[3 * MAX_PATH]; + MakeCopyWithBackslashIfNeeded(nameDelLink, nameDelLinkCopy); + + return DoDeleteDirLinkAux(nameDelLink, err); +} + +BOOL DoDeleteDirLink(HWND hProgressDlg, char* name, const CQuadWord& size, COperations* script, + CQuadWord& totalDone, CProgressDlgData& dlgData) +{ + // if the path ends with a space/dot, we must append '\\'; otherwise CreateFile + // and RemoveDirectory trim the spaces/dots and operate on a different path + const char* nameDelLink = name; + char nameDelLinkCopy[3 * MAX_PATH]; + MakeCopyWithBackslashIfNeeded(nameDelLink, nameDelLinkCopy); + + while (1) + { + DWORD err; + BOOL ok = DoDeleteDirLinkAux(nameDelLink, &err); + + if (ok) + { + script->AddBytesToSpeedMetersAndTFSandPS((DWORD)size.Value, TRUE, 0, NULL, MAX_OP_FILESIZE); + + totalDone += size; + SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); + return TRUE; + } + else + { + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + if (*dlgData.CancelWorker) + return FALSE; + + if (dlgData.SkipAllDeleteErr) + goto SKIP_DELETE_LINK; + + int ret; + ret = IDCANCEL; + char* data[4]; + data[0] = (char*)&ret; + data[1] = LoadStr(IDS_ERRORDELETINGDIRLINK); + data[2] = (char*)nameDelLink; + data[3] = GetErrorText(err); + SendMessage(hProgressDlg, WM_USER_DIALOG, 0, (LPARAM)data); + switch (ret) + { + case IDRETRY: + break; + + case IDB_SKIPALL: + dlgData.SkipAllDeleteErr = TRUE; + case IDB_SKIP: + { + SKIP_DELETE_LINK: + + totalDone += size; + script->SetProgressSize(totalDone); + SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); + return TRUE; + } + + case IDCANCEL: + return FALSE; + } + } + } +} + +// a) create a temporary file in the same directory as file 'name' +// b) transfer the contents of 'name' into the temporary file while applying the +// conversions specified by convertData.CodeType and convertData.EOFType +// c) overwrite file 'name' with the temporary file +// +// convertData.EOFType - determines how line endings are replaced +// CR, LF, and CRLF are all considered end-of-line markers +// 0: leave line endings unchanged +// 1: replace line endings with CRLF (DOS, Windows, OS/2) +// 2: replace line endings with LF (UNIX) +// 3: replace line endings with CR (MAC) +BOOL DoConvert(HWND hProgressDlg, char* name, char* sourceBuffer, char* targetBuffer, + const CQuadWord& size, COperations* script, CQuadWord& totalDone, + CConvertData& convertData, CProgressDlgData& dlgData) +{ + // if the path ends with a space/dot it is invalid and we must not run the conversion, + // CreateFile would trim the spaces/dots and convert a different file + BOOL invalidName = FileNameIsInvalid(name, TRUE); + +CONVERT_AGAIN: + + CQuadWord operationDone; + operationDone = CQuadWord(0, 0); + while (1) + { + // attempt to open the source file + HANDLE hSource; + if (!invalidName) + { + hSource = HANDLES_Q(CreateFileUtf8(name, GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, + OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL)); + } + else + { + hSource = INVALID_HANDLE_VALUE; + } + if (hSource != INVALID_HANDLE_VALUE) + { + // derive the path for the temporary file + char tmpPath[3 * MAX_PATH]; + if (strlen(name) >= _countof(tmpPath)) + { + TRACE_E("DoConvert(): source path is too long for temporary path handling: " << name); + HANDLES(CloseHandle(hSource)); + return FALSE; + } + strcpy(tmpPath, name); + char* terminator = strrchr(tmpPath, '\\'); + if (terminator == NULL) + { + // sanity check + TRACE_E("Parameter 'name' must be full path to file (including path)"); + HANDLES(CloseHandle(hSource)); + return FALSE; + } + *(terminator + 1) = 0; + + // find a name for the temporary file and let the system create it + char tmpFileName[MAX_PATH]; + BOOL tmpFileExists = FALSE; + while (1) + { + if (SalGetTempFileName(tmpPath, "cnv", tmpFileName, _countof(tmpFileName), TRUE)) + { + tmpFileExists = TRUE; + + // align the temp file attributes with the source file + DWORD srcAttrs = SalGetFileAttributes(name); + DWORD tgtAttrs = SalGetFileAttributes(tmpFileName); + BOOL changeAttrs = FALSE; + if (srcAttrs != INVALID_FILE_ATTRIBUTES && tgtAttrs != INVALID_FILE_ATTRIBUTES && srcAttrs != tgtAttrs) + { + changeAttrs = TRUE; // SetFileAttributes will be called later... + // does the NTFS compression flag differ? + if ((srcAttrs & FILE_ATTRIBUTE_COMPRESSED) != (tgtAttrs & FILE_ATTRIBUTE_COMPRESSED) && + (srcAttrs & FILE_ATTRIBUTE_COMPRESSED) == 0) + { + UncompressFile(tmpFileName, tgtAttrs); + } + if ((srcAttrs & FILE_ATTRIBUTE_ENCRYPTED) != (tgtAttrs & FILE_ATTRIBUTE_ENCRYPTED)) + { + BOOL cancelOper = FALSE; + if (srcAttrs & FILE_ATTRIBUTE_ENCRYPTED) + { + MyEncryptFile(hProgressDlg, tmpFileName, tgtAttrs, 0 /* allow encrypting files with the SYSTEM attribute */, + dlgData, cancelOper, FALSE); + } + else + MyDecryptFile(tmpFileName, tgtAttrs, FALSE); + if (*dlgData.CancelWorker || cancelOper) + { + HANDLES(CloseHandle(hSource)); + ClearReadOnlyAttr(tmpFileName); // ensure it can be deleted + DeleteFileUtf8(tmpFileName); + return FALSE; + } + } + if ((srcAttrs & FILE_ATTRIBUTE_COMPRESSED) != (tgtAttrs & FILE_ATTRIBUTE_COMPRESSED) && + (srcAttrs & FILE_ATTRIBUTE_COMPRESSED) != 0) + { + CompressFile(tmpFileName, tgtAttrs); + } + } + + // open the empty temporary file + HANDLE hTarget = HANDLES_Q(CreateFileUtf8(tmpFileName, GENERIC_WRITE, 0, NULL, + OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL)); + if (hTarget != INVALID_HANDLE_VALUE) + { + DWORD read; + BOOL crlfBreak = FALSE; + while (1) + { + if (ReadFile(hSource, sourceBuffer, OPERATION_BUFFER, &read, NULL)) + { + DWORD written; + if (read == 0) + break; // EOF + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + if (*dlgData.CancelWorker) + { + CONVERT_ERROR: + + if (hSource != NULL) + HANDLES(CloseHandle(hSource)); + if (hTarget != NULL) + HANDLES(CloseHandle(hTarget)); + ClearReadOnlyAttr(tmpFileName); // ensure it can be deleted + DeleteFileUtf8(tmpFileName); + return FALSE; + } + + // translate sourceBuffer -> targetBuffer + char* sourceIterator; + char* targetIterator; + sourceIterator = sourceBuffer; + targetIterator = targetBuffer; + while (sourceIterator - sourceBuffer < (int)read) + { + // lastChar is TRUE when sourceIterator points to the final character in the buffer + BOOL lastChar = (sourceIterator - sourceBuffer == (int)read - 1); + + if (convertData.EOFType != 0) + { + if (crlfBreak && sourceIterator == sourceBuffer && *sourceIterator == '\n') + { + // we already processed this CRLF, leave the LF as is now + crlfBreak = FALSE; + } + else + { + if (*sourceIterator == '\r' || *sourceIterator == '\n') + { + switch (convertData.EOFType) + { + case 2: + *targetIterator++ = convertData.CodeTable['\n']; + break; + case 3: + *targetIterator++ = convertData.CodeTable['\r']; + break; + default: + { + *targetIterator++ = convertData.CodeTable['\r']; + *targetIterator++ = convertData.CodeTable['\n']; + break; + } + } + // capture CRLF which splits across the buffer boundary + if (lastChar && *sourceIterator == '\r') + crlfBreak = TRUE; + // capture CRLF that is contiguous � skip the LF + if (!lastChar && + *sourceIterator == '\r' && *(sourceIterator + 1) == '\n') + sourceIterator++; + } + else + { + *targetIterator = convertData.CodeTable[(unsigned char)*sourceIterator]; + targetIterator++; + } + } + } + else + { + *targetIterator = convertData.CodeTable[(unsigned char)*sourceIterator]; + targetIterator++; + } + sourceIterator++; + } + + // write the data to the temp file + while (1) + { + if (WriteFile(hTarget, targetBuffer, (DWORD)(targetIterator - targetBuffer), &written, NULL) && + targetIterator - targetBuffer == (int)written) + break; + + WRITE_ERROR_CONVERT: + + DWORD err; + err = GetLastError(); + + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + if (*dlgData.CancelWorker) + goto CONVERT_ERROR; + + if (dlgData.SkipAllFileWrite) + goto SKIP_CONVERT; + + int ret; + ret = IDCANCEL; + char* data[4]; + data[0] = (char*)&ret; + data[1] = LoadStr(IDS_ERRORWRITINGFILE); + data[2] = tmpFileName; + if (hTarget != NULL && err == NO_ERROR && targetIterator - targetBuffer != (int)written) + err = ERROR_DISK_FULL; + data[3] = GetErrorText(err); + SendMessage(hProgressDlg, WM_USER_DIALOG, 0, (LPARAM)data); + switch (ret) + { + case IDRETRY: + { + if (hSource == NULL && hTarget == NULL) + { + ClearReadOnlyAttr(tmpFileName); // ensure it can be deleted + DeleteFileUtf8(tmpFileName); + SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); + goto CONVERT_AGAIN; + } + break; + } + + case IDB_SKIPALL: + dlgData.SkipAllFileWrite = TRUE; + case IDB_SKIP: + { + SKIP_CONVERT: + + totalDone += size; + if (hSource != NULL) + HANDLES(CloseHandle(hSource)); + if (hTarget != NULL) + HANDLES(CloseHandle(hTarget)); + ClearReadOnlyAttr(tmpFileName); // ensure it can be deleted + DeleteFileUtf8(tmpFileName); + SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); + return TRUE; + } + + case IDCANCEL: + goto CONVERT_ERROR; + } + } + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + if (*dlgData.CancelWorker) + goto CONVERT_ERROR; + + operationDone += CQuadWord(read, 0); + SetProgress(hProgressDlg, + CaclProg(operationDone, size), + CaclProg(totalDone + operationDone, script->TotalSize), dlgData); + } + else + { + DWORD err = GetLastError(); + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + if (*dlgData.CancelWorker) + goto CONVERT_ERROR; + + if (dlgData.SkipAllFileRead) + goto SKIP_CONVERT; + + int ret = IDCANCEL; + char* data[4]; + data[0] = (char*)&ret; + data[1] = LoadStr(IDS_ERRORREADINGFILE); + data[2] = name; + data[3] = GetErrorText(err); + SendMessage(hProgressDlg, WM_USER_DIALOG, 0, (LPARAM)data); + switch (ret) + { + case IDRETRY: + break; + case IDB_SKIPALL: + dlgData.SkipAllFileRead = TRUE; + case IDB_SKIP: + goto SKIP_CONVERT; + case IDCANCEL: + goto CONVERT_ERROR; + } + } + } + // close the files and update the global progress + // do not reuse operationDone so the progress stays correct even if the file changes "under our feet" + HANDLES(CloseHandle(hSource)); + if (!HANDLES(CloseHandle(hTarget))) // even after a failed call we assume the handle is closed, + { // see /viewtopic.php?f=6&t=8455 + hSource = hTarget = NULL; // (it states that the target file can be deleted, so the handle was not left open) + goto WRITE_ERROR_CONVERT; + } + totalDone += size; + // restore attributes (write operations have trouble with read-only) + if (changeAttrs) + SetFileAttributesUtf8(tmpFileName, srcAttrs); + // overwrite the original file with the temp file + while (1) + { + ClearReadOnlyAttr(name); // ensure it can be deleted + if (DeleteFileUtf8(name)) + { + while (1) + { + if (SalMoveFile(tmpFileName, name)) + return TRUE; // success + else + { + DWORD err = GetLastError(); + + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + if (*dlgData.CancelWorker) + { + ClearReadOnlyAttr(tmpFileName); // ensure it can be deleted + DeleteFileUtf8(tmpFileName); + return FALSE; + } + + if (dlgData.SkipAllMoveErrors) + { + ClearReadOnlyAttr(tmpFileName); // ensure it can be deleted + DeleteFileUtf8(tmpFileName); + return TRUE; + } + + int ret = IDCANCEL; + char* data[4]; + data[0] = (char*)&ret; + data[1] = tmpFileName; + data[2] = name; + data[3] = GetErrorText(err); + SendMessage(hProgressDlg, WM_USER_DIALOG, 3, (LPARAM)data); + switch (ret) + { + case IDRETRY: + break; + + case IDB_SKIPALL: + dlgData.SkipAllMoveErrors = TRUE; + case IDB_SKIP: + ClearReadOnlyAttr(tmpFileName); // ensure it can be deleted + DeleteFileUtf8(tmpFileName); + return TRUE; + + case IDCANCEL: + ClearReadOnlyAttr(tmpFileName); // ensure it can be deleted + DeleteFileUtf8(tmpFileName); + return FALSE; + } + } + } + } + else + { + DWORD err = GetLastError(); + + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + if (*dlgData.CancelWorker) + { + CANCEL_CONVERT: + + ClearReadOnlyAttr(tmpFileName); // ensure it can be deleted + DeleteFileUtf8(tmpFileName); + return FALSE; + } + + if (dlgData.SkipAllOverwriteErr) + goto SKIP_OVERWRITE_ERROR; + + int ret; + ret = IDCANCEL; + char* data[4]; + data[0] = (char*)&ret; + data[1] = LoadStr(IDS_ERROROVERWRITINGFILE); + data[2] = name; + data[3] = GetErrorText(err); + SendMessage(hProgressDlg, WM_USER_DIALOG, 0, (LPARAM)data); + switch (ret) + { + case IDRETRY: + break; + + case IDB_SKIPALL: + dlgData.SkipAllOverwriteErr = TRUE; + case IDB_SKIP: + { + SKIP_OVERWRITE_ERROR: + + ClearReadOnlyAttr(tmpFileName); // ensure it can be deleted + DeleteFileUtf8(tmpFileName); + return TRUE; + } + + case IDCANCEL: + goto CANCEL_CONVERT; + } + } + } + } + else + goto TMP_OPEN_ERROR; + } + else + { + TMP_OPEN_ERROR: + + DWORD err = GetLastError(); + + char fakeName[3 * MAX_PATH]; // name of the temp file that cannot be created/opened + if (tmpFileExists) + { + strcpy(fakeName, tmpFileName); + ClearReadOnlyAttr(tmpFileName); // ensure it can be deleted + DeleteFileUtf8(tmpFileName); // the temp file exists, try to remove it + tmpFileExists = FALSE; + } + else + { + // assemble a fictitious temp-file name for the failed creation attempt + char* s = tmpPath + strlen(tmpPath); + if (s > tmpPath && *(s - 1) == '\\') + s--; + size_t fakeNamePrefixLen = (size_t)(s - tmpPath); + const char* fakeNameSuffix = "\\cnv0000.tmp"; + size_t fakeNameSuffixLen = strlen(fakeNameSuffix); + if (fakeNamePrefixLen + fakeNameSuffixLen < _countof(fakeName)) + { + memcpy(fakeName, tmpPath, fakeNamePrefixLen); + memcpy(fakeName + fakeNamePrefixLen, fakeNameSuffix, fakeNameSuffixLen + 1); + } + else + lstrcpyn(fakeName, tmpPath, _countof(fakeName)); + } + + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + if (*dlgData.CancelWorker) + goto CANCEL_OPEN2; + + if (dlgData.SkipAllFileOpenOut) + goto SKIP_OPEN_OUT; + + int ret; + ret = IDCANCEL; + char* data[4]; + data[0] = (char*)&ret; + data[1] = LoadStr(IDS_ERRORCREATINGTMPFILE); + data[2] = fakeName; + data[3] = GetErrorText(err); + SendMessage(hProgressDlg, WM_USER_DIALOG, 0, (LPARAM)data); + switch (ret) + { + case IDRETRY: + break; + + case IDB_SKIPALL: + dlgData.SkipAllFileOpenOut = TRUE; + case IDB_SKIP: + { + SKIP_OPEN_OUT: + + HANDLES(CloseHandle(hSource)); + totalDone += size; + SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); + return TRUE; + } + + case IDCANCEL: + { + CANCEL_OPEN2: + + HANDLES(CloseHandle(hSource)); + return FALSE; + } + } + } + } + } + else + { + DWORD err = GetLastError(); + if (invalidName) + err = ERROR_INVALID_NAME; + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + if (*dlgData.CancelWorker) + return FALSE; + + if (dlgData.SkipAllFileOpenIn) + goto SKIP_OPEN_IN; + + int ret; + ret = IDCANCEL; + char* data[4]; + data[0] = (char*)&ret; + data[1] = LoadStr(IDS_ERROROPENINGFILE); + data[2] = name; + data[3] = GetErrorText(err); + SendMessage(hProgressDlg, WM_USER_DIALOG, 0, (LPARAM)data); + switch (ret) + { + case IDRETRY: + break; + + case IDB_SKIPALL: + dlgData.SkipAllFileOpenIn = TRUE; + case IDB_SKIP: + { + SKIP_OPEN_IN: + + totalDone += size; + SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); + return TRUE; + } + + case IDCANCEL: + return FALSE; + } + } + } +} + +BOOL DoChangeAttrs(HWND hProgressDlg, char* name, const CQuadWord& size, DWORD attrs, + COperations* script, CQuadWord& totalDone, + FILETIME* timeModified, FILETIME* timeCreated, FILETIME* timeAccessed, + BOOL& changeCompression, BOOL& changeEncryption, DWORD fileAttr, + CProgressDlgData& dlgData) +{ + // if the path ends with a space/dot, we must append '\\'; otherwise + // SetFileAttributes (and others) trims the spaces/dots and operates + // on a different path + const char* nameSetAttrs = name; + char nameSetAttrsCopy[3 * MAX_PATH]; + MakeCopyWithBackslashIfNeeded(nameSetAttrs, nameSetAttrsCopy); + + while (1) + { + DWORD error = ERROR_SUCCESS; + BOOL showCompressErr = FALSE; + BOOL showEncryptErr = FALSE; + char* errTitle = NULL; + if (changeCompression && (attrs & FILE_ATTRIBUTE_COMPRESSED) == 0) + { + error = UncompressFile(name, fileAttr); + if (error != ERROR_SUCCESS) + { + errTitle = LoadStr(IDS_ERRORCOMPRESSING); + if (error == ERROR_INVALID_FUNCTION) + showCompressErr = TRUE; // not supported + } + } + if (error == ERROR_SUCCESS && changeEncryption && (attrs & FILE_ATTRIBUTE_ENCRYPTED) == 0) + { + error = MyDecryptFile(name, fileAttr, TRUE); + if (error != ERROR_SUCCESS) + { + errTitle = LoadStr(IDS_ERRORENCRYPTING); + if (error == ERROR_INVALID_FUNCTION) + showEncryptErr = TRUE; // not supported + } + } + if (error == ERROR_SUCCESS && changeCompression && (attrs & FILE_ATTRIBUTE_COMPRESSED)) + { + error = CompressFile(name, fileAttr); + if (error != ERROR_SUCCESS) + { + errTitle = LoadStr(IDS_ERRORCOMPRESSING); + if (error == ERROR_INVALID_FUNCTION) + showCompressErr = TRUE; // not supported + } + } + if (error == ERROR_SUCCESS && changeEncryption && (attrs & FILE_ATTRIBUTE_ENCRYPTED)) + { + BOOL cancelOper = FALSE; + error = MyEncryptFile(hProgressDlg, name, fileAttr, attrs, dlgData, cancelOper, TRUE); + if (*dlgData.CancelWorker || cancelOper) + return FALSE; + if (error != ERROR_SUCCESS) + { + errTitle = LoadStr(IDS_ERRORENCRYPTING); + if (error == ERROR_INVALID_FUNCTION) + showEncryptErr = TRUE; // not supported + } + } + if (showCompressErr || showEncryptErr) + { + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + if (*dlgData.CancelWorker) + return FALSE; + + if (showCompressErr) + changeCompression = FALSE; + if (showEncryptErr) + changeEncryption = FALSE; + char* data[3]; + data[0] = LoadStr((showCompressErr && (attrs & FILE_ATTRIBUTE_COMPRESSED) || !showEncryptErr) ? IDS_ERRORCOMPRESSING : IDS_ERRORENCRYPTING); + data[1] = name; + data[2] = LoadStr((showCompressErr && (attrs & FILE_ATTRIBUTE_COMPRESSED) || !showEncryptErr) ? IDS_COMPRNOTSUPPORTED : IDS_ENCRYPNOTSUPPORTED); + SendMessage(hProgressDlg, WM_USER_DIALOG, 5, (LPARAM)data); + error = ERROR_SUCCESS; + } + if (error == ERROR_SUCCESS && SetFileAttributesUtf8(nameSetAttrs, attrs)) + { + BOOL isDir = ((attrs & FILE_ATTRIBUTE_DIRECTORY) != 0); + // if any of the timestamps need to be set + if (timeModified != NULL || timeCreated != NULL || timeAccessed != NULL) + { + HANDLE file; + if (attrs & FILE_ATTRIBUTE_READONLY) + SetFileAttributesUtf8(nameSetAttrs, attrs & (~FILE_ATTRIBUTE_READONLY)); + file = HANDLES_Q(CreateFileUtf8(nameSetAttrs, GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, isDir ? FILE_FLAG_BACKUP_SEMANTICS : 0, NULL)); + if (file != INVALID_HANDLE_VALUE) + { + FILETIME ftCreated, ftAccessed, ftModified; + GetFileTime(file, &ftCreated, &ftAccessed, &ftModified); + if (timeCreated != NULL) + ftCreated = *timeCreated; + if (timeAccessed != NULL) + ftAccessed = *timeAccessed; + if (timeModified != NULL) + ftModified = *timeModified; + SetFileTime(file, &ftCreated, &ftAccessed, &ftModified); + HANDLES(CloseHandle(file)); + if (attrs & FILE_ATTRIBUTE_READONLY) + SetFileAttributesUtf8(nameSetAttrs, attrs); + } + else + { + if (attrs & FILE_ATTRIBUTE_READONLY) + SetFileAttributesUtf8(nameSetAttrs, attrs); + goto SHOW_ERROR; + } + } + totalDone += size; + SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); + return TRUE; + } + else + { + SHOW_ERROR: + + if (error == ERROR_SUCCESS) + error = GetLastError(); + if (errTitle == NULL) + errTitle = LoadStr(IDS_ERRORCHANGINGATTRS); + + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + if (*dlgData.CancelWorker) + return FALSE; + + if (dlgData.SkipAllChangeAttrs) + goto SKIP_ATTRS_ERROR; + + int ret; + ret = IDCANCEL; + char* data[4]; + data[0] = (char*)&ret; + data[1] = errTitle; + data[2] = name; + data[3] = GetErrorText(error); + SendMessage(hProgressDlg, WM_USER_DIALOG, 0, (LPARAM)data); + switch (ret) + { + case IDRETRY: + break; + + case IDB_SKIPALL: + dlgData.SkipAllChangeAttrs = TRUE; + case IDB_SKIP: + { + SKIP_ATTRS_ERROR: + + totalDone += size; + SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); + return TRUE; + } + + case IDCANCEL: + return FALSE; + } + } + } +} + +unsigned ThreadWorkerBody(void* parameter) +{ + CALL_STACK_MESSAGE1("ThreadWorkerBody()"); + SetThreadNameInVCAndTrace("Worker"); + TRACE_I("Begin"); + + CWorkerData* data = (CWorkerData*)parameter; + //--- create a local copy of the data + HANDLE wContinue = data->WContinue; + CProgressDlgData dlgData; + dlgData.WorkerNotSuspended = data->WorkerNotSuspended; + dlgData.CancelWorker = data->CancelWorker; + dlgData.OperationProgress = data->OperationProgress; + dlgData.SummaryProgress = data->SummaryProgress; + dlgData.OverwriteAll = dlgData.OverwriteHiddenAll = dlgData.DeleteHiddenAll = + dlgData.SkipAllFileWrite = dlgData.SkipAllFileRead = + dlgData.SkipAllOverwrite = dlgData.SkipAllSystemOrHidden = + dlgData.SkipAllFileOpenIn = dlgData.SkipAllFileOpenOut = + dlgData.SkipAllOverwriteErr = dlgData.SkipAllMoveErrors = + dlgData.SkipAllDeleteErr = dlgData.SkipAllDirCreate = + dlgData.SkipAllDirCreateErr = dlgData.SkipAllChangeAttrs = + dlgData.EncryptSystemAll = dlgData.SkipAllEncryptSystem = + dlgData.IgnoreAllADSReadErr = dlgData.SkipAllFileADSOpenIn = + dlgData.SkipAllFileADSOpenOut = dlgData.SkipAllFileADSRead = + dlgData.SkipAllFileADSWrite = dlgData.DirOverwriteAll = + dlgData.SkipAllDirOver = dlgData.IgnoreAllADSOpenOutErr = + dlgData.IgnoreAllSetAttrsErr = dlgData.IgnoreAllCopyPermErr = + dlgData.IgnoreAllCopyDirTimeErr = dlgData.SkipAllFileOutLossEncr = + dlgData.FileOutLossEncrAll = dlgData.SkipAllDirCrLossEncr = + dlgData.DirCrLossEncrAll = dlgData.IgnoreAllGetFileTimeErr = + dlgData.IgnoreAllSetFileTimeErr = dlgData.SkipAllGetFileTime = + dlgData.SkipAllSetFileTime = FALSE; + dlgData.CnfrmFileOver = Configuration.CnfrmFileOver; + dlgData.CnfrmDirOver = Configuration.CnfrmDirOver; + dlgData.CnfrmSHFileOver = Configuration.CnfrmSHFileOver; + dlgData.CnfrmSHFileDel = Configuration.CnfrmSHFileDel; + dlgData.UseRecycleBin = Configuration.UseRecycleBin; + dlgData.RecycleMasks.SetMasksString(Configuration.RecycleMasks.GetMasksString(), + Configuration.RecycleMasks.GetExtendedMode()); + int errorPos; + if (dlgData.UseRecycleBin == 2 && + !dlgData.PrepareRecycleMasks(errorPos)) + TRACE_E("Error in recycle-bin group mask."); + COperations* script = data->Script; + if (script->TotalSize == CQuadWord(0, 0)) + { + script->TotalSize = CQuadWord(1, 0); // guard against division by zero + // TRACE_E("ThreadWorkerBody(): script->TotalSize may not be zero!"); // when building the script we do not set the "synchronizing one", which caused issues in Calculate Occupied Space + } + + if (script->CopySecurity) + GainWriteOwnerAccess(); + + HWND hProgressDlg = data->HProgressDlg; + void* buffer = data->Buffer; + BOOL bufferIsAllocated = data->BufferIsAllocated; + CChangeAttrsData* attrsData = (CChangeAttrsData*)data->Buffer; + DWORD clearReadonlyMask = data->ClearReadonlyMask; + CConvertData convertData; + if (data->ConvertData != NULL) // make a copy of the data for Convert + { + convertData = *data->ConvertData; + } + SetEvent(wContinue); // data ready; resume the main thread or the progress-dialog thread + //--- + SetProgress(hProgressDlg, 0, 0, dlgData); + script->InitSpeedMeters(FALSE); + + char lastLantasticCheckRoot[MAX_PATH]; // last path root checked for Lantastic ("" = nothing checked yet) + lastLantasticCheckRoot[0] = 0; + BOOL lastIsLantasticPath = FALSE; // result of checking root lastLantasticCheckRoot + int mustDeleteFileBeforeOverwrite = 0; /* need test */ // (added for SNAP server - NSA drive - SetEndOfFile fails - 0/1/2 = need-test/yes/no + int allocWholeFileOnStart = 0; /* need test */ // safety measure (e.g. SNAP servers - NSA drives - may fail); cannot risk a broken Copy - 0/1/2 = need-test/yes/no + int setDirTimeAfterMove = script->PreserveDirTime && script->SourcePathIsNetwork ? 0 /* need test */ : 2 /* no */; // e.g. on Samba, moving/renaming a directory changes its date and time - 0/1/2 = need-test/yes/no + + BOOL Error = FALSE; + CQuadWord totalDone; + totalDone = CQuadWord(0, 0); + CProgressData pd; + BOOL novellRenamePatch = FALSE; // TRUE when the read-only attribute must be cleared before MoveFile (required on Novell) + char* tgtBuffer = NULL; // conversion buffer for ocConvert + CAsyncCopyParams* asyncPar = NULL; + if (buffer != NULL) + { + // prefetch strings so we do not load them for every operation individually (fills the LoadStr buffer quickly + throttles) + char opStrCopying[50]; + lstrcpyn(opStrCopying, LoadStr(IDS_COPYING), 50); + char opStrCopyingPrep[50]; + lstrcpyn(opStrCopyingPrep, LoadStr(IDS_COPYINGPREP), 50); + char opStrMoving[50]; + lstrcpyn(opStrMoving, LoadStr(IDS_MOVING), 50); + char opStrMovingPrep[50]; + lstrcpyn(opStrMovingPrep, LoadStr(IDS_MOVINGPREP), 50); + char opStrCreatingDir[50]; + lstrcpyn(opStrCreatingDir, LoadStr(IDS_CREATINGDIR), 50); + char opStrDeleting[50]; + lstrcpyn(opStrDeleting, LoadStr(IDS_DELETING), 50); + char opStrConverting[50]; + lstrcpyn(opStrConverting, LoadStr(IDS_CONVERTING), 50); + char opChangAttrs[50]; + lstrcpyn(opChangAttrs, LoadStr(IDS_CHANGINGATTRS), 50); + + int i; + for (i = 0; !*dlgData.CancelWorker && i < script->Count; i++) + { + COperation* op = &script->At(i); + + switch (op->Opcode) + { + case ocCopyFile: + { + const char* opName = "copy file"; + ExecLogFileOperationStart(opName, op->SourceName, op->TargetName); + pd.Operation = opStrCopying; + pd.Source = op->SourceName; + pd.Preposition = opStrCopyingPrep; + pd.Target = op->TargetName; + SetProgressDialog(hProgressDlg, &pd, dlgData); + + SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); + + BOOL lantasticCheck = IsLantasticDrive(op->TargetName, lastLantasticCheckRoot, _countof(lastLantasticCheckRoot), lastIsLantasticPath); + + Error = !DoCopyFile(op, hProgressDlg, buffer, script, totalDone, + clearReadonlyMask, NULL, lantasticCheck, mustDeleteFileBeforeOverwrite, + allocWholeFileOnStart, dlgData, + (op->OpFlags & OPFL_COPY_ADS) != 0, + (op->OpFlags & OPFL_AS_ENCRYPTED) != 0, + FALSE, asyncPar); + ExecLogFileOperationResult(opName, op->SourceName, op->TargetName, !Error); + break; + } + + case ocMoveDir: + case ocMoveFile: + { + const char* opName = op->Opcode == ocMoveDir ? "move dir" : "move file"; + ExecLogFileOperationStart(opName, op->SourceName, op->TargetName); + pd.Operation = opStrMoving; + pd.Source = op->SourceName; + pd.Preposition = opStrMovingPrep; + pd.Target = op->TargetName; + SetProgressDialog(hProgressDlg, &pd, dlgData); + + SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); + + BOOL lantasticCheck = IsLantasticDrive(op->TargetName, lastLantasticCheckRoot, _countof(lastLantasticCheckRoot), lastIsLantasticPath); + BOOL ignInvalidName = op->Opcode == ocMoveDir && (op->OpFlags & OPFL_IGNORE_INVALID_NAME) != 0; + + Error = !DoMoveFile(op, hProgressDlg, buffer, script, totalDone, + op->Opcode == ocMoveDir, clearReadonlyMask, &novellRenamePatch, + lantasticCheck, mustDeleteFileBeforeOverwrite, + allocWholeFileOnStart, dlgData, + (op->OpFlags & OPFL_COPY_ADS) != 0, + (op->OpFlags & OPFL_AS_ENCRYPTED) != 0, + &setDirTimeAfterMove, asyncPar, ignInvalidName); + ExecLogFileOperationResult(opName, op->SourceName, op->TargetName, !Error); + break; + } + + case ocCreateDir: + { + const char* opName = "create dir"; + ExecLogFileOperationStart(opName, op->TargetName, ""); + BOOL copyADS = (op->OpFlags & OPFL_COPY_ADS) != 0; + BOOL crAsEncrypted = (op->OpFlags & OPFL_AS_ENCRYPTED) != 0; + BOOL ignInvalidName = (op->OpFlags & OPFL_IGNORE_INVALID_NAME) != 0; + pd.Operation = opStrCreatingDir; + pd.Source = op->TargetName; + pd.Preposition = ""; + pd.Target = ""; + SetProgressDialog(hProgressDlg, &pd, dlgData); + + SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); + + BOOL skip, alreadyExisted; + Error = !DoCreateDir(hProgressDlg, op->TargetName, op->Attr, clearReadonlyMask, dlgData, + totalDone, op->Size, op->SourceName, copyADS, script, buffer, skip, + alreadyExisted, crAsEncrypted, ignInvalidName); + ExecLogFileOperationResult(opName, op->TargetName, "", !Error); + if (!Error) + { + if (skip) // skip directory creation + { + // skip all script operations up to the label that closes this directory + CQuadWord skipTotal(0, 0); + int createDirIndex = i; + while (++i < script->Count) + { + COperation* oper = &script->At(i); + if (oper->Opcode == ocLabelForSkipOfCreateDir && (int)oper->Attr == createDirIndex) + { + script->AddBytesToTFS(CQuadWord((DWORD)(DWORD_PTR)oper->SourceName, (DWORD)(DWORD_PTR)oper->TargetName)); + break; + } + skipTotal += oper->Size; + } + if (i == script->Count) + { + i = createDirIndex; + TRACE_E("ThreadWorkerBody(): unable to find end-label for dir-create operation: opcode=" << op->Opcode << ", index=" << i); + } + else + totalDone += skipTotal; + } + else + { + if (alreadyExisted) + op->Attr = 0x10000000 /* dir already existed */; + else + op->Attr = 0x01000000 /* dir was created */; + } + totalDone += op->Size; + script->SetProgressSize(totalDone); + SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); + } + break; + } + + case ocCopyDirTime: + { + BOOL skipSetDirTime = FALSE; + // locate the skip-label; it stores the index of the create-dir operation along with + // whether the target directory already existed or was created (date/time are copied + // only when we created the directory) + COperation* skipLabel = NULL; + if (i + 1 < script->Count && script->At(i + 1).Opcode == ocLabelForSkipOfCreateDir) + skipLabel = &script->At(i + 1); + else + { + if (i + 2 < script->Count && script->At(i + 2).Opcode == ocLabelForSkipOfCreateDir) + skipLabel = &script->At(i + 2); + } + if (skipLabel != NULL) + { + if (skipLabel->Attr < (DWORD)script->Count) + { + COperation* crDir = &script->At(skipLabel->Attr); + if (crDir->Opcode == ocCreateDir && (crDir->OpFlags & OPFL_AS_ENCRYPTED) == 0) + { + if (crDir->Attr == 0x10000000 /* dir already existed */) + skipSetDirTime = TRUE; + else + { + if (crDir->Attr != 0x01000000 /* dir was created */) + TRACE_E("ThreadWorkerBody(): unexpected value of Attr in create-dir operation (not 'existed' nor 'created')!"); + } + } + else + TRACE_E("ThreadWorkerBody(): unexpected opcode or flags of create-dir operation! Opcode=" << crDir->Opcode << ", OpFlags=" << crDir->OpFlags); + } + else + TRACE_E("ThreadWorkerBody(): unexpected index of create-dir operation! index=" << skipLabel->Attr); + } + else + TRACE_E("ThreadWorkerBody(): unable to find end-label for dir-create operation (not in first following item nor in second following item)!"); + + if (!skipSetDirTime) + { + pd.Operation = opChangAttrs; + pd.Source = op->TargetName; + pd.Preposition = ""; + pd.Target = ""; + SetProgressDialog(hProgressDlg, &pd, dlgData); + + SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); + + FILETIME modified; + modified.dwLowDateTime = (DWORD)(DWORD_PTR)op->SourceName; + modified.dwHighDateTime = op->Attr; + Error = !DoCopyDirTime(hProgressDlg, op->TargetName, &modified, dlgData, FALSE); + } + if (!Error) + { + script->AddBytesToSpeedMetersAndTFSandPS((DWORD)op->Size.Value, TRUE, 0, NULL, MAX_OP_FILESIZE); + + totalDone += op->Size; + SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); + } + break; + } + + case ocDeleteFile: + case ocDeleteDir: + case ocDeleteDirLink: + { + const char* opName = op->Opcode == ocDeleteFile ? "delete file" : (op->Opcode == ocDeleteDir ? "delete dir" : "delete dir link"); + ExecLogFileOperationStart(opName, op->SourceName, ""); + pd.Operation = opStrDeleting; + pd.Source = op->SourceName; + pd.Preposition = ""; + pd.Target = ""; + SetProgressDialog(hProgressDlg, &pd, dlgData); + + SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); + + if (op->Opcode == ocDeleteFile) + { + Error = !DoDeleteFile(hProgressDlg, op->SourceName, op->Size, + script, totalDone, op->Attr, dlgData); + } + else + { + if (op->Opcode == ocDeleteDir) + { + Error = !DoDeleteDir(hProgressDlg, op->SourceName, op->Size, + script, totalDone, op->Attr, (DWORD)(DWORD_PTR)op->TargetName != -1, + dlgData); + } + else + { + Error = !DoDeleteDirLink(hProgressDlg, op->SourceName, op->Size, + script, totalDone, dlgData); + } + } + ExecLogFileOperationResult(opName, op->SourceName, "", !Error); + break; + } + + case ocConvert: + { + const char* opName = "convert file"; + ExecLogFileOperationStart(opName, op->SourceName, ""); + // output buffer - the conversion will be performed in it (in the worst case, + // when the input file contains only CR or LF and we translate them to CRLF, + // this buffer is twice the size of sourceBuffer) and afterwards we will write from it + // to the temporary file + if (tgtBuffer == NULL) // first pass? + { + tgtBuffer = (char*)malloc(FAST_LOCAL_COPY_BUFFER * 2); + if (tgtBuffer == NULL) + { + TRACE_E(LOW_MEMORY); + Error = TRUE; + break; // error ... + } + } + pd.Operation = opStrConverting; + pd.Source = op->SourceName; + pd.Preposition = ""; + pd.Target = ""; + SetProgressDialog(hProgressDlg, &pd, dlgData); + + SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); + + Error = !DoConvert(hProgressDlg, op->SourceName, (char*)buffer, tgtBuffer, op->Size, script, + totalDone, convertData, dlgData); + ExecLogFileOperationResult(opName, op->SourceName, "", !Error); + break; + } + + case ocChangeAttrs: + { + const char* opName = "change attrs"; + ExecLogFileOperationStart(opName, op->SourceName, ""); + pd.Operation = opChangAttrs; + pd.Source = op->SourceName; + pd.Preposition = ""; + pd.Target = ""; + SetProgressDialog(hProgressDlg, &pd, dlgData); + + SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); + + Error = !DoChangeAttrs(hProgressDlg, op->SourceName, op->Size, (DWORD)(DWORD_PTR)op->TargetName, + script, totalDone, + attrsData->ChangeTimeModified ? &attrsData->TimeModified : NULL, + attrsData->ChangeTimeCreated ? &attrsData->TimeCreated : NULL, + attrsData->ChangeTimeAccessed ? &attrsData->TimeAccessed : NULL, + attrsData->ChangeCompression, attrsData->ChangeEncryption, + op->Attr, dlgData); + ExecLogFileOperationResult(opName, op->SourceName, "", !Error); + break; + } + + case ocLabelForSkipOfCreateDir: + break; // no action + } + if (Error) + break; + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + } + if (!Error && !*dlgData.CancelWorker && i == script->Count && totalDone != script->TotalSize && + (totalDone != CQuadWord(0, 0) || script->TotalSize != CQuadWord(1, 0))) // intentional change of script->TotalSize to one (prevents division by zero) + { + TRACE_E("ThreadWorkerBody(): operation done: totalDone != script->TotalSize (" << totalDone.Value << " != " << script->TotalSize.Value << ")"); + } + CQuadWord transferredFileSize, progressSize; + if (!Error && !*dlgData.CancelWorker && i == script->Count && + script->GetTFSandProgressSize(&transferredFileSize, &progressSize) && + (transferredFileSize != script->TotalFileSize || + progressSize != script->TotalSize && + (progressSize != CQuadWord(0, 0) || script->TotalSize != CQuadWord(1, 0)))) // intentional change of script->TotalSize to one (prevents division by zero) + { + if (transferredFileSize != script->TotalFileSize) + { + TRACE_E("ThreadWorkerBody(): operation done: transferredFileSize != script->TotalFileSize (" << transferredFileSize.Value << " != " << script->TotalFileSize.Value << ")"); + } + if (progressSize != script->TotalSize && + (progressSize != CQuadWord(0, 0) || script->TotalSize != CQuadWord(1, 0))) + { + TRACE_E("ThreadWorkerBody(): operation done: progressSize != script->TotalSize (" << progressSize.Value << " != " << script->TotalSize.Value << ")"); + } + } + } + if (asyncPar != NULL) + delete asyncPar; + if (tgtBuffer != NULL) + free(tgtBuffer); + if (bufferIsAllocated) + free(buffer); + *dlgData.CancelWorker = Error; // if this was triggered by Cancel, make that obvious ... + SendMessage(hProgressDlg, WM_COMMAND, IDOK, 0); // we're done ... + WaitForSingleObject(wContinue, INFINITE); // we need to stop the main thread + + FreeScript(script); // calls delete, so the main thread cannot be running + + TRACE_I("End"); + return 0; +} + +unsigned ThreadWorkerEH(void* param) +{ +#ifndef CALLSTK_DISABLE + __try + { +#endif // CALLSTK_DISABLE + return ThreadWorkerBody(param); +#ifndef CALLSTK_DISABLE + } + __except (CCallStack::HandleException(GetExceptionInformation())) + { + TRACE_I("Thread Worker: calling ExitProcess(1)."); + // ExitProcess(1); + TerminateProcess(GetCurrentProcess(), 1); // harsher exit (this one still invokes something) + return 1; + } +#endif // CALLSTK_DISABLE +} + +DWORD WINAPI ThreadWorker(void* param) +{ + CCallStack stack; + return ThreadWorkerEH(param); +} + +HANDLE StartWorker(COperations* script, HWND hDlg, CChangeAttrsData* attrsData, + CConvertData* convertData, HANDLE wContinue, HANDLE workerNotSuspended, + BOOL* cancelWorker, int* operationProgress, int* summaryProgress) +{ + CWorkerData data; + data.WorkerNotSuspended = workerNotSuspended; + data.CancelWorker = cancelWorker; + data.OperationProgress = operationProgress; + data.SummaryProgress = summaryProgress; + data.WContinue = wContinue; + data.ConvertData = convertData; + data.Script = script; + data.HProgressDlg = hDlg; + data.ClearReadonlyMask = script->ClearReadonlyMask; + if (attrsData != NULL) + { + data.Buffer = attrsData; + data.BufferIsAllocated = FALSE; + } + else + { + data.BufferIsAllocated = TRUE; + data.Buffer = malloc(FAST_LOCAL_COPY_BUFFER); + if (data.Buffer == NULL) + { + TRACE_E(LOW_MEMORY); + return NULL; + } + } + DWORD threadID; + ResetEvent(wContinue); + *cancelWorker = FALSE; + + // if (Worker != NULL) HANDLES(CloseHandle(Worker)); // was probably unnecessary + HANDLE worker = HANDLES(CreateThread(NULL, 0, ThreadWorker, &data, 0, &threadID)); + if (worker == NULL) + { + if (data.BufferIsAllocated) + free(data.Buffer); + TRACE_E("Unable to start Worker thread."); + return NULL; + } + // SetThreadPriority(Worker, THREAD_PRIORITY_HIGHEST); + WaitForSingleObject(wContinue, INFINITE); // wait until it copies the data (they are on the stack) + return worker; +} + +void FreeScript(COperations* script) +{ + if (script == NULL) + return; + int i; + for (i = 0; i < script->Count; i++) + { + COperation* op = &script->At(i); + if (op->SourceName != NULL && op->Opcode != ocCopyDirTime && op->Opcode != ocLabelForSkipOfCreateDir) + free(op->SourceName); + if (op->TargetName != NULL && op->Opcode != ocChangeAttrs && op->Opcode != ocLabelForSkipOfCreateDir) + free(op->TargetName); + } + if (script->WaitInQueueSubject != NULL) + free(script->WaitInQueueSubject); + if (script->WaitInQueueFrom != NULL) + free(script->WaitInQueueFrom); + if (script->WaitInQueueTo != NULL) + free(script->WaitInQueueTo); + delete script; +} + +BOOL COperationsQueue::AddOperation(HWND dlg, BOOL startOnIdle, BOOL* startPaused) +{ + CALL_STACK_MESSAGE1("COperationsQueue::AddOperation()"); + + HANDLES(EnterCriticalSection(&QueueCritSect)); + + int i; + for (i = 0; i < OperDlgs.Count; i++) // ensure uniqueness (an operation can be added only once) + if (OperDlgs[i] == dlg) + break; + + BOOL ret = FALSE; + if (i == OperDlgs.Count) // the operation can be added + { + OperDlgs.Add(dlg); + if (OperDlgs.IsGood()) + { + if (startOnIdle) + { + int j; + for (j = 0; j < OperPaused.Count && OperPaused[j] == 1 /* auto-paused */; j++) + ; // if another operation is already running or was paused manually, start this one as "auto-paused" + *startPaused = j < OperPaused.Count; + } + else + *startPaused = FALSE; + OperPaused.Add(*startPaused ? 1 /* auto-paused */ : 0 /* running */); + if (!OperPaused.IsGood()) + { + OperPaused.ResetState(); + OperDlgs.Delete(OperDlgs.Count - 1); + if (!OperDlgs.IsGood()) + OperDlgs.ResetState(); + } + else + ret = TRUE; + } + else + OperDlgs.ResetState(); + } + else + TRACE_E("COperationsQueue::AddOperation(): this operation has already been added!"); + + HANDLES(LeaveCriticalSection(&QueueCritSect)); + + return ret; +} + +void COperationsQueue::OperationEnded(HWND dlg, BOOL doNotResume, HWND* foregroundWnd) +{ + CALL_STACK_MESSAGE1("COperationsQueue::OperationEnded()"); + + HANDLES(EnterCriticalSection(&QueueCritSect)); + + BOOL found = FALSE; + int i; + for (i = 0; i < OperDlgs.Count; i++) + { + if (OperDlgs[i] == dlg) + { + found = TRUE; + OperDlgs.Delete(i); + if (!OperDlgs.IsGood()) + OperDlgs.ResetState(); + OperPaused.Delete(i); + if (!OperPaused.IsGood()) + OperPaused.ResetState(); + break; + } + } + if (!found) + TRACE_E("COperationsQueue::OperationEnded(): unexpected situation: operation was not found!"); + else + { + if (!doNotResume) + { + int j; + for (j = 0; j < OperPaused.Count && OperPaused[j] == 1 /* auto-paused */; j++) + ; // if no operation is running and none was paused manually, resume the first one in the queue + if (j == OperPaused.Count && OperDlgs.Count > 0) + { + PostMessage(OperDlgs[0], WM_COMMAND, CM_RESUMEOPER, 0); + if (foregroundWnd != NULL && GetForegroundWindow() == dlg) + *foregroundWnd = OperDlgs[0]; + } + } + } + + HANDLES(LeaveCriticalSection(&QueueCritSect)); +} + +void COperationsQueue::SetPaused(HWND dlg, int paused) +{ + CALL_STACK_MESSAGE1("COperationsQueue::SetPaused()"); + + HANDLES(EnterCriticalSection(&QueueCritSect)); + + int i; + for (i = 0; i < OperDlgs.Count; i++) + { + if (OperDlgs[i] == dlg) + { + OperPaused[i] = paused; + break; + } + } + if (i == OperDlgs.Count) + TRACE_E("COperationsQueue::SetPaused(): operation was not found!"); + + HANDLES(LeaveCriticalSection(&QueueCritSect)); +} + +BOOL COperationsQueue::IsEmpty() +{ + CALL_STACK_MESSAGE1("COperationsQueue::IsEmpty()"); + + HANDLES(EnterCriticalSection(&QueueCritSect)); + BOOL ret = OperDlgs.Count == 0; + HANDLES(LeaveCriticalSection(&QueueCritSect)); + return ret; +} + +void COperationsQueue::AutoPauseOperation(HWND dlg, HWND* foregroundWnd) +{ + CALL_STACK_MESSAGE1("COperationsQueue::AutoPauseOperation()"); + + HANDLES(EnterCriticalSection(&QueueCritSect)); + + int i; + for (i = 0; i < OperDlgs.Count; i++) + { + if (OperDlgs[i] == dlg) + { + int j; + for (j = i; j + 1 < OperDlgs.Count; j++) + OperDlgs[j] = OperDlgs[j + 1]; + for (j = i; j + 1 < OperPaused.Count; j++) + OperPaused[j] = OperPaused[j + 1]; + OperDlgs[j] = dlg; + OperPaused[j] = 1 /* auto-paused */; + break; + } + } + if (i == OperDlgs.Count) + TRACE_E("COperationsQueue::AutoPauseOperation(): operation was not found!"); + + // if no operation is running and none was paused manually, resume the first one in the queue + int j; + for (j = 0; j < OperPaused.Count && OperPaused[j] == 1 /* auto-paused */; j++) + ; + if (j == OperPaused.Count && OperDlgs.Count > 0) + { + PostMessage(OperDlgs[0], WM_COMMAND, CM_RESUMEOPER, 0); + if (foregroundWnd != NULL && GetForegroundWindow() == dlg) + *foregroundWnd = OperDlgs[0]; + } + + HANDLES(LeaveCriticalSection(&QueueCritSect)); +} + +int COperationsQueue::GetNumOfOperations() +{ + CALL_STACK_MESSAGE1("COperationsQueue::GetNumOfOperations()"); + + HANDLES(EnterCriticalSection(&QueueCritSect)); + int c = OperDlgs.Count; + HANDLES(LeaveCriticalSection(&QueueCritSect)); + return c; +} + + + diff --git a/src/salamdr5.cpp b/src/path_checking.cpp similarity index 100% rename from src/salamdr5.cpp rename to src/path_checking.cpp diff --git a/src/salamdr3.cpp b/src/path_utils.cpp similarity index 99% rename from src/salamdr3.cpp rename to src/path_utils.cpp index f0e2fd39..8f642081 100644 --- a/src/salamdr3.cpp +++ b/src/path_utils.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 Taskscape Ltd +// SPDX-FileCopyrightText: 2023 Taskscape Ltd // SPDX-License-Identifier: GPL-2.0-or-later #include "precomp.h" @@ -3494,14 +3494,14 @@ void CFileTimeStamps::CheckAndPackAndClear(HWND parent, BOOL* someFilesChanged, //**************************************************************************** // -// CTopIndexMem +// CScrollPositionMemory // -void CTopIndexMem::Push(const char* path, int topIndex) +void CScrollPositionMemory::Push(const char* path, int topIndex) { if (strlen(path) >= _countof(Path)) { - TRACE_E("CTopIndexMem::Push(): path is too long."); + TRACE_E("CScrollPositionMemory::Push(): path is too long."); Clear(); return; } @@ -3545,7 +3545,7 @@ void CTopIndexMem::Push(const char* path, int topIndex) } } -BOOL CTopIndexMem::FindAndPop(const char* path, int& topIndex) +BOOL CScrollPositionMemory::FindAndPop(const char* path, int& topIndex) { // zjistime, jestli path odpovida Path (path==Path) int l1 = (int)strlen(path); diff --git a/src/plugins/automation/generated/salamander_p.c b/src/plugins/automation/generated/salamander_p.c index 4e35d89b..c775bbb6 100644 --- a/src/plugins/automation/generated/salamander_p.c +++ b/src/plugins/automation/generated/salamander_p.c @@ -4123,133 +4123,6 @@ struct __midl_frag582_t; extern const __midl_frag582_t __midl_frag582; -typedef -NDR64_FORMAT_CHAR -__midl_frag581_t; -extern const __midl_frag581_t __midl_frag581; - -typedef -struct _NDR64_CONSTANT_IID_FORMAT -__midl_frag580_t; -extern const __midl_frag580_t __midl_frag580; - -typedef -struct _NDR64_POINTER_FORMAT -__midl_frag579_t; -extern const __midl_frag579_t __midl_frag579; - -typedef -struct _NDR64_POINTER_FORMAT -__midl_frag578_t; -extern const __midl_frag578_t __midl_frag578; - -typedef -struct _NDR64_POINTER_FORMAT -__midl_frag577_t; -extern const __midl_frag577_t __midl_frag577; - -typedef -struct _NDR64_USER_MARSHAL_FORMAT -__midl_frag576_t; -extern const __midl_frag576_t __midl_frag576; - -typedef -struct _NDR64_POINTER_FORMAT -__midl_frag575_t; -extern const __midl_frag575_t __midl_frag575; - -typedef -struct -{ - struct _NDR64_PROC_FORMAT frag1; - struct _NDR64_PARAM_FORMAT frag2; - struct _NDR64_PARAM_FORMAT frag3; - struct _NDR64_PARAM_FORMAT frag4; -} -__midl_frag574_t; -extern const __midl_frag574_t __midl_frag574; - -typedef -NDR64_FORMAT_CHAR -__midl_frag573_t; -extern const __midl_frag573_t __midl_frag573; - -typedef -struct _NDR64_CONSTANT_IID_FORMAT -__midl_frag572_t; -extern const __midl_frag572_t __midl_frag572; - -typedef -struct _NDR64_POINTER_FORMAT -__midl_frag571_t; -extern const __midl_frag571_t __midl_frag571; - -typedef -struct _NDR64_POINTER_FORMAT -__midl_frag570_t; -extern const __midl_frag570_t __midl_frag570; - -typedef -struct _NDR64_POINTER_FORMAT -__midl_frag569_t; -extern const __midl_frag569_t __midl_frag569; - -typedef -struct _NDR64_USER_MARSHAL_FORMAT -__midl_frag568_t; -extern const __midl_frag568_t __midl_frag568; - -typedef -struct _NDR64_POINTER_FORMAT -__midl_frag567_t; -extern const __midl_frag567_t __midl_frag567; - -typedef -struct -{ - struct _NDR64_PROC_FORMAT frag1; - struct _NDR64_PARAM_FORMAT frag2; - struct _NDR64_PARAM_FORMAT frag3; - struct _NDR64_PARAM_FORMAT frag4; -} -__midl_frag566_t; -extern const __midl_frag566_t __midl_frag566; - -typedef -NDR64_FORMAT_CHAR -__midl_frag565_t; -extern const __midl_frag565_t __midl_frag565; - -typedef -struct _NDR64_CONSTANT_IID_FORMAT -__midl_frag564_t; -extern const __midl_frag564_t __midl_frag564; - -typedef -struct _NDR64_POINTER_FORMAT -__midl_frag563_t; -extern const __midl_frag563_t __midl_frag563; - -typedef -struct _NDR64_POINTER_FORMAT -__midl_frag562_t; -extern const __midl_frag562_t __midl_frag562; - -typedef -struct _NDR64_POINTER_FORMAT -__midl_frag561_t; -extern const __midl_frag561_t __midl_frag561; - -typedef -struct _NDR64_USER_MARSHAL_FORMAT -__midl_frag560_t; -extern const __midl_frag560_t __midl_frag560; - -typedef -struct _NDR64_POINTER_FORMAT -__midl_frag559_t; -extern const __midl_frag559_t __midl_frag559; - typedef struct { @@ -4262,57 +4135,6 @@ struct __midl_frag555_t; extern const __midl_frag555_t __midl_frag555; -typedef -NDR64_FORMAT_CHAR -__midl_frag554_t; -extern const __midl_frag554_t __midl_frag554; - -typedef -struct _NDR64_CONSTANT_IID_FORMAT -__midl_frag553_t; -extern const __midl_frag553_t __midl_frag553; - -typedef -struct _NDR64_POINTER_FORMAT -__midl_frag552_t; -extern const __midl_frag552_t __midl_frag552; - -typedef -struct _NDR64_POINTER_FORMAT -__midl_frag551_t; -extern const __midl_frag551_t __midl_frag551; - -typedef -struct _NDR64_POINTER_FORMAT -__midl_frag550_t; -extern const __midl_frag550_t __midl_frag550; - -typedef -struct _NDR64_USER_MARSHAL_FORMAT -__midl_frag549_t; -extern const __midl_frag549_t __midl_frag549; - -typedef -struct _NDR64_POINTER_FORMAT -__midl_frag548_t; -extern const __midl_frag548_t __midl_frag548; - -typedef -struct -{ - struct _NDR64_PROC_FORMAT frag1; - struct _NDR64_PARAM_FORMAT frag2; - struct _NDR64_PARAM_FORMAT frag3; - struct _NDR64_PARAM_FORMAT frag4; -} -__midl_frag547_t; -extern const __midl_frag547_t __midl_frag547; - -typedef -NDR64_FORMAT_CHAR -__midl_frag546_t; -extern const __midl_frag546_t __midl_frag546; - typedef struct _NDR64_POINTER_FORMAT __midl_frag544_t; @@ -5064,15 +4886,6 @@ struct __midl_frag77_t; extern const __midl_frag77_t __midl_frag77; -typedef -struct -{ - NDR64_FORMAT_UINT32 frag1; - struct _NDR64_EXPR_VAR frag2; -} -__midl_frag73_t; -extern const __midl_frag73_t __midl_frag73; - typedef struct { @@ -5244,15 +5057,6 @@ struct __midl_frag55_t; extern const __midl_frag55_t __midl_frag55; -typedef -struct -{ - NDR64_FORMAT_UINT32 frag1; - struct _NDR64_EXPR_VAR frag2; -} -__midl_frag51_t; -extern const __midl_frag51_t __midl_frag51; - typedef struct { @@ -5287,42 +5091,6 @@ struct __midl_frag49_t; extern const __midl_frag49_t __midl_frag49; -typedef -struct -{ - struct _NDR64_POINTER_FORMAT frag1; -} -__midl_frag48_t; -extern const __midl_frag48_t __midl_frag48; - -typedef -struct -{ - NDR64_FORMAT_UINT32 frag1; - struct _NDR64_EXPR_VAR frag2; -} -__midl_frag44_t; -extern const __midl_frag44_t __midl_frag44; - -typedef -struct -{ - struct _NDR64_CONF_ARRAY_HEADER_FORMAT frag1; - struct - { - struct _NDR64_REPEAT_FORMAT frag1; - struct - { - struct _NDR64_POINTER_INSTANCE_HEADER_FORMAT frag1; - struct _NDR64_POINTER_FORMAT frag2; - } frag2; - NDR64_FORMAT_CHAR frag3; - } frag2; - struct _NDR64_ARRAY_ELEMENT_INFO frag3; -} -__midl_frag43_t; -extern const __midl_frag43_t __midl_frag43; - typedef struct { @@ -5529,15 +5297,6 @@ struct __midl_frag12_t; extern const __midl_frag12_t __midl_frag12; -typedef -struct -{ - NDR64_FORMAT_UINT32 frag1; - struct _NDR64_EXPR_VAR frag2; -} -__midl_frag7_t; -extern const __midl_frag7_t __midl_frag7; - typedef struct { @@ -5695,609 +5454,57 @@ static const __midl_frag582_t __midl_frag582 = 0, 0, 0, - (NDR64_UINT16) 0 /* 0x0 */, - 0 - }, /* MustSize, MustFree, [out] */ - (NDR64_UINT16) 0 /* 0x0 */, - 16 /* 0x10 */, /* Stack offset */ - }, - { - /* HRESULT */ /* parameter HRESULT */ - &__midl_frag589, - { - /* HRESULT */ - 0, - 0, - 0, - 0, - 1, - 1, - 1, - 1, - 0, - 0, - 0, - 0, - 0, - (NDR64_UINT16) 0 /* 0x0 */, - 0 - }, /* [out], IsReturn, Basetype, ByValue */ - (NDR64_UINT16) 0 /* 0x0 */, - 24 /* 0x18 */, /* Stack offset */ - } -}; - -static const __midl_frag581_t __midl_frag581 = -0x5 /* FC64_INT32 */; - -static const __midl_frag580_t __midl_frag580 = -{ -/* struct _NDR64_CONSTANT_IID_FORMAT */ - 0x24, /* FC64_IP */ - (NDR64_UINT8) 1 /* 0x1 */, - (NDR64_UINT16) 0 /* 0x0 */, - { - 0x15711c8c, - 0x52b3, - 0x4a0b, - {0x90, 0xb0, 0x12, 0x3c, 0x8a, 0x0d, 0x52, 0x5b} - } -}; - -static const __midl_frag579_t __midl_frag579 = -{ -/* *struct _NDR64_POINTER_FORMAT */ - 0x24, /* FC64_IP */ - (NDR64_UINT8) 0 /* 0x0 */, - (NDR64_UINT16) 0 /* 0x0 */, - &__midl_frag580 -}; - -static const __midl_frag578_t __midl_frag578 = -{ -/* **struct _NDR64_POINTER_FORMAT */ - 0x20, /* FC64_RP */ - (NDR64_UINT8) 16 /* 0x10 */, - (NDR64_UINT16) 0 /* 0x0 */, - &__midl_frag579 -}; - -static const __midl_frag577_t __midl_frag577 = -{ -/* *_wireVARIANT */ - 0x21, /* FC64_UP */ - (NDR64_UINT8) 0 /* 0x0 */, - (NDR64_UINT16) 0 /* 0x0 */, - &__midl_frag12 -}; - -static const __midl_frag576_t __midl_frag576 = -{ -/* wireVARIANT */ - 0xa2, /* FC64_USER_MARSHAL */ - (NDR64_UINT8) 128 /* 0x80 */, - (NDR64_UINT16) 1 /* 0x1 */, - (NDR64_UINT16) 7 /* 0x7 */, - (NDR64_UINT16) 8 /* 0x8 */, - (NDR64_UINT32) 24 /* 0x18 */, - (NDR64_UINT32) 0 /* 0x0 */, - &__midl_frag577 -}; - -static const __midl_frag575_t __midl_frag575 = -{ -/* *wireVARIANT */ - 0x20, /* FC64_RP */ - (NDR64_UINT8) 0 /* 0x0 */, - (NDR64_UINT16) 0 /* 0x0 */, - &__midl_frag576 -}; - -static const __midl_frag574_t __midl_frag574 = -{ -/* TextBox */ - { - /* TextBox */ /* procedure TextBox */ - (NDR64_UINT32) 3014979 /* 0x2e0143 */, /* auto handle */ /* IsIntrepreted, [object], ServerMustSize, ClientMustSize, HasReturn, ServerCorrelation */ - (NDR64_UINT32) 32 /* 0x20 */ , /* Stack size */ - (NDR64_UINT32) 0 /* 0x0 */, - (NDR64_UINT32) 8 /* 0x8 */, - (NDR64_UINT16) 0 /* 0x0 */, - (NDR64_UINT16) 0 /* 0x0 */, - (NDR64_UINT16) 3 /* 0x3 */, - (NDR64_UINT16) 0 /* 0x0 */ - }, - { - /* text */ /* parameter text */ - &__midl_frag576, - { - /* text */ - 1, - 1, - 0, - 1, - 0, - 0, - 0, - 0, - 1, - 0, - 0, - 0, - 0, - (NDR64_UINT16) 0 /* 0x0 */, - 0 - }, /* MustSize, MustFree, [in], SimpleRef */ - (NDR64_UINT16) 0 /* 0x0 */, - 8 /* 0x8 */, /* Stack offset */ - }, - { - /* component */ /* parameter component */ - &__midl_frag578, - { - /* component */ - 1, - 1, - 0, - 0, - 1, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - (NDR64_UINT16) 0 /* 0x0 */, - 0 - }, /* MustSize, MustFree, [out] */ - (NDR64_UINT16) 0 /* 0x0 */, - 16 /* 0x10 */, /* Stack offset */ - }, - { - /* HRESULT */ /* parameter HRESULT */ - &__midl_frag581, - { - /* HRESULT */ - 0, - 0, - 0, - 0, - 1, - 1, - 1, - 1, - 0, - 0, - 0, - 0, - 0, - (NDR64_UINT16) 0 /* 0x0 */, - 0 - }, /* [out], IsReturn, Basetype, ByValue */ - (NDR64_UINT16) 0 /* 0x0 */, - 24 /* 0x18 */, /* Stack offset */ - } -}; - -static const __midl_frag573_t __midl_frag573 = -0x5 /* FC64_INT32 */; - -static const __midl_frag572_t __midl_frag572 = -{ -/* struct _NDR64_CONSTANT_IID_FORMAT */ - 0x24, /* FC64_IP */ - (NDR64_UINT8) 1 /* 0x1 */, - (NDR64_UINT16) 0 /* 0x0 */, - { - 0x15711c8c, - 0x52b3, - 0x4a0b, - {0x90, 0xb0, 0x12, 0x3c, 0x8a, 0x0d, 0x52, 0x5b} - } -}; - -static const __midl_frag571_t __midl_frag571 = -{ -/* *struct _NDR64_POINTER_FORMAT */ - 0x24, /* FC64_IP */ - (NDR64_UINT8) 0 /* 0x0 */, - (NDR64_UINT16) 0 /* 0x0 */, - &__midl_frag572 -}; - -static const __midl_frag570_t __midl_frag570 = -{ -/* **struct _NDR64_POINTER_FORMAT */ - 0x20, /* FC64_RP */ - (NDR64_UINT8) 16 /* 0x10 */, - (NDR64_UINT16) 0 /* 0x0 */, - &__midl_frag571 -}; - -static const __midl_frag569_t __midl_frag569 = -{ -/* *_wireVARIANT */ - 0x21, /* FC64_UP */ - (NDR64_UINT8) 0 /* 0x0 */, - (NDR64_UINT16) 0 /* 0x0 */, - &__midl_frag12 -}; - -static const __midl_frag568_t __midl_frag568 = -{ -/* wireVARIANT */ - 0xa2, /* FC64_USER_MARSHAL */ - (NDR64_UINT8) 128 /* 0x80 */, - (NDR64_UINT16) 1 /* 0x1 */, - (NDR64_UINT16) 7 /* 0x7 */, - (NDR64_UINT16) 8 /* 0x8 */, - (NDR64_UINT32) 24 /* 0x18 */, - (NDR64_UINT32) 0 /* 0x0 */, - &__midl_frag569 -}; - -static const __midl_frag567_t __midl_frag567 = -{ -/* *wireVARIANT */ - 0x20, /* FC64_RP */ - (NDR64_UINT8) 0 /* 0x0 */, - (NDR64_UINT16) 0 /* 0x0 */, - &__midl_frag568 -}; - -static const __midl_frag566_t __midl_frag566 = -{ -/* CheckBox */ - { - /* CheckBox */ /* procedure CheckBox */ - (NDR64_UINT32) 3014979 /* 0x2e0143 */, /* auto handle */ /* IsIntrepreted, [object], ServerMustSize, ClientMustSize, HasReturn, ServerCorrelation */ - (NDR64_UINT32) 32 /* 0x20 */ , /* Stack size */ - (NDR64_UINT32) 0 /* 0x0 */, - (NDR64_UINT32) 8 /* 0x8 */, - (NDR64_UINT16) 0 /* 0x0 */, - (NDR64_UINT16) 0 /* 0x0 */, - (NDR64_UINT16) 3 /* 0x3 */, - (NDR64_UINT16) 0 /* 0x0 */ - }, - { - /* text */ /* parameter text */ - &__midl_frag568, - { - /* text */ - 1, - 1, - 0, - 1, - 0, - 0, - 0, - 0, - 1, - 0, - 0, - 0, - 0, - (NDR64_UINT16) 0 /* 0x0 */, - 0 - }, /* MustSize, MustFree, [in], SimpleRef */ - (NDR64_UINT16) 0 /* 0x0 */, - 8 /* 0x8 */, /* Stack offset */ - }, - { - /* component */ /* parameter component */ - &__midl_frag570, - { - /* component */ - 1, - 1, - 0, - 0, - 1, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - (NDR64_UINT16) 0 /* 0x0 */, - 0 - }, /* MustSize, MustFree, [out] */ - (NDR64_UINT16) 0 /* 0x0 */, - 16 /* 0x10 */, /* Stack offset */ - }, - { - /* HRESULT */ /* parameter HRESULT */ - &__midl_frag573, - { - /* HRESULT */ - 0, - 0, - 0, - 0, - 1, - 1, - 1, - 1, - 0, - 0, - 0, - 0, - 0, - (NDR64_UINT16) 0 /* 0x0 */, - 0 - }, /* [out], IsReturn, Basetype, ByValue */ - (NDR64_UINT16) 0 /* 0x0 */, - 24 /* 0x18 */, /* Stack offset */ - } -}; - -static const __midl_frag565_t __midl_frag565 = -0x5 /* FC64_INT32 */; - -static const __midl_frag564_t __midl_frag564 = -{ -/* struct _NDR64_CONSTANT_IID_FORMAT */ - 0x24, /* FC64_IP */ - (NDR64_UINT8) 1 /* 0x1 */, - (NDR64_UINT16) 0 /* 0x0 */, - { - 0x15711c8c, - 0x52b3, - 0x4a0b, - {0x90, 0xb0, 0x12, 0x3c, 0x8a, 0x0d, 0x52, 0x5b} - } -}; - -static const __midl_frag563_t __midl_frag563 = -{ -/* *struct _NDR64_POINTER_FORMAT */ - 0x24, /* FC64_IP */ - (NDR64_UINT8) 0 /* 0x0 */, - (NDR64_UINT16) 0 /* 0x0 */, - &__midl_frag564 -}; - -static const __midl_frag562_t __midl_frag562 = -{ -/* **struct _NDR64_POINTER_FORMAT */ - 0x20, /* FC64_RP */ - (NDR64_UINT8) 16 /* 0x10 */, - (NDR64_UINT16) 0 /* 0x0 */, - &__midl_frag563 -}; - -static const __midl_frag561_t __midl_frag561 = -{ -/* *_wireVARIANT */ - 0x21, /* FC64_UP */ - (NDR64_UINT8) 0 /* 0x0 */, - (NDR64_UINT16) 0 /* 0x0 */, - &__midl_frag12 -}; - -static const __midl_frag560_t __midl_frag560 = -{ -/* wireVARIANT */ - 0xa2, /* FC64_USER_MARSHAL */ - (NDR64_UINT8) 128 /* 0x80 */, - (NDR64_UINT16) 1 /* 0x1 */, - (NDR64_UINT16) 7 /* 0x7 */, - (NDR64_UINT16) 8 /* 0x8 */, - (NDR64_UINT32) 24 /* 0x18 */, - (NDR64_UINT32) 0 /* 0x0 */, - &__midl_frag561 -}; - -static const __midl_frag559_t __midl_frag559 = -{ -/* *wireVARIANT */ - 0x20, /* FC64_RP */ - (NDR64_UINT8) 0 /* 0x0 */, - (NDR64_UINT16) 0 /* 0x0 */, - &__midl_frag560 -}; - -static const __midl_frag555_t __midl_frag555 = -{ -/* Button */ - { - /* Button */ /* procedure Button */ - (NDR64_UINT32) 3014979 /* 0x2e0143 */, /* auto handle */ /* IsIntrepreted, [object], ServerMustSize, ClientMustSize, HasReturn, ServerCorrelation */ - (NDR64_UINT32) 40 /* 0x28 */ , /* Stack size */ - (NDR64_UINT32) 0 /* 0x0 */, - (NDR64_UINT32) 8 /* 0x8 */, - (NDR64_UINT16) 0 /* 0x0 */, - (NDR64_UINT16) 0 /* 0x0 */, - (NDR64_UINT16) 4 /* 0x4 */, - (NDR64_UINT16) 0 /* 0x0 */ - }, - { - /* text */ /* parameter text */ - &__midl_frag560, - { - /* text */ - 1, - 1, - 0, - 1, - 0, - 0, - 0, - 0, - 1, - 0, - 0, - 0, - 0, - (NDR64_UINT16) 0 /* 0x0 */, - 0 - }, /* MustSize, MustFree, [in], SimpleRef */ - (NDR64_UINT16) 0 /* 0x0 */, - 8 /* 0x8 */, /* Stack offset */ - }, - { - /* result */ /* parameter result */ - &__midl_frag560, - { - /* result */ - 1, - 1, - 0, - 1, - 0, - 0, - 0, - 0, - 1, - 0, - 0, - 0, - 0, - (NDR64_UINT16) 0 /* 0x0 */, - 0 - }, /* MustSize, MustFree, [in], SimpleRef */ - (NDR64_UINT16) 0 /* 0x0 */, - 16 /* 0x10 */, /* Stack offset */ - }, - { - /* component */ /* parameter component */ - &__midl_frag562, - { - /* component */ - 1, - 1, - 0, - 0, - 1, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - (NDR64_UINT16) 0 /* 0x0 */, - 0 - }, /* MustSize, MustFree, [out] */ - (NDR64_UINT16) 0 /* 0x0 */, - 24 /* 0x18 */, /* Stack offset */ - }, - { - /* HRESULT */ /* parameter HRESULT */ - &__midl_frag565, - { - /* HRESULT */ - 0, - 0, - 0, - 0, - 1, - 1, - 1, - 1, - 0, - 0, - 0, - 0, - 0, - (NDR64_UINT16) 0 /* 0x0 */, - 0 - }, /* [out], IsReturn, Basetype, ByValue */ - (NDR64_UINT16) 0 /* 0x0 */, - 32 /* 0x20 */, /* Stack offset */ - } -}; - -static const __midl_frag554_t __midl_frag554 = -0x5 /* FC64_INT32 */; - -static const __midl_frag553_t __midl_frag553 = -{ -/* struct _NDR64_CONSTANT_IID_FORMAT */ - 0x24, /* FC64_IP */ - (NDR64_UINT8) 1 /* 0x1 */, - (NDR64_UINT16) 0 /* 0x0 */, - { - 0x15711c8c, - 0x52b3, - 0x4a0b, - {0x90, 0xb0, 0x12, 0x3c, 0x8a, 0x0d, 0x52, 0x5b} - } -}; - -static const __midl_frag552_t __midl_frag552 = -{ -/* *struct _NDR64_POINTER_FORMAT */ - 0x24, /* FC64_IP */ - (NDR64_UINT8) 0 /* 0x0 */, - (NDR64_UINT16) 0 /* 0x0 */, - &__midl_frag553 -}; - -static const __midl_frag551_t __midl_frag551 = -{ -/* **struct _NDR64_POINTER_FORMAT */ - 0x20, /* FC64_RP */ - (NDR64_UINT8) 16 /* 0x10 */, - (NDR64_UINT16) 0 /* 0x0 */, - &__midl_frag552 -}; - -static const __midl_frag550_t __midl_frag550 = -{ -/* *_wireVARIANT */ - 0x21, /* FC64_UP */ - (NDR64_UINT8) 0 /* 0x0 */, - (NDR64_UINT16) 0 /* 0x0 */, - &__midl_frag12 -}; - -static const __midl_frag549_t __midl_frag549 = -{ -/* wireVARIANT */ - 0xa2, /* FC64_USER_MARSHAL */ - (NDR64_UINT8) 128 /* 0x80 */, - (NDR64_UINT16) 1 /* 0x1 */, - (NDR64_UINT16) 7 /* 0x7 */, - (NDR64_UINT16) 8 /* 0x8 */, - (NDR64_UINT32) 24 /* 0x18 */, - (NDR64_UINT32) 0 /* 0x0 */, - &__midl_frag550 -}; - -static const __midl_frag548_t __midl_frag548 = -{ -/* *wireVARIANT */ - 0x20, /* FC64_RP */ - (NDR64_UINT8) 0 /* 0x0 */, - (NDR64_UINT16) 0 /* 0x0 */, - &__midl_frag549 + (NDR64_UINT16) 0 /* 0x0 */, + 0 + }, /* MustSize, MustFree, [out] */ + (NDR64_UINT16) 0 /* 0x0 */, + 16 /* 0x10 */, /* Stack offset */ + }, + { + /* HRESULT */ /* parameter HRESULT */ + &__midl_frag589, + { + /* HRESULT */ + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + (NDR64_UINT16) 0 /* 0x0 */, + 0 + }, /* [out], IsReturn, Basetype, ByValue */ + (NDR64_UINT16) 0 /* 0x0 */, + 24 /* 0x18 */, /* Stack offset */ + } }; -static const __midl_frag547_t __midl_frag547 = +static const __midl_frag555_t __midl_frag555 = { -/* Form */ +/* Button */ { - /* Form */ /* procedure Form */ + /* Button */ /* procedure Button */ (NDR64_UINT32) 3014979 /* 0x2e0143 */, /* auto handle */ /* IsIntrepreted, [object], ServerMustSize, ClientMustSize, HasReturn, ServerCorrelation */ - (NDR64_UINT32) 32 /* 0x20 */ , /* Stack size */ + (NDR64_UINT32) 40 /* 0x28 */ , /* Stack size */ (NDR64_UINT32) 0 /* 0x0 */, (NDR64_UINT32) 8 /* 0x8 */, (NDR64_UINT16) 0 /* 0x0 */, (NDR64_UINT16) 0 /* 0x0 */, - (NDR64_UINT16) 3 /* 0x3 */, + (NDR64_UINT16) 4 /* 0x4 */, (NDR64_UINT16) 0 /* 0x0 */ }, { - /* title */ /* parameter title */ - &__midl_frag549, + /* text */ /* parameter text */ + &__midl_frag584, { - /* title */ + /* text */ 1, 1, 0, @@ -6318,8 +5525,32 @@ static const __midl_frag547_t __midl_frag547 = 8 /* 0x8 */, /* Stack offset */ }, { + /* result */ /* parameter result */ + &__midl_frag584, + { + /* result */ + 1, + 1, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + (NDR64_UINT16) 0 /* 0x0 */, + 0 + }, /* MustSize, MustFree, [in], SimpleRef */ + (NDR64_UINT16) 0 /* 0x0 */, + 16 /* 0x10 */, /* Stack offset */ + }, + { /* component */ /* parameter component */ - &__midl_frag551, + &__midl_frag586, { /* component */ 1, @@ -6339,11 +5570,11 @@ static const __midl_frag547_t __midl_frag547 = 0 }, /* MustSize, MustFree, [out] */ (NDR64_UINT16) 0 /* 0x0 */, - 16 /* 0x10 */, /* Stack offset */ + 24 /* 0x18 */, /* Stack offset */ }, { /* HRESULT */ /* parameter HRESULT */ - &__midl_frag554, + &__midl_frag589, { /* HRESULT */ 0, @@ -6363,20 +5594,17 @@ static const __midl_frag547_t __midl_frag547 = 0 }, /* [out], IsReturn, Basetype, ByValue */ (NDR64_UINT16) 0 /* 0x0 */, - 24 /* 0x18 */, /* Stack offset */ + 32 /* 0x20 */, /* Stack offset */ } }; -static const __midl_frag546_t __midl_frag546 = -0x5 /* FC64_INT32 */; - static const __midl_frag544_t __midl_frag544 = { /* *int */ 0x20, /* FC64_RP */ (NDR64_UINT8) 12 /* 0xc */, (NDR64_UINT16) 0 /* 0x0 */, - &__midl_frag546 + &__midl_frag589 }; static const __midl_frag543_t __midl_frag543 = @@ -6395,7 +5623,7 @@ static const __midl_frag543_t __midl_frag543 = }, { /* result */ /* parameter result */ - &__midl_frag546, + &__midl_frag589, { /* result */ 0, @@ -6419,7 +5647,7 @@ static const __midl_frag543_t __midl_frag543 = }, { /* HRESULT */ /* parameter HRESULT */ - &__midl_frag546, + &__midl_frag589, { /* HRESULT */ 0, @@ -6459,7 +5687,7 @@ static const __midl_frag540_t __midl_frag540 = }, { /* val */ /* parameter val */ - &__midl_frag546, + &__midl_frag589, { /* val */ 0, @@ -6483,7 +5711,7 @@ static const __midl_frag540_t __midl_frag540 = }, { /* HRESULT */ /* parameter HRESULT */ - &__midl_frag546, + &__midl_frag589, { /* HRESULT */ 0, @@ -6550,7 +5778,7 @@ static const __midl_frag533_t __midl_frag533 = }, { /* HRESULT */ /* parameter HRESULT */ - &__midl_frag546, + &__midl_frag589, { /* HRESULT */ 0, @@ -6623,7 +5851,7 @@ static const __midl_frag529_t __midl_frag529 = }, { /* HRESULT */ /* parameter HRESULT */ - &__midl_frag546, + &__midl_frag589, { /* HRESULT */ 0, @@ -6709,7 +5937,7 @@ static const __midl_frag525_t __midl_frag525 = }, { /* HRESULT */ /* parameter HRESULT */ - &__midl_frag546, + &__midl_frag589, { /* HRESULT */ 0, @@ -6804,7 +6032,7 @@ static const __midl_frag520_t __midl_frag520 = }, { /* HRESULT */ /* parameter HRESULT */ - &__midl_frag546, + &__midl_frag589, { /* HRESULT */ 0, @@ -6844,7 +6072,7 @@ static const __midl_frag472_t __midl_frag472 = }, { /* HRESULT */ /* parameter HRESULT */ - &__midl_frag546, + &__midl_frag589, { /* HRESULT */ 0, @@ -6884,7 +6112,7 @@ static const __midl_frag465_t __midl_frag465 = }, { /* max */ /* parameter max */ - &__midl_frag549, + &__midl_frag584, { /* max */ 1, @@ -6908,7 +6136,7 @@ static const __midl_frag465_t __midl_frag465 = }, { /* HRESULT */ /* parameter HRESULT */ - &__midl_frag546, + &__midl_frag589, { /* HRESULT */ 0, @@ -7003,7 +6231,7 @@ static const __midl_frag460_t __midl_frag460 = }, { /* HRESULT */ /* parameter HRESULT */ - &__midl_frag546, + &__midl_frag589, { /* HRESULT */ 0, @@ -7099,7 +6327,7 @@ static const __midl_frag387_t __midl_frag387 = }, { /* HRESULT */ /* parameter HRESULT */ - &__midl_frag546, + &__midl_frag589, { /* HRESULT */ 0, @@ -7171,7 +6399,7 @@ static const __midl_frag375_t __midl_frag375 = }, { /* key */ /* parameter key */ - &__midl_frag549, + &__midl_frag584, { /* key */ 1, @@ -7219,7 +6447,7 @@ static const __midl_frag375_t __midl_frag375 = }, { /* HRESULT */ /* parameter HRESULT */ - &__midl_frag546, + &__midl_frag589, { /* HRESULT */ 0, @@ -7295,7 +6523,7 @@ static const __midl_frag367_t __midl_frag367 = }, { /* HRESULT */ /* parameter HRESULT */ - &__midl_frag546, + &__midl_frag589, { /* HRESULT */ 0, @@ -7391,7 +6619,7 @@ static const __midl_frag334_t __midl_frag334 = }, { /* HRESULT */ /* parameter HRESULT */ - &__midl_frag546, + &__midl_frag589, { /* HRESULT */ 0, @@ -7455,7 +6683,7 @@ static const __midl_frag324_t __midl_frag324 = }, { /* HRESULT */ /* parameter HRESULT */ - &__midl_frag546, + &__midl_frag589, { /* HRESULT */ 0, @@ -7519,7 +6747,7 @@ static const __midl_frag305_t __midl_frag305 = }, { /* panel */ /* parameter panel */ - &__midl_frag549, + &__midl_frag584, { /* panel */ 1, @@ -7567,7 +6795,7 @@ static const __midl_frag305_t __midl_frag305 = }, { /* HRESULT */ /* parameter HRESULT */ - &__midl_frag546, + &__midl_frag589, { /* HRESULT */ 0, @@ -7663,7 +6891,7 @@ static const __midl_frag300_t __midl_frag300 = }, { /* HRESULT */ /* parameter HRESULT */ - &__midl_frag546, + &__midl_frag589, { /* HRESULT */ 0, @@ -7751,7 +6979,7 @@ static const __midl_frag293_t __midl_frag293 = }, { /* HRESULT */ /* parameter HRESULT */ - &__midl_frag546, + &__midl_frag589, { /* HRESULT */ 0, @@ -7815,7 +7043,7 @@ static const __midl_frag286_t __midl_frag286 = }, { /* val */ /* parameter val */ - &__midl_frag549, + &__midl_frag584, { /* val */ 1, @@ -7839,7 +7067,7 @@ static const __midl_frag286_t __midl_frag286 = }, { /* HRESULT */ /* parameter HRESULT */ - &__midl_frag546, + &__midl_frag589, { /* HRESULT */ 0, @@ -7935,7 +7163,7 @@ static const __midl_frag281_t __midl_frag281 = }, { /* HRESULT */ /* parameter HRESULT */ - &__midl_frag546, + &__midl_frag589, { /* HRESULT */ 0, @@ -7975,7 +7203,7 @@ static const __midl_frag270_t __midl_frag270 = }, { /* file1 */ /* parameter file1 */ - &__midl_frag549, + &__midl_frag584, { /* file1 */ 1, @@ -7999,7 +7227,7 @@ static const __midl_frag270_t __midl_frag270 = }, { /* file2 */ /* parameter file2 */ - &__midl_frag549, + &__midl_frag584, { /* file2 */ 1, @@ -8023,7 +7251,7 @@ static const __midl_frag270_t __midl_frag270 = }, { /* buttons */ /* parameter buttons */ - &__midl_frag546, + &__midl_frag589, { /* buttons */ 0, @@ -8047,7 +7275,7 @@ static const __midl_frag270_t __midl_frag270 = }, { /* result */ /* parameter result */ - &__midl_frag546, + &__midl_frag589, { /* result */ 0, @@ -8071,7 +7299,7 @@ static const __midl_frag270_t __midl_frag270 = }, { /* HRESULT */ /* parameter HRESULT */ - &__midl_frag546, + &__midl_frag589, { /* HRESULT */ 0, @@ -8111,7 +7339,7 @@ static const __midl_frag257_t __midl_frag257 = }, { /* file */ /* parameter file */ - &__midl_frag549, + &__midl_frag584, { /* file */ 1, @@ -8159,7 +7387,7 @@ static const __midl_frag257_t __midl_frag257 = }, { /* buttons */ /* parameter buttons */ - &__midl_frag546, + &__midl_frag589, { /* buttons */ 0, @@ -8183,7 +7411,7 @@ static const __midl_frag257_t __midl_frag257 = }, { /* title */ /* parameter title */ - &__midl_frag549, + &__midl_frag584, { /* title */ 1, @@ -8207,7 +7435,7 @@ static const __midl_frag257_t __midl_frag257 = }, { /* result */ /* parameter result */ - &__midl_frag546, + &__midl_frag589, { /* result */ 0, @@ -8231,7 +7459,7 @@ static const __midl_frag257_t __midl_frag257 = }, { /* HRESULT */ /* parameter HRESULT */ - &__midl_frag546, + &__midl_frag589, { /* HRESULT */ 0, @@ -8271,7 +7499,7 @@ static const __midl_frag243_t __midl_frag243 = }, { /* file */ /* parameter file */ - &__midl_frag549, + &__midl_frag584, { /* file */ 1, @@ -8295,7 +7523,7 @@ static const __midl_frag243_t __midl_frag243 = }, { /* error */ /* parameter error */ - &__midl_frag549, + &__midl_frag584, { /* error */ 1, @@ -8319,7 +7547,7 @@ static const __midl_frag243_t __midl_frag243 = }, { /* buttons */ /* parameter buttons */ - &__midl_frag546, + &__midl_frag589, { /* buttons */ 0, @@ -8343,7 +7571,7 @@ static const __midl_frag243_t __midl_frag243 = }, { /* title */ /* parameter title */ - &__midl_frag549, + &__midl_frag584, { /* title */ 1, @@ -8367,7 +7595,7 @@ static const __midl_frag243_t __midl_frag243 = }, { /* result */ /* parameter result */ - &__midl_frag546, + &__midl_frag589, { /* result */ 0, @@ -8391,7 +7619,7 @@ static const __midl_frag243_t __midl_frag243 = }, { /* HRESULT */ /* parameter HRESULT */ - &__midl_frag546, + &__midl_frag589, { /* HRESULT */ 0, @@ -8503,7 +7731,7 @@ static const __midl_frag233_t __midl_frag233 = }, { /* HRESULT */ /* parameter HRESULT */ - &__midl_frag546, + &__midl_frag589, { /* HRESULT */ 0, @@ -8599,7 +7827,7 @@ static const __midl_frag221_t __midl_frag221 = }, { /* HRESULT */ /* parameter HRESULT */ - &__midl_frag546, + &__midl_frag589, { /* HRESULT */ 0, @@ -8695,7 +7923,7 @@ static const __midl_frag216_t __midl_frag216 = }, { /* HRESULT */ /* parameter HRESULT */ - &__midl_frag546, + &__midl_frag589, { /* HRESULT */ 0, @@ -8759,7 +7987,7 @@ static const __midl_frag182_t __midl_frag182 = }, { /* title */ /* parameter title */ - &__midl_frag549, + &__midl_frag584, { /* title */ 1, @@ -8783,7 +8011,7 @@ static const __midl_frag182_t __midl_frag182 = }, { /* _default */ /* parameter _default */ - &__midl_frag549, + &__midl_frag584, { /* _default */ 1, @@ -8831,7 +8059,7 @@ static const __midl_frag182_t __midl_frag182 = }, { /* HRESULT */ /* parameter HRESULT */ - &__midl_frag546, + &__midl_frag589, { /* HRESULT */ 0, @@ -8927,7 +8155,7 @@ static const __midl_frag173_t __midl_frag173 = }, { /* HRESULT */ /* parameter HRESULT */ - &__midl_frag546, + &__midl_frag589, { /* HRESULT */ 0, @@ -8957,7 +8185,7 @@ static const __midl_frag150_t __midl_frag150 = 0x21, /* FC64_UP */ (NDR64_UINT8) 8 /* 0x8 */, (NDR64_UINT16) 0 /* 0x0 */, - &__midl_frag546 + &__midl_frag589 }; static const __midl_frag147_t __midl_frag147 = @@ -9031,7 +8259,7 @@ static const __midl_frag130_t __midl_frag130 = 0x21, /* FC64_UP */ (NDR64_UINT8) 16 /* 0x10 */, (NDR64_UINT16) 0 /* 0x0 */, - &__midl_frag550 + &__midl_frag585 }; static const __midl_frag129_t __midl_frag129 = @@ -9310,7 +8538,7 @@ static const __midl_frag90_t __midl_frag90 = { /* struct _NDR64_ARRAY_ELEMENT_INFO */ (NDR64_UINT32) 4 /* 0x4 */, - &__midl_frag546 + &__midl_frag589 } }; @@ -9602,19 +8830,6 @@ static const __midl_frag77_t __midl_frag77 = } }; -static const __midl_frag73_t __midl_frag73 = -{ -/* */ - (NDR64_UINT32) 1 /* 0x1 */, - { - /* struct _NDR64_EXPR_VAR */ - 0x3, /* FC_EXPR_VAR */ - 0x6, /* FC64_UINT32 */ - (NDR64_UINT16) 0 /* 0x0 */, - (NDR64_UINT32) 0 /* 0x0 */ - } -}; - static const __midl_frag72_t __midl_frag72 = { /* ** */ @@ -9635,7 +8850,7 @@ static const __midl_frag72_t __midl_frag72 = }, (NDR64_UINT8) 0 /* 0x0 */, (NDR64_UINT32) 8 /* 0x8 */, - &__midl_frag73 + &__midl_frag96 }, { /* */ @@ -9914,7 +9129,7 @@ static const __midl_frag62_t __midl_frag62 = }, (NDR64_UINT8) 0 /* 0x0 */, (NDR64_UINT32) 8 /* 0x8 */, - &__midl_frag73 + &__midl_frag96 }, { /* */ @@ -10044,7 +9259,7 @@ static const __midl_frag57_t __midl_frag57 = }, (NDR64_UINT8) 0 /* 0x0 */, (NDR64_UINT32) 8 /* 0x8 */, - &__midl_frag73 + &__midl_frag96 }, { /* */ @@ -10081,7 +9296,7 @@ static const __midl_frag57_t __midl_frag57 = { /* struct _NDR64_ARRAY_ELEMENT_INFO */ (NDR64_UINT32) 8 /* 0x8 */, - &__midl_frag550 + &__midl_frag585 } }; @@ -10154,19 +9369,6 @@ static const __midl_frag55_t __midl_frag55 = } }; -static const __midl_frag51_t __midl_frag51 = -{ -/* */ - (NDR64_UINT32) 1 /* 0x1 */, - { - /* struct _NDR64_EXPR_VAR */ - 0x3, /* FC_EXPR_VAR */ - 0x6, /* FC64_UINT32 */ - (NDR64_UINT16) 0 /* 0x0 */, - (NDR64_UINT32) 0 /* 0x0 */ - } -}; - static const __midl_frag50_t __midl_frag50 = { /* ** */ @@ -10187,7 +9389,7 @@ static const __midl_frag50_t __midl_frag50 = }, (NDR64_UINT8) 0 /* 0x0 */, (NDR64_UINT32) 8 /* 0x8 */, - &__midl_frag51 + &__midl_frag96 }, { /* */ @@ -10285,92 +9487,6 @@ static const __midl_frag49_t __midl_frag49 = } }; -static const __midl_frag48_t __midl_frag48 = -{ -/* */ - { - /* **struct _NDR64_POINTER_FORMAT */ - 0x20, /* FC64_RP */ - (NDR64_UINT8) 0 /* 0x0 */, - (NDR64_UINT16) 0 /* 0x0 */, - &__midl_frag43 - } -}; - -static const __midl_frag44_t __midl_frag44 = -{ -/* */ - (NDR64_UINT32) 1 /* 0x1 */, - { - /* struct _NDR64_EXPR_VAR */ - 0x3, /* FC_EXPR_VAR */ - 0x6, /* FC64_UINT32 */ - (NDR64_UINT16) 0 /* 0x0 */, - (NDR64_UINT32) 0 /* 0x0 */ - } -}; - -static const __midl_frag43_t __midl_frag43 = -{ -/* ** */ - { - /* **struct _NDR64_CONF_ARRAY_HEADER_FORMAT */ - 0x41, /* FC64_CONF_ARRAY */ - (NDR64_UINT8) 7 /* 0x7 */, - { - /* **struct _NDR64_CONF_ARRAY_HEADER_FORMAT */ - 1, - 0, - 0, - 0, - 0, - 0, - 0, - 0 - }, - (NDR64_UINT8) 0 /* 0x0 */, - (NDR64_UINT32) 8 /* 0x8 */, - &__midl_frag44 - }, - { - /* */ - { - /* struct _NDR64_REPEAT_FORMAT */ - 0x82, /* FC64_VARIABLE_REPEAT */ - { - /* struct _NDR64_REPEAT_FORMAT */ - (NDR64_UINT8) 0 /* 0x0 */, - (NDR64_UINT8) 0 /* 0x0 */ - }, - (NDR64_UINT16) 0 /* 0x0 */, - (NDR64_UINT32) 8 /* 0x8 */, - (NDR64_UINT32) 0 /* 0x0 */, - (NDR64_UINT32) 1 /* 0x1 */ - }, - { - /* */ - { - /* struct _NDR64_POINTER_INSTANCE_HEADER_FORMAT */ - (NDR64_UINT32) 0 /* 0x0 */, - (NDR64_UINT32) 0 /* 0x0 */ - }, - { - /* *struct _NDR64_POINTER_FORMAT */ - 0x24, /* FC64_IP */ - (NDR64_UINT8) 0 /* 0x0 */, - (NDR64_UINT16) 0 /* 0x0 */, - &__midl_frag390 - } - }, - 0x93 /* FC64_END */ - }, - { - /* struct _NDR64_ARRAY_ELEMENT_INFO */ - (NDR64_UINT32) 8 /* 0x8 */, - &__midl_frag389 - } -}; - static const __midl_frag42_t __midl_frag42 = { /* SAFEARR_UNKNOWN */ @@ -10393,7 +9509,7 @@ static const __midl_frag42_t __midl_frag42 = (NDR64_UINT32) 16 /* 0x10 */, 0, 0, - &__midl_frag48, + &__midl_frag77, }, { /* */ @@ -10460,7 +9576,7 @@ static const __midl_frag38_t __midl_frag38 = }, (NDR64_UINT8) 0 /* 0x0 */, (NDR64_UINT32) 8 /* 0x8 */, - &__midl_frag44 + &__midl_frag96 }, { /* */ @@ -10829,7 +9945,7 @@ static const __midl_frag13_t __midl_frag13 = { /* struct _NDR64_UNION_ARM */ (NDR64_INT64) 3 /* 0x3 */, - &__midl_frag546, + &__midl_frag589, (NDR64_UINT32) 0 /* 0x0 */ }, { @@ -10865,7 +9981,7 @@ static const __midl_frag13_t __midl_frag13 = { /* struct _NDR64_UNION_ARM */ (NDR64_INT64) 10 /* 0xa */, - &__midl_frag546, + &__midl_frag589, (NDR64_UINT32) 0 /* 0x0 */ }, { @@ -11021,7 +10137,7 @@ static const __midl_frag13_t __midl_frag13 = { /* struct _NDR64_UNION_ARM */ (NDR64_INT64) 19 /* 0x13 */, - &__midl_frag546, + &__midl_frag589, (NDR64_UINT32) 0 /* 0x0 */ }, { @@ -11033,13 +10149,13 @@ static const __midl_frag13_t __midl_frag13 = { /* struct _NDR64_UNION_ARM */ (NDR64_INT64) 22 /* 0x16 */, - &__midl_frag546, + &__midl_frag589, (NDR64_UINT32) 0 /* 0x0 */ }, { /* struct _NDR64_UNION_ARM */ (NDR64_INT64) 23 /* 0x17 */, - &__midl_frag546, + &__midl_frag589, (NDR64_UINT32) 0 /* 0x0 */ }, { @@ -11155,19 +10271,6 @@ static const __midl_frag12_t __midl_frag12 = } }; -static const __midl_frag7_t __midl_frag7 = -{ -/* */ - (NDR64_UINT32) 1 /* 0x1 */, - { - /* struct _NDR64_EXPR_VAR */ - 0x3, /* FC_EXPR_VAR */ - 0x6, /* FC64_UINT32 */ - (NDR64_UINT16) 0 /* 0x0 */, - (NDR64_UINT32) 4 /* 0x4 */ - } -}; - static const __midl_frag6_t __midl_frag6 = { /* */ @@ -11188,7 +10291,7 @@ static const __midl_frag6_t __midl_frag6 = }, (NDR64_UINT8) 0 /* 0x0 */, (NDR64_UINT32) 2 /* 0x2 */, - &__midl_frag7 + &__midl_frag67 }, { /* struct _NDR64_ARRAY_ELEMENT_INFO */ @@ -11261,7 +10364,7 @@ static const __midl_frag2_t __midl_frag2 = }, { /* buttons */ /* parameter buttons */ - &__midl_frag549, + &__midl_frag584, { /* buttons */ 1, @@ -11285,7 +10388,7 @@ static const __midl_frag2_t __midl_frag2 = }, { /* title */ /* parameter title */ - &__midl_frag549, + &__midl_frag584, { /* title */ 1, @@ -11309,7 +10412,7 @@ static const __midl_frag2_t __midl_frag2 = }, { /* result */ /* parameter result */ - &__midl_frag546, + &__midl_frag589, { /* result */ 0, @@ -11333,7 +10436,7 @@ static const __midl_frag2_t __midl_frag2 = }, { /* HRESULT */ /* parameter HRESULT */ - &__midl_frag546, + &__midl_frag589, { /* HRESULT */ 0, @@ -12506,10 +11609,10 @@ const CInterfaceStubVtbl _ISalamanderGuiFormStubVtbl = #pragma code_seg(".orpc") static const FormatInfoRef ISalamanderGui_Ndr64ProcTable[] = { - &__midl_frag547, + &__midl_frag582, &__midl_frag555, - &__midl_frag566, - &__midl_frag574, + &__midl_frag582, + &__midl_frag582, &__midl_frag582 }; diff --git a/src/plugins/demoplug/demoplug.h b/src/plugins/demoplug/demoplug.h index 761eef7a..835e8121 100644 --- a/src/plugins/demoplug/demoplug.h +++ b/src/plugins/demoplug/demoplug.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 Taskscape Ltd +// SPDX-FileCopyrightText: 2023 Taskscape Ltd // SPDX-License-Identifier: GPL-2.0-or-later //**************************************************************************** @@ -499,14 +499,14 @@ class CPluginFSDataInterface : public CPluginDataInterfaceAbstract //**************************************************************************** // -// CTopIndexMem +// CScrollPositionMemory // // memory for the listbox top index in the panel - used by CPluginFSInterface for proper // ExecuteOnFS behavior (preserves the top index when entering and leaving subdirectories) #define TOP_INDEX_MEM_SIZE 50 // number of remembered top indexes (levels), at least 1 -class CTopIndexMem +class CScrollPositionMemory { protected: // path for the last remembered top index @@ -515,7 +515,7 @@ class CTopIndexMem int TopIndexesCount; // number of stored top indexes public: - CTopIndexMem() { Clear(); } + CScrollPositionMemory() { Clear(); } void Clear() { Path[0] = 0; @@ -537,7 +537,7 @@ class CPluginFSInterface : public CPluginFSInterfaceAbstract char Path[MAX_PATH]; // current path BOOL PathError; // TRUE if ListCurrentPath failed (path error); ChangePath will be called BOOL FatalError; // TRUE if ListCurrentPath failed (fatal error); ChangePath will be called - CTopIndexMem TopIndexMem; // top-index cache used by ExecuteOnFS() + CScrollPositionMemory TopIndexMem; // top-index cache used by ExecuteOnFS() BOOL CalledFromDisconnectDialog; // TRUE = the user wants to disconnect this FS from the Disconnect dialog (F12) public: diff --git a/src/plugins/demoplug/fs1.cpp b/src/plugins/demoplug/fs1.cpp index 48a5af51..5ef3dbff 100644 --- a/src/plugins/demoplug/fs1.cpp +++ b/src/plugins/demoplug/fs1.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 Taskscape Ltd +// SPDX-FileCopyrightText: 2023 Taskscape Ltd // SPDX-License-Identifier: GPL-2.0-or-later //**************************************************************************** @@ -446,10 +446,10 @@ CPluginInterfaceForFS::ExecuteOnFS(int panel, CPluginFSInterfaceAbstract* plugin //**************************************************************************** // -// CTopIndexMem +// CScrollPositionMemory // -void CTopIndexMem::Push(const char* path, int topIndex) +void CScrollPositionMemory::Push(const char* path, int topIndex) { // determine whether path follows Path (path == Path+"\\name") const char* s = path + strlen(path); @@ -491,7 +491,7 @@ void CTopIndexMem::Push(const char* path, int topIndex) } } -BOOL CTopIndexMem::FindAndPop(const char* path, int& topIndex) +BOOL CScrollPositionMemory::FindAndPop(const char* path, int& topIndex) { // determine whether path matches Path (path == Path) int l1 = (int)strlen(path); diff --git a/src/plugins/ftp/fs1.cpp b/src/plugins/ftp/fs1.cpp index 6ac68b51..2801dae7 100644 --- a/src/plugins/ftp/fs1.cpp +++ b/src/plugins/ftp/fs1.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 Taskscape Ltd +// SPDX-FileCopyrightText: 2023 Taskscape Ltd // SPDX-License-Identifier: GPL-2.0-or-later // CommentsTranslationProject: TRANSLATED @@ -742,10 +742,10 @@ void CPluginInterfaceForFS::ConvertPathToExternal(const char* fsName, int fsName //**************************************************************************** // -// CTopIndexMem +// CScrollPositionMemory // -void CTopIndexMem::Push(CFTPServerPathType type, const char* path, int topIndex) +void CScrollPositionMemory::Push(CFTPServerPathType type, const char* path, int topIndex) { // determine whether path follows Path (path == Path+"/name") char testPath[FTP_MAX_PATH]; @@ -776,7 +776,7 @@ void CTopIndexMem::Push(CFTPServerPathType type, const char* path, int topIndex) } } -BOOL CTopIndexMem::FindAndPop(CFTPServerPathType type, const char* path, int& topIndex) +BOOL CScrollPositionMemory::FindAndPop(CFTPServerPathType type, const char* path, int& topIndex) { // determine whether path matches Path (path == Path) if (FTPIsTheSameServerPath(type, path, Path)) diff --git a/src/plugins/ftp/ftp.h b/src/plugins/ftp/ftp.h index 8d795b86..6a1b01d5 100644 --- a/src/plugins/ftp/ftp.h +++ b/src/plugins/ftp/ftp.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 Taskscape Ltd +// SPDX-FileCopyrightText: 2023 Taskscape Ltd // SPDX-License-Identifier: GPL-2.0-or-later // CommentsTranslationProject: TRANSLATED @@ -1242,14 +1242,14 @@ class CPluginInterface : public CPluginInterfaceAbstract //**************************************************************************** // -// CTopIndexMem +// CScrollPositionMemory // // top-index memory of the list box in the panel - used by CPluginFSInterface for correct // behavior of ExecuteOnFS (preserving the top index after entering and leaving a subdirectory) #define TOP_INDEX_MEM_SIZE 50 // number of remembered top indices (levels), at least 1 -class CTopIndexMem +class CScrollPositionMemory { protected: // path for the last remembered top index (note it is case-sensitive) @@ -1258,7 +1258,7 @@ class CTopIndexMem int TopIndexesCount; // number of remembered top indices public: - CTopIndexMem() { Clear(); } + CScrollPositionMemory() { Clear(); } // clears the memory void Clear() @@ -1502,7 +1502,7 @@ class CPluginFSInterface : public CPluginFSInterfaceAbstract CFTPErrorState ErrorState; BOOL IsDetached; // is this FS detached? (FALSE = it is in a panel) - CTopIndexMem TopIndexMem; // top-index memory for ExecuteOnFS() + CScrollPositionMemory TopIndexMem; // top-index memory for ExecuteOnFS() CControlConnectionSocket* ControlConnection; // "control connection" socket to the FTP server (NULL == never connected) char RescuePath[FTP_MAX_PATH]; // rescue path on FTP - try when ChangePath() can no longer shorten the path diff --git a/src/plugins/pak/dll/pak_dll.cpp b/src/plugins/pak/dll/pak_dll.cpp index dde446a3..21439638 100644 --- a/src/plugins/pak/dll/pak_dll.cpp +++ b/src/plugins/pak/dll/pak_dll.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 Taskscape Ltd +// SPDX-FileCopyrightText: 2023 Taskscape Ltd // SPDX-License-Identifier: GPL-2.0-or-later #include "precomp.h" @@ -13,19 +13,19 @@ #ifdef PAK_DLL // **************************************************************************** -class C__StrCriticalSection +class CStringResourceLock { public: CRITICAL_SECTION cs; - C__StrCriticalSection() { InitializeCriticalSection(&cs); } - ~C__StrCriticalSection() { DeleteCriticalSection(&cs); } + CStringResourceLock() { InitializeCriticalSection(&cs); } + ~CStringResourceLock() { DeleteCriticalSection(&cs); } }; // ensure timely construction of the critical section #pragma warning(disable : 4073) #pragma init_seg(lib) -C__StrCriticalSection __StrCriticalSection; +CStringResourceLock __StrCriticalSection; // **************************************************************************** diff --git a/src/plugins/regedt/fs.cpp b/src/plugins/regedt/fs.cpp index fcd0c00e..a8908854 100644 --- a/src/plugins/regedt/fs.cpp +++ b/src/plugins/regedt/fs.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 Taskscape Ltd +// SPDX-FileCopyrightText: 2023 Taskscape Ltd // SPDX-License-Identifier: GPL-2.0-or-later #include "precomp.h" @@ -119,12 +119,12 @@ void ReleaseFS() //**************************************************************************** // -// CTopIndexMem +// CScrollPositionMemory // -void CTopIndexMem::Push(LPCWSTR path, int topIndex) +void CScrollPositionMemory::Push(LPCWSTR path, int topIndex) { - CALL_STACK_MESSAGE2("CTopIndexMem::Push(, %d)", topIndex); + CALL_STACK_MESSAGE2("CScrollPositionMemory::Push(, %d)", topIndex); // determine whether path continues Path (path==Path+"\\name") LPCWSTR s = path + wcslen(path); if (s > path && *(s - 1) == L'\\') @@ -166,9 +166,9 @@ void CTopIndexMem::Push(LPCWSTR path, int topIndex) } } -BOOL CTopIndexMem::FindAndPop(LPCWSTR path, int& topIndex) +BOOL CScrollPositionMemory::FindAndPop(LPCWSTR path, int& topIndex) { - CALL_STACK_MESSAGE2("CTopIndexMem::FindAndPop(, %d)", topIndex); + CALL_STACK_MESSAGE2("CScrollPositionMemory::FindAndPop(, %d)", topIndex); // determine whether path matches Path (path==Path) int l1 = (int)wcslen(path); if (l1 > 0 && path[l1 - 1] == L'\\') diff --git a/src/plugins/regedt/regedt.h b/src/plugins/regedt/regedt.h index f3b8b101..9e505371 100644 --- a/src/plugins/regedt/regedt.h +++ b/src/plugins/regedt/regedt.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 Taskscape Ltd +// SPDX-FileCopyrightText: 2023 Taskscape Ltd // SPDX-License-Identifier: GPL-2.0-or-later #pragma once @@ -233,7 +233,7 @@ class CPluginInterfaceForFS : public CPluginInterfaceForFSAbstract //**************************************************************************** // -// CTopIndexMem +// CScrollPositionMemory // // listbox top-index cache in the panel - used by CPluginFSInterface to keep // ExecuteOnFS behavior correct (preserve the top index when entering and @@ -241,7 +241,7 @@ class CPluginInterfaceForFS : public CPluginInterfaceForFSAbstract #define TOP_INDEX_MEM_SIZE 50 // number of remembered top indexes (levels), at least 1 -class CTopIndexMem +class CScrollPositionMemory { protected: // path for the last remembered top index @@ -250,7 +250,7 @@ class CTopIndexMem int TopIndexesCount; // number of cached top indexes public: - CTopIndexMem() { Clear(); } + CScrollPositionMemory() { Clear(); } void Clear() { Path[0] = L'\0'; @@ -299,7 +299,7 @@ class CPluginFSInterface : public CPluginFSInterfaceAbstract public: int CurrentKeyRoot; WCHAR CurrentKeyName[MAX_KEYNAME]; - CTopIndexMem TopIndexMem; // top-index cache for ExecuteOnFS() + CScrollPositionMemory TopIndexMem; // top-index cache for ExecuteOnFS() BOOL FocusFirstNewItem; protected: diff --git a/src/plugins/undelete/library/miscstr.cpp b/src/plugins/undelete/library/miscstr.cpp index 0e59ae77..8fbe00de 100644 --- a/src/plugins/undelete/library/miscstr.cpp +++ b/src/plugins/undelete/library/miscstr.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 Taskscape Ltd +// SPDX-FileCopyrightText: 2023 Taskscape Ltd // SPDX-License-Identifier: GPL-2.0-or-later #include "precomp.h" @@ -19,18 +19,18 @@ // LoadStr() - helper function for reading strings from resources // -class C__StrCriticalSection +class CStringResourceLock { public: CRITICAL_SECTION cs; - C__StrCriticalSection() { NOHANDLES(InitializeCriticalSection(&cs)); } - ~C__StrCriticalSection() { NOHANDLES(DeleteCriticalSection(&cs)); } + CStringResourceLock() { NOHANDLES(InitializeCriticalSection(&cs)); } + ~CStringResourceLock() { NOHANDLES(DeleteCriticalSection(&cs)); } }; // ensure critical section is initialized in the beginning #pragma warning(disable : 4073) #pragma init_seg(lib) -C__StrCriticalSection __StrCriticalSection; +CStringResourceLock __StrCriticalSection; const unsigned short String::STRBUFSIZE = 10240; char* String::StringBuffer = NULL; diff --git a/src/plugins/undelete/undelete.cpp b/src/plugins/undelete/undelete.cpp index 4b0f8cd0..1ef8b47a 100644 --- a/src/plugins/undelete/undelete.cpp +++ b/src/plugins/undelete/undelete.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 Taskscape Ltd +// SPDX-FileCopyrightText: 2023 Taskscape Ltd // SPDX-License-Identifier: GPL-2.0-or-later // **************************************************************************** @@ -69,12 +69,12 @@ CLUSTER_MAP_I cluster_map; // **************************************************************************** // -// CTopIndexMem +// CScrollPositionMemory // -void CTopIndexMem::Push(const char* path, int topIndex) +void CScrollPositionMemory::Push(const char* path, int topIndex) { - CALL_STACK_MESSAGE3("CTopIndexMem::Push(%s, %d)", path, topIndex); + CALL_STACK_MESSAGE3("CScrollPositionMemory::Push(%s, %d)", path, topIndex); // detect if path continues after Path (path==Path+"\\name") const char* s = path + strlen(path); @@ -116,9 +116,9 @@ void CTopIndexMem::Push(const char* path, int topIndex) } } -BOOL CTopIndexMem::FindAndPop(const char* path, int& topIndex) +BOOL CScrollPositionMemory::FindAndPop(const char* path, int& topIndex) { - CALL_STACK_MESSAGE3("CTopIndexMem::FindAndPop(%s, %d)", path, topIndex); + CALL_STACK_MESSAGE3("CScrollPositionMemory::FindAndPop(%s, %d)", path, topIndex); // detect if path match to Path (path==Path) int l1 = (int)strlen(path); diff --git a/src/plugins/undelete/undelete.h b/src/plugins/undelete/undelete.h index 54c3df5e..c64dbe2c 100644 --- a/src/plugins/undelete/undelete.h +++ b/src/plugins/undelete/undelete.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 Taskscape Ltd +// SPDX-FileCopyrightText: 2023 Taskscape Ltd // SPDX-License-Identifier: GPL-2.0-or-later #pragma once @@ -131,14 +131,14 @@ class CPluginFSDataInterface : public CPluginDataInterfaceAbstract //**************************************************************************** // -// CTopIndexMem +// CScrollPositionMemory // // panel listing top index memory - using CPluginFSInterface for correct behavior // of ExecuteOnFS (persistent top-index while entering / leaving directory) #define TOP_INDEX_MEM_SIZE 50 // count of stored top-index (levels), minimal 1 -class CTopIndexMem +class CScrollPositionMemory { protected: // path for last stored top-index @@ -147,7 +147,7 @@ class CTopIndexMem int TopIndexesCount; // count of stored top-index public: - CTopIndexMem() { Clear(); } + CScrollPositionMemory() { Clear(); } void Clear() { Path[0] = 0; @@ -245,7 +245,7 @@ class CPluginFSInterface : public CPluginFSInterfaceAbstract FILE_RECORD_I* CurrentDir; BOOL FatalError; // TRUE when ListCurrentPath failed (fatal error), ChangePath will be called - CTopIndexMem TopIndexMem; // top-index array for ExecuteOnFS() + CScrollPositionMemory TopIndexMem; // top-index array for ExecuteOnFS() BOOL IsSnapshotValid; CVolume Volume; diff --git a/src/plugins/wmobile/fs1.cpp b/src/plugins/wmobile/fs1.cpp index fa63eb6e..34e44e39 100644 --- a/src/plugins/wmobile/fs1.cpp +++ b/src/plugins/wmobile/fs1.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 Taskscape Ltd +// SPDX-FileCopyrightText: 2023 Taskscape Ltd // SPDX-License-Identifier: GPL-2.0-or-later #include "precomp.h" @@ -199,10 +199,10 @@ CPluginInterfaceForFS::DisconnectFS(HWND parent, BOOL isInPanel, int panel, //**************************************************************************** // -// CTopIndexMem +// CScrollPositionMemory // -void CTopIndexMem::Push(const char* path, int topIndex) +void CScrollPositionMemory::Push(const char* path, int topIndex) { // determine whether the path follows Path (path==Path+"\\name") const char* s = path + strlen(path); @@ -244,7 +244,7 @@ void CTopIndexMem::Push(const char* path, int topIndex) } } -BOOL CTopIndexMem::FindAndPop(const char* path, int& topIndex) +BOOL CScrollPositionMemory::FindAndPop(const char* path, int& topIndex) { // determine whether the path corresponds to Path (path==Path) int l1 = (int)strlen(path); diff --git a/src/plugins/wmobile/wmobile.h b/src/plugins/wmobile/wmobile.h index 0bba09d6..75b2c8f7 100644 --- a/src/plugins/wmobile/wmobile.h +++ b/src/plugins/wmobile/wmobile.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 Taskscape Ltd +// SPDX-FileCopyrightText: 2023 Taskscape Ltd // SPDX-License-Identifier: GPL-2.0-or-later #pragma once @@ -127,14 +127,14 @@ class CPluginInterface : public CPluginInterfaceAbstract //**************************************************************************** // -// CTopIndexMem +// CScrollPositionMemory // // top-index memory for the panel list box - used by CPluginFSInterface to keep // ExecuteOnFS behavior consistent (preserves the top index when entering and leaving a subdirectory) #define TOP_INDEX_MEM_SIZE 50 // number of remembered top indexes (levels), at least 1 -class CTopIndexMem +class CScrollPositionMemory { protected: // path for the last remembered top index @@ -143,7 +143,7 @@ class CTopIndexMem int TopIndexesCount; // number of stored top indexes public: - CTopIndexMem() { Clear(); } + CScrollPositionMemory() { Clear(); } void Clear() { Path[0] = 0; @@ -165,7 +165,7 @@ class CPluginFSInterface : public CPluginFSInterfaceAbstract char Path[MAX_PATH]; // current path BOOL PathError; // TRUE if ListCurrentPath failed (path error); triggers ChangePath BOOL FatalError; // TRUE if ListCurrentPath failed (fatal error); triggers ChangePath - CTopIndexMem TopIndexMem; // stored top indexes for ExecuteOnFS() + CScrollPositionMemory TopIndexMem; // stored top indexes for ExecuteOnFS() public: CPluginFSInterface(); diff --git a/src/plugins3.cpp b/src/plugins_archiver.cpp similarity index 100% rename from src/plugins3.cpp rename to src/plugins_archiver.cpp diff --git a/src/plugins4.cpp b/src/plugins_filesystem.cpp similarity index 100% rename from src/plugins4.cpp rename to src/plugins_filesystem.cpp diff --git a/src/plugins2.cpp b/src/plugins_interface.cpp similarity index 100% rename from src/plugins2.cpp rename to src/plugins_interface.cpp diff --git a/src/plugins1.cpp b/src/plugins_loading.cpp similarity index 100% rename from src/plugins1.cpp rename to src/plugins_loading.cpp diff --git a/src/salmon/salmon.cpp b/src/salmon/salmon.cpp index 05f1e3c6..b51872eb 100644 --- a/src/salmon/salmon.cpp +++ b/src/salmon/salmon.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 Taskscape Ltd +// SPDX-FileCopyrightText: 2023 Taskscape Ltd // SPDX-License-Identifier: GPL-2.0-or-later #include "precomp.h" @@ -39,20 +39,20 @@ static WCHAR* AllocUtf8ToWide(const char* text) // **************************************************************************** -class C__StrCriticalSection +class CStringResourceLock { public: CRITICAL_SECTION cs; - C__StrCriticalSection() { HANDLES(InitializeCriticalSection(&cs)); } - ~C__StrCriticalSection() { HANDLES(DeleteCriticalSection(&cs)); } + CStringResourceLock() { HANDLES(InitializeCriticalSection(&cs)); } + ~CStringResourceLock() { HANDLES(DeleteCriticalSection(&cs)); } }; // ensure the critical section is constructed in time #pragma warning(disable : 4073) #pragma init_seg(lib) -C__StrCriticalSection __StrCriticalSection; -C__StrCriticalSection __StrCriticalSection2; +CStringResourceLock __StrCriticalSection; +CStringResourceLock __StrCriticalSection2; // **************************************************************************** diff --git a/src/salamdr7.cpp b/src/shell_environment.cpp similarity index 100% rename from src/salamdr7.cpp rename to src/shell_environment.cpp diff --git a/src/salamdr2.cpp b/src/string_resources.cpp similarity index 99% rename from src/salamdr2.cpp rename to src/string_resources.cpp index cc3c7509..18e7c042 100644 --- a/src/salamdr2.cpp +++ b/src/string_resources.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 Taskscape Ltd +// SPDX-FileCopyrightText: 2023 Taskscape Ltd // SPDX-License-Identifier: GPL-2.0-or-later // CommentsTranslationProject: TRANSLATED @@ -16,20 +16,20 @@ // **************************************************************************** -class C__StrCriticalSection +class CStringResourceLock { public: CRITICAL_SECTION cs; - C__StrCriticalSection() { HANDLES(InitializeCriticalSection(&cs)); } - ~C__StrCriticalSection() { HANDLES(DeleteCriticalSection(&cs)); } + CStringResourceLock() { HANDLES(InitializeCriticalSection(&cs)); } + ~CStringResourceLock() { HANDLES(DeleteCriticalSection(&cs)); } }; // ensure timely construction of the critical section #pragma warning(disable : 4073) #pragma init_seg(lib) -C__StrCriticalSection __StrCriticalSection; -C__StrCriticalSection __StrCriticalSection2; +CStringResourceLock __StrCriticalSection; +CStringResourceLock __StrCriticalSection2; // **************************************************************************** diff --git a/src/stswnd.cpp b/src/stswnd.cpp index f36b2c48..84780623 100644 --- a/src/stswnd.cpp +++ b/src/stswnd.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 Taskscape Ltd +// SPDX-FileCopyrightText: 2023 Taskscape Ltd // SPDX-License-Identifier: GPL-2.0-or-later #include "precomp.h" @@ -182,10 +182,10 @@ static BOOL GetTextExtentExPointUtf8(HDC hdc, const char* text, int textLen, int // // **************************************************************************** -// CStatusWindow +// CPanelStatusBar // -CStatusWindow::CStatusWindow(CFilesWindow* filesWindow, int border, CObjectOrigin origin) : CWindow(origin), HotTrackItems(10, 5) +CPanelStatusBar::CPanelStatusBar(CFilesWindow* filesWindow, int border, CObjectOrigin origin) : CWindow(origin), HotTrackItems(10, 5) { CALL_STACK_MESSAGE_NONE Text = NULL; @@ -231,9 +231,9 @@ CStatusWindow::CStatusWindow(CFilesWindow* filesWindow, int border, CObjectOrigi IDropTargetPtr = NULL; } -CStatusWindow::~CStatusWindow() +CPanelStatusBar::~CPanelStatusBar() { - CALL_STACK_MESSAGE1("CStatusWindow::~CStatusWindow()"); + CALL_STACK_MESSAGE1("CPanelStatusBar::~CPanelStatusBar()"); if (SubTexts != NULL) free(SubTexts); if (Text != NULL) @@ -254,9 +254,9 @@ CStatusWindow::~CStatusWindow() } } -BOOL CStatusWindow::SetSubTexts(DWORD* subTexts, DWORD subTextsCount) +BOOL CPanelStatusBar::SetSubTexts(DWORD* subTexts, DWORD subTextsCount) { - CALL_STACK_MESSAGE2("CStatusWindow::SetSubTexts(, %u)", subTextsCount); + CALL_STACK_MESSAGE2("CPanelStatusBar::SetSubTexts(, %u)", subTextsCount); HotItem = NULL; LastHotItem = NULL; if (SubTexts != NULL) @@ -283,9 +283,9 @@ BOOL CStatusWindow::SetSubTexts(DWORD* subTexts, DWORD subTextsCount) return TRUE; } -BOOL CStatusWindow::SetText(const char* txt, int pathLen) +BOOL CPanelStatusBar::SetText(const char* txt, int pathLen) { - CALL_STACK_MESSAGE3("CStatusWindow::SetText(%s, %d)", txt, pathLen); + CALL_STACK_MESSAGE3("CPanelStatusBar::SetText(%s, %d)", txt, pathLen); if (Text != NULL && strcmp(Text, txt) == 0) { PathLen = pathLen; @@ -331,9 +331,9 @@ BOOL CStatusWindow::SetText(const char* txt, int pathLen) return TRUE; } -void CStatusWindow::BuildHotTrackItems() +void CPanelStatusBar::BuildHotTrackItems() { - CALL_STACK_MESSAGE1("CStatusWindow::BuildHotTrackItems()"); + CALL_STACK_MESSAGE1("CPanelStatusBar::BuildHotTrackItems()"); HDC dc = HANDLES(GetDC(HWindow)); HFONT oldFont = (HFONT)SelectObject(dc, EnvFont); @@ -468,9 +468,9 @@ void CStatusWindow::BuildHotTrackItems() HANDLES(ReleaseDC(HWindow, dc)); } -void CStatusWindow::DestroyWindow() +void CPanelStatusBar::DestroyWindow() { - CALL_STACK_MESSAGE1("CStatusWindow::DestroyWindow()"); + CALL_STACK_MESSAGE1("CPanelStatusBar::DestroyWindow()"); if (ToolBar != NULL) { if (ToolBar->HWindow != NULL) @@ -484,7 +484,7 @@ void CStatusWindow::DestroyWindow() ::DestroyWindow(HWindow); } -void CStatusWindow::SetHidden(int hiddenFilesCount, int hiddenDirsCount) +void CPanelStatusBar::SetHidden(int hiddenFilesCount, int hiddenDirsCount) { CALL_STACK_MESSAGE_NONE BOOL hidden = hiddenFilesCount != 0 || hiddenDirsCount != 0; @@ -501,7 +501,7 @@ void CStatusWindow::SetHidden(int hiddenFilesCount, int hiddenDirsCount) } } -void CStatusWindow::SetHistory(BOOL history) +void CPanelStatusBar::SetHistory(BOOL history) { CALL_STACK_MESSAGE_NONE if (History != history) @@ -515,7 +515,7 @@ void CStatusWindow::SetHistory(BOOL history) } } -void CStatusWindow::SetThrobber(BOOL show, int delay, BOOL calledFromDestroyWindow) +void CPanelStatusBar::SetThrobber(BOOL show, int delay, BOOL calledFromDestroyWindow) { CALL_STACK_MESSAGE_NONE if (!calledFromDestroyWindow) @@ -525,9 +525,9 @@ void CStatusWindow::SetThrobber(BOOL show, int delay, BOOL calledFromDestroyWind if (DelayedThrobber) // ceka se na zobrazeni { if (HWindow == NULL) - TRACE_E("Unexpected situation in CStatusWindow::SetThrobber(): DelayedThrobber is TRUE but HWindow is NULL"); + TRACE_E("Unexpected situation in CPanelStatusBar::SetThrobber(): DelayedThrobber is TRUE but HWindow is NULL"); if (Throbber) - TRACE_E("Unexpected situation in CStatusWindow::SetThrobber(): DelayedThrobber and Throbber are both TRUE"); + TRACE_E("Unexpected situation in CPanelStatusBar::SetThrobber(): DelayedThrobber and Throbber are both TRUE"); KillTimer(HWindow, IDT_DELAYEDTHROBBER); if (Throbber /* jen korekce nekonzistentniho stavu */ || delay <= 0 || !SetTimer(HWindow, IDT_DELAYEDTHROBBER, delay, NULL)) @@ -559,7 +559,7 @@ void CStatusWindow::SetThrobber(BOOL show, int delay, BOOL calledFromDestroyWind if (DelayedThrobber) // ceka se na zobrazeni, ale throbber se ma schovat, konec cekani { if (HWindow == NULL) - TRACE_E("Unexpected situation 2 in CStatusWindow::SetThrobber(): DelayedThrobber is TRUE but HWindow is NULL"); + TRACE_E("Unexpected situation 2 in CPanelStatusBar::SetThrobber(): DelayedThrobber is TRUE but HWindow is NULL"); KillTimer(HWindow, IDT_DELAYEDTHROBBER); DelayedThrobber = FALSE; } @@ -569,7 +569,7 @@ void CStatusWindow::SetThrobber(BOOL show, int delay, BOOL calledFromDestroyWind if (HWindow == NULL && Throbber) { Throbber = FALSE; - TRACE_E("Unexpected situation in CStatusWindow::SetThrobber(): Throbber is TRUE but HWindow is NULL"); + TRACE_E("Unexpected situation in CPanelStatusBar::SetThrobber(): Throbber is TRUE but HWindow is NULL"); } if (HWindow != NULL && !DelayedThrobber && Throbber != show) { @@ -597,7 +597,7 @@ void CStatusWindow::SetThrobber(BOOL show, int delay, BOOL calledFromDestroyWind } } -void CStatusWindow::SetThrobberTooltip(const char* throbberTooltip) +void CPanelStatusBar::SetThrobberTooltip(const char* throbberTooltip) { if (ThrobberTooltip != NULL) { @@ -608,7 +608,7 @@ void CStatusWindow::SetThrobberTooltip(const char* throbberTooltip) ThrobberTooltip = DupStr(throbberTooltip); } -void CStatusWindow::SetSecurity(CSecurityIconState iconState) +void CPanelStatusBar::SetSecurity(CSecurityIconState iconState) { CALL_STACK_MESSAGE_NONE if (Security != iconState) @@ -622,7 +622,7 @@ void CStatusWindow::SetSecurity(CSecurityIconState iconState) } } -void CStatusWindow::SetSecurityTooltip(const char* tooltip) +void CPanelStatusBar::SetSecurityTooltip(const char* tooltip) { if (SecurityTooltip != NULL) { @@ -633,7 +633,7 @@ void CStatusWindow::SetSecurityTooltip(const char* tooltip) SecurityTooltip = DupStr(tooltip); } -int CStatusWindow::ChangeThrobberID() +int CPanelStatusBar::ChangeThrobberID() { static int NewID = 0; // id throbberu musi byt unikatni (tzn. jediny counter pro oba panely) ThrobberID = NewID++; @@ -642,7 +642,7 @@ int CStatusWindow::ChangeThrobberID() return ThrobberID; } -void CStatusWindow::HideThrobberAndSecurityIcon() +void CPanelStatusBar::HideThrobberAndSecurityIcon() { SetThrobber(FALSE); SetThrobberTooltip(NULL); @@ -650,7 +650,7 @@ void CStatusWindow::HideThrobberAndSecurityIcon() SetSecurityTooltip(NULL); } -void CStatusWindow::InvalidateIfNeeded() +void CPanelStatusBar::InvalidateIfNeeded() { CALL_STACK_MESSAGE_NONE if (NeedToInvalidate) @@ -661,7 +661,7 @@ void CStatusWindow::InvalidateIfNeeded() } } -int CStatusWindow::GetNeededHeight() +int CPanelStatusBar::GetNeededHeight() { CALL_STACK_MESSAGE_NONE int height = 2 + EnvFontCharHeight + 2; @@ -678,7 +678,7 @@ int CStatusWindow::GetNeededHeight() return height; } -void CStatusWindow::SetSize(const CQuadWord& size) +void CPanelStatusBar::SetSize(const CQuadWord& size) { CALL_STACK_MESSAGE_NONE if (Size == NULL) @@ -704,7 +704,7 @@ void CStatusWindow::SetSize(const CQuadWord& size) InvalidateRect(HWindow, NULL, FALSE); } -void CStatusWindow::SetLeftPanel(BOOL left) +void CPanelStatusBar::SetLeftPanel(BOOL left) { CALL_STACK_MESSAGE_NONE Left = left; @@ -715,9 +715,9 @@ void CStatusWindow::SetLeftPanel(BOOL left) } } -BOOL CStatusWindow::ToggleToolBar() +BOOL CPanelStatusBar::ToggleToolBar() { - CALL_STACK_MESSAGE1("CStatusWindow::ToggleToolBar()"); + CALL_STACK_MESSAGE1("CPanelStatusBar::ToggleToolBar()"); if (ToolBar == NULL) return FALSE; if (ToolBar->HWindow != NULL) @@ -740,7 +740,7 @@ BOOL CStatusWindow::ToggleToolBar() return TRUE; } -BOOL CStatusWindow::SetDriveIcon(HICON hIcon) +BOOL CPanelStatusBar::SetDriveIcon(HICON hIcon) { CALL_STACK_MESSAGE_NONE if (ToolBar != NULL && ToolBar->HWindow != NULL) @@ -748,7 +748,7 @@ BOOL CStatusWindow::SetDriveIcon(HICON hIcon) return TRUE; } -void CStatusWindow::SetDrivePressed(BOOL pressed) +void CPanelStatusBar::SetDrivePressed(BOOL pressed) { CALL_STACK_MESSAGE_NONE if (ToolBar != NULL && ToolBar->HWindow != NULL) @@ -761,7 +761,7 @@ void CStatusWindow::SetDrivePressed(BOOL pressed) } } -void CStatusWindow::LayoutWindow() +void CPanelStatusBar::LayoutWindow() { CALL_STACK_MESSAGE_NONE SendMessage(HWindow, WM_SIZE, 0, 0); @@ -769,7 +769,7 @@ void CStatusWindow::LayoutWindow() UpdateWindow(HWindow); } -void CStatusWindow::GetHotText(char* buffer, int bufSize) +void CPanelStatusBar::GetHotText(char* buffer, int bufSize) { CALL_STACK_MESSAGE_NONE if (HotItem != NULL && Text != NULL) @@ -783,7 +783,7 @@ void CStatusWindow::GetHotText(char* buffer, int bufSize) buffer[0] = 0; } -BOOL CStatusWindow::FindHotTrackItem(int xPos, int& index) +BOOL CPanelStatusBar::FindHotTrackItem(int xPos, int& index) { CALL_STACK_MESSAGE_NONE int i; @@ -804,7 +804,7 @@ BOOL CStatusWindow::FindHotTrackItem(int xPos, int& index) return FALSE; } -void CStatusWindow::FlashText(BOOL hotTrackOnly) +void CPanelStatusBar::FlashText(BOOL hotTrackOnly) { CALL_STACK_MESSAGE_NONE Repaint(TRUE, hotTrackOnly); @@ -845,7 +845,7 @@ void PaintSymbol(HDC hDC, HDC hMemDC, HBITMAP hBitmap, int xOffset, int width, i SelectObject(hMemDC, hOldBitmap); } -void CStatusWindow::PaintThrobber(HDC hDC) +void CPanelStatusBar::PaintThrobber(HDC hDC) { if ((Border & blTop) == 0) return; @@ -866,7 +866,7 @@ void CStatusWindow::PaintThrobber(HDC hDC) ThrobberFrames->Draw(ThrobberFrame, hDC, x, y, fgClr, IL_DRAW_ASALPHA); } -void CStatusWindow::PaintSecurity(HDC hDC) +void CPanelStatusBar::PaintSecurity(HDC hDC) { if (Security == sisNone || (Border & blTop) == 0) return; @@ -903,9 +903,9 @@ void CStatusWindow::PaintSecurity(HDC hDC) #define ZOOM_WIDTH 9 #define ZOOM_HEIGHT 8 -void CStatusWindow::Paint(HDC hdc, BOOL highlightText, BOOL highlightHotTrackOnly) +void CPanelStatusBar::Paint(HDC hdc, BOOL highlightText, BOOL highlightHotTrackOnly) { - CALL_STACK_MESSAGE3("CStatusWindow::Paint(, %d, %d)", highlightText, highlightHotTrackOnly); + CALL_STACK_MESSAGE3("CPanelStatusBar::Paint(, %d, %d)", highlightText, highlightHotTrackOnly); HDC dc = ItemBitmap.HMemDC; BOOL isDirectoryLine = (Border & blTop) != 0; @@ -1354,7 +1354,7 @@ void CStatusWindow::Paint(HDC hdc, BOOL highlightText, BOOL highlightHotTrackOnl dc, ToolBarWidth, 0, SRCCOPY); } -void CStatusWindow::Repaint(BOOL flashText, BOOL hotTrackOnly) +void CPanelStatusBar::Repaint(BOOL flashText, BOOL hotTrackOnly) { CALL_STACK_MESSAGE_NONE if (HWindow == NULL) @@ -1365,7 +1365,7 @@ void CStatusWindow::Repaint(BOOL flashText, BOOL hotTrackOnly) } /* void -CStatusWindow::RepaintThrobber() +CPanelStatusBar::RepaintThrobber() { CALL_STACK_MESSAGE_NONE if (HWindow == NULL) @@ -1376,7 +1376,7 @@ CStatusWindow::RepaintThrobber() } */ -void CStatusWindow::InvalidateAndUpdate(BOOL update) +void CPanelStatusBar::InvalidateAndUpdate(BOOL update) { CALL_STACK_MESSAGE_NONE if (HWindow == NULL) @@ -1722,9 +1722,9 @@ class CTextDropTarget : public IDropTarget } }; -void CStatusWindow::RegisterDragDrop() +void CPanelStatusBar::RegisterDragDrop() { - CALL_STACK_MESSAGE1("CStatusWindow::RegisterDragDrop()"); + CALL_STACK_MESSAGE1("CPanelStatusBar::RegisterDragDrop()"); CTextDropTarget* dropTarget = new CTextDropTarget(FilesWindow); if (dropTarget != NULL) { @@ -1738,7 +1738,7 @@ void CStatusWindow::RegisterDragDrop() } } -void CStatusWindow::RevokeDragDrop() +void CPanelStatusBar::RevokeDragDrop() { CALL_STACK_MESSAGE_NONE HANDLES(RevokeDragDrop(HWindow)); @@ -1747,9 +1747,9 @@ void CStatusWindow::RevokeDragDrop() #define BUTTON_OFFSET 0 LRESULT -CStatusWindow::WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam) +CPanelStatusBar::WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam) { - SLOW_CALL_STACK_MESSAGE4("CStatusWindow::WindowProc(0x%X, 0x%IX, 0x%IX)", uMsg, wParam, lParam); + SLOW_CALL_STACK_MESSAGE4("CPanelStatusBar::WindowProc(0x%X, 0x%IX, 0x%IX)", uMsg, wParam, lParam); switch (uMsg) { case WM_CREATE: @@ -2359,7 +2359,7 @@ CStatusWindow::WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam) else { KillTimer(HWindow, IDT_DELAYEDTHROBBER); - TRACE_E("CStatusWindow::WindowProc(): Unexpected timer: IDT_DELAYEDTHROBBER"); + TRACE_E("CPanelStatusBar::WindowProc(): Unexpected timer: IDT_DELAYEDTHROBBER"); } } break; @@ -2370,9 +2370,9 @@ CStatusWindow::WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam) } HIMAGELIST -CStatusWindow::CreateDragImage(const char* text, int& dxHotspot, int& dyHotspot, int& imgWidth, int& imgHeight) +CPanelStatusBar::CreateDragImage(const char* text, int& dxHotspot, int& dyHotspot, int& imgWidth, int& imgHeight) { - CALL_STACK_MESSAGE6("CStatusWindow::CreateDragImage(%s, %d, %d, %d, %d)", + CALL_STACK_MESSAGE6("CPanelStatusBar::CreateDragImage(%s, %d, %d, %d, %d)", text, dxHotspot, dyHotspot, imgWidth, imgHeight); int textLen = lstrlen(text); HDC hDC = ItemBitmap.HMemDC; @@ -2406,7 +2406,7 @@ CStatusWindow::CreateDragImage(const char* text, int& dxHotspot, int& dyHotspot, return himl; } -BOOL CStatusWindow::GetTextFrameRect(RECT* r) +BOOL CPanelStatusBar::GetTextFrameRect(RECT* r) { CALL_STACK_MESSAGE_NONE if (HWindow == NULL) @@ -2418,7 +2418,7 @@ BOOL CStatusWindow::GetTextFrameRect(RECT* r) return TRUE; } -BOOL CStatusWindow::GetFilterFrameRect(RECT* r) +BOOL CPanelStatusBar::GetFilterFrameRect(RECT* r) { CALL_STACK_MESSAGE_NONE if (HWindow == NULL) @@ -2432,13 +2432,13 @@ BOOL CStatusWindow::GetFilterFrameRect(RECT* r) return TRUE; } -void CStatusWindow::OnColorsChanged() +void CPanelStatusBar::OnColorsChanged() { if (ToolBar != NULL) ToolBar->OnColorsChanged(); } -void CStatusWindow::SetFont() +void CPanelStatusBar::SetFont() { // mohlo dojit ke zmene velikosti fontu InvalidateRect(HWindow, NULL, TRUE); diff --git a/src/stswnd.h b/src/stswnd.h index 465760d5..62ec7e4d 100644 --- a/src/stswnd.h +++ b/src/stswnd.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 Taskscape Ltd +// SPDX-FileCopyrightText: 2023 Taskscape Ltd // SPDX-License-Identifier: GPL-2.0-or-later #pragma once @@ -56,7 +56,7 @@ struct CHotTrackItem WORD Pixels; // jejich delka v bodech }; -class CStatusWindow : public CWindow +class CPanelStatusBar : public CWindow { public: CMainToolBar* ToolBar; @@ -129,8 +129,8 @@ class CStatusWindow : public CWindow IDropTarget* IDropTargetPtr; public: - CStatusWindow(CFilesWindow* filesWindow, int border, CObjectOrigin origin = ooAllocated); - ~CStatusWindow(); + CPanelStatusBar(CFilesWindow* filesWindow, int border, CObjectOrigin origin = ooAllocated); + ~CPanelStatusBar(); BOOL SetSubTexts(DWORD* subTexts, DWORD subTextsCount); // nastavuje text 'text' do status-line, 'pathLen' urcuje delku cesty (zbytek je filter), diff --git a/src/toolbar4.cpp b/src/toolbar_button_defs.cpp similarity index 100% rename from src/toolbar4.cpp rename to src/toolbar_button_defs.cpp diff --git a/src/toolbar1.cpp b/src/toolbar_core.cpp similarity index 100% rename from src/toolbar1.cpp rename to src/toolbar_core.cpp diff --git a/src/toolbar3.cpp b/src/toolbar_dnd.cpp similarity index 100% rename from src/toolbar3.cpp rename to src/toolbar_dnd.cpp diff --git a/src/toolbar6.cpp b/src/toolbar_drivebar.cpp similarity index 100% rename from src/toolbar6.cpp rename to src/toolbar_drivebar.cpp diff --git a/src/toolbar7.cpp b/src/toolbar_hotpaths.cpp similarity index 100% rename from src/toolbar7.cpp rename to src/toolbar_hotpaths.cpp diff --git a/src/toolbar8.cpp b/src/toolbar_pluginsbar.cpp similarity index 100% rename from src/toolbar8.cpp rename to src/toolbar_pluginsbar.cpp diff --git a/src/toolbar2.cpp b/src/toolbar_rendering.cpp similarity index 100% rename from src/toolbar2.cpp rename to src/toolbar_rendering.cpp diff --git a/src/toolbar5.cpp b/src/toolbar_usermenu.cpp similarity index 100% rename from src/toolbar5.cpp rename to src/toolbar_usermenu.cpp diff --git a/src/transfer_speed.cpp b/src/transfer_speed.cpp new file mode 100644 index 00000000..9922ca0a --- /dev/null +++ b/src/transfer_speed.cpp @@ -0,0 +1,448 @@ +// SPDX-FileCopyrightText: 2023 Taskscape Ltd +// SPDX-License-Identifier: GPL-2.0-or-later +// CommentsTranslationProject: TRANSLATED + +#include "precomp.h" + +#include "cfgdlg.h" +#include "worker.h" +#include "execlog.h" + +#include +#include + +CTransferSpeedMeter::CTransferSpeedMeter() +{ + Clear(); +} + +void CTransferSpeedMeter::Clear() +{ + ActIndexInTrBytes = 0; + ActIndexInTrBytesTimeLim = 0; + CountOfTrBytesItems = 0; + ActIndexInLastPackets = 0; + CountOfLastPackets = 0; + ResetSpeed = TRUE; + MaxPacketSize = 0; +} + +void CTransferSpeedMeter::GetSpeed(CQuadWord* speed) +{ + CALL_STACK_MESSAGE1("CTransferSpeedMeter::GetSpeed()"); + + DWORD time = GetTickCount(); + + if (CountOfLastPackets >= 2) + { // test whether this is a low speed (calculated from LastPacketsSize and LastPacketsTime) + int firstPacket = ((TRSPMETER_NUMOFSTOREDPACKETS + 1) + ActIndexInLastPackets - CountOfLastPackets) % (TRSPMETER_NUMOFSTOREDPACKETS + 1); + int lastPacket = ((TRSPMETER_NUMOFSTOREDPACKETS + 1) + ActIndexInLastPackets - 1) % (TRSPMETER_NUMOFSTOREDPACKETS + 1); + DWORD lastPacketTime = LastPacketsTime[lastPacket]; + DWORD totalTime = lastPacketTime - LastPacketsTime[firstPacket]; // time between receiving the first and last packet + if (totalTime >= ((DWORD)(CountOfLastPackets - 1) * TRSPMETER_STPCKTSMININTERVAL) / TRSPMETER_NUMOFSTOREDPACKETS) + { // this is a low speed (up to TRSPMETER_NUMOFSTOREDPACKETS packets per TRSPMETER_STPCKTSMININTERVAL ms) + if (time - lastPacketTime > 2000) // two-second "protection" period for the last computed slow speed + { // check whether the speed has dropped by more than double compared to the speed of the last packet; if so, display + // zero speed (so that when a slow transfer stops we do not keep showing the last recorded speed value) + int preLastPacket = ((TRSPMETER_NUMOFSTOREDPACKETS + 1) + ActIndexInLastPackets - 2) % (TRSPMETER_NUMOFSTOREDPACKETS + 1); + if ((UINT64)2 * MaxPacketSize * (lastPacketTime - LastPacketsTime[preLastPacket]) < (UINT64)LastPacketsSize[lastPacket] * (time - lastPacketTime)) + { + speed->SetUI64(0); + ResetSpeed = TRUE; + return; // speed dropped at least two times, better show zero + } + } + if (totalTime > TRSPMETER_ACTSPEEDSTEP * TRSPMETER_ACTSPEEDNUMOFSTEPS) + { // compute the speed only from data closest to TRSPMETER_ACTSPEEDSTEP * TRSPMETER_ACTSPEEDNUMOFSTEPS + // (if packets arrive slowly, the queue may contain packets from the last five minutes - but here we + // compute the "instant" speed, not the average over the last five minutes) + int i = firstPacket; + while (1) + { + if (++i >= TRSPMETER_NUMOFSTOREDPACKETS + 1) + i = 0; + if (i == lastPacket || lastPacketTime - LastPacketsTime[i] < TRSPMETER_ACTSPEEDSTEP * TRSPMETER_ACTSPEEDNUMOFSTEPS) + break; + firstPacket = i; + } + totalTime = lastPacketTime - LastPacketsTime[firstPacket]; + } + UINT64 totalSize = 0; // sum of all packet sizes except the first one (from whitch we use only the time) + do + { + if (++firstPacket >= TRSPMETER_NUMOFSTOREDPACKETS + 1) + firstPacket = 0; + totalSize += LastPacketsSize[firstPacket]; + } while (firstPacket != lastPacket); + speed->SetUI64((1000 * totalSize) / totalTime); + return; // low speed computed, we are done + } + else // this is a high speed (more than TRSPMETER_NUMOFSTOREDPACKETS packets per TRSPMETER_STPCKTSMININTERVAL ms), + { // perform a sudden speed drop test (especially when copying zero-sized files or creating empty directories begins) + if (time - lastPacketTime > 800) + { // if no packet has arrived for 800 ms, report zero speed + speed->SetUI64(0); + ResetSpeed = TRUE; + return; + } + } + } + else // nothing to calculate from yet, report "0 B/s" + { + speed->SetUI64(0); + return; + } + // high speed (more than TRSPMETER_NUMOFSTOREDPACKETS packets per TRSPMETER_STPCKTSMININTERVAL ms) + if (CountOfTrBytesItems > 0) // after the connection is established this is "always true" + { + int actIndexAdded = 0; // 0 = current index not included, 1 = current index included + int emptyTrBytes = 0; // number of counted empty steps + UINT64 total = 0; // total number of bytes over the last at most TRSPMETER_ACTSPEEDNUMOFSTEPS steps + int addFromTrBytes = CountOfTrBytesItems - 1; // number of closed steps to add from the queue + DWORD restTime = 0; // time from the last counted step to now + if ((int)(time - ActIndexInTrBytesTimeLim) >= 0) // current index already closed + empty steps may be needed + { + emptyTrBytes = (time - ActIndexInTrBytesTimeLim) / TRSPMETER_ACTSPEEDSTEP; + restTime = (time - ActIndexInTrBytesTimeLim) % TRSPMETER_ACTSPEEDSTEP; + emptyTrBytes = min(emptyTrBytes, TRSPMETER_ACTSPEEDNUMOFSTEPS); + if (emptyTrBytes < TRSPMETER_ACTSPEEDNUMOFSTEPS) // empty steps are not enough; include the current index as well + { + total = TransferedBytes[ActIndexInTrBytes]; + actIndexAdded = 1; + } + addFromTrBytes = TRSPMETER_ACTSPEEDNUMOFSTEPS - actIndexAdded - emptyTrBytes; + addFromTrBytes = min(addFromTrBytes, CountOfTrBytesItems - 1); // how many closed steps from the queue to include + } + else + { + restTime = time + TRSPMETER_ACTSPEEDSTEP - ActIndexInTrBytesTimeLim; + total = TransferedBytes[ActIndexInTrBytes]; + } + + int actIndex = ActIndexInTrBytes; + int i; + for (i = 0; i < addFromTrBytes; i++) + { + if (--actIndex < 0) + actIndex = TRSPMETER_ACTSPEEDNUMOFSTEPS; // moving along the circular queue + total += TransferedBytes[actIndex]; + } + DWORD t = (addFromTrBytes + actIndexAdded + emptyTrBytes) * TRSPMETER_ACTSPEEDSTEP + restTime; + if (t > 0) + speed->SetUI64((total * 1000) / t); + else + speed->SetUI64(0); // nothing to calculate from yet, report "0 B/s" + } + else + speed->SetUI64(0); // nothing to calculate from yet, report "0 B/s" +} + +void CTransferSpeedMeter::JustConnected() +{ + CALL_STACK_MESSAGE_NONE + // CALL_STACK_MESSAGE1("CTransferSpeedMeter::JustConnected()"); + + TransferedBytes[0] = 0; + ActIndexInTrBytes = 0; + ActIndexInTrBytesTimeLim = (LastPacketsTime[0] = GetTickCount()) + TRSPMETER_ACTSPEEDSTEP; + CountOfTrBytesItems = 1; + LastPacketsSize[0] = 0; + ActIndexInLastPackets = 1; + CountOfLastPackets = 1; + ResetSpeed = TRUE; + MaxPacketSize = 0; +} + +void CTransferSpeedMeter::BytesReceived(DWORD count, DWORD time, DWORD maxPacketSize) +{ + DEBUG_SLOW_CALL_STACK_MESSAGE1("CTransferSpeedMeter::BytesReceived(, ,)"); // ignore parameters for performance reasons (the call stack already slows us down) + + MaxPacketSize = maxPacketSize; + + if (count > 0) + { + // if (count > MaxPacketSize) // happens when the speed changes (due to SpeedLimit or ProgressBufferLimit); packets arrive that were read using the old buffer size + // TRACE_E("CTransferSpeedMeter::BytesReceived(): count > MaxPacketSize (" << count << " > " << MaxPacketSize << ")"); + + if (ResetSpeed) + ResetSpeed = FALSE; + + LastPacketsSize[ActIndexInLastPackets] = count; + LastPacketsTime[ActIndexInLastPackets] = time; + if (++ActIndexInLastPackets >= TRSPMETER_NUMOFSTOREDPACKETS + 1) + ActIndexInLastPackets = 0; + if (CountOfLastPackets < TRSPMETER_NUMOFSTOREDPACKETS + 1) + CountOfLastPackets++; + } + if ((int)(time - ActIndexInTrBytesTimeLim) < 0) // within the current time interval, just add the byte count to the interval + { + TransferedBytes[ActIndexInTrBytes] += count; + } + else // outside the current time interval, we must create a new interval + { + int emptyTrBytes = (time - ActIndexInTrBytesTimeLim) / TRSPMETER_ACTSPEEDSTEP; + int i = min(emptyTrBytes, TRSPMETER_ACTSPEEDNUMOFSTEPS); // more has no effect (the entire queue would be reset) + if (i > 0 && CountOfTrBytesItems <= TRSPMETER_ACTSPEEDNUMOFSTEPS) + CountOfTrBytesItems = min(TRSPMETER_ACTSPEEDNUMOFSTEPS + 1, CountOfTrBytesItems + i); + while (i--) + { + if (++ActIndexInTrBytes > TRSPMETER_ACTSPEEDNUMOFSTEPS) + ActIndexInTrBytes = 0; // moving along the circular queue + TransferedBytes[ActIndexInTrBytes] = 0; + } + ActIndexInTrBytesTimeLim += (emptyTrBytes + 1) * TRSPMETER_ACTSPEEDSTEP; + if (++ActIndexInTrBytes > TRSPMETER_ACTSPEEDNUMOFSTEPS) + ActIndexInTrBytes = 0; // moving along the circular queue + if (CountOfTrBytesItems <= TRSPMETER_ACTSPEEDNUMOFSTEPS) + CountOfTrBytesItems++; + TransferedBytes[ActIndexInTrBytes] = count; + } +} + +void CTransferSpeedMeter::AdjustProgressBufferLimit(DWORD* progressBufferLimit, DWORD lastFileBlockCount, + DWORD lastFileStartTime) +{ + if (CountOfLastPackets > 1 && lastFileBlockCount > 0) // "always true": at the start of the file CountOfLastPackets is 1 (2 = we already have one packet) + { + unsigned __int64 size = 0; // total size of stored packets of the last file + int i = ((TRSPMETER_NUMOFSTOREDPACKETS + 1) + ActIndexInLastPackets - 1) % (TRSPMETER_NUMOFSTOREDPACKETS + 1); + int c = min((DWORD)(CountOfLastPackets - 1), lastFileBlockCount); + int packets = c; + DWORD ti = GetTickCount(); + while (c--) + { + size += LastPacketsSize[i]; + if (i-- == 0) + i = TRSPMETER_NUMOFSTOREDPACKETS; + if (ti - LastPacketsTime[i] > 2000) + { + packets -= c; + break; // take packets at most 2 seconds old (trying to compute the "current" speed) + } + } + DWORD totalTime = min(ti - LastPacketsTime[i], ti - lastFileStartTime); // LastPacketsTime[i] may be older than lastFileStartTime (it is the last packet of the previous file); we care only about the time spent on this file + if (totalTime == 0) + totalTime = 10; // treat 0 ms as 10 ms (approx. the GetTickCount() step) + unsigned __int64 speed = (size * 1000) / totalTime; + DWORD bufLimit = ASYNC_SLOW_COPY_BUF_SIZE; + while (bufLimit < ASYNC_COPY_BUF_SIZE) + { + // determined experimentally that Windows 7 loves a 32 KB buffer size; with it the utilization curve + // of the network link is usually nicely smooth, whereas with 64 KB it jumps like crazy + // and the overall achieved speed is about 5% lower... so a dirty bloody hack: we will also + // prefer 32 KB... up to 8 * 128 (1024 KB/s)... that skips 64 KB and 128 KB, the next + // buffer limit is as high as 256 KB + // + + // introduce a measure against oscillation between two buffer limit sizes when the speed is on the boundary + // between two buffer limit sizes; raising it by one level will be harder (to choose the same buffer + // the speed may be up to 9 * bufLimit instead of the standard 8 * bufLimit) + if (bufLimit == 32 * 1024) // for the 32 KB buffer limit we use the values for the 128 KB buffer limit (instead of choosing 64 KB and 128 KB we pick 32 KB) + { + if (speed <= (bufLimit == *progressBufferLimit ? 9 * 128 * 1024 : 8 * 128 * 1024)) + break; + bufLimit = 256 * 1024; // 32 KB did not work, try up to 256 KB (64 KB and 128 KB cannot happen, 32 KB would have been chosen) + } + else + { + if (speed <= (bufLimit == *progressBufferLimit ? 9 * bufLimit : 8 * bufLimit)) + break; + bufLimit *= 2; + } + } + if (bufLimit > ASYNC_COPY_BUF_SIZE) + bufLimit = ASYNC_COPY_BUF_SIZE; + *progressBufferLimit = bufLimit; +#ifdef WORKER_COPY_DEBUG_MSG + TRACE_I("AdjustProgressBufferLimit(): speed=" << speed / 1024.0 << " KB/s, size=" << size << " B, packets=" << packets << ", new buffer limit=" << bufLimit); +#endif // WORKER_COPY_DEBUG_MSG + } + else + TRACE_E("Unexpected situation in CTransferSpeedMeter::AdjustProgressBufferLimit()!"); +} + +// +// **************************************************************************** +// CProgressSpeedMeter +// + +CProgressSpeedMeter::CProgressSpeedMeter() +{ + Clear(); +} + +void CProgressSpeedMeter::Clear() +{ + ActIndexInTrBytes = 0; + ActIndexInTrBytesTimeLim = 0; + CountOfTrBytesItems = 0; + ActIndexInLastPackets = 0; + CountOfLastPackets = 0; + MaxPacketSize = 0; +} + +void CProgressSpeedMeter::GetSpeed(CQuadWord* speed) +{ + CALL_STACK_MESSAGE1("CProgressSpeedMeter::GetSpeed()"); + + DWORD time = GetTickCount(); + + if (CountOfLastPackets >= 2) + { // test whether this is a low speed (calculated from LastPacketsSize and LastPacketsTime) + int firstPacket = ((PRSPMETER_NUMOFSTOREDPACKETS + 1) + ActIndexInLastPackets - CountOfLastPackets) % (PRSPMETER_NUMOFSTOREDPACKETS + 1); + int lastPacket = ((PRSPMETER_NUMOFSTOREDPACKETS + 1) + ActIndexInLastPackets - 1) % (PRSPMETER_NUMOFSTOREDPACKETS + 1); + DWORD lastPacketTime = LastPacketsTime[lastPacket]; + DWORD totalTime = lastPacketTime - LastPacketsTime[firstPacket]; // time between receiving the first and last packet + if (totalTime >= ((DWORD)(CountOfLastPackets - 1) * PRSPMETER_STPCKTSMININTERVAL) / PRSPMETER_NUMOFSTOREDPACKETS) + { // this is a low speed (up to PRSPMETER_NUMOFSTOREDPACKETS packets per PRSPMETER_STPCKTSMININTERVAL ms) + if (time - lastPacketTime > 5000) // five-second "protection" period for the last computed slow speed + { // check whether the speed has dropped by more than four times compared to the speed of the last packet; if so, display + // zero speed (so that when a slow transfer stops we do not keep showing the last recorded time-left value) + int preLastPacket = ((PRSPMETER_NUMOFSTOREDPACKETS + 1) + ActIndexInLastPackets - 2) % (PRSPMETER_NUMOFSTOREDPACKETS + 1); + if ((UINT64)4 * MaxPacketSize * (lastPacketTime - LastPacketsTime[preLastPacket]) < (UINT64)LastPacketsSize[lastPacket] * (time - lastPacketTime)) + { + speed->SetUI64(0); + return; // speed dropped at least two times, better show zero + } + } + if (totalTime > PRSPMETER_ACTSPEEDSTEP * PRSPMETER_ACTSPEEDNUMOFSTEPS) + { // compute the speed only from data closest to PRSPMETER_ACTSPEEDSTEP * PRSPMETER_ACTSPEEDNUMOFSTEPS + // (if packets arrive slowly, the queue may contain packets from the last five minutes, but here we + // compute the speed over the last X seconds, not the average over the last five minutes) + int i = firstPacket; + while (1) + { + if (++i >= PRSPMETER_NUMOFSTOREDPACKETS + 1) + i = 0; + if (i == lastPacket || lastPacketTime - LastPacketsTime[i] < PRSPMETER_ACTSPEEDSTEP * PRSPMETER_ACTSPEEDNUMOFSTEPS) + break; + firstPacket = i; + } + totalTime = lastPacketTime - LastPacketsTime[firstPacket]; + } + UINT64 totalSize = 0; // sum of all packet sizes except the first one (from whitch we use only the time) + do + { + if (++firstPacket >= PRSPMETER_NUMOFSTOREDPACKETS + 1) + firstPacket = 0; + totalSize += LastPacketsSize[firstPacket]; + } while (firstPacket != lastPacket); + speed->SetUI64((1000 * totalSize) / totalTime); + return; // low speed computed, we are done + } + else // this is a high speed (more than PRSPMETER_NUMOFSTOREDPACKETS packets per PRSPMETER_STPCKTSMININTERVAL ms), + { // perform a sudden speed drop test (especially when copying zero-sized files or creating empty directories begins) + if (time - lastPacketTime > 5000) + { // if no packet has arrived for 5000 ms, report zero speed + speed->SetUI64(0); + return; + } + } + } + else // nothing to calculate from yet, report "0 B/s" + { + speed->SetUI64(0); + return; + } + // high speed (more than PRSPMETER_NUMOFSTOREDPACKETS packets per PRSPMETER_STPCKTSMININTERVAL ms) + if (CountOfTrBytesItems > 0) // after the connection is established this is "always true" + { + int actIndexAdded = 0; // 0 = current index not included, 1 = current index included + int emptyTrBytes = 0; // number of counted empty steps + UINT64 total = 0; // total number of bytes over the last at most PRSPMETER_ACTSPEEDNUMOFSTEPS steps + int addFromTrBytes = CountOfTrBytesItems - 1; // number of closed steps to add from the queue + DWORD restTime = 0; // time from the last counted step to now + if ((int)(time - ActIndexInTrBytesTimeLim) >= 0) // current index already closed + empty steps may be needed + { + emptyTrBytes = (time - ActIndexInTrBytesTimeLim) / PRSPMETER_ACTSPEEDSTEP; + restTime = (time - ActIndexInTrBytesTimeLim) % PRSPMETER_ACTSPEEDSTEP; + emptyTrBytes = min(emptyTrBytes, PRSPMETER_ACTSPEEDNUMOFSTEPS); + if (emptyTrBytes < PRSPMETER_ACTSPEEDNUMOFSTEPS) // empty steps are not enough; include the current index as well + { + total = TransferedBytes[ActIndexInTrBytes]; + actIndexAdded = 1; + } + addFromTrBytes = PRSPMETER_ACTSPEEDNUMOFSTEPS - actIndexAdded - emptyTrBytes; + addFromTrBytes = min(addFromTrBytes, CountOfTrBytesItems - 1); // how many closed steps from the queue to include + } + else + { + restTime = time + PRSPMETER_ACTSPEEDSTEP - ActIndexInTrBytesTimeLim; + total = TransferedBytes[ActIndexInTrBytes]; + } + + int actIndex = ActIndexInTrBytes; + int i; + for (i = 0; i < addFromTrBytes; i++) + { + if (--actIndex < 0) + actIndex = PRSPMETER_ACTSPEEDNUMOFSTEPS; // moving along the circular queue + total += TransferedBytes[actIndex]; + } + DWORD t = (addFromTrBytes + actIndexAdded + emptyTrBytes) * PRSPMETER_ACTSPEEDSTEP + restTime; + if (t > 0) + speed->SetUI64((total * 1000) / t); + else + speed->SetUI64(0); // nothing to calculate from yet, report "0 B/s" + } + else + speed->SetUI64(0); // nothing to calculate from yet, report "0 B/s" +} + +void CProgressSpeedMeter::JustConnected() +{ + CALL_STACK_MESSAGE_NONE + // CALL_STACK_MESSAGE1("CProgressSpeedMeter::JustConnected()"); + + TransferedBytes[0] = 0; + ActIndexInTrBytes = 0; + ActIndexInTrBytesTimeLim = (LastPacketsTime[0] = GetTickCount()) + PRSPMETER_ACTSPEEDSTEP; + CountOfTrBytesItems = 1; + LastPacketsSize[0] = 0; + ActIndexInLastPackets = 1; + CountOfLastPackets = 1; + MaxPacketSize = 0; +} + +void CProgressSpeedMeter::BytesReceived(DWORD count, DWORD time, DWORD maxPacketSize) +{ + DEBUG_SLOW_CALL_STACK_MESSAGE1("CProgressSpeedMeter::BytesReceived(, ,)"); // ignore parameters for performance reasons (the call stack already slows us down) + + MaxPacketSize = maxPacketSize; + + if (count > 0) + { + // if (count > MaxPacketSize) // happens when the speed changes (due to SpeedLimit or ProgressBufferLimit); packets arrive that were read using the old buffer size + // TRACE_E("CProgressSpeedMeter::BytesReceived(): count > MaxPacketSize (" << count << " > " << MaxPacketSize << ")"); + + LastPacketsSize[ActIndexInLastPackets] = count; + LastPacketsTime[ActIndexInLastPackets] = time; + if (++ActIndexInLastPackets >= PRSPMETER_NUMOFSTOREDPACKETS + 1) + ActIndexInLastPackets = 0; + if (CountOfLastPackets < PRSPMETER_NUMOFSTOREDPACKETS + 1) + CountOfLastPackets++; + } + if ((int)(time - ActIndexInTrBytesTimeLim) < 0) // within the current time interval, just add the byte count to the interval + { + TransferedBytes[ActIndexInTrBytes] += count; + } + else // outside the current time interval, we must create a new interval + { + int emptyTrBytes = (time - ActIndexInTrBytesTimeLim) / PRSPMETER_ACTSPEEDSTEP; + int i = min(emptyTrBytes, PRSPMETER_ACTSPEEDNUMOFSTEPS); // more has no effect (the entire queue would be reset) + if (i > 0 && CountOfTrBytesItems <= PRSPMETER_ACTSPEEDNUMOFSTEPS) + CountOfTrBytesItems = min(PRSPMETER_ACTSPEEDNUMOFSTEPS + 1, CountOfTrBytesItems + i); + while (i--) + { + if (++ActIndexInTrBytes > PRSPMETER_ACTSPEEDNUMOFSTEPS) + ActIndexInTrBytes = 0; // moving along the circular queue + TransferedBytes[ActIndexInTrBytes] = 0; + } + ActIndexInTrBytesTimeLim += (emptyTrBytes + 1) * PRSPMETER_ACTSPEEDSTEP; + if (++ActIndexInTrBytes > PRSPMETER_ACTSPEEDNUMOFSTEPS) + ActIndexInTrBytes = 0; // moving along the circular queue + if (CountOfTrBytesItems <= PRSPMETER_ACTSPEEDNUMOFSTEPS) + CountOfTrBytesItems++; + TransferedBytes[ActIndexInTrBytes] = count; + } +} + diff --git a/src/translator/translator.cpp b/src/translator/translator.cpp index 55dd3454..4eb2451c 100644 --- a/src/translator/translator.cpp +++ b/src/translator/translator.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 Taskscape Ltd +// SPDX-FileCopyrightText: 2023 Taskscape Ltd // SPDX-License-Identifier: GPL-2.0-or-later #include "precomp.h" @@ -36,19 +36,19 @@ BOOL Windows7AndLater = FALSE; // **************************************************************************** -class C__StrCriticalSection +class CStringResourceLock { public: CRITICAL_SECTION cs; - C__StrCriticalSection() { InitializeCriticalSection(&cs); } - ~C__StrCriticalSection() { DeleteCriticalSection(&cs); } + CStringResourceLock() { InitializeCriticalSection(&cs); } + ~CStringResourceLock() { DeleteCriticalSection(&cs); } }; // ensure the critical section is constructed as early as possible #pragma warning(disable : 4073) #pragma init_seg(lib) -C__StrCriticalSection __StrCriticalSection; +CStringResourceLock __StrCriticalSection; // **************************************************************************** diff --git a/src/salamdr4.cpp b/src/truncated_string.cpp similarity index 100% rename from src/salamdr4.cpp rename to src/truncated_string.cpp diff --git a/src/vcxproj/build_check.err b/src/vcxproj/build_check.err new file mode 100644 index 00000000..e69de29b diff --git a/src/vcxproj/build_check.wrn b/src/vcxproj/build_check.wrn new file mode 100644 index 00000000..e69de29b diff --git a/src/vcxproj/salamand.vcxproj b/src/vcxproj/salamand.vcxproj index 89446d9b..261e297e 100644 --- a/src/vcxproj/salamand.vcxproj +++ b/src/vcxproj/salamand.vcxproj @@ -1,4 +1,4 @@ - + @@ -339,21 +339,21 @@ - + - + - + - + - + - + - + - + @@ -363,47 +363,53 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + - + + + + + @@ -415,15 +421,19 @@ - + - + - + - + - + + + + + @@ -431,13 +441,13 @@ - + - + - + - + @@ -457,13 +467,13 @@ - + - + - + - + @@ -497,19 +507,21 @@ - + + + - + - + - + - + - + - + @@ -553,21 +565,21 @@ - + - + - + - + - + - + - + - + @@ -583,9 +595,21 @@ + + + + + + - + + + + + + + diff --git a/src/worker.cpp b/src/worker.cpp index 569ffde9..aecbc229 100644 --- a/src/worker.cpp +++ b/src/worker.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 Taskscape Ltd +// SPDX-FileCopyrightText: 2023 Taskscape Ltd // SPDX-License-Identifier: GPL-2.0-or-later // CommentsTranslationProject: TRANSLATED @@ -17,7 +17,7 @@ NTFSCONTROLFILE DynNtFsControlFile = NULL; COperationsQueue OperationsQueue; // queue of disk operations -static WCHAR* SafeConvertAllocUtf8ToWide(const char* src, int len) +WCHAR* SafeConvertAllocUtf8ToWide(const char* src, int len) { __try { return ConvertAllocUtf8ToWide(src, len); @@ -27,7 +27,7 @@ static WCHAR* SafeConvertAllocUtf8ToWide(const char* src, int len) } } -static HANDLE SafeCreateFileW(LPCWSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, +HANDLE SafeCreateFileW(LPCWSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile) { @@ -40,7 +40,7 @@ static HANDLE SafeCreateFileW(LPCWSTR lpFileName, DWORD dwDesiredAccess, DWORD d } } -static BOOL SafeDeleteFileW(LPCWSTR lpFileName) +BOOL SafeDeleteFileW(LPCWSTR lpFileName) { __try { return DeleteFileW(lpFileName); @@ -50,7 +50,7 @@ static BOOL SafeDeleteFileW(LPCWSTR lpFileName) } } -static BOOL SafeSetFileAttributesW(LPCWSTR lpFileName, DWORD dwFileAttributes) +BOOL SafeSetFileAttributesW(LPCWSTR lpFileName, DWORD dwFileAttributes) { __try { return SetFileAttributesW(lpFileName, dwFileAttributes); @@ -60,7 +60,7 @@ static BOOL SafeSetFileAttributesW(LPCWSTR lpFileName, DWORD dwFileAttributes) } } -static BOOL SafeRemoveDirectoryW(LPCWSTR lpPathName) +BOOL SafeRemoveDirectoryW(LPCWSTR lpPathName) { __try { return RemoveDirectoryW(lpPathName); @@ -70,56 +70,8 @@ static BOOL SafeRemoveDirectoryW(LPCWSTR lpPathName) } } -static HANDLE CreateFileUtf8(const char* fileName, DWORD desiredAccess, DWORD shareMode, - LPSECURITY_ATTRIBUTES securityAttributes, DWORD creationDisposition, - DWORD flagsAndAttributes, HANDLE templateFile) -{ - WCHAR* wName = SafeConvertAllocUtf8ToWide(fileName, -1); - CStrP fileNameW(wName); - if (fileNameW == NULL) - { - SetLastError(ERROR_NO_UNICODE_TRANSLATION); - return INVALID_HANDLE_VALUE; - } - return SafeCreateFileW(fileNameW, desiredAccess, shareMode, securityAttributes, creationDisposition, - flagsAndAttributes, templateFile); -} - -static BOOL DeleteFileUtf8(const char* fileName) -{ - WCHAR* wName = SafeConvertAllocUtf8ToWide(fileName, -1); - CStrP fileNameW(wName); - if (fileNameW == NULL) - { - SetLastError(ERROR_NO_UNICODE_TRANSLATION); - return FALSE; - } - return SafeDeleteFileW(fileNameW); -} - -static BOOL SetFileAttributesUtf8(const char* fileName, DWORD attrs) -{ - WCHAR* wName = SafeConvertAllocUtf8ToWide(fileName, -1); - CStrP fileNameW(wName); - if (fileNameW == NULL) - { - SetLastError(ERROR_NO_UNICODE_TRANSLATION); - return FALSE; - } - return SafeSetFileAttributesW(fileNameW, attrs); -} - -static BOOL RemoveDirectoryUtf8(const char* path) -{ - WCHAR* wName = SafeConvertAllocUtf8ToWide(path, -1); - CStrP pathW(wName); - if (pathW == NULL) - { - SetLastError(ERROR_NO_UNICODE_TRANSLATION); - return FALSE; - } - return SafeRemoveDirectoryW(pathW); -} +// CreateFileUtf8, DeleteFileUtf8, SetFileAttributesUtf8, RemoveDirectoryUtf8 +// are globally defined in common/strutils.cpp — no local definitions needed here. // if defined, various debug messages are written to TRACE //#define WORKER_COPY_DEBUG_MSG @@ -129,7 +81,7 @@ static BOOL RemoveDirectoryUtf8(const char* path) // Helper function to determine optimal buffer size for synchronous copy operations // Returns the buffer size based on drive types and operation flags -static int GetOptimalSyncCopyBufferSize(COperations* script, DWORD opFlags) +int GetOptimalSyncCopyBufferSize(COperations* script, DWORD opFlags) { // For removable disks (floppies), use the smallest buffer if (script->RemovableSrcDisk || script->RemovableTgtDisk) @@ -152,441 +104,6 @@ static int GetOptimalSyncCopyBufferSize(COperations* script, DWORD opFlags) // CTransferSpeedMeter // -CTransferSpeedMeter::CTransferSpeedMeter() -{ - Clear(); -} - -void CTransferSpeedMeter::Clear() -{ - ActIndexInTrBytes = 0; - ActIndexInTrBytesTimeLim = 0; - CountOfTrBytesItems = 0; - ActIndexInLastPackets = 0; - CountOfLastPackets = 0; - ResetSpeed = TRUE; - MaxPacketSize = 0; -} - -void CTransferSpeedMeter::GetSpeed(CQuadWord* speed) -{ - CALL_STACK_MESSAGE1("CTransferSpeedMeter::GetSpeed()"); - - DWORD time = GetTickCount(); - - if (CountOfLastPackets >= 2) - { // test whether this is a low speed (calculated from LastPacketsSize and LastPacketsTime) - int firstPacket = ((TRSPMETER_NUMOFSTOREDPACKETS + 1) + ActIndexInLastPackets - CountOfLastPackets) % (TRSPMETER_NUMOFSTOREDPACKETS + 1); - int lastPacket = ((TRSPMETER_NUMOFSTOREDPACKETS + 1) + ActIndexInLastPackets - 1) % (TRSPMETER_NUMOFSTOREDPACKETS + 1); - DWORD lastPacketTime = LastPacketsTime[lastPacket]; - DWORD totalTime = lastPacketTime - LastPacketsTime[firstPacket]; // time between receiving the first and last packet - if (totalTime >= ((DWORD)(CountOfLastPackets - 1) * TRSPMETER_STPCKTSMININTERVAL) / TRSPMETER_NUMOFSTOREDPACKETS) - { // this is a low speed (up to TRSPMETER_NUMOFSTOREDPACKETS packets per TRSPMETER_STPCKTSMININTERVAL ms) - if (time - lastPacketTime > 2000) // two-second "protection" period for the last computed slow speed - { // check whether the speed has dropped by more than double compared to the speed of the last packet; if so, display - // zero speed (so that when a slow transfer stops we do not keep showing the last recorded speed value) - int preLastPacket = ((TRSPMETER_NUMOFSTOREDPACKETS + 1) + ActIndexInLastPackets - 2) % (TRSPMETER_NUMOFSTOREDPACKETS + 1); - if ((UINT64)2 * MaxPacketSize * (lastPacketTime - LastPacketsTime[preLastPacket]) < (UINT64)LastPacketsSize[lastPacket] * (time - lastPacketTime)) - { - speed->SetUI64(0); - ResetSpeed = TRUE; - return; // speed dropped at least two times, better show zero - } - } - if (totalTime > TRSPMETER_ACTSPEEDSTEP * TRSPMETER_ACTSPEEDNUMOFSTEPS) - { // compute the speed only from data closest to TRSPMETER_ACTSPEEDSTEP * TRSPMETER_ACTSPEEDNUMOFSTEPS - // (if packets arrive slowly, the queue may contain packets from the last five minutes - but here we - // compute the "instant" speed, not the average over the last five minutes) - int i = firstPacket; - while (1) - { - if (++i >= TRSPMETER_NUMOFSTOREDPACKETS + 1) - i = 0; - if (i == lastPacket || lastPacketTime - LastPacketsTime[i] < TRSPMETER_ACTSPEEDSTEP * TRSPMETER_ACTSPEEDNUMOFSTEPS) - break; - firstPacket = i; - } - totalTime = lastPacketTime - LastPacketsTime[firstPacket]; - } - UINT64 totalSize = 0; // sum of all packet sizes except the first one (from whitch we use only the time) - do - { - if (++firstPacket >= TRSPMETER_NUMOFSTOREDPACKETS + 1) - firstPacket = 0; - totalSize += LastPacketsSize[firstPacket]; - } while (firstPacket != lastPacket); - speed->SetUI64((1000 * totalSize) / totalTime); - return; // low speed computed, we are done - } - else // this is a high speed (more than TRSPMETER_NUMOFSTOREDPACKETS packets per TRSPMETER_STPCKTSMININTERVAL ms), - { // perform a sudden speed drop test (especially when copying zero-sized files or creating empty directories begins) - if (time - lastPacketTime > 800) - { // if no packet has arrived for 800 ms, report zero speed - speed->SetUI64(0); - ResetSpeed = TRUE; - return; - } - } - } - else // nothing to calculate from yet, report "0 B/s" - { - speed->SetUI64(0); - return; - } - // high speed (more than TRSPMETER_NUMOFSTOREDPACKETS packets per TRSPMETER_STPCKTSMININTERVAL ms) - if (CountOfTrBytesItems > 0) // after the connection is established this is "always true" - { - int actIndexAdded = 0; // 0 = current index not included, 1 = current index included - int emptyTrBytes = 0; // number of counted empty steps - UINT64 total = 0; // total number of bytes over the last at most TRSPMETER_ACTSPEEDNUMOFSTEPS steps - int addFromTrBytes = CountOfTrBytesItems - 1; // number of closed steps to add from the queue - DWORD restTime = 0; // time from the last counted step to now - if ((int)(time - ActIndexInTrBytesTimeLim) >= 0) // current index already closed + empty steps may be needed - { - emptyTrBytes = (time - ActIndexInTrBytesTimeLim) / TRSPMETER_ACTSPEEDSTEP; - restTime = (time - ActIndexInTrBytesTimeLim) % TRSPMETER_ACTSPEEDSTEP; - emptyTrBytes = min(emptyTrBytes, TRSPMETER_ACTSPEEDNUMOFSTEPS); - if (emptyTrBytes < TRSPMETER_ACTSPEEDNUMOFSTEPS) // empty steps are not enough; include the current index as well - { - total = TransferedBytes[ActIndexInTrBytes]; - actIndexAdded = 1; - } - addFromTrBytes = TRSPMETER_ACTSPEEDNUMOFSTEPS - actIndexAdded - emptyTrBytes; - addFromTrBytes = min(addFromTrBytes, CountOfTrBytesItems - 1); // how many closed steps from the queue to include - } - else - { - restTime = time + TRSPMETER_ACTSPEEDSTEP - ActIndexInTrBytesTimeLim; - total = TransferedBytes[ActIndexInTrBytes]; - } - - int actIndex = ActIndexInTrBytes; - int i; - for (i = 0; i < addFromTrBytes; i++) - { - if (--actIndex < 0) - actIndex = TRSPMETER_ACTSPEEDNUMOFSTEPS; // moving along the circular queue - total += TransferedBytes[actIndex]; - } - DWORD t = (addFromTrBytes + actIndexAdded + emptyTrBytes) * TRSPMETER_ACTSPEEDSTEP + restTime; - if (t > 0) - speed->SetUI64((total * 1000) / t); - else - speed->SetUI64(0); // nothing to calculate from yet, report "0 B/s" - } - else - speed->SetUI64(0); // nothing to calculate from yet, report "0 B/s" -} - -void CTransferSpeedMeter::JustConnected() -{ - CALL_STACK_MESSAGE_NONE - // CALL_STACK_MESSAGE1("CTransferSpeedMeter::JustConnected()"); - - TransferedBytes[0] = 0; - ActIndexInTrBytes = 0; - ActIndexInTrBytesTimeLim = (LastPacketsTime[0] = GetTickCount()) + TRSPMETER_ACTSPEEDSTEP; - CountOfTrBytesItems = 1; - LastPacketsSize[0] = 0; - ActIndexInLastPackets = 1; - CountOfLastPackets = 1; - ResetSpeed = TRUE; - MaxPacketSize = 0; -} - -void CTransferSpeedMeter::BytesReceived(DWORD count, DWORD time, DWORD maxPacketSize) -{ - DEBUG_SLOW_CALL_STACK_MESSAGE1("CTransferSpeedMeter::BytesReceived(, ,)"); // ignore parameters for performance reasons (the call stack already slows us down) - - MaxPacketSize = maxPacketSize; - - if (count > 0) - { - // if (count > MaxPacketSize) // happens when the speed changes (due to SpeedLimit or ProgressBufferLimit); packets arrive that were read using the old buffer size - // TRACE_E("CTransferSpeedMeter::BytesReceived(): count > MaxPacketSize (" << count << " > " << MaxPacketSize << ")"); - - if (ResetSpeed) - ResetSpeed = FALSE; - - LastPacketsSize[ActIndexInLastPackets] = count; - LastPacketsTime[ActIndexInLastPackets] = time; - if (++ActIndexInLastPackets >= TRSPMETER_NUMOFSTOREDPACKETS + 1) - ActIndexInLastPackets = 0; - if (CountOfLastPackets < TRSPMETER_NUMOFSTOREDPACKETS + 1) - CountOfLastPackets++; - } - if ((int)(time - ActIndexInTrBytesTimeLim) < 0) // within the current time interval, just add the byte count to the interval - { - TransferedBytes[ActIndexInTrBytes] += count; - } - else // outside the current time interval, we must create a new interval - { - int emptyTrBytes = (time - ActIndexInTrBytesTimeLim) / TRSPMETER_ACTSPEEDSTEP; - int i = min(emptyTrBytes, TRSPMETER_ACTSPEEDNUMOFSTEPS); // more has no effect (the entire queue would be reset) - if (i > 0 && CountOfTrBytesItems <= TRSPMETER_ACTSPEEDNUMOFSTEPS) - CountOfTrBytesItems = min(TRSPMETER_ACTSPEEDNUMOFSTEPS + 1, CountOfTrBytesItems + i); - while (i--) - { - if (++ActIndexInTrBytes > TRSPMETER_ACTSPEEDNUMOFSTEPS) - ActIndexInTrBytes = 0; // moving along the circular queue - TransferedBytes[ActIndexInTrBytes] = 0; - } - ActIndexInTrBytesTimeLim += (emptyTrBytes + 1) * TRSPMETER_ACTSPEEDSTEP; - if (++ActIndexInTrBytes > TRSPMETER_ACTSPEEDNUMOFSTEPS) - ActIndexInTrBytes = 0; // moving along the circular queue - if (CountOfTrBytesItems <= TRSPMETER_ACTSPEEDNUMOFSTEPS) - CountOfTrBytesItems++; - TransferedBytes[ActIndexInTrBytes] = count; - } -} - -void CTransferSpeedMeter::AdjustProgressBufferLimit(DWORD* progressBufferLimit, DWORD lastFileBlockCount, - DWORD lastFileStartTime) -{ - if (CountOfLastPackets > 1 && lastFileBlockCount > 0) // "always true": at the start of the file CountOfLastPackets is 1 (2 = we already have one packet) - { - unsigned __int64 size = 0; // total size of stored packets of the last file - int i = ((TRSPMETER_NUMOFSTOREDPACKETS + 1) + ActIndexInLastPackets - 1) % (TRSPMETER_NUMOFSTOREDPACKETS + 1); - int c = min((DWORD)(CountOfLastPackets - 1), lastFileBlockCount); - int packets = c; - DWORD ti = GetTickCount(); - while (c--) - { - size += LastPacketsSize[i]; - if (i-- == 0) - i = TRSPMETER_NUMOFSTOREDPACKETS; - if (ti - LastPacketsTime[i] > 2000) - { - packets -= c; - break; // take packets at most 2 seconds old (trying to compute the "current" speed) - } - } - DWORD totalTime = min(ti - LastPacketsTime[i], ti - lastFileStartTime); // LastPacketsTime[i] may be older than lastFileStartTime (it is the last packet of the previous file); we care only about the time spent on this file - if (totalTime == 0) - totalTime = 10; // treat 0 ms as 10 ms (approx. the GetTickCount() step) - unsigned __int64 speed = (size * 1000) / totalTime; - DWORD bufLimit = ASYNC_SLOW_COPY_BUF_SIZE; - while (bufLimit < ASYNC_COPY_BUF_SIZE) - { - // determined experimentally that Windows 7 loves a 32 KB buffer size; with it the utilization curve - // of the network link is usually nicely smooth, whereas with 64 KB it jumps like crazy - // and the overall achieved speed is about 5% lower... so a dirty bloody hack: we will also - // prefer 32 KB... up to 8 * 128 (1024 KB/s)... that skips 64 KB and 128 KB, the next - // buffer limit is as high as 256 KB - // + - // introduce a measure against oscillation between two buffer limit sizes when the speed is on the boundary - // between two buffer limit sizes; raising it by one level will be harder (to choose the same buffer - // the speed may be up to 9 * bufLimit instead of the standard 8 * bufLimit) - if (bufLimit == 32 * 1024) // for the 32 KB buffer limit we use the values for the 128 KB buffer limit (instead of choosing 64 KB and 128 KB we pick 32 KB) - { - if (speed <= (bufLimit == *progressBufferLimit ? 9 * 128 * 1024 : 8 * 128 * 1024)) - break; - bufLimit = 256 * 1024; // 32 KB did not work, try up to 256 KB (64 KB and 128 KB cannot happen, 32 KB would have been chosen) - } - else - { - if (speed <= (bufLimit == *progressBufferLimit ? 9 * bufLimit : 8 * bufLimit)) - break; - bufLimit *= 2; - } - } - if (bufLimit > ASYNC_COPY_BUF_SIZE) - bufLimit = ASYNC_COPY_BUF_SIZE; - *progressBufferLimit = bufLimit; -#ifdef WORKER_COPY_DEBUG_MSG - TRACE_I("AdjustProgressBufferLimit(): speed=" << speed / 1024.0 << " KB/s, size=" << size << " B, packets=" << packets << ", new buffer limit=" << bufLimit); -#endif // WORKER_COPY_DEBUG_MSG - } - else - TRACE_E("Unexpected situation in CTransferSpeedMeter::AdjustProgressBufferLimit()!"); -} - -// -// **************************************************************************** -// CProgressSpeedMeter -// - -CProgressSpeedMeter::CProgressSpeedMeter() -{ - Clear(); -} - -void CProgressSpeedMeter::Clear() -{ - ActIndexInTrBytes = 0; - ActIndexInTrBytesTimeLim = 0; - CountOfTrBytesItems = 0; - ActIndexInLastPackets = 0; - CountOfLastPackets = 0; - MaxPacketSize = 0; -} - -void CProgressSpeedMeter::GetSpeed(CQuadWord* speed) -{ - CALL_STACK_MESSAGE1("CProgressSpeedMeter::GetSpeed()"); - - DWORD time = GetTickCount(); - - if (CountOfLastPackets >= 2) - { // test whether this is a low speed (calculated from LastPacketsSize and LastPacketsTime) - int firstPacket = ((PRSPMETER_NUMOFSTOREDPACKETS + 1) + ActIndexInLastPackets - CountOfLastPackets) % (PRSPMETER_NUMOFSTOREDPACKETS + 1); - int lastPacket = ((PRSPMETER_NUMOFSTOREDPACKETS + 1) + ActIndexInLastPackets - 1) % (PRSPMETER_NUMOFSTOREDPACKETS + 1); - DWORD lastPacketTime = LastPacketsTime[lastPacket]; - DWORD totalTime = lastPacketTime - LastPacketsTime[firstPacket]; // time between receiving the first and last packet - if (totalTime >= ((DWORD)(CountOfLastPackets - 1) * PRSPMETER_STPCKTSMININTERVAL) / PRSPMETER_NUMOFSTOREDPACKETS) - { // this is a low speed (up to PRSPMETER_NUMOFSTOREDPACKETS packets per PRSPMETER_STPCKTSMININTERVAL ms) - if (time - lastPacketTime > 5000) // five-second "protection" period for the last computed slow speed - { // check whether the speed has dropped by more than four times compared to the speed of the last packet; if so, display - // zero speed (so that when a slow transfer stops we do not keep showing the last recorded time-left value) - int preLastPacket = ((PRSPMETER_NUMOFSTOREDPACKETS + 1) + ActIndexInLastPackets - 2) % (PRSPMETER_NUMOFSTOREDPACKETS + 1); - if ((UINT64)4 * MaxPacketSize * (lastPacketTime - LastPacketsTime[preLastPacket]) < (UINT64)LastPacketsSize[lastPacket] * (time - lastPacketTime)) - { - speed->SetUI64(0); - return; // speed dropped at least two times, better show zero - } - } - if (totalTime > PRSPMETER_ACTSPEEDSTEP * PRSPMETER_ACTSPEEDNUMOFSTEPS) - { // compute the speed only from data closest to PRSPMETER_ACTSPEEDSTEP * PRSPMETER_ACTSPEEDNUMOFSTEPS - // (if packets arrive slowly, the queue may contain packets from the last five minutes, but here we - // compute the speed over the last X seconds, not the average over the last five minutes) - int i = firstPacket; - while (1) - { - if (++i >= PRSPMETER_NUMOFSTOREDPACKETS + 1) - i = 0; - if (i == lastPacket || lastPacketTime - LastPacketsTime[i] < PRSPMETER_ACTSPEEDSTEP * PRSPMETER_ACTSPEEDNUMOFSTEPS) - break; - firstPacket = i; - } - totalTime = lastPacketTime - LastPacketsTime[firstPacket]; - } - UINT64 totalSize = 0; // sum of all packet sizes except the first one (from whitch we use only the time) - do - { - if (++firstPacket >= PRSPMETER_NUMOFSTOREDPACKETS + 1) - firstPacket = 0; - totalSize += LastPacketsSize[firstPacket]; - } while (firstPacket != lastPacket); - speed->SetUI64((1000 * totalSize) / totalTime); - return; // low speed computed, we are done - } - else // this is a high speed (more than PRSPMETER_NUMOFSTOREDPACKETS packets per PRSPMETER_STPCKTSMININTERVAL ms), - { // perform a sudden speed drop test (especially when copying zero-sized files or creating empty directories begins) - if (time - lastPacketTime > 5000) - { // if no packet has arrived for 5000 ms, report zero speed - speed->SetUI64(0); - return; - } - } - } - else // nothing to calculate from yet, report "0 B/s" - { - speed->SetUI64(0); - return; - } - // high speed (more than PRSPMETER_NUMOFSTOREDPACKETS packets per PRSPMETER_STPCKTSMININTERVAL ms) - if (CountOfTrBytesItems > 0) // after the connection is established this is "always true" - { - int actIndexAdded = 0; // 0 = current index not included, 1 = current index included - int emptyTrBytes = 0; // number of counted empty steps - UINT64 total = 0; // total number of bytes over the last at most PRSPMETER_ACTSPEEDNUMOFSTEPS steps - int addFromTrBytes = CountOfTrBytesItems - 1; // number of closed steps to add from the queue - DWORD restTime = 0; // time from the last counted step to now - if ((int)(time - ActIndexInTrBytesTimeLim) >= 0) // current index already closed + empty steps may be needed - { - emptyTrBytes = (time - ActIndexInTrBytesTimeLim) / PRSPMETER_ACTSPEEDSTEP; - restTime = (time - ActIndexInTrBytesTimeLim) % PRSPMETER_ACTSPEEDSTEP; - emptyTrBytes = min(emptyTrBytes, PRSPMETER_ACTSPEEDNUMOFSTEPS); - if (emptyTrBytes < PRSPMETER_ACTSPEEDNUMOFSTEPS) // empty steps are not enough; include the current index as well - { - total = TransferedBytes[ActIndexInTrBytes]; - actIndexAdded = 1; - } - addFromTrBytes = PRSPMETER_ACTSPEEDNUMOFSTEPS - actIndexAdded - emptyTrBytes; - addFromTrBytes = min(addFromTrBytes, CountOfTrBytesItems - 1); // how many closed steps from the queue to include - } - else - { - restTime = time + PRSPMETER_ACTSPEEDSTEP - ActIndexInTrBytesTimeLim; - total = TransferedBytes[ActIndexInTrBytes]; - } - - int actIndex = ActIndexInTrBytes; - int i; - for (i = 0; i < addFromTrBytes; i++) - { - if (--actIndex < 0) - actIndex = PRSPMETER_ACTSPEEDNUMOFSTEPS; // moving along the circular queue - total += TransferedBytes[actIndex]; - } - DWORD t = (addFromTrBytes + actIndexAdded + emptyTrBytes) * PRSPMETER_ACTSPEEDSTEP + restTime; - if (t > 0) - speed->SetUI64((total * 1000) / t); - else - speed->SetUI64(0); // nothing to calculate from yet, report "0 B/s" - } - else - speed->SetUI64(0); // nothing to calculate from yet, report "0 B/s" -} - -void CProgressSpeedMeter::JustConnected() -{ - CALL_STACK_MESSAGE_NONE - // CALL_STACK_MESSAGE1("CProgressSpeedMeter::JustConnected()"); - - TransferedBytes[0] = 0; - ActIndexInTrBytes = 0; - ActIndexInTrBytesTimeLim = (LastPacketsTime[0] = GetTickCount()) + PRSPMETER_ACTSPEEDSTEP; - CountOfTrBytesItems = 1; - LastPacketsSize[0] = 0; - ActIndexInLastPackets = 1; - CountOfLastPackets = 1; - MaxPacketSize = 0; -} - -void CProgressSpeedMeter::BytesReceived(DWORD count, DWORD time, DWORD maxPacketSize) -{ - DEBUG_SLOW_CALL_STACK_MESSAGE1("CProgressSpeedMeter::BytesReceived(, ,)"); // ignore parameters for performance reasons (the call stack already slows us down) - - MaxPacketSize = maxPacketSize; - - if (count > 0) - { - // if (count > MaxPacketSize) // happens when the speed changes (due to SpeedLimit or ProgressBufferLimit); packets arrive that were read using the old buffer size - // TRACE_E("CProgressSpeedMeter::BytesReceived(): count > MaxPacketSize (" << count << " > " << MaxPacketSize << ")"); - - LastPacketsSize[ActIndexInLastPackets] = count; - LastPacketsTime[ActIndexInLastPackets] = time; - if (++ActIndexInLastPackets >= PRSPMETER_NUMOFSTOREDPACKETS + 1) - ActIndexInLastPackets = 0; - if (CountOfLastPackets < PRSPMETER_NUMOFSTOREDPACKETS + 1) - CountOfLastPackets++; - } - if ((int)(time - ActIndexInTrBytesTimeLim) < 0) // within the current time interval, just add the byte count to the interval - { - TransferedBytes[ActIndexInTrBytes] += count; - } - else // outside the current time interval, we must create a new interval - { - int emptyTrBytes = (time - ActIndexInTrBytesTimeLim) / PRSPMETER_ACTSPEEDSTEP; - int i = min(emptyTrBytes, PRSPMETER_ACTSPEEDNUMOFSTEPS); // more has no effect (the entire queue would be reset) - if (i > 0 && CountOfTrBytesItems <= PRSPMETER_ACTSPEEDNUMOFSTEPS) - CountOfTrBytesItems = min(PRSPMETER_ACTSPEEDNUMOFSTEPS + 1, CountOfTrBytesItems + i); - while (i--) - { - if (++ActIndexInTrBytes > PRSPMETER_ACTSPEEDNUMOFSTEPS) - ActIndexInTrBytes = 0; // moving along the circular queue - TransferedBytes[ActIndexInTrBytes] = 0; - } - ActIndexInTrBytesTimeLim += (emptyTrBytes + 1) * PRSPMETER_ACTSPEEDSTEP; - if (++ActIndexInTrBytes > PRSPMETER_ACTSPEEDNUMOFSTEPS) - ActIndexInTrBytes = 0; // moving along the circular queue - if (CountOfTrBytesItems <= PRSPMETER_ACTSPEEDNUMOFSTEPS) - CountOfTrBytesItems++; - TransferedBytes[ActIndexInTrBytes] = count; - } -} - // // **************************************************************************** // COperations @@ -958,7875 +475,3 @@ void COperations::GetSpeedLimit(BOOL* useSpeedLimit, DWORD* speedLimit) } // -// **************************************************************************** -// CAsyncCopyParams -// - -struct CAsyncCopyParams -{ - void* Buffers[8]; // allocated buffers of size ASYNC_COPY_BUF_SIZE bytes - OVERLAPPED Overlapped[8]; // structures for asynchronous operations - - BOOL UseAsyncAlg; // TRUE = use the asynchronous algorithm (data must be allocated), FALSE = old synchronous algorithm (allocate nothing) - - BOOL HasFailed; // TRUE = failed to create an event for the Overlapped array, the structure is unusable - - CAsyncCopyParams(); - ~CAsyncCopyParams(); - - void Init(BOOL useAsyncAlg); - - BOOL Failed() { return HasFailed; } - - DWORD GetOverlappedFlag() { return UseAsyncAlg ? FILE_FLAG_OVERLAPPED : 0; } - - OVERLAPPED* InitOverlapped(int i); // zeroes and returns Overlapped[i] - OVERLAPPED* InitOverlappedWithOffset(int i, const CQuadWord& offset); // zeroes it, sets 'offset', and returns Overlapped[i] - OVERLAPPED* GetOverlapped(int i) { return &Overlapped[i]; } - void SetOverlappedToEOF(int i, const CQuadWord& offset); // sets Overlapped[i] to the state after finishing asynchronous reading that detected EOF -}; - -CAsyncCopyParams::CAsyncCopyParams() -{ - memset(Buffers, 0, sizeof(Buffers)); - memset(Overlapped, 0, sizeof(Overlapped)); - UseAsyncAlg = FALSE; - HasFailed = FALSE; -} - -void CAsyncCopyParams::Init(BOOL useAsyncAlg) -{ - UseAsyncAlg = useAsyncAlg; - if (UseAsyncAlg && Buffers[0] == NULL) - { - for (int i = 0; i < 8; i++) - { - Buffers[i] = malloc(ASYNC_COPY_BUF_SIZE); - Overlapped[i].hEvent = HANDLES(CreateEvent(NULL, TRUE, FALSE, NULL)); - if (Overlapped[i].hEvent == NULL) - { - DWORD err = GetLastError(); - TRACE_E("Unable to create synchronization object for Copy rutine: " << GetErrorText(err)); - HasFailed = TRUE; - } - } - } -} - -CAsyncCopyParams::~CAsyncCopyParams() -{ - for (int i = 0; i < 8; i++) - { - if (Buffers[i] != NULL) - free(Buffers[i]); - if (Overlapped[i].hEvent != NULL) - HANDLES(CloseHandle(Overlapped[i].hEvent)); - } -} - -OVERLAPPED* -CAsyncCopyParams::InitOverlapped(int i) -{ - if (!UseAsyncAlg) - TRACE_C("CAsyncCopyParams::InitOverlapped(): unexpected call, UseAsyncAlg is FALSE!"); - Overlapped[i].Internal = 0; - Overlapped[i].InternalHigh = 0; - Overlapped[i].Offset = 0; - Overlapped[i].OffsetHigh = 0; - // Overlapped[i].Pointer = 0; // this is a union, Pointer overlaps with Offset and OffsetHigh - return &Overlapped[i]; -} - -OVERLAPPED* -CAsyncCopyParams::InitOverlappedWithOffset(int i, const CQuadWord& offset) -{ - if (!UseAsyncAlg) - TRACE_C("CAsyncCopyParams::InitOverlappedWithOffset(): unexpected call, UseAsyncAlg is FALSE!"); - Overlapped[i].Internal = 0; - Overlapped[i].InternalHigh = 0; - Overlapped[i].Offset = offset.LoDWord; - Overlapped[i].OffsetHigh = offset.HiDWord; - // Overlapped[i].Pointer = 0; // this is a union, Pointer overlaps with Offset and OffsetHigh - return &Overlapped[i]; -} - -void CAsyncCopyParams::SetOverlappedToEOF(int i, const CQuadWord& offset) -{ - if (!UseAsyncAlg) - TRACE_C("CAsyncCopyParams::SetOverlappedToEOF(): unexpected call, UseAsyncAlg is FALSE!"); - Overlapped[i].Internal = 0xC0000011 /* STATUS_END_OF_FILE */; // NTSTATUS code equivalent to system error code ERROR_HANDLE_EOF - Overlapped[i].InternalHigh = 0; - Overlapped[i].Offset = offset.LoDWord; - Overlapped[i].OffsetHigh = offset.HiDWord; - // Overlapped[i].Pointer = 0; // this is a union, Pointer overlaps with Offset and OffsetHigh - SetEvent(Overlapped[i].hEvent); -} - -// ********************************************************************************** - -BOOL HaveWriteOwnerRight = FALSE; // does the process have the WRITE_OWNER right? - -void InitWorker() -{ - if (NtDLL != NULL) // "always true" - { - DynNtQueryInformationFile = (NTQUERYINFORMATIONFILE)GetProcAddress(NtDLL, "NtQueryInformationFile"); // has no header - DynNtFsControlFile = (NTFSCONTROLFILE)GetProcAddress(NtDLL, "NtFsControlFile"); // has no header - } -} - -void ReleaseWorker() -{ - DynNtQueryInformationFile = NULL; - DynNtFsControlFile = NULL; -} - -struct CWorkerData -{ - COperations* Script; - HWND HProgressDlg; - void* Buffer; - BOOL BufferIsAllocated; - DWORD ClearReadonlyMask; - CConvertData* ConvertData; - HANDLE WContinue; - - HANDLE WorkerNotSuspended; - BOOL* CancelWorker; - int* OperationProgress; - int* SummaryProgress; -}; - -struct CProgressDlgData -{ - HANDLE WorkerNotSuspended; - BOOL* CancelWorker; - int* OperationProgress; - int* SummaryProgress; - - BOOL OverwriteAll; // keeps the state of automatic overwriting of the target with the source - BOOL OverwriteHiddenAll; - BOOL DeleteHiddenAll; - BOOL EncryptSystemAll; - BOOL DirOverwriteAll; - BOOL FileOutLossEncrAll; - BOOL DirCrLossEncrAll; - - BOOL SkipAllFileWrite; // has the Skip All button already been used? - BOOL SkipAllFileRead; - BOOL SkipAllOverwrite; - BOOL SkipAllSystemOrHidden; - BOOL SkipAllFileOpenIn; - BOOL SkipAllFileOpenOut; - BOOL SkipAllOverwriteErr; - BOOL SkipAllMoveErrors; - BOOL SkipAllDeleteErr; - BOOL SkipAllDirCreate; - BOOL SkipAllDirCreateErr; - BOOL SkipAllChangeAttrs; - BOOL SkipAllEncryptSystem; - BOOL SkipAllFileADSOpenIn; - BOOL SkipAllFileADSOpenOut; - BOOL SkipAllGetFileTime; - BOOL SkipAllSetFileTime; - BOOL SkipAllFileADSRead; - BOOL SkipAllFileADSWrite; - BOOL SkipAllDirOver; - BOOL SkipAllFileOutLossEncr; - BOOL SkipAllDirCrLossEncr; - - BOOL IgnoreAllADSReadErr; - BOOL IgnoreAllADSOpenOutErr; - BOOL IgnoreAllGetFileTimeErr; - BOOL IgnoreAllSetFileTimeErr; - BOOL IgnoreAllSetAttrsErr; - BOOL IgnoreAllCopyPermErr; - BOOL IgnoreAllCopyDirTimeErr; - - int CnfrmFileOver; // local copy of the Salamander configuration - int CnfrmDirOver; - int CnfrmSHFileOver; - int CnfrmSHFileDel; - int UseRecycleBin; - CMaskGroup RecycleMasks; - - BOOL PrepareRecycleMasks(int& errorPos) - { - return RecycleMasks.PrepareMasks(errorPos); - } - - BOOL AgreeRecycleMasks(const char* fileName, const char* fileExt) - { - return RecycleMasks.AgreeMasks(fileName, fileExt); - } -}; - -void SetProgressDialog(HWND hProgressDlg, CProgressData* data, CProgressDlgData& dlgData) -{ // wait for the response; the dialog must be updated - WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - if (!*dlgData.CancelWorker) // we need to stop the main thread - SendMessage(hProgressDlg, WM_USER_SETDIALOG, (WPARAM)data, 0); -} - -int CaclProg(const CQuadWord& progressCurrent, const CQuadWord& progressTotal) -{ - return progressCurrent >= progressTotal ? (progressTotal.Value == 0 ? 0 : 1000) : (int)((progressCurrent * CQuadWord(1000, 0)) / progressTotal).Value; -} - -void SetProgress(HWND hProgressDlg, int operation, int summary, CProgressDlgData& dlgData) -{ // notify about the change and continue without waiting for a reply - WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - if (!*dlgData.CancelWorker && - (*dlgData.OperationProgress != operation || *dlgData.SummaryProgress != summary)) - { - *dlgData.OperationProgress = operation; - *dlgData.SummaryProgress = summary; - SendMessage(hProgressDlg, WM_USER_SETDIALOG, 0, 0); - } -} - -void SetProgressWithoutSuspend(HWND hProgressDlg, int operation, int summary, CProgressDlgData& dlgData) -{ // notify about the change and continue without waiting for a reply - if (!*dlgData.CancelWorker && - (*dlgData.OperationProgress != operation || *dlgData.SummaryProgress != summary)) - { - *dlgData.OperationProgress = operation; - *dlgData.SummaryProgress = summary; - SendMessage(hProgressDlg, WM_USER_SETDIALOG, 0, 0); - } -} - -BOOL GetDirTime(const char* dirName, FILETIME* ftModified); -BOOL DoCopyDirTime(HWND hProgressDlg, const char* targetName, FILETIME* modified, CProgressDlgData& dlgData, BOOL quiet); - -void GetFileOverwriteInfo(char* buff, int buffLen, HANDLE file, const char* fileName, FILETIME* fileTime, BOOL* getTimeFailed) -{ - FILETIME lastWrite; - SYSTEMTIME st; - FILETIME ft; - char date[50], time[50]; - if (!GetFileTime(file, NULL, NULL, &lastWrite) || - !FileTimeToLocalFileTime(&lastWrite, &ft) || - !FileTimeToSystemTime(&ft, &st)) - { - if (getTimeFailed != NULL) - *getTimeFailed = TRUE; - date[0] = 0; - time[0] = 0; - } - else - { - if (fileTime != NULL) - *fileTime = ft; - if (GetTimeFormat(LOCALE_USER_DEFAULT, 0, &st, NULL, time, 50) == 0) - _snprintf_s(time, _countof(time), _TRUNCATE, "%u:%02u:%02u", st.wHour, st.wMinute, st.wSecond); - if (GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &st, NULL, date, 50) == 0) - _snprintf_s(date, _countof(date), _TRUNCATE, "%u.%u.%u", st.wDay, st.wMonth, st.wYear); - } - - char attr[30]; - lstrcpy(attr, ", "); - DWORD attrs = SalGetFileAttributes(fileName); - if (attrs != 0xFFFFFFFF) - GetAttrsString(attr + 2, attrs); - if (strlen(attr) == 2) - attr[0] = 0; - - char number[50]; - CQuadWord size; - DWORD err; - if (SalGetFileSize(file, size, err)) - NumberToStr(number, size); - else - number[0] = 0; // error - size unknown - - _snprintf_s(buff, buffLen, _TRUNCATE, "%s, %s, %s%s", number, date, time, attr); -} - -void GetDirInfo(char* buffer, int bufferLen, const char* dir) -{ - if (bufferLen <= 0) - return; - const char* dirFindFirst = dir; - char dirFindFirstCopy[3 * MAX_PATH]; - MakeCopyWithBackslashIfNeeded(dirFindFirst, dirFindFirstCopy); - - BOOL ok = FALSE; - FILETIME lastWrite; - if (NameEndsWithBackslash(dirFindFirst)) - { // FindFirstFile fails for a dir ending with a backslash (used for invalid directory names), - // so in this situation we handle it through CreateFile and GetFileTime - CStrP dirFindFirstW(ConvertAllocUtf8ToWide(dirFindFirst, -1)); - HANDLE file = dirFindFirstW != NULL - ? HANDLES_Q(CreateFileW(dirFindFirstW, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, - NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL)) - : INVALID_HANDLE_VALUE; - if (file != INVALID_HANDLE_VALUE) - { - if (GetFileTime(file, NULL, NULL, &lastWrite)) - ok = TRUE; - HANDLES(CloseHandle(file)); - } - } - else - { - WIN32_FIND_DATAW data; - CStrP dirFindFirstW(ConvertAllocUtf8ToWide(dirFindFirst, -1)); - HANDLE find = dirFindFirstW != NULL ? HANDLES_Q(FindFirstFileW(dirFindFirstW, &data)) : INVALID_HANDLE_VALUE; - if (find != INVALID_HANDLE_VALUE) - { - HANDLES(FindClose(find)); - lastWrite = data.ftLastWriteTime; - ok = TRUE; - } - } - if (ok) - { - SYSTEMTIME st; - FILETIME ft; - if (FileTimeToLocalFileTime(&lastWrite, &ft) && - FileTimeToSystemTime(&ft, &st)) - { - char date[50], time[50]; - if (GetTimeFormat(LOCALE_USER_DEFAULT, 0, &st, NULL, time, 50) == 0) - _snprintf_s(time, _countof(time), _TRUNCATE, "%u:%02u:%02u", st.wHour, st.wMinute, st.wSecond); - if (GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &st, NULL, date, 50) == 0) - _snprintf_s(date, _countof(date), _TRUNCATE, "%u.%u.%u", st.wDay, st.wMonth, st.wYear); - - _snprintf_s(buffer, bufferLen, _TRUNCATE, "%s, %s", date, time); - } - else - _snprintf_s(buffer, bufferLen, _TRUNCATE, "%s, %s", LoadStr(IDS_INVALID_DATEORTIME), LoadStr(IDS_INVALID_DATEORTIME)); - } - else - buffer[0] = 0; -} - -BOOL IsDirectoryEmpty(const char* name) // directories/subdirectories contain no files -{ - char dir[MAX_PATH + 5]; - int len = (int)strlen(name); - if (len <= 0 || len >= _countof(dir) - 2) - return FALSE; - memcpy(dir, name, len); - dir[len] = 0; - if (dir[len - 1] != '\\') - dir[len++] = '\\'; - char* end = dir + len; - *end++ = '*'; - *end = 0; - - WIN32_FIND_DATAW fileData; - HANDLE search; - CStrP dirW(ConvertAllocUtf8ToWide(dir, -1)); - search = dirW != NULL ? HANDLES_Q(FindFirstFileW(dirW, &fileData)) : INVALID_HANDLE_VALUE; - if (search != INVALID_HANDLE_VALUE) - { - do - { - if (fileData.cFileName[0] == 0 || - fileData.cFileName[0] == L'.' && (fileData.cFileName[1] == 0 || - fileData.cFileName[1] == L'.' && fileData.cFileName[2] == 0)) - continue; - - if (fileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) - { - int remaining = (int)(_countof(dir) - (end - dir)); - if (remaining <= 1 || ConvertWideToUtf8(fileData.cFileName, -1, end, remaining) == 0) - continue; - if (!IsDirectoryEmpty(dir)) // the subdirectory is not empty - { - HANDLES(FindClose(search)); - return FALSE; - } - } - else - { - HANDLES(FindClose(search)); // a file exists here - return FALSE; - } - } while (FindNextFileW(search, &fileData)); - HANDLES(FindClose(search)); - } - return TRUE; -} - -BOOL CurrentProcessTokenUserValid = FALSE; -char CurrentProcessTokenUserBuf[200]; -TOKEN_USER* CurrentProcessTokenUser = (TOKEN_USER*)CurrentProcessTokenUserBuf; - -void GainWriteOwnerAccess() -{ - static BOOL firstCall = TRUE; - if (firstCall) - { - firstCall = FALSE; - - HANDLE tokenHandle; - if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &tokenHandle)) - { - TRACE_E("GainWriteOwnerAccess(): OpenProcessToken failed!"); - return; - } - - DWORD reqSize; - if (GetTokenInformation(tokenHandle, TokenUser, CurrentProcessTokenUser, 200, &reqSize)) - CurrentProcessTokenUserValid = TRUE; - - int i; - for (i = 0; i < 3; i++) - { - const char* privName = NULL; - switch (i) - { - case 0: - privName = SE_RESTORE_NAME; - break; - case 1: - privName = SE_TAKE_OWNERSHIP_NAME; - break; - case 2: - privName = SE_SECURITY_NAME; - break; - } - - LUID value; - if (privName != NULL && LookupPrivilegeValue(NULL, privName, &value)) - { - TOKEN_PRIVILEGES tokenPrivileges; - tokenPrivileges.PrivilegeCount = 1; - tokenPrivileges.Privileges[0].Luid = value; - tokenPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; - - AdjustTokenPrivileges(tokenHandle, FALSE, &tokenPrivileges, sizeof(tokenPrivileges), NULL, NULL); - if (GetLastError() != NO_ERROR) - { - DWORD err = GetLastError(); - TRACE_E("GainWriteOwnerAccess(): AdjustTokenPrivileges(" << privName << ") failed! error: " << GetErrorText(err)); - } - else - { - if (i == 0) - HaveWriteOwnerRight = TRUE; // successfully obtained SE_RESTORE_NAME, WRITE_OWNER is guaranteed - } - } - else - { - DWORD err = GetLastError(); - TRACE_E("GainWriteOwnerAccess(): LookupPrivilegeValue(" << (privName != NULL ? privName : "null") << ") failed! error: " << GetErrorText(err)); - } - } - CloseHandle(tokenHandle); - } -} -/* -// Purpose: Determines if the user is a member of the administrators group. -// Return: TRUE if user is a admin -// FALSE if not -#define STATUS_SUCCESS ((NTSTATUS)0x00000000L) // ntsubauth -#define STATUS_BUFFER_TOO_SMALL ((NTSTATUS)0xC0000023L) -#define NT_SUCCESS(Status) ((NTSTATUS)(Status) >= 0) -typedef NTSTATUS (WINAPI *FNtQueryInformationToken)( - HANDLE TokenHandle, // IN - TOKEN_INFORMATION_CLASS TokenInformationClass, // IN - PVOID TokenInformation, // OUT - ULONG TokenInformationLength, // IN - PULONG ReturnLength // OUT - ); - - -BOOL IsUserAdmin() -{ - if (NtDLL == NULL) - return TRUE; - - GainWriteOwnerAccess(); - - FNtQueryInformationToken DynNTNtQueryInformationToken = (FNtQueryInformationToken)GetProcAddress(NtDLL, "NtQueryInformationToken"); // has no header - if (DynNTNtQueryInformationToken == NULL) - { - TRACE_E("Getting NtQueryInformationToken export failed!"); - return FALSE; - } - - static int fIsUserAnAdmin = -1; // cache - - if (-1 == fIsUserAnAdmin) - { - SID_IDENTIFIER_AUTHORITY authNT = SECURITY_NT_AUTHORITY; - NTSTATUS Status; - ULONG InfoLength; - PTOKEN_GROUPS TokenGroupList; - ULONG GroupIndex; - BOOL FoundAdmins; - PSID AdminsDomainSid; - HANDLE hUserToken; - - // Open the user's token - if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hUserToken)) - return FALSE; - - // Create Admins domain sid. - Status = AllocateAndInitializeSid( - &authNT, - 2, - SECURITY_BUILTIN_DOMAIN_RID, - DOMAIN_ALIAS_RID_ADMINS, - 0, 0, 0, 0, 0, 0, - &AdminsDomainSid - ); - - // Test if user is in the Admins domain - - // Get a list of groups in the token - Status = DynNTNtQueryInformationToken( - hUserToken, // Handle - TokenGroups, // TokenInformationClass - NULL, // TokenInformation - 0, // TokenInformationLength - &InfoLength // ReturnLength - ); - - if ((Status != STATUS_SUCCESS) && (Status != STATUS_BUFFER_TOO_SMALL)) - { - FreeSid(AdminsDomainSid); - CloseHandle(hUserToken); - return FALSE; - } - - TokenGroupList = (PTOKEN_GROUPS)GlobalAlloc(GPTR, InfoLength); - - if (TokenGroupList == NULL) - { - FreeSid(AdminsDomainSid); - CloseHandle(hUserToken); - return FALSE; - } - - Status = DynNTNtQueryInformationToken( - hUserToken, // Handle - TokenGroups, // TokenInformationClass - TokenGroupList, // TokenInformation - InfoLength, // TokenInformationLength - &InfoLength // ReturnLength - ); - - if (!NT_SUCCESS(Status)) - { - GlobalFree(TokenGroupList); - FreeSid(AdminsDomainSid); - CloseHandle(hUserToken); - return FALSE; - } - - - // Search group list for Admins alias - FoundAdmins = FALSE; - - for (GroupIndex=0; GroupIndex < TokenGroupList->GroupCount; GroupIndex++ ) - { - if (EqualSid(TokenGroupList->Groups[GroupIndex].Sid, AdminsDomainSid)) - { - FoundAdmins = TRUE; - break; - } - } - - // Tidy up - GlobalFree(TokenGroupList); - FreeSid(AdminsDomainSid); - CloseHandle(hUserToken); - - fIsUserAnAdmin = FoundAdmins ? 1 : 0; - } - - return (BOOL)fIsUserAnAdmin; -} - -*/ - -/* according to http://vcfaq.mvps.org/sdk/21.htm */ -#define BUFF_SIZE 1024 -BOOL IsUserAdmin() -{ - HANDLE hToken = NULL; - PSID pAdminSid = NULL; - BYTE buffer[BUFF_SIZE]; - PTOKEN_GROUPS pGroups = (PTOKEN_GROUPS)buffer; - DWORD dwSize; // buffer size - DWORD i; - BOOL bSuccess; - SID_IDENTIFIER_AUTHORITY siaNtAuth = SECURITY_NT_AUTHORITY; - - // get token handle - if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) - return FALSE; - - bSuccess = GetTokenInformation(hToken, TokenGroups, (LPVOID)pGroups, BUFF_SIZE, &dwSize); - CloseHandle(hToken); - if (!bSuccess) - return FALSE; - - if (!AllocateAndInitializeSid(&siaNtAuth, 2, - SECURITY_BUILTIN_DOMAIN_RID, - DOMAIN_ALIAS_RID_ADMINS, - 0, 0, 0, 0, 0, 0, &pAdminSid)) - return FALSE; - - bSuccess = FALSE; - for (i = 0; (i < pGroups->GroupCount) && !bSuccess; i++) - { - if (EqualSid(pAdminSid, pGroups->Groups[i].Sid)) - bSuccess = TRUE; - } - FreeSid(pAdminSid); - - return bSuccess; -} - -struct CSrcSecurity // helper structure for keeping security info for MoveFile (the source disappears after the operation, its security info must be stored beforehand) -{ - PSID SrcOwner; - PSID SrcGroup; - PACL SrcDACL; - PSECURITY_DESCRIPTOR SrcSD; - DWORD SrcError; - - CSrcSecurity() { Clear(); } - ~CSrcSecurity() - { - if (SrcSD != NULL) - LocalFree(SrcSD); - } - void Clear() - { - SrcOwner = NULL; - SrcGroup = NULL; - SrcDACL = NULL; - SrcSD = NULL; - SrcError = NO_ERROR; - } -}; - -BOOL DoCopySecurity(const char* sourceName, const char* targetName, DWORD* err, CSrcSecurity* srcSecurity) -{ - // if the path ends with a space or dot, we must append '\\', otherwise - // GetNamedSecurityInfo (and others) trim the spaces/dots and then work - // with a different path - const char* sourceNameSec = sourceName; - char sourceNameSecCopy[3 * MAX_PATH]; - MakeCopyWithBackslashIfNeeded(sourceNameSec, sourceNameSecCopy); - const char* targetNameSec = targetName; - char targetNameSecCopy[3 * MAX_PATH]; - MakeCopyWithBackslashIfNeeded(targetNameSec, targetNameSecCopy); - - PSID srcOwner = NULL; - PSID srcGroup = NULL; - PACL srcDACL = NULL; - PSECURITY_DESCRIPTOR srcSD = NULL; - if (srcSecurity != NULL) // MoveFile: simply take over the security info - { - srcOwner = srcSecurity->SrcOwner; - srcGroup = srcSecurity->SrcGroup; - srcDACL = srcSecurity->SrcDACL; - srcSD = srcSecurity->SrcSD; - *err = srcSecurity->SrcError; - srcSecurity->Clear(); - } - else // obtain the security info from the source - { - CStrP sourceNameSecW(ConvertAllocUtf8ToWide(sourceNameSec, -1)); - if (sourceNameSecW != NULL) - { - *err = GetNamedSecurityInfoW(sourceNameSecW, SE_FILE_OBJECT, - DACL_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION, - &srcOwner, &srcGroup, &srcDACL, NULL, &srcSD); - } - else - { - *err = ERROR_NO_UNICODE_TRANSLATION; - } - } - BOOL ret = *err == ERROR_SUCCESS; - - if (ret) - { - SECURITY_DESCRIPTOR_CONTROL srcSDControl; - DWORD srcSDRevision; - if (!GetSecurityDescriptorControl(srcSD, &srcSDControl, &srcSDRevision)) - { - *err = GetLastError(); - ret = FALSE; - } - else - { - CStrP targetNameSecW(ConvertAllocUtf8ToWide(targetNameSec, -1)); - if (targetNameSecW == NULL) - { - *err = ERROR_NO_UNICODE_TRANSLATION; - ret = FALSE; - } - else - { - BOOL inheritedDACL = /*(srcSDControl & SE_DACL_AUTO_INHERITED) != 0 &&*/ (srcSDControl & SE_DACL_PROTECTED) == 0; // SE_DACL_AUTO_INHERITED unfortunately is not always set (for example Total Commander clears it after moving a file, so we ignore it) - DWORD attr = GetFileAttributesUtf8(targetNameSec); - *err = SetNamedSecurityInfoW(targetNameSecW, SE_FILE_OBJECT, - DACL_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION | - (inheritedDACL ? UNPROTECTED_DACL_SECURITY_INFORMATION : PROTECTED_DACL_SECURITY_INFORMATION), - srcOwner, srcGroup, srcDACL, NULL); - ret = *err == ERROR_SUCCESS; - - if (!ret) - { - // if the owner and group cannot be changed (we do not have the rights in the directory - for example we only have "change" rights), - // check whether the owner and group are already set (that would not be an error) - PSID tgtOwner = NULL; - PSID tgtGroup = NULL; - PACL tgtDACL = NULL; - PSECURITY_DESCRIPTOR tgtSD = NULL; - BOOL tgtRead = GetNamedSecurityInfoW(targetNameSecW, SE_FILE_OBJECT, - DACL_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION, - &tgtOwner, &tgtGroup, &tgtDACL, NULL, &tgtSD) == ERROR_SUCCESS; - // if the owner of the target file is not the current user, try to set it ("take ownership") - only - // provided we have the right to write the owner so that we can write back the original owner afterwards - BOOL ownerOfFile = FALSE; - if (!tgtRead || // if the security info cannot be read from the target, the owner is most likely not the current user (the owner has unblocked read rights) - tgtOwner == NULL || // probably nonsense, the file must have some owner; if it happens, try to set the owner to the current user - CurrentProcessTokenUserValid && CurrentProcessTokenUser->User.Sid != NULL && - !EqualSid(tgtOwner, CurrentProcessTokenUser->User.Sid)) - { - if (HaveWriteOwnerRight && - CurrentProcessTokenUserValid && CurrentProcessTokenUser->User.Sid != NULL && - SetNamedSecurityInfoW(targetNameSecW, SE_FILE_OBJECT, OWNER_SECURITY_INFORMATION, - CurrentProcessTokenUser->User.Sid, NULL, NULL, NULL) == ERROR_SUCCESS) - { // setting succeeded; we must retrieve 'tgtSD' again - ownerOfFile = TRUE; - if (tgtSD != NULL) - LocalFree(tgtSD); - tgtOwner = NULL; - tgtGroup = NULL; - tgtDACL = NULL; - tgtSD = NULL; - tgtRead = GetNamedSecurityInfoW(targetNameSecW, SE_FILE_OBJECT, - DACL_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION, - &tgtOwner, &tgtGroup, &tgtDACL, NULL, &tgtSD) == ERROR_SUCCESS; - } - } - else - { - ownerOfFile = tgtRead && tgtOwner != NULL && CurrentProcessTokenUserValid && - CurrentProcessTokenUser->User.Sid != NULL; - } - BOOL daclOK = FALSE; - BOOL ownerOK = FALSE; - BOOL groupOK = FALSE; - if (ownerOfFile && CurrentProcessTokenUserValid && CurrentProcessTokenUser->User.Sid != NULL) - { // we are the file owner -> the DACL can be written; try to allow owner/group/DACL write and set the required values - int allowChPermDACLSize = sizeof(ACL) + sizeof(ACCESS_ALLOWED_ACE) - sizeof(ACCESS_ALLOWED_ACE().SidStart) + - GetLengthSid(CurrentProcessTokenUser->User.Sid) + 200 /* +200 bytes is just paranoia */; - char buff3[500]; - PACL allowChPermDACL; - if (allowChPermDACLSize > 500) - allowChPermDACL = (PACL)malloc(allowChPermDACLSize); - else - allowChPermDACL = (PACL)buff3; - if (allowChPermDACL != NULL && InitializeAcl(allowChPermDACL, allowChPermDACLSize, ACL_REVISION) && - AddAccessAllowedAce(allowChPermDACL, ACL_REVISION, READ_CONTROL | WRITE_DAC | WRITE_OWNER, - CurrentProcessTokenUser->User.Sid) && - SetNamedSecurityInfoW(targetNameSecW, SE_FILE_OBJECT, - DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION, - NULL, NULL, allowChPermDACL, NULL) == ERROR_SUCCESS) - { - ownerOK = SetNamedSecurityInfoW(targetNameSecW, SE_FILE_OBJECT, - OWNER_SECURITY_INFORMATION, - srcOwner, NULL, NULL, NULL) == ERROR_SUCCESS; - groupOK = SetNamedSecurityInfoW(targetNameSecW, SE_FILE_OBJECT, - GROUP_SECURITY_INFORMATION, - NULL, srcGroup, NULL, NULL) == ERROR_SUCCESS; - daclOK = SetNamedSecurityInfoW(targetNameSecW, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION | (inheritedDACL ? UNPROTECTED_DACL_SECURITY_INFORMATION : PROTECTED_DACL_SECURITY_INFORMATION), - NULL, NULL, srcDACL, NULL) == ERROR_SUCCESS; - } - if (allowChPermDACL != (PACL)buff3 && allowChPermDACL != NULL) - free(allowChPermDACL); - } - if (!ownerOK && - (SetNamedSecurityInfoW(targetNameSecW, SE_FILE_OBJECT, OWNER_SECURITY_INFORMATION, - srcOwner, NULL, NULL, NULL) == ERROR_SUCCESS || - tgtRead && (srcOwner == NULL && tgtOwner == NULL || // if the owner is already set, ignore a potential error while setting - srcOwner != NULL && tgtOwner != NULL && EqualSid(srcOwner, tgtOwner)))) - { - ownerOK = TRUE; - } - if (!groupOK && - (SetNamedSecurityInfoW(targetNameSecW, SE_FILE_OBJECT, GROUP_SECURITY_INFORMATION, - NULL, srcGroup, NULL, NULL) == ERROR_SUCCESS || - tgtRead && (srcGroup == NULL && tgtGroup == NULL || // if the group is already set, ignore a potential error while setting - srcGroup != NULL && tgtGroup != NULL && EqualSid(srcGroup, tgtGroup)))) - { - groupOK = TRUE; - } - if (!daclOK && // the DACL must be set last because it depends on the owner (CREATOR OWNER is replaced with the real owner, etc.) - SetNamedSecurityInfoW(targetNameSecW, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION | (inheritedDACL ? UNPROTECTED_DACL_SECURITY_INFORMATION : PROTECTED_DACL_SECURITY_INFORMATION), - NULL, NULL, srcDACL, NULL) == ERROR_SUCCESS) - { - daclOK = TRUE; - } - if (ownerOK && groupOK && daclOK) - { - ret = TRUE; // all three components are OK -> the whole thing is OK - *err = NO_ERROR; - } - if (tgtSD != NULL) - LocalFree(tgtSD); - } - if (attr != INVALID_FILE_ATTRIBUTES) - SetFileAttributesUtf8(targetNameSec, attr); - } - } - } - if (srcSD != NULL) - LocalFree(srcSD); - return ret; -} - -DWORD CompressFile(char* fileName, DWORD attrs) -{ - DWORD ret = ERROR_SUCCESS; - if (attrs & FILE_ATTRIBUTE_COMPRESSED) - return ret; // already compressed - - // if the path ends with a space or dot, we must append '\\', otherwise CreateFile - // trims the spaces/dots and works with a different path - const char* fileNameCrFile = fileName; - char fileNameCrFileCopy[3 * MAX_PATH]; - MakeCopyWithBackslashIfNeeded(fileNameCrFile, fileNameCrFileCopy); - - BOOL attrsChange = FALSE; - if (attrs & FILE_ATTRIBUTE_READONLY) - { - attrsChange = TRUE; - SetFileAttributesUtf8(fileNameCrFile, attrs & ~FILE_ATTRIBUTE_READONLY); - } - HANDLE file = HANDLES_Q(CreateFileUtf8(fileNameCrFile, FILE_READ_DATA | FILE_WRITE_DATA, - FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, - OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, - NULL)); - if (file == INVALID_HANDLE_VALUE) - ret = GetLastError(); - else - { - USHORT state = COMPRESSION_FORMAT_DEFAULT; - ULONG length; - if (!DeviceIoControl(file, FSCTL_SET_COMPRESSION, &state, - sizeof(USHORT), NULL, 0, &length, FALSE)) - ret = GetLastError(); - HANDLES(CloseHandle(file)); - } - if (attrsChange) - SetFileAttributesUtf8(fileNameCrFile, attrs); // revert to the original attributes (on error the attributes would remain nonsensically changed) - return ret; -} - -DWORD UncompressFile(char* fileName, DWORD attrs) -{ - DWORD ret = ERROR_SUCCESS; - if ((attrs & FILE_ATTRIBUTE_COMPRESSED) == 0) - return ret; // not compressed - - // if the path ends with a space or dot, we must append '\\', otherwise CreateFile - // trims the spaces/dots and works with a different path - const char* fileNameCrFile = fileName; - char fileNameCrFileCopy[3 * MAX_PATH]; - MakeCopyWithBackslashIfNeeded(fileNameCrFile, fileNameCrFileCopy); - CStrP fileNameW(ConvertAllocUtf8ToWide(fileNameCrFile, -1)); - if (fileNameW == NULL) - return ERROR_NO_UNICODE_TRANSLATION; - - BOOL attrsChange = FALSE; - if (attrs & FILE_ATTRIBUTE_READONLY) - { - attrsChange = TRUE; - SetFileAttributesW(fileNameW, attrs & ~FILE_ATTRIBUTE_READONLY); - } - - HANDLE file = HANDLES_Q(CreateFileW(fileNameW, FILE_READ_DATA | FILE_WRITE_DATA, - FILE_SHARE_READ | FILE_SHARE_WRITE, - NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, - NULL)); - if (file == INVALID_HANDLE_VALUE) - ret = GetLastError(); - else - { - USHORT state = COMPRESSION_FORMAT_NONE; - ULONG length; - if (!DeviceIoControl(file, FSCTL_SET_COMPRESSION, &state, - sizeof(USHORT), NULL, 0, &length, FALSE)) - ret = GetLastError(); - HANDLES(CloseHandle(file)); - } - if (attrsChange) - SetFileAttributesW(fileNameW, attrs); // revert to the original attributes (on error the attributes would remain nonsensically changed) - return ret; -} - -DWORD MyEncryptFile(HWND hProgressDlg, char* fileName, DWORD attrs, DWORD finalAttrs, - CProgressDlgData& dlgData, BOOL& cancelOper, BOOL preserveDate) -{ - DWORD retEnc = ERROR_SUCCESS; - cancelOper = FALSE; - if (attrs & FILE_ATTRIBUTE_ENCRYPTED) - return retEnc; // already encrypted - - // if the path ends with a space or dot, we must append '\\', otherwise CreateFile - // trims the spaces/dots and works with a different path - const char* fileNameCrFile = fileName; - char fileNameCrFileCopy[3 * MAX_PATH]; - MakeCopyWithBackslashIfNeeded(fileNameCrFile, fileNameCrFileCopy); - CStrP fileNameW(ConvertAllocUtf8ToWide(fileNameCrFile, -1)); - if (fileNameW == NULL) - return ERROR_NO_UNICODE_TRANSLATION; - - // if the file has the SYSTEM attribute, the EncryptFile API function reports "access denied"; handle it: - if ((attrs & FILE_ATTRIBUTE_SYSTEM) && (finalAttrs & FILE_ATTRIBUTE_SYSTEM)) - { // if it has and will keep the SYSTEM attribute, ask the user whether they really mean it - if (!dlgData.EncryptSystemAll) - { - WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - if (*dlgData.CancelWorker) - return retEnc; - - if (dlgData.SkipAllEncryptSystem) - return retEnc; - - int ret = IDCANCEL; - char* data[4]; - data[0] = (char*)&ret; - data[1] = LoadStr(IDS_CONFIRMSFILEENCRYPT); - data[2] = fileName; - data[3] = LoadStr(IDS_ENCRYPTSFILE); - SendMessage(hProgressDlg, WM_USER_DIALOG, 2, (LPARAM)data); - switch (ret) - { - case IDB_ALL: - dlgData.EncryptSystemAll = TRUE; - case IDYES: - break; - - case IDB_SKIPALL: - dlgData.SkipAllEncryptSystem = TRUE; - case IDB_SKIP: - return retEnc; - - case IDCANCEL: - { - cancelOper = TRUE; - return retEnc; - } - } - } - } - - BOOL attrsChange = FALSE; - if (attrs & (FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_READONLY)) - { - attrsChange = TRUE; - SetFileAttributesW(fileNameW, attrs & ~(FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_READONLY)); - } - if (preserveDate) - { - HANDLE file; - file = HANDLES_Q(CreateFileW(fileNameW, GENERIC_READ, - FILE_SHARE_READ | FILE_SHARE_WRITE, - NULL, OPEN_EXISTING, - (attrs & FILE_ATTRIBUTE_DIRECTORY) ? FILE_FLAG_BACKUP_SEMANTICS : 0, - NULL)); - if (file != INVALID_HANDLE_VALUE) - { - FILETIME ftCreated, /*ftAccessed,*/ ftModified; - GetFileTime(file, &ftCreated, NULL /*&ftAccessed*/, &ftModified); - HANDLES(CloseHandle(file)); - - if (!EncryptFileW(fileNameW)) - retEnc = GetLastError(); - - file = HANDLES_Q(CreateFileW(fileNameW, GENERIC_WRITE, - FILE_SHARE_READ | FILE_SHARE_WRITE, - NULL, OPEN_EXISTING, - (attrs & FILE_ATTRIBUTE_DIRECTORY) ? FILE_FLAG_BACKUP_SEMANTICS : 0, - NULL)); - if (file != INVALID_HANDLE_VALUE) - { - SetFileTime(file, &ftCreated, NULL /*&ftAccessed*/, &ftModified); - HANDLES(CloseHandle(file)); - } - } - else - retEnc = GetLastError(); - } - else - { - if (!EncryptFileW(fileNameW)) - retEnc = GetLastError(); - } - if (attrsChange) - SetFileAttributesW(fileNameW, attrs); // revert to the original attributes (on error the attributes would remain nonsensically changed) - return retEnc; -} - -DWORD MyDecryptFile(char* fileName, DWORD attrs, BOOL preserveDate) -{ - DWORD ret = ERROR_SUCCESS; - if ((attrs & FILE_ATTRIBUTE_ENCRYPTED) == 0) - return ret; // not encrypted - - // if the path ends with a space or dot, we must append '\\', otherwise CreateFile - // trims the spaces/dots and works with a different path - const char* fileNameCrFile = fileName; - char fileNameCrFileCopy[3 * MAX_PATH]; - MakeCopyWithBackslashIfNeeded(fileNameCrFile, fileNameCrFileCopy); - CStrP fileNameW(ConvertAllocUtf8ToWide(fileNameCrFile, -1)); - if (fileNameW == NULL) - return ERROR_NO_UNICODE_TRANSLATION; - - BOOL attrsChange = FALSE; - if (attrs & FILE_ATTRIBUTE_READONLY) - { - attrsChange = TRUE; - SetFileAttributesW(fileNameW, attrs & ~FILE_ATTRIBUTE_READONLY); - } - if (preserveDate) - { - HANDLE file; - file = HANDLES_Q(CreateFileW(fileNameW, GENERIC_READ, - FILE_SHARE_READ | FILE_SHARE_WRITE, - NULL, OPEN_EXISTING, - (attrs & FILE_ATTRIBUTE_DIRECTORY) ? FILE_FLAG_BACKUP_SEMANTICS : 0, - NULL)); - if (file != INVALID_HANDLE_VALUE) - { - FILETIME ftCreated, /*ftAccessed,*/ ftModified; - GetFileTime(file, &ftCreated, NULL /*&ftAccessed*/, &ftModified); - HANDLES(CloseHandle(file)); - - if (!DecryptFileW(fileNameW, 0)) - ret = GetLastError(); - - file = HANDLES_Q(CreateFileW(fileNameW, GENERIC_WRITE, - FILE_SHARE_READ | FILE_SHARE_WRITE, - NULL, OPEN_EXISTING, - (attrs & FILE_ATTRIBUTE_DIRECTORY) ? FILE_FLAG_BACKUP_SEMANTICS : 0, - NULL)); - if (file != INVALID_HANDLE_VALUE) - { - SetFileTime(file, &ftCreated, NULL /*&ftAccessed*/, &ftModified); - HANDLES(CloseHandle(file)); - } - } - else - ret = GetLastError(); - } - else - { - if (!DecryptFileW(fileNameW, 0)) - ret = GetLastError(); - } - if (attrsChange) - SetFileAttributesW(fileNameW, attrs); // revert to the original attributes (on error the attributes would remain nonsensically changed) - return ret; -} - -BOOL CheckFileOrDirADS(const char* fileName, BOOL isDir, CQuadWord* adsSize, wchar_t*** streamNames, - int* streamNamesCount, BOOL* lowMemory, DWORD* winError, - DWORD bytesPerCluster, CQuadWord* adsOccupiedSpace, - BOOL* onlyDiscardableStreams) -{ - if (adsSize != NULL) - adsSize->SetUI64(0); - if (adsOccupiedSpace != NULL) - adsOccupiedSpace->SetUI64(0); - if (streamNames != NULL) - *streamNames = NULL; - if (streamNamesCount != NULL) - *streamNamesCount = 0; - if (lowMemory != NULL) - *lowMemory = FALSE; - if (winError != NULL) - *winError = NO_ERROR; - if (onlyDiscardableStreams != NULL) - *onlyDiscardableStreams = TRUE; - - if (DynNtQueryInformationFile != NULL) // "always true" - { - // if the path ends with a space or dot, we must append '\\', otherwise CreateFile - // trims the spaces/dots and works with a different path - const char* fileNameCrFile = fileName; - char fileNameCrFileCopy[3 * MAX_PATH]; - MakeCopyWithBackslashIfNeeded(fileNameCrFile, fileNameCrFileCopy); - - HANDLE file = HANDLES_Q(CreateFileUtf8(fileNameCrFile, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, - NULL, OPEN_EXISTING, - isDir ? FILE_FLAG_BACKUP_SEMANTICS : 0, NULL)); - if (file == INVALID_HANDLE_VALUE) - { - if (winError != NULL) - *winError = GetLastError(); - return FALSE; - } - - // get stream info - NTSTATUS uStatus; - IO_STATUS_BLOCK ioStatus; - BYTE buffer[65535]; // Windows XP cannot handle more than 65535 (no idea why) - uStatus = DynNtQueryInformationFile(file, &ioStatus, buffer, sizeof(buffer), FileStreamInformation); - HANDLES(CloseHandle(file)); - if (uStatus != 0 /* anything other than success is an error (including warnings) */) - { - if (winError != NULL) - { - if (uStatus == STATUS_BUFFER_OVERFLOW) - *winError = ERROR_INSUFFICIENT_BUFFER; - else - *winError = LsaNtStatusToWinError(uStatus); - } - return FALSE; - } - - TDirectArray* streamNamesAux = NULL; - if (streamNames != NULL) - { - streamNamesAux = new TDirectArray(10, 100); - if (streamNamesAux == NULL) - { - if (lowMemory != NULL) - *lowMemory = TRUE; - TRACE_E(LOW_MEMORY); - return FALSE; - } - } - - // iterate through the streams - PFILE_STREAM_INFORMATION psi = (PFILE_STREAM_INFORMATION)buffer; - BOOL ret = FALSE; - BOOL lowMem = FALSE; - if (ioStatus.Information > 0) // check whether we obtained any data at all - { - while (1) - { - if (psi->NameLength != 7 * 2 || _memicmp(psi->Name, L"::$DATA", 7 * 2)) // ignore default stream - { - ret = TRUE; - if (adsSize != NULL) - *adsSize += CQuadWord(psi->Size.LowPart, psi->Size.HighPart); // sum of the total size of all alternate data streams - if (adsOccupiedSpace != NULL && bytesPerCluster != 0) - { - CQuadWord fileSize(psi->Size.LowPart, psi->Size.HighPart); - *adsOccupiedSpace += fileSize - ((fileSize - CQuadWord(1, 0)) % CQuadWord(bytesPerCluster, 0)) + - CQuadWord(bytesPerCluster - 1, 0); - } - - if (onlyDiscardableStreams != NULL) - { // if an ADS appears that is unknown or indispensable, switch 'onlyDiscardableStreams' to FALSE - if ((psi->NameLength < 29 * 2 || _memicmp(psi->Name, L":\x05Q30lsldxJoudresxAaaqpcawXc:", 29 * 2) != 0) && // Win2K thumbnail in an ADS: 5952 bytes (depends on JPEG compression) - (psi->NameLength < 40 * 2 || _memicmp(psi->Name, L":{4c8cc155-6c1e-11d1-8e41-00c04fb9386d}:", 40 * 2) != 0) && // Win2K thumbnail in an ADS: 0 bytes - (psi->NameLength < 9 * 2 || _memicmp(psi->Name, L":KAVICHS:", 9 * 2) != 0)) // Kaspersky antivirus: 36/68 bytes - { - *onlyDiscardableStreams = FALSE; - } - } - - if (streamNamesAux != NULL) // collecting Unicode names of alternate data streams - { - wchar_t* str = (wchar_t*)malloc(psi->NameLength + 2); - if (str != NULL) - { - memcpy(str, psi->Name, psi->NameLength); - str[psi->NameLength / 2] = 0; - streamNamesAux->Add(str); - if (!streamNamesAux->IsGood()) - { - free(str); - streamNamesAux->ResetState(); - if (lowMemory != NULL) - *lowMemory = TRUE; - lowMem = TRUE; - break; - } - } - else - { - if (lowMemory != NULL) - *lowMemory = TRUE; - lowMem = TRUE; - TRACE_E(LOW_MEMORY); - break; - } - } - else - { - if (adsSize == NULL && adsOccupiedSpace == NULL && onlyDiscardableStreams == NULL) - break; // nothing else to find out (no names, stream sizes, or only-discardable-streams collected) - } - } - if (psi->NextEntry == 0) - break; - psi = (PFILE_STREAM_INFORMATION)((BYTE*)psi + psi->NextEntry); // move to next item - } - } - if (streamNamesAux != NULL) - { - if (lowMem || !ret) // lack of memory or no ADS, release all names - { - int i; - for (i = 0; i < streamNamesAux->Count; i++) - free(streamNamesAux->At(i)); - } - else // everything OK, pass the names to the caller - { - if (streamNamesCount != NULL) - *streamNamesCount = streamNamesAux->Count; - *streamNames = streamNamesAux->GetData(); - streamNamesAux->DetachArray(); - } - delete streamNamesAux; - } - return ret; - } - return FALSE; -} - -BOOL DeleteAllADS(HANDLE file, const char* fileName) -{ - if (DynNtQueryInformationFile != NULL) // "always true" - { - // get stream info - NTSTATUS uStatus; - IO_STATUS_BLOCK ioStatus; - BYTE buffer[65535]; // Windows XP cannot handle more than 65535 (no idea why) - uStatus = DynNtQueryInformationFile(file, &ioStatus, buffer, sizeof(buffer), FileStreamInformation); - if (uStatus != 0 /* anything other than success is an error (including warnings) */) - { - DWORD err; - if (uStatus == STATUS_BUFFER_OVERFLOW) - err = ERROR_INSUFFICIENT_BUFFER; - else - err = LsaNtStatusToWinError(uStatus); - TRACE_I("DeleteAllADS(" << fileName << "): NtQueryInformationFile failed: " << GetErrorText(err)); - return FALSE; - } - - // iterate through the streams - PFILE_STREAM_INFORMATION psi = (PFILE_STREAM_INFORMATION)buffer; - if (ioStatus.Information > 0) // verify that we received any data at all - { - WCHAR adsFullName[2 * MAX_PATH]; - adsFullName[0] = 0; - WCHAR* adsPart = NULL; - int adsPartSize = 0; - while (1) - { - if (psi->NameLength != 7 * 2 || _memicmp(psi->Name, L"::$DATA", 7 * 2)) // ignore default stream - { - if (adsFullName[0] == 0) // convert the file name only when needed for the first time to save CPU time - { - if (ConvertA2U(fileName, -1, adsFullName, 2 * MAX_PATH) == 0) - return FALSE; // "always false" - adsPart = adsFullName + wcslen(adsFullName); - adsPartSize = (int)((adsFullName + 2 * MAX_PATH) - adsPart); - if (adsPartSize > 0) - { - *adsPart++ = L':'; - adsPartSize--; - } - else - return FALSE; // "always false" - } - WCHAR* start = (WCHAR*)psi->Name; - WCHAR* nameEnd = (WCHAR*)((char*)psi->Name + psi->NameLength); - if (start < nameEnd && *start == L':') - start++; - WCHAR* end = start; - while (end < nameEnd && *end != L':') - end++; - if (end - start >= adsPartSize) - { - TRACE_I("DeleteAllADS(" << fileName << "): too long ADS name!"); - return FALSE; - } - if (end > start) - { - memcpy(adsPart, start, (end - start) * sizeof(WCHAR)); - adsPart[end - start] = 0; - if (!DeleteFileW(adsFullName)) - { - DWORD err = GetLastError(); - TRACE_IW(L"DeleteAllADS(" << adsFullName << L"): DeleteFile has failed: " << GetErrorTextW(err)); - return FALSE; - } - } - } - if (psi->NextEntry == 0) - break; - psi = (PFILE_STREAM_INFORMATION)((BYTE*)psi + psi->NextEntry); // move to next item - } - } - } - return TRUE; -} - -void MyStrCpyNW(wchar_t* s1, wchar_t* s2, int maxChars) -{ - if (maxChars == 0) - return; - while (--maxChars && *s2 != 0) - *s1++ = *s2++; - *s1 = 0; -} - -void CutADSNameSuffix(char* s) -{ - char* end = strrchr(s, ':'); - if (end != NULL && stricmp(end, ":$DATA") == 0) - *end = 0; -} - -// conversion to the extended-path variant, see the MSDN article "File Name Conventions" -void DoLongName(char* buf, const char* name, int bufSize) -{ - if (*name == '\\') - _snprintf_s(buf, bufSize, _TRUNCATE, "\\\\?\\UNC%s", name + 1); // UNC - else - _snprintf_s(buf, bufSize, _TRUNCATE, "\\\\?\\%s", name); // standard path -} - -BOOL SalSetFilePointer(HANDLE file, const CQuadWord& offset) -{ - LONG lo = offset.LoDWord; - LONG hi = offset.HiDWord; - lo = SetFilePointer(file, lo, &hi, FILE_BEGIN); - return (lo != INVALID_SET_FILE_POINTER || GetLastError() == NO_ERROR) && - lo == (LONG)offset.LoDWord && hi == (LONG)offset.HiDWord; -} - -#define RETRYCOPY_TAIL_MINSIZE (32 * 1024) // at least two blocks of this size are verified at the end of the file tested in CheckTailOfOutFile(); afterwards the block size grows up to ASYNC_COPY_BUF_SIZE (if reading is fast enough); NOTE: must be <= ASYNC_COPY_BUF_SIZE -#define RETRYCOPY_TESTINGTIME 3000 // duration of the CheckTailOfOutFile() test in [ms] - -void CheckTailOfOutFileShowErr(const char* txt) -{ - DWORD err = GetLastError(); - TRACE_I("CheckTailOfOutFile(): " << txt << " Error: " << GetErrorText(err)); -} - -BOOL CheckTailOfOutFile(CAsyncCopyParams* asyncPar, HANDLE in, HANDLE out, const CQuadWord& offset, - const CQuadWord& curInOffset, BOOL ignoreReadErrOnOut) -{ - char* bufIn = (char*)malloc(ASYNC_COPY_BUF_SIZE); - char* bufOut = (char*)malloc(ASYNC_COPY_BUF_SIZE); - - DWORD startTime = GetTickCount(); - DWORD rutineStartTime = startTime; - CQuadWord lastOffset = offset; - int roundNum = 1; - DWORD curBufSize = RETRYCOPY_TAIL_MINSIZE; - DWORD lastRoundStartTime = 0; - DWORD lastRoundBufSize = 0; - BOOL searchLongLastingBlock = TRUE; - BOOL ok; - while (1) - { - DWORD roundStartTime = GetTickCount(); - ok = FALSE; - CQuadWord start; - start.Value = lastOffset.Value > curBufSize ? lastOffset.Value - curBufSize : 0; - DWORD size = (DWORD)(lastOffset.Value - start.Value); - if (size == 0) - { - ok = TRUE; - break; // nothing to verify - } -#ifdef WORKER_COPY_DEBUG_MSG - TRACE_I("CheckTailOfOutFile(): check: " << start.Value << " - " << lastOffset.Value << ", size: " << size); -#endif // WORKER_COPY_DEBUG_MSG - if (asyncPar == NULL) - { - if (SalSetFilePointer(in, start)) - { // set the 'start' offset in the input - if (SalSetFilePointer(out, start)) - { // set the 'start' offset in the output - DWORD read; - if (ReadFile(out, bufOut, size, &read, NULL) && read == size) - { // read 'size' bytes into the output buffer (fails if opened without read access) - if (ReadFile(in, bufIn, size, &read, NULL) && read == size) - { // read 'size' bytes into the input buffer - if (memcmp(bufIn, bufOut, size) == 0) // compare whether the input/output buffers match - ok = TRUE; - else - TRACE_I("CheckTailOfOutFile(): tail of target file is different from source file, tail without differences: " << (offset.Value - lastOffset.Value)); - } - else - CheckTailOfOutFileShowErr("Unable to read IN file."); - } - else - { - if (ignoreReadErrOnOut) // if the input file failed earlier, ignore that we cannot read the output (input was reopened, output has remained open) - { - CheckTailOfOutFileShowErr("Unable to read OUT file, but it was not broken, so it's no problem."); - ok = TRUE; - break; - } - else - CheckTailOfOutFileShowErr("Unable to read OUT file."); - } - } - else - CheckTailOfOutFileShowErr("Unable to set file pointer to start offset in OUT file."); - } - else - CheckTailOfOutFileShowErr("Unable to set file pointer to start offset in IN file."); - } - else - { - // asynchronously read the block starting at 'start' of length 'size' bytes from in and out, then compare - DWORD readOut; - if ((ReadFile(out, bufOut, size, NULL, - asyncPar->InitOverlappedWithOffset(0, start)) || - GetLastError() == ERROR_IO_PENDING) && - GetOverlappedResult(out, asyncPar->GetOverlapped(0), &readOut, TRUE)) - { - DWORD readIn; - if ((ReadFile(in, bufIn, size, NULL, - asyncPar->InitOverlappedWithOffset(1, start)) || - GetLastError() == ERROR_IO_PENDING) && - GetOverlappedResult(in, asyncPar->GetOverlapped(1), &readIn, TRUE)) - { - if (readOut != size || readIn != size || - memcmp(bufIn, bufOut, size) != 0) // compare whether the input/output buffers match - { - TRACE_I("CheckTailOfOutFile(): tail of target file is different from source file (async), tail without differences: " << (offset.Value - lastOffset.Value)); - } - else - ok = TRUE; - } - else - CheckTailOfOutFileShowErr("Unable to read IN file (async)."); - } - else - { - if (ignoreReadErrOnOut) // if the input file failed earlier, ignore that we cannot read the output (input was reopened, output has remained open) - { - CheckTailOfOutFileShowErr("Unable to read OUT file (async), but it was not broken, so it's no problem."); - ok = TRUE; - break; - } - else - CheckTailOfOutFileShowErr("Unable to read OUT file (async)."); - } - } - if (!ok) - break; - lastOffset = start; - DWORD curBufSizeBackup = curBufSize; - if (roundNum > 1) - { - DWORD ti = GetTickCount(); - if (searchLongLastingBlock) - { - DWORD t1 = roundStartTime - lastRoundStartTime; - DWORD t2 = ti - roundStartTime; - if (roundNum == 2 && t1 > 300 && 10 * t2 < t1) // first iteration waits for the disk/network to be ready, shift the start time (so we spend the configured time reading instead of just waiting) - { -#ifdef WORKER_COPY_DEBUG_MSG - TRACE_I("CheckTailOfOutFile(): detected long lasting first block, start time shifted by " << ((roundStartTime - startTime) / 1000.0) << " secs."); -#endif // WORKER_COPY_DEBUG_MSG - startTime = roundStartTime; - } - else - { - if (t2 > 1000 && ((curBufSize * 10) / lastRoundBufSize) * t1 < t2) - { // unexpectedly long block read, likely waiting for disk "verification" or similar; ignore this block once so the overall check still behaves normally - searchLongLastingBlock = FALSE; - DWORD sh = t2 - ((unsigned __int64)curBufSize * t1) / lastRoundBufSize; -#ifdef WORKER_COPY_DEBUG_MSG - TRACE_I("CheckTailOfOutFile(): detected long lasting block, start time shifted by " << (sh / 1000.0) << " secs."); -#endif // WORKER_COPY_DEBUG_MSG - startTime += sh; - } - } - } - if (ti - startTime > RETRYCOPY_TESTINGTIME) - break; // we have been reading long enough; stop after the mandatory two rounds - if (ti - roundStartTime < 300 && curBufSize < ASYNC_COPY_BUF_SIZE) - { // when reading is fast enough, enlarge the buffer to avoid excessive reverse seeking (toward the beginning of the file) - curBufSize *= 2; - if (curBufSize > ASYNC_COPY_BUF_SIZE) - curBufSize = ASYNC_COPY_BUF_SIZE; - } - } - roundNum++; - lastRoundStartTime = roundStartTime; - lastRoundBufSize = curBufSizeBackup; - } - - if (ok && asyncPar == NULL) // reposition input/output to required offsets - { - if (!SalSetFilePointer(in, curInOffset)) - { - CheckTailOfOutFileShowErr("Unable to set file pointer back to current offset in IN file."); - ok = FALSE; - } - if (ok && !SalSetFilePointer(out, offset)) - { - CheckTailOfOutFileShowErr("Unable to set file pointer back to current offset in OUT file."); - ok = FALSE; - } - } -#ifdef WORKER_COPY_DEBUG_MSG - if (!ok) - TRACE_I("CheckTailOfOutFile(): aborting Retry..."); - else - { - TRACE_I("CheckTailOfOutFile(): " << (offset.Value - lastOffset.Value) / 1024.0 << " KB tested in " << (GetTickCount() - rutineStartTime) / 1000.0 << " secs (clear read time: " << (GetTickCount() - startTime) / 1000.0 << " secs)."); - } -#endif // WORKER_COPY_DEBUG_MSG - free(bufIn); - free(bufOut); - return ok; -} - -// copies ADS into the newly created file/directory -// returns FALSE only when cancelled; success + Skip both return TRUE; Skip sets 'skip' -// (when not NULL) to TRUE -// 'optimalBufferSize' is the pre-computed optimal buffer size (0 = use default based on script flags) -BOOL DoCopyADS(HWND hProgressDlg, const char* sourceName, BOOL isDir, const char* targetName, - CQuadWord const& totalDone, CQuadWord& operDone, CQuadWord const& operTotal, - CProgressDlgData& dlgData, COperations* script, BOOL* skip, void* buffer, - int optimalBufferSize = 0) -{ - BOOL doCopyADSRet = TRUE; - BOOL lowMemory; - DWORD adsWinError; - wchar_t** streamNames; - int streamNamesCount; - BOOL skipped = FALSE; - CQuadWord lastTransferredFileSize, finalTransferredFileSize; - script->GetTFSandResetTrSpeedIfNeeded(&lastTransferredFileSize); - finalTransferredFileSize = lastTransferredFileSize; - if (operTotal > operDone) // it should always be at least equal, but we play it safe... - finalTransferredFileSize += (operTotal - operDone); - -COPY_ADS_AGAIN: - - if (CheckFileOrDirADS(sourceName, isDir, NULL, &streamNames, &streamNamesCount, - &lowMemory, &adsWinError, 0, NULL, NULL) && - !lowMemory && streamNames != NULL) - { // we have the list of ADS, let's try to copy them to the target file/directory - wchar_t srcName[2 * MAX_PATH]; // MAX_PATH for the file name as well as the ADS name (no idea what the actual maximum lengths are) - wchar_t tgtName[2 * MAX_PATH]; - char longSourceName[MAX_PATH + 100]; - char longTargetName[MAX_PATH + 100]; - DoLongName(longSourceName, sourceName, MAX_PATH + 100); - DoLongName(longTargetName, targetName, MAX_PATH + 100); - if (!ConvertUtf8ToWide(longSourceName, -1, srcName, 2 * MAX_PATH)) - srcName[0] = 0; - if (!ConvertUtf8ToWide(longTargetName, -1, tgtName, 2 * MAX_PATH)) - tgtName[0] = 0; - wchar_t* srcEnd = srcName + lstrlenW(srcName); - if (srcEnd > srcName && *(srcEnd - 1) == L'\\') - *--srcEnd = 0; - wchar_t* tgtEnd = tgtName + lstrlenW(tgtName); - if (tgtEnd > tgtName && *(tgtEnd - 1) == L'\\') - *--tgtEnd = 0; - - // Use pre-computed buffer size if provided, otherwise fall back to default logic - int bufferSize = (optimalBufferSize > 0) ? optimalBufferSize : - (script->RemovableSrcDisk || script->RemovableTgtDisk ? REMOVABLE_DISK_COPY_BUFFER : OPERATION_BUFFER); - - char nameBuf[2 * MAX_PATH]; - BOOL endProcessing = FALSE; - CQuadWord operationDone; - int i; - for (i = 0; i < streamNamesCount; i++) - { - MyStrCpyNW(srcEnd, streamNames[i], (int)(2 * MAX_PATH - (srcEnd - srcName))); - MyStrCpyNW(tgtEnd, streamNames[i], (int)(2 * MAX_PATH - (tgtEnd - tgtName))); - - COPY_AGAIN_ADS: - - operationDone = CQuadWord(0, 0); - int limitBufferSize = bufferSize; - script->SetTFSandProgressSize(lastTransferredFileSize, totalDone + operDone, &limitBufferSize, bufferSize); - - BOOL doNextFile = FALSE; - while (1) - { - HANDLE in = CreateFileW(srcName, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, - OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL); - HANDLES_ADD_EX(__otQuiet, in != INVALID_HANDLE_VALUE, __htFile, - __hoCreateFile, in, GetLastError(), TRUE); - if (in != INVALID_HANDLE_VALUE) - { - CQuadWord fileSize; - fileSize.LoDWord = GetFileSize(in, &fileSize.HiDWord); - if (fileSize.LoDWord == INVALID_FILE_SIZE && GetLastError() != NO_ERROR) - { - DWORD err = GetLastError(); - TRACE_E("GetFileSize(some ADS of " << sourceName << "): unexpected error: " << GetErrorText(err)); - fileSize.SetUI64(0); - } - - while (1) - { - HANDLE out = CreateFileW(tgtName, GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_FLAG_SEQUENTIAL_SCAN, NULL); - HANDLES_ADD_EX(__otQuiet, out != INVALID_HANDLE_VALUE, __htFile, - __hoCreateFile, out, GetLastError(), TRUE); - - BOOL canOverwriteMACADSs = TRUE; - - COPY_OVERWRITE: - - if (out != INVALID_HANDLE_VALUE) - { - canOverwriteMACADSs = FALSE; - - // if possible, pre-allocate the required space (avoids disk fragmentation and smooths writes to floppies) - BOOL wholeFileAllocated = FALSE; - if (fileSize > CQuadWord(limitBufferSize, 0) && // pointless to pre-allocate below the copy buffer size - fileSize < CQuadWord(0, 0x80000000)) // file size must be positive (otherwise seeking fails � values above 8 EB, so practically never) - { - BOOL fatal = TRUE; - BOOL ignoreErr = FALSE; - if (SalSetFilePointer(out, fileSize)) - { - if (SetEndOfFile(out)) - { - if (SetFilePointer(out, 0, NULL, FILE_BEGIN) == 0) - { - fatal = FALSE; - wholeFileAllocated = TRUE; - } - } - else - { - if (GetLastError() == ERROR_DISK_FULL) - ignoreErr = TRUE; // low disk space - } - } - if (fatal) - { - if (!ignoreErr) - { - DWORD err = GetLastError(); - TRACE_E("DoCopyADS(): unable to allocate whole file size before copy operation, please report under what conditions this occurs! GetLastError(): " << GetErrorText(err)); - } - - // try truncating the file to zero so closing it does not trigger unnecessary writes - SetFilePointer(out, 0, NULL, FILE_BEGIN); - SetEndOfFile(out); - - HANDLES(CloseHandle(out)); - out = INVALID_HANDLE_VALUE; - if (DeleteFileW(tgtName)) - { - out = CreateFileW(tgtName, GENERIC_WRITE, 0, NULL, CREATE_NEW, FILE_FLAG_SEQUENTIAL_SCAN, NULL); - HANDLES_ADD_EX(__otQuiet, out != INVALID_HANDLE_VALUE, __htFile, - __hoCreateFile, out, GetLastError(), TRUE); - if (out == INVALID_HANDLE_VALUE) - goto CREATE_ERROR_ADS; - } - else - goto CREATE_ERROR_ADS; - } - } - - DWORD read; - DWORD written; - while (1) - { - if (ReadFile(in, buffer, limitBufferSize, &read, NULL)) - { - if (read == 0) - break; // EOF - if (!script->ChangeSpeedLimit) // if the speed limit can change, this is not a "suitable" place to wait - WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - if (*dlgData.CancelWorker) - { - COPY_ERROR_ADS: - - if (in != NULL) - HANDLES(CloseHandle(in)); - if (out != NULL) - { - if (wholeFileAllocated) - SetEndOfFile(out); // otherwise on a floppy the remaining bytes would be written - HANDLES(CloseHandle(out)); - } - DeleteFileW(tgtName); - doCopyADSRet = FALSE; - endProcessing = TRUE; - break; - } - - while (1) - { - if (WriteFile(out, buffer, read, &written, NULL) && read == written) - break; - - WRITE_ERROR_ADS: - - DWORD err; - err = GetLastError(); - - WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - if (*dlgData.CancelWorker) - goto COPY_ERROR_ADS; - - if (dlgData.SkipAllFileADSWrite) - goto SKIP_COPY_ADS; - - int ret; - ret = IDCANCEL; - char* data[4]; - data[0] = (char*)&ret; - data[1] = LoadStr(IDS_ERRORWRITINGADS); - ConvertWideToUtf8(tgtName, -1, nameBuf, 2 * MAX_PATH); - nameBuf[2 * MAX_PATH - 1] = 0; - CutADSNameSuffix(nameBuf); - data[2] = nameBuf; - if (err == NO_ERROR && read != written) - err = ERROR_DISK_FULL; - data[3] = GetErrorText(err); - SendMessage(hProgressDlg, WM_USER_DIALOG, 0, (LPARAM)data); - switch (ret) - { - case IDRETRY: // on a network we must reopen the handle; local access would not allow sharing - { - if (in == NULL && out == NULL) - { - DeleteFileW(tgtName); - goto COPY_AGAIN_ADS; - } - if (out != NULL) - { - if (wholeFileAllocated) - SetEndOfFile(out); // otherwise on a floppy the remaining bytes would be written - HANDLES(CloseHandle(out)); // close the invalid handle - } - out = CreateFileW(tgtName, GENERIC_WRITE | GENERIC_READ, 0, NULL, OPEN_ALWAYS, - FILE_FLAG_SEQUENTIAL_SCAN, NULL); - HANDLES_ADD_EX(__otQuiet, out != INVALID_HANDLE_VALUE, __htFile, - __hoCreateFile, out, GetLastError(), TRUE); - if (out != INVALID_HANDLE_VALUE) // opened successfully; now adjust the offset - { - LONG lo, hi; - lo = GetFileSize(out, (DWORD*)&hi); - if (lo == INVALID_FILE_SIZE && GetLastError() != NO_ERROR || - CQuadWord(lo, hi) < operationDone || - !CheckTailOfOutFile(NULL, in, out, operationDone, operationDone + CQuadWord(read, 0), FALSE)) - { // cannot determine the size or the file is too small; restart the entire copy - HANDLES(CloseHandle(in)); - HANDLES(CloseHandle(out)); - DeleteFileW(tgtName); - goto COPY_AGAIN_ADS; - } - } - else // still cannot open; problem persists - { - out = NULL; - goto WRITE_ERROR_ADS; - } - break; - } - - case IDB_SKIPALL: - dlgData.SkipAllFileADSWrite = TRUE; - case IDB_SKIP: - { - SKIP_COPY_ADS: - - if (in != NULL) - HANDLES(CloseHandle(in)); - if (out != NULL) - { - if (wholeFileAllocated) - SetEndOfFile(out); // otherwise on a floppy the remaining bytes would be written - HANDLES(CloseHandle(out)); - } - DeleteFileW(tgtName); - if (skip != NULL) - *skip = TRUE; - skipped = TRUE; - endProcessing = TRUE; - break; - } - - case IDCANCEL: - goto COPY_ERROR_ADS; - } - if (endProcessing) - break; - } - if (endProcessing) - break; - if (!script->ChangeSpeedLimit) // when the speed limit can change, this is not a suitable wait point - WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - if (*dlgData.CancelWorker) - goto COPY_ERROR_ADS; - - script->AddBytesToSpeedMetersAndTFSandPS(read, FALSE, bufferSize, &limitBufferSize); - - if (!script->ChangeSpeedLimit) // when the speed limit can change, this is not a suitable wait point - WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - operationDone += CQuadWord(read, 0); - SetProgressWithoutSuspend(hProgressDlg, CaclProg(operDone + operationDone, operTotal), - CaclProg(totalDone + operDone + operationDone, script->TotalSize), - dlgData); - - if (script->ChangeSpeedLimit) // speed limit may change; this is the right place to wait until the - { // worker resumes and fetch a fresh copy buffer size - WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - script->GetNewBufSize(&limitBufferSize, bufferSize); - } - } - else - { - READ_ERROR_ADS: - - DWORD err; - err = GetLastError(); - WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - if (*dlgData.CancelWorker) - goto COPY_ERROR_ADS; - - if (dlgData.SkipAllFileADSRead) - goto SKIP_COPY_ADS; - - int ret = IDCANCEL; - char* data[4]; - data[0] = (char*)&ret; - data[1] = LoadStr(IDS_ERRORREADINGADS); - ConvertWideToUtf8(srcName, -1, nameBuf, 2 * MAX_PATH); - nameBuf[2 * MAX_PATH - 1] = 0; - CutADSNameSuffix(nameBuf); - data[2] = nameBuf; - data[3] = GetErrorText(err); - SendMessage(hProgressDlg, WM_USER_DIALOG, 0, (LPARAM)data); - switch (ret) - { - case IDRETRY: - { - if (in != NULL) - HANDLES(CloseHandle(in)); // close the invalid handle - - in = CreateFileW(srcName, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, - OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL); - HANDLES_ADD_EX(__otQuiet, in != INVALID_HANDLE_VALUE, __htFile, - __hoCreateFile, in, GetLastError(), TRUE); - if (in != INVALID_HANDLE_VALUE) // opened successfully; now adjust the offset - { - LONG lo, hi; - lo = GetFileSize(in, (DWORD*)&hi); - if (lo == INVALID_FILE_SIZE && GetLastError() != NO_ERROR || - CQuadWord(lo, hi) < operationDone || - !CheckTailOfOutFile(NULL, in, out, operationDone, operationDone, TRUE)) - { // cannot obtain size or the file is too small; restart the entire operation - HANDLES(CloseHandle(in)); - if (wholeFileAllocated) - SetEndOfFile(out); // otherwise on a floppy the remaining bytes would be written - HANDLES(CloseHandle(out)); - DeleteFileW(tgtName); - goto COPY_AGAIN_ADS; - } - } - else // still cannot open; problem persists - { - in = NULL; - goto READ_ERROR_ADS; - } - break; - } - case IDB_SKIPALL: - dlgData.SkipAllFileADSRead = TRUE; - case IDB_SKIP: - goto SKIP_COPY_ADS; - case IDCANCEL: - goto COPY_ERROR_ADS; - } - } - } - if (endProcessing) - break; - - if (wholeFileAllocated && // the entire target layout was pre-allocated - operationDone < fileSize) // and the source file shrank - { - if (!SetEndOfFile(out)) // trim it here - { - written = read = 0; - goto WRITE_ERROR_ADS; - } - } - - // commented out because it sets the time of the file/directory that owns the ADS instead of the ADS timestamps - // FILETIME creation, lastAccess, lastWrite; - // GetFileTime(in, NULL /*&creation*/, NULL /*&lastAccess*/, &lastWrite); - // SetFileTime(out, NULL /*&creation*/, NULL /*&lastAccess*/, &lastWrite); - - HANDLES(CloseHandle(in)); - if (!HANDLES(CloseHandle(out))) // even after a failed call we assume the handle is closed, - { // see /viewtopic.php?f=6&t=8455 - in = out = NULL; // (reports that the target file can be deleted, so its handle was not left open) - written = read = 0; - goto WRITE_ERROR_ADS; - } - - // commented out because it sets the attributes of the file/directory that owns the ADS instead of the ADS attributes - // DWORD attr = DynGetFileAttributesW(srcName); - // if (attr != INVALID_FILE_ATTRIBUTES) DynSetFileAttributesW(tgtName, attr); - - operDone += operationDone; - lastTransferredFileSize += operationDone; - doNextFile = TRUE; - } - else - { - CREATE_ERROR_ADS: - - DWORD err = GetLastError(); - - // Macintosh compatibility: NTFS automatically creates ADS entries myFile:Afp_Resource and myFile:Afp_AfpInfo, - // overwrite them silently with the versions from the source file - if (canOverwriteMACADSs && - (err == ERROR_FILE_EXISTS || err == ERROR_ALREADY_EXISTS) && - (_wcsnicmp(streamNames[i], L":Afp_Resource", 13) == 0 && - (streamNames[i][13] == 0 || streamNames[i][13] == L':') || - _wcsnicmp(streamNames[i], L":Afp_AfpInfo", 12) == 0 && - (streamNames[i][12] == 0 || streamNames[i][12] == L':'))) - { - out = CreateFileW(tgtName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, - FILE_FLAG_SEQUENTIAL_SCAN, NULL); - HANDLES_ADD_EX(__otQuiet, out != INVALID_HANDLE_VALUE, __htFile, - __hoCreateFile, out, GetLastError(), TRUE); - - canOverwriteMACADSs = FALSE; - goto COPY_OVERWRITE; - } - - WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - if (*dlgData.CancelWorker) - goto CANCEL_OPEN2_ADS; - - if (dlgData.SkipAllFileADSOpenOut) - goto SKIP_OPEN_OUT_ADS; - - if (dlgData.IgnoreAllADSOpenOutErr) - goto IGNORE_OPENOUTADS; - - int ret; - ret = IDCANCEL; - char* data[4]; - data[0] = (char*)&ret; - data[1] = LoadStr(IDS_ERROROPENINGADS); - ConvertWideToUtf8(tgtName, -1, nameBuf, 2 * MAX_PATH); - nameBuf[2 * MAX_PATH - 1] = 0; - CutADSNameSuffix(nameBuf); - data[2] = nameBuf; - data[3] = GetErrorText(err); - SendMessage(hProgressDlg, WM_USER_DIALOG, 8, (LPARAM)data); - switch (ret) - { - case IDRETRY: - break; - - case IDB_IGNOREALL: - dlgData.IgnoreAllADSOpenOutErr = TRUE; // break is intentionally omitted here - case IDB_IGNORE: - { - IGNORE_OPENOUTADS: - - HANDLES(CloseHandle(in)); - operDone += fileSize; - lastTransferredFileSize += fileSize; - script->SetTFSandProgressSize(lastTransferredFileSize, totalDone + operDone); - doNextFile = TRUE; - break; - } - - case IDB_SKIPALL: - dlgData.SkipAllFileADSOpenOut = TRUE; - case IDB_SKIP: - { - SKIP_OPEN_OUT_ADS: - - HANDLES(CloseHandle(in)); - if (skip != NULL) - *skip = TRUE; - skipped = TRUE; - endProcessing = TRUE; - break; - } - - case IDCANCEL: - { - CANCEL_OPEN2_ADS: - - HANDLES(CloseHandle(in)); - doCopyADSRet = FALSE; - endProcessing = TRUE; - break; - } - } - } - if (doNextFile || endProcessing) - break; - } - } - else - { - DWORD err = GetLastError(); - WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - if (*dlgData.CancelWorker) - { - doCopyADSRet = FALSE; - endProcessing = TRUE; - break; - } - - if (dlgData.SkipAllFileADSOpenIn) - goto SKIP_OPEN_IN_ADS; - - int ret; - ret = IDCANCEL; - char* data[4]; - data[0] = (char*)&ret; - data[1] = LoadStr(IDS_ERROROPENINGADS); - ConvertWideToUtf8(srcName, -1, nameBuf, 2 * MAX_PATH); - nameBuf[2 * MAX_PATH - 1] = 0; - CutADSNameSuffix(nameBuf); - data[2] = nameBuf; - data[3] = GetErrorText(err); - SendMessage(hProgressDlg, WM_USER_DIALOG, 0, (LPARAM)data); - switch (ret) - { - case IDRETRY: - break; - - case IDB_SKIPALL: - dlgData.SkipAllFileADSOpenIn = TRUE; - case IDB_SKIP: - { - SKIP_OPEN_IN_ADS: - - if (skip != NULL) - *skip = TRUE; - skipped = TRUE; - endProcessing = TRUE; - break; - } - - case IDCANCEL: - { - doCopyADSRet = FALSE; - endProcessing = TRUE; - break; - } - } - } - if (doNextFile || endProcessing) - break; - } - if (endProcessing) - break; - } - - for (i = 0; i < streamNamesCount; i++) - free(streamNames[i]); - free(streamNames); - } - else - { - if (adsWinError != NO_ERROR) // display the Windows error (low-memory warning goes only to TRACE_E) - { - WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - if (*dlgData.CancelWorker) - return FALSE; - - if (dlgData.IgnoreAllADSReadErr) - goto IGNORE_ADS; - - int ret; - ret = IDCANCEL; - char* data[3]; - data[0] = (char*)&ret; - data[1] = (char*)sourceName; - data[2] = GetErrorText(adsWinError); - SendMessage(hProgressDlg, WM_USER_DIALOG, 6, (LPARAM)data); - switch (ret) - { - case IDRETRY: - goto COPY_ADS_AGAIN; - - case IDB_IGNOREALL: - dlgData.IgnoreAllADSReadErr = TRUE; // break is intentionally omitted here - case IDB_IGNORE: - { - IGNORE_ADS: - - script->SetTFSandProgressSize(finalTransferredFileSize, totalDone + operTotal); - - SetProgress(hProgressDlg, 0, CaclProg(totalDone + operTotal, script->TotalSize), dlgData); - return TRUE; - } - - case IDCANCEL: - return FALSE; - } - } - if (lowMemory) - doCopyADSRet = FALSE; // lack of memory -> cancel the operation - } - if (doCopyADSRet && skipped) - { - script->SetTFSandProgressSize(finalTransferredFileSize, totalDone + operTotal); - - SetProgress(hProgressDlg, 0, CaclProg(totalDone + operTotal, script->TotalSize), dlgData); - } - return doCopyADSRet; -} - -HANDLE SalCreateFileEx(const char* fileName, DWORD desiredAccess, - DWORD shareMode, DWORD flagsAndAttributes, BOOL* encryptionNotSupported) -{ - CStrP fileNameW(ConvertAllocUtf8ToWide(fileName, -1)); - if (fileNameW == NULL) - { - SetLastError(ERROR_NO_UNICODE_TRANSLATION); - return INVALID_HANDLE_VALUE; - } - HANDLE out = NOHANDLES(CreateFileW(fileNameW, desiredAccess, shareMode, NULL, - CREATE_NEW, flagsAndAttributes, NULL)); - if (out == INVALID_HANDLE_VALUE) - { - DWORD err = GetLastError(); - if (encryptionNotSupported != NULL && (flagsAndAttributes & FILE_ATTRIBUTE_ENCRYPTED)) - { // when the target disk cannot create an Encrypted file (observed on NTFS network disk (tested on share from XP) while logged in under a different username than we have in the system (on the current console) - the remote machine has a same-named user without a password, so it cannot be used over the network) - out = NOHANDLES(CreateFileW(fileNameW, desiredAccess, shareMode, NULL, - CREATE_NEW, (flagsAndAttributes & ~(FILE_ATTRIBUTE_ENCRYPTED | FILE_ATTRIBUTE_READONLY)), NULL)); - if (out != INVALID_HANDLE_VALUE) - { - *encryptionNotSupported = TRUE; - NOHANDLES(CloseHandle(out)); - out = INVALID_HANDLE_VALUE; - if (!DeleteFileW(fileNameW)) // XP and Vista ignore this scenario, so do the same (at worst warn user that a zero-length file was added on disk and cannot be deleted) - TRACE_I("Unable to delete testing target file: " << fileName); - } - } - if (err == ERROR_FILE_EXISTS || // check whether this is merely overwriting the DOS name - err == ERROR_ALREADY_EXISTS || - err == ERROR_ACCESS_DENIED) - { - WIN32_FIND_DATAW data; - HANDLE find = HANDLES_Q(FindFirstFileW(fileNameW, &data)); - if (find != INVALID_HANDLE_VALUE) - { - HANDLES(FindClose(find)); - if (err != ERROR_ACCESS_DENIED || (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) - { - const char* tgtName = SalPathFindFileName(fileName); - char altName[MAX_PATH]; - char fullName[MAX_PATH]; - if (ConvertWideToUtf8(data.cAlternateFileName, -1, altName, _countof(altName)) == 0) - altName[0] = 0; - if (ConvertWideToUtf8(data.cFileName, -1, fullName, _countof(fullName)) == 0) - fullName[0] = 0; - if (StrICmp(tgtName, altName) == 0 && // match only for DOS name - StrICmp(tgtName, fullName) != 0) // (full name differs) - { - // rename ("tidy up") the file/directory with the conflicting DOS name to a temporary 8.3 name (no extra DOS name needed) - char tmpName[MAX_PATH + 20]; - if (strlen(fileName) >= _countof(tmpName)) - { - TRACE_E("SalCreateFileEx(): path too long for DOS-name collision workaround: " << fileName); - } - else - { - lstrcpyn(tmpName, fileName, _countof(tmpName)); - CutDirectory(tmpName); - SalPathAddBackslash(tmpName, _countof(tmpName)); - char* tmpNamePart = tmpName + strlen(tmpName); - char origFullName[MAX_PATH + 20]; - if (SalPathAppend(tmpName, fullName, _countof(tmpName))) - { - strcpy(origFullName, tmpName); - DWORD num = (GetTickCount() / 10) % 0xFFF; - DWORD origFullNameAttr = SalGetFileAttributes(origFullName); - while (1) - { - sprintf(tmpNamePart, "sal%03X", num++); - if (SalMoveFile(origFullName, tmpName)) - break; - DWORD e = GetLastError(); - if (e != ERROR_FILE_EXISTS && e != ERROR_ALREADY_EXISTS) - { - tmpName[0] = 0; - break; - } - } - if (tmpName[0] != 0) // if we successfully "tidied" the conflicting file, try creating - { // the target file again, then restore the original name - out = NOHANDLES(CreateFileW(fileNameW, desiredAccess, shareMode, NULL, - CREATE_NEW, flagsAndAttributes, NULL)); - if (out == INVALID_HANDLE_VALUE && encryptionNotSupported != NULL && - (flagsAndAttributes & FILE_ATTRIBUTE_ENCRYPTED)) - { // when the target disk cannot create an Encrypted file (observed on NTFS network disk (tested on share from XP) while logged in under a different username than we have in the system (on the current console) - the remote machine has a same-named user without a password, so it cannot be used over the network) - out = NOHANDLES(CreateFileW(fileNameW, desiredAccess, shareMode, NULL, - CREATE_NEW, (flagsAndAttributes & ~(FILE_ATTRIBUTE_ENCRYPTED | FILE_ATTRIBUTE_READONLY)), NULL)); - if (out != INVALID_HANDLE_VALUE) - { - *encryptionNotSupported = TRUE; - NOHANDLES(CloseHandle(out)); - out = INVALID_HANDLE_VALUE; - if (!DeleteFileW(fileNameW)) // XP and Vista ignore this scenario, so do the same (at worst warn user that a zero-length file was added on disk and cannot be deleted) - TRACE_E("Unable to delete testing target file: " << fileName); - } - } - if (!SalMoveFile(tmpName, origFullName)) - { // this apparently can happen; inexplicably, Windows creates a file named origFullName instead of fileName (the DOS name) - TRACE_I("Unexpected situation in SalCreateFileEx(): unable to rename file from tmp-name to original long file name! " << origFullName); - - if (out != INVALID_HANDLE_VALUE) - { - NOHANDLES(CloseHandle(out)); - out = INVALID_HANDLE_VALUE; - DeleteFileW(fileNameW); - if (!SalMoveFile(tmpName, origFullName)) - TRACE_E("Fatal unexpected situation in SalCreateFileEx(): unable to rename file from tmp-name to original long file name! " << origFullName); - } - } - else - { - if ((origFullNameAttr & FILE_ATTRIBUTE_ARCHIVE) == 0) - { - CStrP origFullNameW(ConvertAllocUtf8ToWide(origFullName, -1)); - if (origFullNameW != NULL) - SetFileAttributesW(origFullNameW, origFullNameAttr); // leave without extra handling or retry; not critical (normally toggles unpredictably) - } - } - } - } - else - TRACE_E("SalCreateFileEx(): Original full file name is too long, unable to bypass only-dos-name-overwrite problem!"); - } - } - } - } - } - if (out == INVALID_HANDLE_VALUE) - SetLastError(err); - } - return out; -} - -BOOL SyncOrAsyncDeviceIoControl(CAsyncCopyParams* asyncPar, HANDLE hDevice, DWORD dwIoControlCode, - LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer, - DWORD nOutBufferSize, LPDWORD lpBytesReturned, DWORD* err) -{ - if (asyncPar->UseAsyncAlg) // asynchronous variant - { - if (!DeviceIoControl(hDevice, dwIoControlCode, lpInBuffer, nInBufferSize, lpOutBuffer, - nOutBufferSize, NULL, asyncPar->InitOverlapped(0)) && - GetLastError() != ERROR_IO_PENDING || - !GetOverlappedResult(hDevice, asyncPar->GetOverlapped(0), lpBytesReturned, TRUE)) - { // error, return FALSE - *err = GetLastError(); - return FALSE; - } - } - else // synchronous variant - { - if (!DeviceIoControl(hDevice, dwIoControlCode, lpInBuffer, nInBufferSize, lpOutBuffer, - nOutBufferSize, lpBytesReturned, NULL)) - { // error, return FALSE - *err = GetLastError(); - return FALSE; - } - } - *err = NO_ERROR; - return TRUE; -} - -void SetCompressAndEncryptedAttrs(const char* name, DWORD attr, HANDLE* out, BOOL openAlsoForRead, - BOOL* encryptionNotSupported, CAsyncCopyParams* asyncPar) -{ - if (*out != INVALID_HANDLE_VALUE) - { - DWORD err = NO_ERROR; - DWORD curAttr = SalGetFileAttributes(name); - if ((curAttr == INVALID_FILE_ATTRIBUTES || - (attr & FILE_ATTRIBUTE_COMPRESSED) != (curAttr & FILE_ATTRIBUTE_COMPRESSED)) && - (attr & FILE_ATTRIBUTE_COMPRESSED) == 0) - { - USHORT state = COMPRESSION_FORMAT_NONE; - ULONG length; - if (!SyncOrAsyncDeviceIoControl(asyncPar, *out, FSCTL_SET_COMPRESSION, &state, - sizeof(USHORT), NULL, 0, &length, &err)) - { - TRACE_I("SetCompressAndEncryptedAttrs(): Unable to set Compressed attribute for " << name << "! error=" << GetErrorText(err)); - } - } - if (curAttr == INVALID_FILE_ATTRIBUTES || - (attr & FILE_ATTRIBUTE_ENCRYPTED) != (curAttr & FILE_ATTRIBUTE_ENCRYPTED)) - { // SalCreateFileEx above likely failed - err = NO_ERROR; - HANDLES(CloseHandle(*out)); // close the file; otherwise we cannot change its encrypted attribute - CStrP nameW(ConvertAllocUtf8ToWide(name, -1)); - if (nameW == NULL) - err = ERROR_NO_UNICODE_TRANSLATION; - if (attr & FILE_ATTRIBUTE_ENCRYPTED) - { - if (err == NO_ERROR && !EncryptFileW(nameW)) - { - err = GetLastError(); - if (encryptionNotSupported != NULL) - *encryptionNotSupported = TRUE; - } - } - else - { - if (err == NO_ERROR && !DecryptFileW(nameW, 0)) - err = GetLastError(); - } - if (err != NO_ERROR) - TRACE_I("SetCompressAndEncryptedAttrs(): Unable to set Encrypted attribute for " << name << "! error=" << GetErrorText(err)); - // reopen the existing file to continue writing - if (err == NO_ERROR) - { - *out = HANDLES_Q(CreateFileW(nameW, GENERIC_WRITE | (openAlsoForRead ? GENERIC_READ : 0), 0, NULL, OPEN_ALWAYS, - asyncPar->GetOverlappedFlag() | FILE_FLAG_SEQUENTIAL_SCAN, NULL)); - } - else - *out = INVALID_HANDLE_VALUE; - if (openAlsoForRead && *out == INVALID_HANDLE_VALUE) // problem: reopening failed, try write-only - { - *out = HANDLES_Q(CreateFileW(nameW, GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, - asyncPar->GetOverlappedFlag() | FILE_FLAG_SEQUENTIAL_SCAN, NULL)); - } - if (*out == INVALID_HANDLE_VALUE) // still a problem: cannot reopen; delete it + report an error - { - err = GetLastError(); - if (nameW != NULL) - DeleteFileW(nameW); - SetLastError(err); - } - } - if (*out != INVALID_HANDLE_VALUE && // only when reopening succeeded (and we did not delete the file) - (curAttr == INVALID_FILE_ATTRIBUTES || - (attr & FILE_ATTRIBUTE_COMPRESSED) != (curAttr & FILE_ATTRIBUTE_COMPRESSED)) && - (attr & FILE_ATTRIBUTE_COMPRESSED) != 0) - { - USHORT state = COMPRESSION_FORMAT_DEFAULT; - ULONG length; - if (!SyncOrAsyncDeviceIoControl(asyncPar, *out, FSCTL_SET_COMPRESSION, &state, - sizeof(USHORT), NULL, 0, &length, &err)) - { - TRACE_I("SetCompressAndEncryptedAttrs(): Unable to set Compressed attribute for " << name << "! error=" << GetErrorText(err)); - } - } - } -} - -void CorrectCaseOfTgtName(char* tgtName, BOOL dataRead, WIN32_FIND_DATAW* data) -{ - if (!dataRead) - { - CStrP tgtNameW(ConvertAllocUtf8ToWide(tgtName, -1)); - if (tgtNameW == NULL) - return; - HANDLE find = HANDLES_Q(FindFirstFileW(tgtNameW, data)); - if (find != INVALID_HANDLE_VALUE) - HANDLES(FindClose(find)); - else - return; // failed to read data for the target file; abort - } - char dataName[MAX_PATH]; - if (ConvertWideToUtf8(data->cFileName, -1, dataName, _countof(dataName)) == 0) - return; - int len = (int)strlen(dataName); - int tgtNameLen = (int)strlen(tgtName); - if (tgtNameLen >= len && StrICmp(tgtName + tgtNameLen - len, dataName) == 0) - memcpy(tgtName + tgtNameLen - len, dataName, len); -} - -void SetTFSandPSforSkippedFile(COperation* op, CQuadWord& lastTransferredFileSize, - COperations* script, const CQuadWord& pSize) -{ - if (op->FileSize < COPY_MIN_FILE_SIZE) - { - lastTransferredFileSize += op->FileSize; // file size - if (op->Size > COPY_MIN_FILE_SIZE) // should always be at least COPY_MIN_FILE_SIZE, but be safe... - lastTransferredFileSize += op->Size - COPY_MIN_FILE_SIZE; // add the ADS size - } - else - lastTransferredFileSize += op->Size; // file size + ADS - script->SetTFSandProgressSize(lastTransferredFileSize, pSize); -} - -void DoCopyFileLoopOrig(HANDLE& in, HANDLE& out, void* buffer, int& limitBufferSize, - COperations* script, CProgressDlgData& dlgData, BOOL wholeFileAllocated, - COperation* op, const CQuadWord& totalDone, BOOL& copyError, BOOL& skipCopy, - HWND hProgressDlg, CQuadWord& operationDone, CQuadWord& fileSize, - int bufferSize, int& allocWholeFileOnStart, BOOL& copyAgain) -{ - int autoRetryAttemptsSNAP = 0; - DWORD read; - DWORD written; - while (1) - { - if (ReadFile(in, buffer, limitBufferSize, &read, NULL)) - { - autoRetryAttemptsSNAP = 0; - if (read == 0) - break; // EOF - if (!script->ChangeSpeedLimit) // when the speed limit can change, this is not a suitable wait point - WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - if (*dlgData.CancelWorker) - { - copyError = TRUE; // goto COPY_ERROR - return; - } - - while (1) - { - if (WriteFile(out, buffer, read, &written, NULL) && - read == written) - { - break; - } - - WRITE_ERROR: - - DWORD err; - err = GetLastError(); - - WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - if (*dlgData.CancelWorker) - { - copyError = TRUE; // goto COPY_ERROR - return; - } - - if (dlgData.SkipAllFileWrite) - { - skipCopy = TRUE; // goto SKIP_COPY - return; - } - - int ret; - ret = IDCANCEL; - char* data[4]; - data[0] = (char*)&ret; - data[1] = LoadStr(IDS_ERRORWRITINGFILE); - data[2] = op->TargetName; - if (err == NO_ERROR && read != written) - err = ERROR_DISK_FULL; - data[3] = GetErrorText(err); - SendMessage(hProgressDlg, WM_USER_DIALOG, 0, (LPARAM)data); - switch (ret) - { - case IDRETRY: // on a network we must reopen the handle; local access forbids it due to sharing - { - if (out != NULL) - { - if (wholeFileAllocated) - SetEndOfFile(out); // otherwise on a floppy the remaining bytes would be written - HANDLES(CloseHandle(out)); // close the invalid handle - } - out = HANDLES_Q(CreateFileUtf8(op->TargetName, GENERIC_WRITE | GENERIC_READ, 0, NULL, - OPEN_ALWAYS, FILE_FLAG_SEQUENTIAL_SCAN, NULL)); - if (out != INVALID_HANDLE_VALUE) // opened successfully; now adjust the offset - { - LONG lo, hi; - lo = GetFileSize(out, (DWORD*)&hi); - if (lo == INVALID_FILE_SIZE && GetLastError() != NO_ERROR || // cannot obtain the size - CQuadWord(lo, hi) < operationDone || // file is too small - wholeFileAllocated && CQuadWord(lo, hi) > fileSize && - CQuadWord(lo, hi) > operationDone + CQuadWord(read, 0) || // pre-allocated file is too large (beyond the reserved size and beyond the written portion including the current block) = extra bytes were appended (allocWholeFileOnStart should be 0 /* need-test */) - !CheckTailOfOutFile(NULL, in, out, operationDone, operationDone + CQuadWord(read, 0), FALSE)) - { // restart the whole operation - HANDLES(CloseHandle(in)); - HANDLES(CloseHandle(out)); - DeleteFileUtf8(op->TargetName); - copyAgain = TRUE; // goto COPY_AGAIN; - return; - } - } - else // still cannot open; problem persists - { - out = NULL; - goto WRITE_ERROR; - } - break; - } - - case IDB_SKIPALL: - dlgData.SkipAllFileWrite = TRUE; - case IDB_SKIP: - { - skipCopy = TRUE; // goto SKIP_COPY - return; - } - - case IDCANCEL: - { - copyError = TRUE; // goto COPY_ERROR - return; - } - } - } - if (!script->ChangeSpeedLimit) // when the speed limit can change, this is not a suitable wait point - WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - if (*dlgData.CancelWorker) - { - copyError = TRUE; // goto COPY_ERROR - return; - } - - script->AddBytesToSpeedMetersAndTFSandPS(read, FALSE, bufferSize, &limitBufferSize); - - if (!script->ChangeSpeedLimit) // when the speed limit can change, this is not a suitable wait point - WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - operationDone += CQuadWord(read, 0); - SetProgressWithoutSuspend(hProgressDlg, CaclProg(operationDone, op->Size), - CaclProg(totalDone + operationDone, script->TotalSize), dlgData); - - if (script->ChangeSpeedLimit) // speed limit may change; this is the right place to wait until the - { // worker resumes and fetches a fresh copy buffer size - WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - script->GetNewBufSize(&limitBufferSize, bufferSize); - } - } - else - { - READ_ERROR: - - DWORD err; - err = GetLastError(); - WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - if (*dlgData.CancelWorker) - { - copyError = TRUE; // goto COPY_ERROR - return; - } - - if (dlgData.SkipAllFileRead) - { - skipCopy = TRUE; // goto SKIP_COPY - return; - } - - if (err == ERROR_NETNAME_DELETED && ++autoRetryAttemptsSNAP <= 3) - { // on SNAP server reading sometimes randomly fails with ERROR_NETNAME_DELETED; Retry button reportedly helps, so trigger it automatically - Sleep(100); - goto RETRY_COPY; - } - - int ret; - ret = IDCANCEL; - char* data[4]; - data[0] = (char*)&ret; - data[1] = LoadStr(IDS_ERRORREADINGFILE); - data[2] = op->SourceName; - data[3] = GetErrorText(err); - SendMessage(hProgressDlg, WM_USER_DIALOG, 0, (LPARAM)data); - switch (ret) - { - case IDRETRY: - { - RETRY_COPY: - - if (in != NULL) - HANDLES(CloseHandle(in)); // close the invalid handle - in = HANDLES_Q(CreateFileUtf8(op->SourceName, GENERIC_READ, - FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, - OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL)); - if (in != INVALID_HANDLE_VALUE) // opened successfully; now adjust the offset - { - LONG lo, hi; - lo = GetFileSize(in, (DWORD*)&hi); - if (lo == INVALID_FILE_SIZE && GetLastError() != NO_ERROR || - CQuadWord(lo, hi) < operationDone || - !CheckTailOfOutFile(NULL, in, out, operationDone, operationDone, TRUE)) - { // cannot obtain the size or the file is too small; restart the whole operation - HANDLES(CloseHandle(in)); - if (wholeFileAllocated) - SetEndOfFile(out); // otherwise on a floppy the remaining bytes would be written - HANDLES(CloseHandle(out)); - DeleteFileUtf8(op->TargetName); - copyAgain = TRUE; // goto COPY_AGAIN; - return; - } - } - else // still cannot open; problem persists - { - in = NULL; - goto READ_ERROR; - } - break; - } - - case IDB_SKIPALL: - dlgData.SkipAllFileRead = TRUE; - case IDB_SKIP: - { - skipCopy = TRUE; // goto SKIP_COPY - return; - } - - case IDCANCEL: - { - copyError = TRUE; // goto COPY_ERROR - return; - } - } - } - } - - if (wholeFileAllocated) // we pre-allocated the complete file layout (meaning the allocation was useful; for example, the file cannot be empty) - { - if (operationDone < fileSize) // and the source file shrank - { - if (!SetEndOfFile(out)) // trim it here - { - written = read = 0; - goto WRITE_ERROR; - } - } - - if (allocWholeFileOnStart == 0 /* need-test */) - { - CQuadWord curFileSize; - curFileSize.LoDWord = GetFileSize(out, &curFileSize.HiDWord); - BOOL getFileSizeSuccess = (curFileSize.LoDWord != INVALID_FILE_SIZE || GetLastError() == NO_ERROR); - if (getFileSizeSuccess && curFileSize == operationDone) - { // verify that no extra bytes were appended at the end and that truncation works - allocWholeFileOnStart = 1 /* yes */; - } - else - { -#ifdef _DEBUG - if (getFileSizeSuccess) - { - char num1[50]; - char num2[50]; - TRACE_E("DoCopyFileLoopOrig(): unable to allocate whole file size before copy operation, please report " - "under what conditions this occurs! Error: different file sizes: target=" - << NumberToStr(num1, curFileSize) << " bytes, source=" << NumberToStr(num2, operationDone) << " bytes"); - } - else - { - DWORD err = GetLastError(); - TRACE_E("DoCopyFileLoopOrig(): unable to test result of allocation of whole file size before copy operation, please report " - "under what conditions this occurs! GetFileSize(" - << op->TargetName << ") error: " << GetErrorText(err)); - } -#endif - allocWholeFileOnStart = 2 /* no */; // skip further attempts on this target disk - - HANDLES(CloseHandle(out)); - out = NULL; - ClearReadOnlyAttr(op->TargetName); // if it somehow became read-only (should never happen), so we know how to handle it - if (DeleteFileUtf8(op->TargetName)) - { - HANDLES(CloseHandle(in)); - copyAgain = TRUE; // goto COPY_AGAIN; - return; - } - else - { - written = read = 0; - goto WRITE_ERROR; - } - } - } - } -} - -enum CCopy_BlkState -{ - cbsFree, // block not in use - cbsRead, // reading source file blocks - completed (waiting to be written) - cbsInProgress, // --- states below mean "waiting for the operation to finish" (completed states are above) - cbsReading, // reading source file blocks - requested (in progress) - cbsTestingEOF, // checking the end of the source file - cbsWriting, // writing the block to the target file - cbsDiscarded, // attempted to read beyond the end of the source file (should only return error: EOF) -}; - -enum CCopy_ForceOp -{ - fopNotUsed, // free to read or write as needed - fopReading, // forced to read - fopWriting // forced to write -}; - -struct CCopy_Context -{ - CAsyncCopyParams* AsyncPar; - - CCopy_ForceOp ForceOp; // TRUE = must read now, FALSE = must write now - BOOL ReadingDone; // TRUE = the source file has been fully read - CCopy_BlkState BlockState[8]; // block state - DWORD BlockDataLen[8]; // for each block: expected data (cbsReading + cbsTestingEOF), valid data (cbsWriting) - CQuadWord BlockOffset[8]; // for each block: block offset in the source/target file (also stored in the 'AsyncPar' OVERLAPPED) - DWORD BlockTime[8]; // for each block: "time" when the last async operation in this block started - DWORD CurTime; // "time" counter for 'BlockTime', handles wrap-around (though unlikely) - int FreeBlocks; // current number of free blocks (cbsFree) - int FreeBlockIndex; // candidate index of a free block (cbsFree); must be verified - int ReadingBlocks; // current number of blocks being read(cbsReading and cbsTestingEOF) - int WritingBlocks; // current number of blocks being written (cbsWriting) - CQuadWord ReadOffset; // offset for reading the next block from the source file (previous one is already in progress) - CQuadWord WriteOffset; // offset for writing the next block to the target file (previous one is already in progress) - int AutoRetryAttemptsSNAP; // number of automatic Retry attempts (max 3): SNAP servers sporadically return ERROR_NETNAME_DELETED while reading, Retry button reportedly helps, so trigger it automatically - - // selected DoCopyFileLoopAsync parameters to avoid passing a long argument list everywhere - CProgressDlgData* DlgData; - COperation* Op; - HWND HProgressDlg; - HANDLE* In; - HANDLE* Out; - BOOL WholeFileAllocated; - COperations* Script; - CQuadWord* OperationDone; - const CQuadWord* TotalDone; - const CQuadWord* LastTransferredFileSize; - - CCopy_Context(CAsyncCopyParams* asyncPar, int numOfBlocks, CProgressDlgData* dlgData, COperation* op, - HWND hProgressDlg, HANDLE* in, HANDLE* out, BOOL wholeFileAllocated, COperations* script, - CQuadWord* operationDone, const CQuadWord* totalDone, const CQuadWord* lastTransferredFileSize) - { - AsyncPar = asyncPar; - ForceOp = fopNotUsed; - ReadingDone = FALSE; - CurTime = 0; - for (int i = 0; i < _countof(BlockState); i++) - BlockState[i] = cbsFree; - memset(BlockDataLen, 0, sizeof(BlockDataLen)); - memset(BlockOffset, 0, sizeof(BlockOffset)); - memset(BlockTime, 0, sizeof(BlockTime)); - FreeBlocks = numOfBlocks; - FreeBlockIndex = 0; - ReadingBlocks = 0; - WritingBlocks = 0; - ReadOffset.SetUI64(0); - WriteOffset.SetUI64(0); - AutoRetryAttemptsSNAP = 0; - - DlgData = dlgData; - Op = op; - HProgressDlg = hProgressDlg; - In = in; - Out = out; - WholeFileAllocated = wholeFileAllocated; - Script = script; - OperationDone = operationDone; - TotalDone = totalDone; - LastTransferredFileSize = lastTransferredFileSize; - } - - BOOL IsOperationDone(int numOfBlocks) - { - return ReadingDone && FreeBlocks == numOfBlocks; - } - - BOOL StartReading(int blkIndex, DWORD readSize, DWORD* err, BOOL testEOF); - BOOL StartWriting(int blkIndex, DWORD* err); - int FindBlock(CCopy_BlkState state); - void FreeBlock(int blkIndex); - void DiscardBlocksBehindEOF(const CQuadWord& fileSize, int excludeIndex); - void GetNewFileSize(const char* fileName, HANDLE file, CQuadWord* fileSize, const CQuadWord& minFileSize); - - BOOL HandleReadingErr(int blkIndex, DWORD err, BOOL* copyError, BOOL* skipCopy, BOOL* copyAgain); - BOOL HandleWritingErr(int blkIndex, DWORD err, BOOL* copyError, BOOL* skipCopy, BOOL* copyAgain, - const CQuadWord& allocFileSize, const CQuadWord& maxWriteOffset); - - // interrupts any pending asynchronous operations - void CancelOpPhase1(); - // ensures that all asynchronous operations have really finished + positions the pointer at the end of the contiguous - // portion of the target file so the file is truncated correctly (before a possible closing and deletion) - // WARNING: frees unnecessary blocks; only those with data read from the input file remain, and they still - // follow WriteOffset (usable for retry) - void CancelOpPhase2(int errBlkIndex); - BOOL RetryCopyReadErr(DWORD* err, BOOL* copyAgain, BOOL* errAgain); - BOOL RetryCopyWriteErr(DWORD* err, BOOL* copyAgain, BOOL* errAgain, const CQuadWord& allocFileSize, - const CQuadWord& maxWriteOffset); - BOOL HandleSuspModeAndCancel(BOOL* copyError); -}; - -BOOL DisableLocalBuffering(CAsyncCopyParams* asyncPar, HANDLE file, DWORD* err) -{ - CALL_STACK_MESSAGE1("DisableLocalBuffering()"); - if (DynNtFsControlFile != NULL) // "always true" - { - IO_STATUS_BLOCK ioStatus; - ResetEvent(asyncPar->Overlapped[0].hEvent); - ULONG status = DynNtFsControlFile(file, asyncPar->Overlapped[0].hEvent, NULL, - 0, &ioStatus, 0x00140390 /* IOCTL_LMR_DISABLE_LOCAL_BUFFERING */, - NULL, 0, NULL, 0); - if (status == STATUS_PENDING) // must wait for the operation to finish; it runs asynchronously - { - CALL_STACK_MESSAGE1("DisableLocalBuffering(): STATUS_PENDING"); - WaitForSingleObject(asyncPar->Overlapped[0].hEvent, INFINITE); - status = ioStatus.Status; - } - if (status == 0 /* STATUS_SUCCESS */) - return TRUE; - *err = LsaNtStatusToWinError(status); - } - else - *err = ERROR_INVALID_FUNCTION; - return FALSE; -} - -BOOL CCopy_Context::StartReading(int blkIndex, DWORD readSize, DWORD* err, BOOL testEOF) -{ -#ifdef ASYNC_COPY_DEBUG_MSG - char sss[1000]; - sprintf(sss, "ReadFile: %d 0x%08X 0x%08X", blkIndex, ReadOffset.LoDWord, readSize); - TRACE_I(sss); -#endif // ASYNC_COPY_DEBUG_MSG - - if (!ReadFile(*In, AsyncPar->Buffers[blkIndex], readSize, NULL, - AsyncPar->InitOverlappedWithOffset(blkIndex, ReadOffset)) && - GetLastError() != ERROR_IO_PENDING) - { // a read error occurred; handle it - *err = GetLastError(); - if (*err == ERROR_HANDLE_EOF) // synchronously reported EOF; convert it to an asynchronously reported EOF - AsyncPar->SetOverlappedToEOF(blkIndex, ReadOffset); - else - return FALSE; - } - // if the read was completed synchronously (or via cache, which we cannot detect), - // we must write something now; otherwise writing may idle and slow down the whole operation - BOOL opCompleted = HasOverlappedIoCompleted(AsyncPar->GetOverlapped(blkIndex)); - ForceOp = opCompleted ? fopWriting : fopNotUsed; - -#ifdef ASYNC_COPY_DEBUG_MSG - TRACE_I("ReadFile result: " << (opCompleted ? "DONE" : "ASYNC")); -#endif // ASYNC_COPY_DEBUG_MSG - - if (opCompleted && !Script->ChangeSpeedLimit) // when the speed limit can change, this is not a suitable wait point - WaitForSingleObject(DlgData->WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - if (*DlgData->CancelWorker) - { - *err = ERROR_CANCELLED; - return FALSE; // cancellation will be handled in the error-handling - } - - BlockOffset[blkIndex] = ReadOffset; - BlockDataLen[blkIndex] = readSize; - if (!testEOF) // block was cbsFree before calling this method - { - ReadOffset.Value += readSize; - BlockState[blkIndex] = cbsReading; - } - else - BlockState[blkIndex] = cbsTestingEOF; - BlockTime[blkIndex] = CurTime++; - FreeBlocks--; - ReadingBlocks++; - return TRUE; -} - -BOOL CCopy_Context::StartWriting(int blkIndex, DWORD* err) -{ -#ifdef ASYNC_COPY_DEBUG_MSG - char sss[1000]; - sprintf(sss, "WriteFile: %d 0x%08X 0x%08X", blkIndex, WriteOffset.LoDWord, BlockDataLen[blkIndex]); - TRACE_I(sss); -#endif // ASYNC_COPY_DEBUG_MSG - - if (!WriteFile(*Out, AsyncPar->Buffers[blkIndex], BlockDataLen[blkIndex], NULL, - AsyncPar->InitOverlappedWithOffset(blkIndex, WriteOffset)) && - GetLastError() != ERROR_IO_PENDING) - { // a write error occurred; handle it - *err = GetLastError(); - return FALSE; - } - // if the write was completed synchronously (or via cache, which we cannot detect), - // we must read something now; otherwise reading may idle and slow down the whole operation - BOOL opCompleted = HasOverlappedIoCompleted(AsyncPar->GetOverlapped(blkIndex)); - ForceOp = !ReadingDone && opCompleted ? fopReading : fopNotUsed; - -#ifdef ASYNC_COPY_DEBUG_MSG - TRACE_I("WriteFile result: " << (opCompleted ? "DONE" : "ASYNC")); -#endif // ASYNC_COPY_DEBUG_MSG - - if (opCompleted && !Script->ChangeSpeedLimit) // when the speed limit can change, this is not a suitable wait point - WaitForSingleObject(DlgData->WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - if (*DlgData->CancelWorker) - { - *err = ERROR_CANCELLED; - return FALSE; // cancellation will be handled in the error-handling - } - - WriteOffset.Value += BlockDataLen[blkIndex]; - BlockState[blkIndex] = cbsWriting; // block was cbsRead before calling this method - BlockTime[blkIndex] = CurTime++; - WritingBlocks++; - return TRUE; -} - -int CCopy_Context::FindBlock(CCopy_BlkState state) -{ - for (int i = 0; i < _countof(BlockState); i++) - if (BlockState[i] == state) - return i; - TRACE_C("CCopy_Context::FindBlock(): unable to find block with required state (" << (int)state << ")."); - return -1; // dead code, only for the compiler -} - -void CCopy_Context::FreeBlock(int blkIndex) -{ - if (BlockState[blkIndex] == cbsReading || BlockState[blkIndex] == cbsTestingEOF) - ReadingBlocks--; - if (BlockState[blkIndex] == cbsWriting) - WritingBlocks--; - BlockState[blkIndex] = cbsFree; - FreeBlockIndex = blkIndex; - FreeBlocks++; -} - -void CCopy_Context::DiscardBlocksBehindEOF(const CQuadWord& fileSize, int excludeIndex) -{ - for (int i = 0; i < _countof(BlockState); i++) - { - if (i == excludeIndex) - continue; - CCopy_BlkState st = BlockState[i]; - if ((st == cbsRead || st == cbsReading) && BlockOffset[i] >= fileSize) - { - if (st == cbsRead) // discard data read beyond the end of the file; they are useless - FreeBlock(i); - else - { - BlockState[i] = cbsDiscarded; // reading past the end of the file is pointless; no reason to adjust BlockTime - ReadingBlocks--; - } - } - } -} - -void CCopy_Context::GetNewFileSize(const char* fileName, HANDLE file, CQuadWord* fileSize, const CQuadWord& minFileSize) -{ - fileSize->LoDWord = GetFileSize(file, &fileSize->HiDWord); - if (fileSize->LoDWord == INVALID_FILE_SIZE && GetLastError() != NO_ERROR) - { - DWORD err = GetLastError(); - TRACE_E("CCopy_Context::GetNewFileSize(): GetFileSize(" << fileName << "): unexpected error: " << GetErrorText(err)); - *fileSize = minFileSize; - } - else - { - if (*fileSize < minFileSize) // if GetFileSize happened to return a shorter length than already read - *fileSize = minFileSize; - } -} - -void CCopy_Context::CancelOpPhase1() -{ - if (!CancelIo(*In)) - { - DWORD err = GetLastError(); - TRACE_E("CCopy_Context::CancelOpPhase1(): CancelIo(IN) failed, error: " << GetErrorText(err)); - } - if (*Out != NULL && !CancelIo(*Out)) - { - DWORD err = GetLastError(); - TRACE_E("CCopy_Context::CancelOpPhase1(): CancelIo(OUT) failed, error: " << GetErrorText(err)); - } -} - -void CCopy_Context::CancelOpPhase2(int errBlkIndex) -{ - // NOTE: errBlkIndex == -1 for errors when issuing an async reading (no block assigned), - // for errors while truncating the file after the main copy loop finished (no block assigned), - // or for Cancel in the progress dialog (no block assigned) - - DWORD bytes; - for (int i = 0; i < _countof(BlockState); i++) - { - if (BlockState[i] > cbsInProgress) - { // GetOverlappedResult should return results immediately because CancelIo() was called for both files - if (GetOverlappedResult(BlockState[i] == cbsWriting ? *Out : *In, AsyncPar->GetOverlapped(i), &bytes, TRUE)) - { - if (BlockState[i] == cbsReading && BlockDataLen[i] == bytes) // fully read -> convert to cbsRead block - { - BlockState[i] = cbsRead; - ReadingBlocks--; - } - else - { - if (BlockState[i] == cbsWriting && BlockDataLen[i] == bytes) // fully written -> convert to cbsRead block (might write again, so keep it) - { - BlockState[i] = cbsRead; - WritingBlocks--; - } - } - } - else - { - DWORD err = GetLastError(); - if (i != errBlkIndex && // already reporting the error for this block; no need to repeat it in TRACE - err != ERROR_OPERATION_ABORTED) // not an error, merely reports cancellation (CancelIo() call) - { // log issues in other blocks, usually harmless and best ignored - TRACE_I("CCopy_Context::CancelOpPhase2(): GetOverlappedResult(" << (BlockState[i] == cbsWriting ? "OUT" : "IN") << ", " << i << ") returned error: " << GetErrorText(err)); - } - } - switch (BlockState[i]) - { - case cbsReading: // not fully read - case cbsTestingEOF: // EOF test not finished - case cbsDiscarded: - FreeBlock(i); - break; - - case cbsWriting: // unwritten block - if (WriteOffset > BlockOffset[i]) // lower WriteOffset if needed - WriteOffset = BlockOffset[i]; - BlockState[i] = cbsRead; // not fully written but already read -> convert to cbsRead block (might write again, so keep it) - WritingBlocks--; - break; - } - } - } - - ReadOffset = WriteOffset; // determine how far we have contiguous data from the offset where writing should resume - for (int i = 0; i < _countof(BlockState); i++) - { - if (BlockState[i] == cbsRead && BlockOffset[i] == ReadOffset) // block read directly after ReadOffset - { - ReadOffset.Value += BlockDataLen[i]; - i = -1; // start the search from the beginning again (with 8 blocks this is affordable, max 36 loop iterations) - } - } - - // drop blocks that are already written or too far ahead (not contiguous) - // so they can be read again later - for (int i = 0; i < _countof(BlockState); i++) - if (BlockState[i] == cbsRead && (BlockOffset[i] < WriteOffset || BlockOffset[i] > ReadOffset)) - FreeBlock(i); - - // when deleting the target file, set the file pointer to the end of the written portion; - // the caller will truncate it with SetEndOfFile before deletion (otherwise zeroes might be written - // from the end of the written part to the end of the pre-allocated file - pre-allocation is - // used to prevent fragmentation) - if (*Out != NULL) // only if the target file was not closed meanwhile - { - if (!SalSetFilePointer(*Out, WriteOffset)) - { - DWORD err = GetLastError(); - TRACE_E("CCopy_Context::CancelOpPhase2(): unable to set file pointer in OUT file, error: " << GetErrorText(err)); - } - } -} - -BOOL CCopy_Context::RetryCopyReadErr(DWORD* err, BOOL* copyAgain, BOOL* errAgain) -{ - if (*In != NULL) - HANDLES(CloseHandle(*In)); // close the invalid handle - *In = HANDLES_Q(CreateFileUtf8(Op->SourceName, GENERIC_READ, - FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, - OPEN_EXISTING, AsyncPar->GetOverlappedFlag() | FILE_FLAG_SEQUENTIAL_SCAN, NULL)); - if (*In != INVALID_HANDLE_VALUE) // opened successfully; now adjust the offset - { - CQuadWord size; - size.LoDWord = GetFileSize(*In, (DWORD*)&size.HiDWord); - if ((size.LoDWord != INVALID_FILE_SIZE || GetLastError() == NO_ERROR) && size >= ReadOffset) - { // size obtained and the file is large enough - // if the source is on a network: disable local client-side in-memory caching - // http://msdn.microsoft.com/en-us/library/ee210753%28v=vs.85%29.aspx - // - // using Overlapped[0].hEvent from AsyncPar is OK; nothing is "in-progress" now, the event is unused - // (but WARNING: for example Buffers[0] from AsyncPar may still be in use) - if ((Op->OpFlags & OPFL_SRCPATH_IS_NET) && !DisableLocalBuffering(AsyncPar, *In, err)) - TRACE_E("CCopy_Context::RetryCopyReadErr(): IOCTL_LMR_DISABLE_LOCAL_BUFFERING failed for network source file: " << Op->SourceName << ", error: " << GetErrorText(*err)); - // using Overlapped[0 and 1].hEvent and Overlapped[0 and 1] from AsyncPar is OK; nothing is - // "in-progress", the event nor the overlapped structures are used (but WARNING: for example Buffers[0] - // from AsyncPar may still be in use) - if (CheckTailOfOutFile(AsyncPar, *In, *Out, WriteOffset, WriteOffset, TRUE)) - { - ForceOp = ReadOffset > WriteOffset ? fopWriting : fopNotUsed; // if the read side is ahead, resume with writing - *OperationDone = WriteOffset; - Script->SetTFSandProgressSize(*LastTransferredFileSize + *OperationDone, *TotalDone + *OperationDone); - SetProgressWithoutSuspend(HProgressDlg, CaclProg(*OperationDone, Op->Size), - CaclProg(*TotalDone + *OperationDone, Script->TotalSize), *DlgData); - return TRUE; // success: proceed with retry - } - } - // cannot obtain the size, the file is too small, or the last written part differs from the source -> restart from scratch - HANDLES(CloseHandle(*In)); - if (WholeFileAllocated) - SetEndOfFile(*Out); // otherwise on a floppy the remaining bytes would be written - HANDLES(CloseHandle(*Out)); - DeleteFileUtf8(Op->TargetName); - *copyAgain = TRUE; // goto COPY_AGAIN; - return FALSE; - } - else // still cannot open; problem persists - { - *err = GetLastError(); - *In = NULL; - *errAgain = TRUE; // goto READ_ERROR; - return FALSE; - } -} - -BOOL CCopy_Context::HandleReadingErr(int blkIndex, DWORD err, BOOL* copyError, BOOL* skipCopy, BOOL* copyAgain) -{ - // NOTE: blkIndex == -1 when the async read request failed (no block assigned) - - CancelOpPhase1(); - - while (1) - { - WaitForSingleObject(DlgData->WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - if (*DlgData->CancelWorker) - { - CancelOpPhase2(blkIndex); - *copyError = TRUE; // goto COPY_ERROR - return FALSE; - } - - if (DlgData->SkipAllFileRead) - { - CancelOpPhase2(blkIndex); - *skipCopy = TRUE; // goto SKIP_COPY - return FALSE; - } - - int ret = IDCANCEL; - if (err == ERROR_NETNAME_DELETED && ++AutoRetryAttemptsSNAP <= 3) - { // SNAP servers occasionally return ERROR_NETNAME_DELETED while reading; Retry button reportedly helps, so trigger it automatically - Sleep(100); - ret = IDRETRY; - } - else - { - char* data[4]; - data[0] = (char*)&ret; - data[1] = LoadStr(IDS_ERRORREADINGFILE); - data[2] = Op->SourceName; - data[3] = GetErrorText(err); - SendMessage(HProgressDlg, WM_USER_DIALOG, 0, (LPARAM)data); - } - CancelOpPhase2(blkIndex); - BOOL errAgain = FALSE; - switch (ret) - { - case IDRETRY: - { - if (RetryCopyReadErr(&err, copyAgain, &errAgain)) - return TRUE; // retry - else - { - if (errAgain) - break; // same problem again; repeat the message - return FALSE; // copyAgain==TRUE, goto COPY_AGAIN; - } - } - - case IDB_SKIPALL: - DlgData->SkipAllFileRead = TRUE; - case IDB_SKIP: - { - *skipCopy = TRUE; // goto SKIP_COPY - return FALSE; - } - - case IDCANCEL: - { - *copyError = TRUE; // goto COPY_ERROR - return FALSE; - } - } - if (errAgain) - continue; // IDRETRY: same problem again; repeat the message - TRACE_C("CCopy_Context::HandleReadingErr(): unexpected result of WM_USER_DIALOG(0)."); - return TRUE; - } -} - -BOOL CCopy_Context::RetryCopyWriteErr(DWORD* err, BOOL* copyAgain, BOOL* errAgain, - const CQuadWord& allocFileSize, const CQuadWord& maxWriteOffset) -{ - if (*Out != NULL) - { - if (WholeFileAllocated) - SetEndOfFile(*Out); // otherwise on a floppy the remaining bytes would be written - HANDLES(CloseHandle(*Out)); // close the invalid handle - } - *Out = HANDLES_Q(CreateFileUtf8(Op->TargetName, GENERIC_WRITE | GENERIC_READ, 0, NULL, - OPEN_ALWAYS, AsyncPar->GetOverlappedFlag() | FILE_FLAG_SEQUENTIAL_SCAN, NULL)); - if (*Out != INVALID_HANDLE_VALUE) // opened successfully; now adjust the offset - { - BOOL ok = TRUE; - CQuadWord size; - size.LoDWord = GetFileSize(*Out, (DWORD*)&size.HiDWord); - if (size.LoDWord == INVALID_FILE_SIZE && GetLastError() != NO_ERROR || // cannot obtain the size - size < WriteOffset || // file is too small - WholeFileAllocated && size > allocFileSize && size > maxWriteOffset) // pre-allocated file is too large (greater than the pre-allocated size and the written portion including the current block) = extra bytes appended (allocWholeFileOnStart should be 0 /* need-test */) - { // restart the entire thing - ok = FALSE; - } - // success (file size matches what we need) - // if the target is on a network: disable local client-side in-memory caching - // http://msdn.microsoft.com/en-us/library/ee210753%28v=vs.85%29.aspx - // - // using Overlapped[0].hEvent from AsyncPar is OK; nothing is "in-progress" now, the event is unused - // (but WARNING: for example Buffers[0] from AsyncPar may still be in use) - if (ok && (Op->OpFlags & OPFL_TGTPATH_IS_NET) && !DisableLocalBuffering(AsyncPar, *Out, err)) - TRACE_E("CCopy_Context::RetryCopyWriteErr(): IOCTL_LMR_DISABLE_LOCAL_BUFFERING failed for network target file: " << Op->TargetName << ", error: " << GetErrorText(*err)); - // using Overlapped[0 and 1].hEvent and Overlapped[0 and 1] from AsyncPar is OK; nothing is - // "in-progress", the event nor the overlapped structures are used (but WARNING: for example Buffers[0] - // from AsyncPar may still be in use) - if (!ok || !CheckTailOfOutFile(AsyncPar, *In, *Out, WriteOffset, WriteOffset, FALSE)) - { - HANDLES(CloseHandle(*In)); - HANDLES(CloseHandle(*Out)); - DeleteFileUtf8(Op->TargetName); - *copyAgain = TRUE; // goto COPY_AGAIN; - return FALSE; - } - ForceOp = ReadOffset > WriteOffset ? fopWriting : fopNotUsed; // if the read side is ahead, resume with writing - *OperationDone = WriteOffset; - Script->SetTFSandProgressSize(*LastTransferredFileSize + *OperationDone, *TotalDone + *OperationDone); - SetProgressWithoutSuspend(HProgressDlg, CaclProg(*OperationDone, Op->Size), - CaclProg(*TotalDone + *OperationDone, Script->TotalSize), *DlgData); - return TRUE; // success: proceed with retry - } - else // still cannot open; problem persists - { - *err = GetLastError(); - *Out = NULL; - *errAgain = TRUE; // goto WRITE_ERROR; - return FALSE; - } -} - -BOOL CCopy_Context::HandleWritingErr(int blkIndex, DWORD err, BOOL* copyError, BOOL* skipCopy, BOOL* copyAgain, - const CQuadWord& allocFileSize, const CQuadWord& maxWriteOffset) -{ - // NOTE: blkIndex == -1 for an error while truncating the file after the main copy loop finishes (it has no assigned block) - - CancelOpPhase1(); - - while (1) - { - WaitForSingleObject(DlgData->WorkerNotSuspended, INFINITE); // if we are supposed to be in suspend mode, wait ... - if (*DlgData->CancelWorker) - { - CancelOpPhase2(blkIndex); - *copyError = TRUE; // goto COPY_ERROR - return FALSE; - } - - if (DlgData->SkipAllFileWrite) - { - CancelOpPhase2(blkIndex); - *skipCopy = TRUE; // goto SKIP_COPY - return FALSE; - } - - int ret = IDCANCEL; - char* data[4]; - data[0] = (char*)&ret; - data[1] = LoadStr(IDS_ERRORWRITINGFILE); - data[2] = Op->TargetName; - data[3] = GetErrorText(err); - SendMessage(HProgressDlg, WM_USER_DIALOG, 0, (LPARAM)data); - CancelOpPhase2(blkIndex); - BOOL errAgain = FALSE; - switch (ret) - { - case IDRETRY: - { - if (RetryCopyWriteErr(&err, copyAgain, &errAgain, allocFileSize, maxWriteOffset)) - return TRUE; // retry - else - { - if (errAgain) - break; // same problem again, repeat the message - return FALSE; // copyAgain==TRUE, goto COPY_AGAIN; - } - } - - case IDB_SKIPALL: - DlgData->SkipAllFileWrite = TRUE; - case IDB_SKIP: - { - *skipCopy = TRUE; // goto SKIP_COPY - return FALSE; - } - - case IDCANCEL: - { - *copyError = TRUE; // goto COPY_ERROR - return FALSE; - } - } - if (errAgain) - continue; // IDRETRY: same problem again, repeat the message - TRACE_C("CCopy_Context::HandleWritingErr(): unexpected result of WM_USER_DIALOG(0)."); - return TRUE; - } -} - -BOOL CCopy_Context::HandleSuspModeAndCancel(BOOL* copyError) -{ - if (!Script->ChangeSpeedLimit) // if the speed limit cannot change (otherwise this is not a "suitable" place to wait) - WaitForSingleObject(DlgData->WorkerNotSuspended, INFINITE); // if we are supposed to be in suspend mode, wait ... - if (*DlgData->CancelWorker) - { - CancelOpPhase1(); - CancelOpPhase2(-1); - *copyError = TRUE; // goto COPY_ERROR - return TRUE; - } - return FALSE; -} - -void DoCopyFileLoopAsync(CAsyncCopyParams* asyncPar, HANDLE& in, HANDLE& out, void* buffer, int& limitBufferSize, - COperations* script, CProgressDlgData& dlgData, BOOL wholeFileAllocated, COperation* op, - const CQuadWord& totalDone, BOOL& copyError, BOOL& skipCopy, HWND hProgressDlg, - CQuadWord& operationDone, CQuadWord& fileSize, int bufferSize, - int& allocWholeFileOnStart, BOOL& copyAgain, const CQuadWord& lastTransferredFileSize) -{ - CQuadWord allocFileSize = fileSize; - DWORD err = NO_ERROR; - DWORD bytes = 0; // helper DWORD - how many bytes were read/written in the block - - // if the source/target is on the network: disable local client-side in-memory caching - // http://msdn.microsoft.com/en-us/library/ee210753%28v=vs.85%29.aspx - if ((op->OpFlags & OPFL_SRCPATH_IS_NET) && !DisableLocalBuffering(asyncPar, in, &err)) - TRACE_E("DoCopyFileLoopAsync(): IOCTL_LMR_DISABLE_LOCAL_BUFFERING failed for network source file: " << op->SourceName << ", error: " << GetErrorText(err)); - if ((op->OpFlags & OPFL_TGTPATH_IS_NET) && !DisableLocalBuffering(asyncPar, out, &err)) - TRACE_E("DoCopyFileLoopAsync(): IOCTL_LMR_DISABLE_LOCAL_BUFFERING failed for network target file: " << op->TargetName << ", error: " << GetErrorText(err)); - - // copy loop parameters - int numOfBlocks = 8; - - // Copy operation context (prevents passing heaps of parameters to helper functions, now context methods) - CCopy_Context ctx(asyncPar, numOfBlocks, &dlgData, op, hProgressDlg, &in, &out, wholeFileAllocated, script, - &operationDone, &totalDone, &lastTransferredFileSize); - BOOL doCopy = TRUE; - while (doCopy) - { - if (ctx.ForceOp != fopWriting && ctx.FreeBlocks > 0 && !ctx.ReadingDone && ctx.ReadingBlocks < (numOfBlocks + 1) / 2) // read in parallel at most up to half of the blocks - { - DWORD toRead = ctx.ReadOffset + CQuadWord(limitBufferSize, 0) <= fileSize ? limitBufferSize : (fileSize - ctx.ReadOffset).LoDWord; - BOOL testEOF = toRead == 0; - if (!testEOF || ctx.ReadingBlocks == 0) // data read or EOF test (the EOF test runs only when all reads are finished) - { - if (ctx.BlockState[ctx.FreeBlockIndex] != cbsFree) - ctx.FreeBlockIndex = ctx.FindBlock(cbsFree); - // EOF test = read the entire block, otherwise read the usual 'toRead' - if (ctx.StartReading(ctx.FreeBlockIndex, testEOF ? limitBufferSize : toRead, &err, testEOF)) - continue; // success (asynchronous read started), try to start another read - else - { // error (starting asynchronous read) - if (!ctx.HandleReadingErr(-1, err, ©Error, &skipCopy, ©Again)) - return; // cancel/skip(skip-all)/retry-complete - continue; // retry-resume - } - } - } - // reading has already been issued or is unnecessary, check whether something is completed - BOOL shouldWait = TRUE; // TRUE = nothing else can be queued asynchronously, we must wait for some pending operation to finish - BOOL retryCopy = FALSE; // TRUE = after an error we should run Retry = start over from the beginning of the "doCopy" loop - // two passes are needed only for synchronous writes (we want to mark it - // completed immediately and not after another read, mainly for progress reporting) - for (int afterWriting = 0; afterWriting < 2; afterWriting++) - { - for (int i = 0; i < _countof(ctx.BlockState); i++) - { - if (ctx.BlockState[i] > cbsInProgress && HasOverlappedIoCompleted(asyncPar->GetOverlapped(i))) - { - shouldWait = FALSE; // in the spirit of "keep it simple" (there are situations where it could remain TRUE, but we ignore them) - switch (ctx.BlockState[i]) - { - case cbsReading: // reading the source file into a block - requested (in progress) - case cbsTestingEOF: // testing for the end of the source file - { - BOOL testingEOF = ctx.BlockState[i] == cbsTestingEOF; - -#ifdef ASYNC_COPY_DEBUG_MSG - TRACE_I("READ done: " << i); -#endif // ASYNC_COPY_DEBUG_MSG - - BOOL res = GetOverlappedResult(in, asyncPar->GetOverlapped(i), &bytes, TRUE); - if (testingEOF && res && bytes == 0) - { - res = FALSE; // MSDN says it should return FALSE and ERROR_HANDLE_EOF at EOF, so enforce that (Novell Netware 6.5 disk returns TRUE) - SetLastError(ERROR_HANDLE_EOF); - } - if (res || GetLastError() == ERROR_HANDLE_EOF) - { - ctx.AutoRetryAttemptsSNAP = 0; - if (!res) // EOF at the beginning of the block (for cbsReading only: EOF can also be before this block and will be handled later in a block with a lower offset) - { - // when GetOverlappedResult() returns FALSE it does not have to return bytes==0 - // (TRACE_C existed for that and crashes happened), so zero the bytes explicitly - bytes = 0; - if (testingEOF) - ctx.ReadingDone = TRUE; // confirmed end of the source file, stop reading further - // we must not force fopWriting (we have not read anything, there is nothing to write), unless this is an EOF test, - // let the other asynchronous reads finish, then perform the EOF test, and only then continue with writing - ctx.ForceOp = fopNotUsed; - } - if (bytes < ctx.BlockDataLen[i]) // the file is shorter than expected -> set the new file size - { - if (!testingEOF || bytes != 0) - ctx.ReadOffset = fileSize = ctx.BlockOffset[i] + CQuadWord(bytes, 0); - if (!testingEOF) - ctx.DiscardBlocksBehindEOF(fileSize, i); - if (bytes == 0) // EOF = no data, free the block - { - ctx.FreeBlock(i); - if (testingEOF) - doCopy = !ctx.IsOperationDone(numOfBlocks); // verify whether this finished the copy - } - else - ctx.BlockDataLen[i] = bytes; // pretend we intended to read exactly this much - } - else - { - if (testingEOF) // we were looking for EOF and read a full block; the file probably grew significantly, determine the new size - { - ctx.ReadOffset = ctx.BlockOffset[i] + CQuadWord(bytes, 0); - ctx.GetNewFileSize(op->SourceName, in, &fileSize, ctx.ReadOffset); - } - } - if (ctx.BlockState[i] == cbsReading || ctx.BlockState[i] == cbsTestingEOF) - { - ctx.ReadingBlocks--; - ctx.BlockState[i] = cbsRead; - } - } - else // error - { - if (!ctx.HandleReadingErr(i, GetLastError(), ©Error, &skipCopy, ©Again)) - return; // cancel/skip(skip-all)/retry-complete - retryCopy = TRUE; // retry-resume - } - break; - } - - case cbsWriting: // writing a block to the target file - { -#ifdef ASYNC_COPY_DEBUG_MSG - TRACE_I("WRITE done: " << i); -#endif // ASYNC_COPY_DEBUG_MSG - - BOOL res = GetOverlappedResult(out, asyncPar->GetOverlapped(i), &bytes, TRUE); - if (!res || bytes != ctx.BlockDataLen[i]) // error - { - err = GetLastError(); - if (err == NO_ERROR && bytes != ctx.BlockDataLen[i]) - err = ERROR_DISK_FULL; - CQuadWord maxWriteOffset = ctx.WriteOffset; - if (!ctx.HandleWritingErr(i, err, ©Error, &skipCopy, ©Again, allocFileSize, maxWriteOffset)) - return; // cancel/skip(skip-all)/retry-complete - retryCopy = TRUE; // retry-resume - break; - } - - if (ctx.HandleSuspModeAndCancel(©Error)) - return; // cancel - - script->AddBytesToSpeedMetersAndTFSandPS(bytes, FALSE, bufferSize, &limitBufferSize); - - if (!script->ChangeSpeedLimit) // if the speed limit can change, this is not a "suitable" place to wait - WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - operationDone += CQuadWord(bytes, 0); - SetProgressWithoutSuspend(hProgressDlg, CaclProg(operationDone, op->Size), - CaclProg(totalDone + operationDone, script->TotalSize), dlgData); - - if (script->ChangeSpeedLimit) // the speed limit is likely to change, this is a "suitable" place to wait until the - { // worker resumes so we can get the buffer size for copying again - WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - script->GetNewBufSize(&limitBufferSize, bufferSize); - } - - // break; // the break is intentionally missing here... - } - case cbsDiscarded: // reading the source file beyond its end (should only return the EOF error) - { - ctx.FreeBlock(i); - doCopy = !ctx.IsOperationDone(numOfBlocks); - break; - } - } - } - if (!doCopy || retryCopy) - break; - } - if (!doCopy || retryCopy) - break; - - // we have read data into blocks, check whether they can be written to the target file; - // written/discarded blocks were freed (we will read into them again at the top of the loop) - CQuadWord nextReadBlkOffset; // lowest offset of a skipped cbsRead block - do - { - nextReadBlkOffset.SetUI64(0); - // write in parallel at most up to half of the blocks - for (int i = 0; ctx.ForceOp != fopReading && i < _countof(ctx.BlockState) && ctx.WritingBlocks < (numOfBlocks + 1) / 2; i++) - { - if (ctx.BlockState[i] == cbsRead) - { - if (ctx.WriteOffset == ctx.BlockOffset[i]) - { - if (!ctx.StartWriting(i, &err)) - { // error (asynchronous write) - CQuadWord maxWriteOffset = ctx.WriteOffset + CQuadWord(ctx.BlockDataLen[i], 0); - if (!ctx.HandleWritingErr(i, err, ©Error, &skipCopy, ©Again, allocFileSize, maxWriteOffset)) - return; // cancel/skip(skip-all)/retry-complete - retryCopy = TRUE; // retry-resume - break; - } - } - else - { - if (nextReadBlkOffset.Value == 0 || ctx.BlockOffset[i] < nextReadBlkOffset) - nextReadBlkOffset = ctx.BlockOffset[i]; - } - } - } // we have another cbsRead block adjoining the written portion of the target file -> keep writing - } while (!retryCopy && ctx.ForceOp != fopReading && nextReadBlkOffset.Value != 0 && nextReadBlkOffset == ctx.WriteOffset && - ctx.WritingBlocks < (numOfBlocks + 1) / 2); // write in parallel at most up to half of the blocks - if (retryCopy || ctx.ForceOp != fopReading) - break; // we are going to Retry or the write was not synchronous (finished in about 0 ms) or we only write now, so two passes are pointless - } - if (!doCopy || retryCopy) - continue; - - if (shouldWait) // another pass through the loop is pointless, no chance to start a new read or write, wait - { // for the oldest asynchronous operation to finish - DWORD oldestBlockTime = 0; - int oldestBlockIndex = -1; - for (int i = 0; i < _countof(ctx.BlockState); i++) - { - if (ctx.BlockState[i] > cbsInProgress) - { - DWORD ti = ctx.CurTime - ctx.BlockTime[i]; - if (oldestBlockTime < ti) - { - oldestBlockTime = ti; - oldestBlockIndex = i; - } - } - } - if (oldestBlockIndex == -1) - TRACE_C("DoCopyFileLoopAsync(): unexpected situation: unable to find any block with operation in progress!"); - -#ifdef ASYNC_COPY_DEBUG_MSG - TRACE_I("wait: GetOverlappedResult: " << oldestBlockIndex << (ctx.BlockState[oldestBlockIndex] == cbsWriting ? " WRITE" : " READ")); -#endif // ASYNC_COPY_DEBUG_MSG - - // wait for the oldest pending asynchronous operation to complete here - // for the source file ('in') this covers: cbsReading, cbsTestingEOF, and cbsDiscarded - // for the target file ('out') this covers only cbsWriting - GetOverlappedResult(ctx.BlockState[oldestBlockIndex] == cbsWriting ? out : in, - asyncPar->GetOverlapped(oldestBlockIndex), &bytes, TRUE); - -#ifdef ASYNC_COPY_DEBUG_MSG - char sss[1000]; - sprintf(sss, "wait done: 0x%08X 0x%08X", ctx.BlockOffset[oldestBlockIndex].LoDWord, bytes); - TRACE_I(sss); -#endif // ASYNC_COPY_DEBUG_MSG - - if (ctx.HandleSuspModeAndCancel(©Error)) - return; // cancel - } - } - if (ctx.ReadOffset != ctx.WriteOffset || operationDone != ctx.WriteOffset) - TRACE_C("DoCopyFileLoopAsync(): unexpected situation after copy: ReadOffset != WriteOffset || operationDone != ctx.WriteOffset"); - - if (wholeFileAllocated) // we allocated the full size of the file (meaning the allocation made sense, e.g. the file cannot be empty) - { - if (operationDone < allocFileSize) // and the source file shrank, trim it here - { - while (1) - { - CQuadWord off = ctx.WriteOffset; - off.LoDWord = SetFilePointer(out, off.LoDWord, (LONG*)&(off.HiDWord), FILE_BEGIN); - if (off.LoDWord == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR || - off != ctx.WriteOffset || - !SetEndOfFile(out)) - { - DWORD err2 = GetLastError(); - if ((off.LoDWord != INVALID_SET_FILE_POINTER || err2 == NO_ERROR) && off != ctx.WriteOffset) - err2 = ERROR_INVALID_FUNCTION; // successful SetFilePointer, but off != ctx.WriteOffset: will probably never happen, included for completeness - if (!ctx.HandleWritingErr(-1, err2, ©Error, &skipCopy, ©Again, allocFileSize, CQuadWord(0, 0))) - return; // cancel/skip(skip-all)/retry-complete - // retry-resume - } - else - break; // success - } - } - - if (allocWholeFileOnStart == 0 /* need-test */) - { - CQuadWord curFileSize; - curFileSize.LoDWord = GetFileSize(out, &curFileSize.HiDWord); - BOOL getFileSizeSuccess = (curFileSize.LoDWord != INVALID_FILE_SIZE || GetLastError() == NO_ERROR); - if (getFileSizeSuccess && curFileSize == operationDone) - { // verify that no extra bytes were appended to the end of the file + that we can truncate the file - allocWholeFileOnStart = 1 /* yes */; - } - else - { -#ifdef _DEBUG - if (getFileSizeSuccess) - { - char num1[50]; - char num2[50]; - TRACE_E("DoCopyFileLoopAsync(): unable to allocate whole file size before copy operation, please report " - "under what conditions this occurs! Error: different file sizes: target=" - << NumberToStr(num1, curFileSize) << " bytes, source=" << NumberToStr(num2, operationDone) << " bytes"); - } - else - { - DWORD err2 = GetLastError(); - TRACE_E("DoCopyFileLoopAsync(): unable to test result of allocation of whole file size before copy operation, please report " - "under what conditions this occurs! GetFileSize(" - << op->TargetName << ") error: " << GetErrorText(err2)); - } -#endif - allocWholeFileOnStart = 2 /* no */; // skip further attempts on this target disk - - while (1) - { - HANDLES(CloseHandle(out)); - out = NULL; - ClearReadOnlyAttr(op->TargetName); // in case it was created as read-only (should never happen) so we can handle it - if (DeleteFileUtf8(op->TargetName)) - { - HANDLES(CloseHandle(in)); - copyAgain = TRUE; // goto COPY_AGAIN; - return; - } - else - { - if (!ctx.HandleWritingErr(-1, GetLastError(), ©Error, &skipCopy, ©Again, allocFileSize, CQuadWord(0, 0))) - return; // cancel/skip(skip-all)/retry-complete - // retry-resume - } - } - } - } - } -} - -BOOL DoCopyFile(COperation* op, HWND hProgressDlg, void* buffer, - COperations* script, CQuadWord& totalDone, - DWORD clearReadonlyMask, BOOL* skip, BOOL lantasticCheck, - int& mustDeleteFileBeforeOverwrite, int& allocWholeFileOnStart, - CProgressDlgData& dlgData, BOOL copyADS, BOOL copyAsEncrypted, - BOOL isMove, CAsyncCopyParams*& asyncPar) -{ - if (script->CopyAttrs && copyAsEncrypted) - TRACE_E("DoCopyFile(): unexpected parameter value: copyAsEncrypted is TRUE when script->CopyAttrs is TRUE!"); - - // if the path ends with a space/dot, it is invalid and we must not copy it, - // CreateFile would trim the spaces/dots and copy a different file or under a different name - BOOL invalidSrcName = FileNameIsInvalid(op->SourceName, TRUE); - BOOL invalidTgtName = FileNameIsInvalid(op->TargetName, TRUE); - - // optimization: skipping all "older and identical" files is about 4x faster, - // slowing down when the file is newer is 5%, so it should be well worth it - // (it is safe to assume the user enables "Overwrite Older" when the skips occur) - BOOL tgtNameCaseCorrected = FALSE; // TRUE = the letter case in the target name was already adjusted to match the existing target file (so overwriting does not change it) - WIN32_FIND_DATAW dataIn, dataOut; - if ((op->OpFlags & OPFL_OVERWROLDERALRTESTED) == 0 && - !invalidSrcName && !invalidTgtName && script->OverwriteOlder) - { - HANDLE find; - CStrP targetNameW(ConvertAllocUtf8ToWide(op->TargetName, -1)); - find = targetNameW != NULL ? HANDLES_Q(FindFirstFileW(targetNameW, &dataOut)) : INVALID_HANDLE_VALUE; - if (find != INVALID_HANDLE_VALUE) - { - HANDLES(FindClose(find)); - - CorrectCaseOfTgtName(op->TargetName, TRUE, &dataOut); - tgtNameCaseCorrected = TRUE; - - const char* tgtName = SalPathFindFileName(op->TargetName); - char outName[MAX_PATH]; - if (ConvertWideToUtf8(dataOut.cFileName, -1, outName, _countof(outName)) == 0) - outName[0] = 0; - if (StrICmp(tgtName, outName) == 0 && // ensure it is not just a DOS-name match (that would change the DOS-name instead of overwriting) - (dataOut.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) // ensure it is not a directory (overwrite-older cannot help there) - { - CStrP sourceNameW(ConvertAllocUtf8ToWide(op->SourceName, -1)); - find = sourceNameW != NULL ? HANDLES_Q(FindFirstFileW(sourceNameW, &dataIn)) : INVALID_HANDLE_VALUE; - if (find != INVALID_HANDLE_VALUE) - { - HANDLES(FindClose(find)); - - // truncate times to seconds (different file systems store timestamps with different precision, leading to "differences" even between "identical" times) - *(unsigned __int64*)&dataIn.ftLastWriteTime = *(unsigned __int64*)&dataIn.ftLastWriteTime - (*(unsigned __int64*)&dataIn.ftLastWriteTime % 10000000); - *(unsigned __int64*)&dataOut.ftLastWriteTime = *(unsigned __int64*)&dataOut.ftLastWriteTime - (*(unsigned __int64*)&dataOut.ftLastWriteTime % 10000000); - - if ((dataIn.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0 && // verify the source is still a file - CompareFileTime(&dataIn.ftLastWriteTime, &dataOut.ftLastWriteTime) <= 0) // source file is not newer than the target file - skip the copy operation - { - CQuadWord fileSize(op->FileSize); - if (fileSize < COPY_MIN_FILE_SIZE) - { - if (op->Size > COPY_MIN_FILE_SIZE) // should always be at least COPY_MIN_FILE_SIZE, but play it safe... - fileSize += op->Size - COPY_MIN_FILE_SIZE; // add the size of ADS streams - } - else - fileSize = op->Size; - totalDone += op->Size; - script->AddBytesToTFSandSetProgressSize(fileSize, totalDone); - - SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); - if (skip != NULL) - *skip = TRUE; - return TRUE; - } - } - } - } - } - - // decide which algorithm to use for copying: the ancient synchronous one or - // the asynchronous one inspired by the Windows 7 CopyFileEx version: - // - under Vista it misbehaved badly; forget Vista, it is almost dead anyway, and - // when using the old algorithm against Win7 over the network I saw no speed difference - // for uploads, and downloads were only 15% slower (acceptable) - // - the asynchronous algorithm makes sense only over the network + when source/target is fast or network-based - // - with the old algorithm, copying on Win7 over the network is easily 2x-3x slower for downloads, - // almost 2x slower for uploads, and about 30% slower for network-to-network copies - BOOL useAsyncAlg = Windows7AndLater && Configuration.UseAsyncCopyAlg && - op->FileSize.Value > 0 && // empty files are copied synchronously (no data) - ((op->OpFlags & OPFL_SRCPATH_IS_NET) && ((op->OpFlags & OPFL_TGTPATH_IS_NET) || - (op->OpFlags & OPFL_TGTPATH_IS_FAST)) || - (op->OpFlags & OPFL_TGTPATH_IS_NET) && (op->OpFlags & OPFL_SRCPATH_IS_FAST)); - - if (asyncPar == NULL) - asyncPar = new CAsyncCopyParams; - - asyncPar->Init(useAsyncAlg); - script->EnableProgressBufferLimit(useAsyncAlg); - struct CDisableProgressBufferLimit // ensure Script->EnableProgressBufferLimit(FALSE) is called on every exit from this function - { - COperations* Script; - CDisableProgressBufferLimit(COperations* script) { Script = script; } - ~CDisableProgressBufferLimit() { Script->EnableProgressBufferLimit(FALSE); } - } DisableProgressBufferLimit(script); - - CQuadWord operationDone; - CQuadWord lastTransferredFileSize; - script->GetTFSandResetTrSpeedIfNeeded(&lastTransferredFileSize); - -COPY_AGAIN: - - operationDone = CQuadWord(0, 0); - HANDLE in; - - if (skip != NULL) - *skip = FALSE; - - int bufferSize; - if (useAsyncAlg) - { - if (op->FileSize.Value <= 512 * 1024) - bufferSize = ASYNC_COPY_BUF_SIZE_512KB; - else if (op->FileSize.Value <= 2 * 1024 * 1024) - bufferSize = ASYNC_COPY_BUF_SIZE_2MB; - else if (op->FileSize.Value <= 8 * 1024 * 1024) - bufferSize = ASYNC_COPY_BUF_SIZE_8MB; - else - bufferSize = ASYNC_COPY_BUF_SIZE; - } - else - bufferSize = GetOptimalSyncCopyBufferSize(script, op->OpFlags); - - int limitBufferSize = bufferSize; - script->SetTFSandProgressSize(lastTransferredFileSize, totalDone, &limitBufferSize, bufferSize); - - while (1) - { - if (!invalidSrcName && !asyncPar->Failed()) - { - in = HANDLES_Q(CreateFileUtf8(op->SourceName, GENERIC_READ, - FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, - OPEN_EXISTING, asyncPar->GetOverlappedFlag() | FILE_FLAG_SEQUENTIAL_SCAN, NULL)); - } - else - { - in = INVALID_HANDLE_VALUE; - } - if (in != INVALID_HANDLE_VALUE) - { - CQuadWord fileSize = op->FileSize; - - HANDLE out; - BOOL lossEncryptionAttr = FALSE; - BOOL skipAllocWholeFileOnStart = FALSE; - while (1) - { - OPEN_TGT_FILE: - - BOOL encryptionNotSupported = FALSE; - DWORD fileAttrs = asyncPar->GetOverlappedFlag() | FILE_FLAG_SEQUENTIAL_SCAN | - (!lossEncryptionAttr && copyAsEncrypted ? FILE_ATTRIBUTE_ENCRYPTED : 0) | - (script->CopyAttrs ? (op->Attr & (FILE_ATTRIBUTE_COMPRESSED | (lossEncryptionAttr ? 0 : FILE_ATTRIBUTE_ENCRYPTED))) : 0); - if (!invalidTgtName) - { - // GENERIC_READ for 'out' slows asynchronous copying from disk to network (measured 95 MB/s instead of 111 MB/s on Win7 x64 GLAN) - out = SalCreateFileEx(op->TargetName, GENERIC_WRITE | (script->CopyAttrs ? GENERIC_READ : 0), 0, fileAttrs, &encryptionNotSupported); - if (!encryptionNotSupported && script->CopyAttrs && out == INVALID_HANDLE_VALUE) // in case read access to the directory is not allowed (we added it only for setting the Compressed attribute), try creating a write-only file - out = SalCreateFileEx(op->TargetName, GENERIC_WRITE, 0, fileAttrs, &encryptionNotSupported); - - if (out == INVALID_HANDLE_VALUE && encryptionNotSupported && dlgData.FileOutLossEncrAll && !lossEncryptionAttr) - { // the user agreed to lose the Encrypted attribute for all problematic files, so make that happen here - lossEncryptionAttr = TRUE; - continue; - } - HANDLES_ADD_EX(__otQuiet, out != INVALID_HANDLE_VALUE, __htFile, - __hoCreateFile, out, GetLastError(), TRUE); - if (script->CopyAttrs) - { - fileAttrs = lossEncryptionAttr ? (op->Attr & ~FILE_ATTRIBUTE_ENCRYPTED) : op->Attr; - SetCompressAndEncryptedAttrs(op->TargetName, fileAttrs, &out, TRUE, NULL, asyncPar); - } - - if (out != INVALID_HANDLE_VALUE && (fileAttrs & FILE_ATTRIBUTE_ENCRYPTED)) - { // verify that the Encrypted attribute is really set (on FAT it is simply ignored, the system does not return an error (for CreateFile specifically)) - DWORD attrs; - attrs = SalGetFileAttributes(op->TargetName); - if (attrs != INVALID_FILE_ATTRIBUTES && (attrs & FILE_ATTRIBUTE_ENCRYPTED) == 0) - { // unable to apply the Encrypted attribute, ask the user what to do... - if (dlgData.FileOutLossEncrAll) - lossEncryptionAttr = TRUE; - else - { - WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - if (*dlgData.CancelWorker) - goto CANCEL_ENCNOTSUP; - - if (dlgData.SkipAllFileOutLossEncr) - goto SKIP_ENCNOTSUP; - - int ret; - ret = IDCANCEL; - char* data[4]; - data[0] = (char*)&ret; - data[1] = (char*)TRUE; - data[2] = op->TargetName; - data[3] = (char*)(INT_PTR)isMove; - SendMessage(hProgressDlg, WM_USER_DIALOG, 12, (LPARAM)data); - switch (ret) - { - case IDB_ALL: - dlgData.FileOutLossEncrAll = TRUE; // the break; is intentionally missing here - case IDYES: - lossEncryptionAttr = TRUE; - break; - - case IDB_SKIPALL: - dlgData.SkipAllFileOutLossEncr = TRUE; - case IDB_SKIP: - { - SKIP_ENCNOTSUP: - - HANDLES(CloseHandle(out)); - DeleteFileUtf8(op->TargetName); - goto SKIP_OPEN_OUT; - } - - case IDCANCEL: - { - CANCEL_ENCNOTSUP: - - HANDLES(CloseHandle(out)); - DeleteFileUtf8(op->TargetName); - goto CANCEL_OPEN2; - } - } - } - } - } - } - else - { - out = INVALID_HANDLE_VALUE; - } - - if (out != INVALID_HANDLE_VALUE) - { - - COPY: - - // if possible, allocate the required space for the file (prevents disk fragmentation + smoother writes to floppies) - BOOL wholeFileAllocated = FALSE; - if (!skipAllocWholeFileOnStart && // last time failed, so the same would probably happen now - allocWholeFileOnStart != 2 /* no */ && // allocating the whole file is not forbidden - fileSize > CQuadWord(limitBufferSize, 0) && // allocation is pointless below the copy buffer size - fileSize < CQuadWord(0, 0x80000000)) // file size is positive number (otherwise seeking is impossible - numbers above 8EB, so likely never happens) - { - BOOL fatal = TRUE; - BOOL ignoreErr = FALSE; - if (SalSetFilePointer(out, fileSize)) - { - if (SetEndOfFile(out)) - { - if (SetFilePointer(out, 0, NULL, FILE_BEGIN) == 0) - { - fatal = FALSE; - wholeFileAllocated = TRUE; - } - } - else - { - if (GetLastError() == ERROR_DISK_FULL) - ignoreErr = TRUE; // not enough space on the disk - } - } - if (fatal) - { - if (!ignoreErr) - { - DWORD err = GetLastError(); - TRACE_E("DoCopyFile(): unable to allocate whole file size before copy operation, please report under what conditions this occurs! GetLastError(): " << GetErrorText(err)); - allocWholeFileOnStart = 2 /* no */; // we will forego further attempts on this target disk - } - - // try truncating the file to zero so closing it does not trigger any unnecessary writes - SetFilePointer(out, 0, NULL, FILE_BEGIN); - SetEndOfFile(out); - - HANDLES(CloseHandle(out)); - out = INVALID_HANDLE_VALUE; - ClearReadOnlyAttr(op->TargetName); // in case it was created as read-only (should never happen) so we can handle it - if (DeleteFileUtf8(op->TargetName)) - { - skipAllocWholeFileOnStart = TRUE; - goto OPEN_TGT_FILE; - } - else - goto CREATE_ERROR; - } - } - - script->SetFileStartParams(); - - BOOL copyError = FALSE; - BOOL skipCopy = FALSE; - BOOL copyAgain = FALSE; - if (useAsyncAlg) - { - DoCopyFileLoopAsync(asyncPar, in, out, buffer, limitBufferSize, script, dlgData, wholeFileAllocated, op, - totalDone, copyError, skipCopy, hProgressDlg, operationDone, fileSize, - bufferSize, allocWholeFileOnStart, copyAgain, lastTransferredFileSize); - // NOTE: neither 'in' nor 'out' has the file pointer (SetFilePointer) positioned at the end of the file, - // 'out' has it set only when (copyError || skipCopy) - } - else - { - DoCopyFileLoopOrig(in, out, buffer, limitBufferSize, script, dlgData, wholeFileAllocated, op, - totalDone, copyError, skipCopy, hProgressDlg, operationDone, fileSize, - bufferSize, allocWholeFileOnStart, copyAgain); - } - - if (copyError) - { - COPY_ERROR: - - if (in != NULL) - HANDLES(CloseHandle(in)); - if (out != NULL) - { - if (wholeFileAllocated) - SetEndOfFile(out); // otherwise on a floppy the remaining part of the file would be written - HANDLES(CloseHandle(out)); - } - DeleteFileUtf8(op->TargetName); - return FALSE; - } - if (skipCopy) - { - SKIP_COPY: - - totalDone += op->Size; - SetTFSandPSforSkippedFile(op, lastTransferredFileSize, script, totalDone); - - if (in != NULL) - HANDLES(CloseHandle(in)); - if (out != NULL) - { - if (wholeFileAllocated) - SetEndOfFile(out); // otherwise on a floppy the remaining part of the file would be written - HANDLES(CloseHandle(out)); - } - DeleteFileUtf8(op->TargetName); - SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); - if (skip != NULL) - *skip = TRUE; - return TRUE; - } - if (copyAgain) - goto COPY_AGAIN; - - if (lantasticCheck) - { - CQuadWord inSize, outSize; - inSize.LoDWord = GetFileSize(in, &inSize.HiDWord); - outSize.LoDWord = GetFileSize(out, &outSize.HiDWord); - if (inSize != outSize) - { // Lantastic 7.0: everything seems fine, but the result is wrong - WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - if (*dlgData.CancelWorker) - goto COPY_ERROR; - - if (dlgData.SkipAllFileWrite) - goto SKIP_COPY; - - int ret = IDCANCEL; - char* data[4]; - data[0] = (char*)&ret; - data[1] = LoadStr(IDS_ERRORWRITINGFILE); - data[2] = op->TargetName; - data[3] = GetErrorText(ERROR_DISK_FULL); - SendMessage(hProgressDlg, WM_USER_DIALOG, 0, (LPARAM)data); - switch (ret) - { - case IDRETRY: - { - operationDone = CQuadWord(0, 0); - script->SetTFSandProgressSize(lastTransferredFileSize, totalDone); - SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); - SetFilePointer(in, 0, NULL, FILE_BEGIN); // read again - SetFilePointer(out, 0, NULL, FILE_BEGIN); // write again - SetEndOfFile(out); // truncate the output file - goto COPY; - } - - case IDB_SKIPALL: - dlgData.SkipAllFileWrite = TRUE; - case IDB_SKIP: - goto SKIP_COPY; - - case IDCANCEL: - goto COPY_ERROR; - } - } - } - - FILETIME /*creation, lastAccess,*/ lastWrite; - BOOL ignoreGetFileTimeErr = FALSE; - while (!ignoreGetFileTimeErr && - !GetFileTime(in, NULL /*&creation*/, NULL /*&lastAccess*/, &lastWrite)) - { - DWORD err = GetLastError(); - - WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - if (*dlgData.CancelWorker) - goto COPY_ERROR; - - if (dlgData.SkipAllGetFileTime) - goto SKIP_COPY; - - if (dlgData.IgnoreAllGetFileTimeErr) - goto IGNORE_GETFILETIME; - - int ret; - ret = IDCANCEL; - char* data[4]; - data[0] = (char*)&ret; - data[1] = LoadStr(IDS_ERRORGETTINGFILETIME); - data[2] = op->SourceName; - data[3] = GetErrorText(err); - SendMessage(hProgressDlg, WM_USER_DIALOG, 8, (LPARAM)data); - switch (ret) - { - case IDRETRY: - break; - - case IDB_IGNOREALL: - dlgData.IgnoreAllGetFileTimeErr = TRUE; // the break; is intentionally missing here - case IDB_IGNORE: - { - IGNORE_GETFILETIME: - - ignoreGetFileTimeErr = TRUE; - break; - } - - case IDB_SKIPALL: - dlgData.SkipAllGetFileTime = TRUE; - case IDB_SKIP: - goto SKIP_COPY; - - case IDCANCEL: - goto COPY_ERROR; - } - } - - HANDLES(CloseHandle(in)); - in = NULL; - - if (operationDone < COPY_MIN_FILE_SIZE) // zero/small files take at least as long as files of size COPY_MIN_FILE_SIZE - script->AddBytesToSpeedMetersAndTFSandPS((DWORD)(COPY_MIN_FILE_SIZE - operationDone).Value, TRUE, 0, NULL, MAX_OP_FILESIZE); - - DWORD attr = op->Attr & clearReadonlyMask; - if (copyADS) // copy ADS streams if needed - { - SetFileAttributesUtf8(op->TargetName, FILE_ATTRIBUTE_ARCHIVE); // probably unnecessary, it hardly slows copying; reason: the file must not be read-only to work with it - CQuadWord operDone = operationDone; // the file is already copied - if (operDone < COPY_MIN_FILE_SIZE) - operDone = COPY_MIN_FILE_SIZE; // zero/small files take at least as long as files of size COPY_MIN_FILE_SIZE - BOOL adsSkip = FALSE; - // Pass the optimal buffer size computed from op->OpFlags for ADS copy - int adsBufferSize = GetOptimalSyncCopyBufferSize(script, op->OpFlags); - if (!DoCopyADS(hProgressDlg, op->SourceName, FALSE, op->TargetName, totalDone, - operDone, op->Size, dlgData, script, &adsSkip, buffer, adsBufferSize) || - adsSkip) // user hit cancel or skipped at least one ADS - { - if (out != NULL) - HANDLES(CloseHandle(out)); - out = NULL; - if (DeleteFileUtf8(op->TargetName) == 0) - { - DWORD err = GetLastError(); - TRACE_E("DoCopyFile(): Unable to remove newly created file: " << op->TargetName << ", error: " << GetErrorText(err)); - } - if (!adsSkip) - return FALSE; // cancel the entire operation - if (skip != NULL) - *skip = TRUE; // it is a Skip, must report higher up (Move must not delete the source file) - } - } - - if (out != NULL) - { - if (!ignoreGetFileTimeErr) // only if we did not ignore the error while reading the file time (nothing to set otherwise) - { - BOOL ignoreSetFileTimeErr = FALSE; - while (!ignoreSetFileTimeErr && - !SetFileTime(out, NULL /*&creation*/, NULL /*&lastAccess*/, &lastWrite)) - { - DWORD err = GetLastError(); - - WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - if (*dlgData.CancelWorker) - goto COPY_ERROR; - - if (dlgData.SkipAllSetFileTime) - goto SKIP_COPY; - - if (dlgData.IgnoreAllSetFileTimeErr) - goto IGNORE_SETFILETIME; - - int ret; - ret = IDCANCEL; - char* data[4]; - data[0] = (char*)&ret; - data[1] = LoadStr(IDS_ERRORSETTINGFILETIME); - data[2] = op->TargetName; - data[3] = GetErrorText(err); - SendMessage(hProgressDlg, WM_USER_DIALOG, 8, (LPARAM)data); - switch (ret) - { - case IDRETRY: - break; - - case IDB_IGNOREALL: - dlgData.IgnoreAllSetFileTimeErr = TRUE; // the break; is intentionally missing here - case IDB_IGNORE: - { - IGNORE_SETFILETIME: - - ignoreSetFileTimeErr = TRUE; - break; - } - - case IDB_SKIPALL: - dlgData.SkipAllSetFileTime = TRUE; - case IDB_SKIP: - goto SKIP_COPY; - - case IDCANCEL: - goto COPY_ERROR; - } - } - } - if (!HANDLES(CloseHandle(out))) - { - out = NULL; - DWORD err = GetLastError(); - WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - if (*dlgData.CancelWorker) - goto COPY_ERROR; - - if (dlgData.SkipAllFileWrite) - goto SKIP_COPY; - - int ret = IDCANCEL; - char* data[4]; - data[0] = (char*)&ret; - data[1] = LoadStr(IDS_ERRORWRITINGFILE); - data[2] = op->TargetName; - data[3] = GetErrorText(err); - SendMessage(hProgressDlg, WM_USER_DIALOG, 0, (LPARAM)data); - switch (ret) - { - case IDRETRY: - { - if (DeleteFileUtf8(op->TargetName) == 0) - { - DWORD err2 = GetLastError(); - TRACE_E("DoCopyFile(): Unable to remove newly created file: " << op->TargetName << ", error: " << GetErrorText(err2)); - } - goto COPY_AGAIN; - } - - case IDB_SKIPALL: - dlgData.SkipAllFileWrite = TRUE; - case IDB_SKIP: - goto SKIP_COPY; - - case IDCANCEL: - goto COPY_ERROR; - } - } - - SetFileAttributesUtf8(op->TargetName, script->CopyAttrs ? attr : (attr | FILE_ATTRIBUTE_ARCHIVE)); - } - - if (script->CopyAttrs) // verify whether the source file attributes were preserved - { - DWORD curAttrs; - curAttrs = SalGetFileAttributes(op->TargetName); - if (curAttrs == INVALID_FILE_ATTRIBUTES || (curAttrs & DISPLAYED_ATTRIBUTES) != (attr & DISPLAYED_ATTRIBUTES)) - { // attributes probably were not preserved, warn the user - WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - if (*dlgData.CancelWorker) - goto COPY_ERROR_2; - - int ret; - ret = IDCANCEL; - if (dlgData.IgnoreAllSetAttrsErr) - ret = IDB_IGNORE; - else - { - char* data[4]; - data[0] = (char*)&ret; - data[1] = op->TargetName; - data[2] = (char*)(DWORD_PTR)(attr & DISPLAYED_ATTRIBUTES); - data[3] = (char*)(DWORD_PTR)(curAttrs == INVALID_FILE_ATTRIBUTES ? 0 : (curAttrs & DISPLAYED_ATTRIBUTES)); - SendMessage(hProgressDlg, WM_USER_DIALOG, 9, (LPARAM)data); - } - switch (ret) - { - case IDB_IGNOREALL: - dlgData.IgnoreAllSetAttrsErr = TRUE; // break is intentional here; nothing is missing - case IDB_IGNORE: - break; - - case IDCANCEL: - { - COPY_ERROR_2: - - ClearReadOnlyAttr(op->TargetName); // the file must not be read-only if it is to be deleted - DeleteFileUtf8(op->TargetName); - return FALSE; - } - } - } - } - - if (script->CopySecurity) // should we copy NTFS security permissions? - { - DWORD err; - if (!DoCopySecurity(op->SourceName, op->TargetName, &err, NULL)) - { - WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - if (*dlgData.CancelWorker) - goto COPY_ERROR_2; - - int ret; - ret = IDCANCEL; - if (dlgData.IgnoreAllCopyPermErr) - ret = IDB_IGNORE; - else - { - char* data[4]; - data[0] = (char*)&ret; - data[1] = op->SourceName; - data[2] = op->TargetName; - data[3] = (char*)(DWORD_PTR)err; - SendMessage(hProgressDlg, WM_USER_DIALOG, 10, (LPARAM)data); - } - switch (ret) - { - case IDB_IGNOREALL: - dlgData.IgnoreAllCopyPermErr = TRUE; // the break; is intentionally missing here - case IDB_IGNORE: - break; - - case IDCANCEL: - goto COPY_ERROR_2; - } - } - } - - totalDone += op->Size; - script->SetProgressSize(totalDone); - return TRUE; - } - else - { - if (!invalidTgtName && encryptionNotSupported) - { - WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - if (*dlgData.CancelWorker) - goto CANCEL_OPEN2; - - if (dlgData.SkipAllFileOutLossEncr) - goto SKIP_OPEN_OUT; - - int ret; - ret = IDCANCEL; - char* data[4]; - data[0] = (char*)&ret; - data[1] = (char*)TRUE; - data[2] = op->TargetName; - data[3] = (char*)(INT_PTR)isMove; - SendMessage(hProgressDlg, WM_USER_DIALOG, 12, (LPARAM)data); - switch (ret) - { - case IDB_ALL: - dlgData.FileOutLossEncrAll = TRUE; // the break; is intentionally missing here - case IDYES: - lossEncryptionAttr = TRUE; - break; - - case IDB_SKIPALL: - dlgData.SkipAllFileOutLossEncr = TRUE; - case IDB_SKIP: - { - SKIP_OPEN_OUT: - - totalDone += op->Size; - SetTFSandPSforSkippedFile(op, lastTransferredFileSize, script, totalDone); - - HANDLES(CloseHandle(in)); - SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); - if (skip != NULL) - *skip = TRUE; - return TRUE; - } - - case IDCANCEL: - { - CANCEL_OPEN2: - - HANDLES(CloseHandle(in)); - return FALSE; - } - } - } - else - { - CREATE_ERROR: - - DWORD err = GetLastError(); - if (invalidTgtName) - err = ERROR_INVALID_NAME; - BOOL errDeletingFile = FALSE; - if (err == ERROR_FILE_EXISTS || // overwrite the file? - err == ERROR_ALREADY_EXISTS) - { - if (!dlgData.OverwriteAll && (dlgData.CnfrmFileOver || script->OverwriteOlder)) - { - char sAttr[101], tAttr[101]; - BOOL getTimeFailed; - getTimeFailed = FALSE; - FILETIME sFileTime, tFileTime; - GetFileOverwriteInfo(sAttr, _countof(sAttr), in, op->SourceName, &sFileTime, &getTimeFailed); - HANDLES(CloseHandle(in)); - in = NULL; - out = HANDLES_Q(CreateFileUtf8(op->TargetName, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, - OPEN_EXISTING, 0, NULL)); - if (out != INVALID_HANDLE_VALUE) - { - GetFileOverwriteInfo(tAttr, _countof(tAttr), out, op->TargetName, &tFileTime, &getTimeFailed); - HANDLES(CloseHandle(out)); - } - else - { - getTimeFailed = TRUE; - lstrcpyn(tAttr, LoadStr(IDS_ERR_FILEOPEN), _countof(tAttr)); - } - out = NULL; - - WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - if (*dlgData.CancelWorker) - goto CANCEL_OPEN; - - if (dlgData.SkipAllOverwrite) - goto SKIP_OPEN; - - int ret; - ret = IDCANCEL; - - if (!getTimeFailed && script->OverwriteOlder) // option from the Copy/Move dialog - { - // trim times to seconds (different file systems store times with different precision, so "differences" occurred even between "identical" times) - *(unsigned __int64*)&sFileTime = *(unsigned __int64*)&sFileTime - (*(unsigned __int64*)&sFileTime % 10000000); - *(unsigned __int64*)&tFileTime = *(unsigned __int64*)&tFileTime - (*(unsigned __int64*)&tFileTime % 10000000); - - if (CompareFileTime(&sFileTime, &tFileTime) > 0) - ret = IDYES; // overwrite older files without asking - else - ret = IDB_SKIP; // skip other existing files - } - else - { - // show the prompt - char* data[5]; - data[0] = (char*)&ret; - data[1] = op->TargetName; - data[2] = tAttr; - data[3] = op->SourceName; - data[4] = sAttr; - SendMessage(hProgressDlg, WM_USER_DIALOG, 1, (LPARAM)data); - } - switch (ret) - { - case IDB_ALL: - dlgData.OverwriteAll = TRUE; - case IDYES: - default: // for safety (to prevent exiting this block with the 'in' handle closed) - { - in = HANDLES_Q(CreateFileUtf8(op->SourceName, GENERIC_READ, - FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, - OPEN_EXISTING, asyncPar->GetOverlappedFlag() | FILE_FLAG_SEQUENTIAL_SCAN, NULL)); - if (in == INVALID_HANDLE_VALUE) - goto OPEN_IN_ERROR; - break; - } - - case IDB_SKIPALL: - dlgData.SkipAllOverwrite = TRUE; - case IDB_SKIP: - { - SKIP_OPEN: - - totalDone += op->Size; - SetTFSandPSforSkippedFile(op, lastTransferredFileSize, script, totalDone); - - SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); - if (skip != NULL) - *skip = TRUE; - return TRUE; - } - - case IDCANCEL: - { - CANCEL_OPEN: - - return FALSE; - } - } - } - - DWORD attr = SalGetFileAttributes(op->TargetName); - if (attr != INVALID_FILE_ATTRIBUTES && (attr & (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM))) - { - if (!dlgData.OverwriteHiddenAll && dlgData.CnfrmSHFileOver) // ignore script->OverwriteOlder here; user wants to see that this is a SYSTEM or HIDDEN file even with the option enabled - { - HANDLES(CloseHandle(in)); - in = NULL; - - WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - if (*dlgData.CancelWorker) - goto CANCEL_OPEN; - - if (dlgData.SkipAllSystemOrHidden) - goto SKIP_OPEN; - - int ret = IDCANCEL; - char* data[4]; - data[0] = (char*)&ret; - data[1] = LoadStr(IDS_CONFIRMFILEOVERWRITING); - data[2] = op->TargetName; - data[3] = LoadStr(IDS_WANTOVERWRITESHFILE); - SendMessage(hProgressDlg, WM_USER_DIALOG, 2, (LPARAM)data); - switch (ret) - { - case IDB_ALL: - dlgData.OverwriteHiddenAll = TRUE; - case IDYES: - default: // for safety (to prevent exiting this block with the 'in' handle closed) - { - in = HANDLES_Q(CreateFileUtf8(op->SourceName, GENERIC_READ, - FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, - OPEN_EXISTING, asyncPar->GetOverlappedFlag() | FILE_FLAG_SEQUENTIAL_SCAN, NULL)); - if (in == INVALID_HANDLE_VALUE) - goto OPEN_IN_ERROR; - attr = SalGetFileAttributes(op->TargetName); // refresh attributes in case the user changed them - break; - } - - case IDB_SKIPALL: - dlgData.SkipAllSystemOrHidden = TRUE; - case IDB_SKIP: - goto SKIP_OPEN; - - case IDCANCEL: - goto CANCEL_OPEN; - } - } - } - - BOOL targetCannotOpenForWrite = FALSE; - while (1) - { - if (targetCannotOpenForWrite || mustDeleteFileBeforeOverwrite == 1 /* yes */) - { // the file must be deleted first - BOOL chAttr = ClearReadOnlyAttr(op->TargetName, attr); - - if (!tgtNameCaseCorrected) - { - CorrectCaseOfTgtName(op->TargetName, FALSE, &dataOut); - tgtNameCaseCorrected = TRUE; - } - - if (DeleteFileUtf8(op->TargetName)) - goto OPEN_TGT_FILE; // if it is read-only (clearing the attribute may have failed), it can be deleted only on Samba with "delete readonly" enabled - else // cannot delete either, end with an error... - { - err = GetLastError(); - if (chAttr) - SetFileAttributesUtf8(op->TargetName, attr); - errDeletingFile = TRUE; - goto NORMAL_ERROR; - } - } - else // overwrite the file in place - { - // if we have not yet tested truncating the file to zero, obtain the current file size - CQuadWord origFileSize(0, 0); // file size before truncation - if (mustDeleteFileBeforeOverwrite == 0 /* need test */) - { - out = HANDLES_Q(CreateFileUtf8(op->TargetName, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, - OPEN_EXISTING, 0, NULL)); - if (out != INVALID_HANDLE_VALUE) - { - origFileSize.LoDWord = GetFileSize(out, &origFileSize.HiDWord); - if (origFileSize.LoDWord == INVALID_FILE_SIZE && GetLastError() == NO_ERROR) - origFileSize.Set(0, 0); // error => set the size to zero and test it on another file - HANDLES(CloseHandle(out)); - } - } - - // open the file with ADS removal and truncation to zero - BOOL chAttr = FALSE; - if (attr != INVALID_FILE_ATTRIBUTES && - (attr & (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM))) - { // CREATE_ALWAYS does not play well with read-only, hidden, or system attributes, so drop them if needed - chAttr = TRUE; - SetFileAttributesUtf8(op->TargetName, 0); - } - // GENERIC_READ for 'out' slows asynchronous copying from disk to network (measured 95 MB/s instead of 111 MB/s on Win7 x64 GLAN) - DWORD access = GENERIC_WRITE | (script->CopyAttrs ? GENERIC_READ : 0); - fileAttrs = asyncPar->GetOverlappedFlag() | FILE_FLAG_SEQUENTIAL_SCAN | - (!lossEncryptionAttr && copyAsEncrypted ? FILE_ATTRIBUTE_ENCRYPTED : 0) | // setting attributes during CREATE_ALWAYS works since XP and is the only way to apply Encrypted attribute when the file denies read access - (script->CopyAttrs ? (op->Attr & (FILE_ATTRIBUTE_COMPRESSED | (lossEncryptionAttr ? 0 : FILE_ATTRIBUTE_ENCRYPTED))) : 0); - out = HANDLES_Q(CreateFileUtf8(op->TargetName, access, 0, NULL, CREATE_ALWAYS, fileAttrs, NULL)); - if (out == INVALID_HANDLE_VALUE && fileAttrs != (asyncPar->GetOverlappedFlag() | FILE_FLAG_SEQUENTIAL_SCAN)) // when the target disk cannot create an Encrypted file (observed on NTFS network disk (tested on share from XP) while logged in under a different username than we have in the system (on the current console) - the remote machine has a same-named user without a password, so it cannot be used over the network) - out = HANDLES_Q(CreateFileUtf8(op->TargetName, access, 0, NULL, CREATE_ALWAYS, asyncPar->GetOverlappedFlag() | FILE_FLAG_SEQUENTIAL_SCAN, NULL)); - if (script->CopyAttrs && out == INVALID_HANDLE_VALUE) - { // if read access to the directory is denied (we added it only for setting the Compressed attribute), try opening the file for write only - access = GENERIC_WRITE; - out = HANDLES_Q(CreateFileUtf8(op->TargetName, access, 0, NULL, CREATE_ALWAYS, fileAttrs, NULL)); - if (out == INVALID_HANDLE_VALUE && fileAttrs != (asyncPar->GetOverlappedFlag() | FILE_FLAG_SEQUENTIAL_SCAN)) // when the target disk cannot create an Encrypted file (observed on NTFS network disk (tested on share from XP) while logged in under a different username than we have in the system (on the current console) - the remote machine has a same-named user without a password, so it cannot be used over the network) - out = HANDLES_Q(CreateFileUtf8(op->TargetName, access, 0, NULL, CREATE_ALWAYS, asyncPar->GetOverlappedFlag() | FILE_FLAG_SEQUENTIAL_SCAN, NULL)); - } - if (out == INVALID_HANDLE_VALUE) // target file cannot be opened for writing, so delete it and create it again - { - // handles the situation when a Samba file must be overwritten: - // the file has mode 440+different_owner and sits in a directory where the current user has write access - // (deletion works, but direct overwrite does not (cannot open for writing) - workaround: - // delete and recreate the file) - // (Samba can allow deleting read-only files, which enables deleting them, - // otherwise Windows cannot delete a read-only file and we cannot drop - // the "read-only" attribute because the current user is not the owner) - if (chAttr) - SetFileAttributesUtf8(op->TargetName, attr); - targetCannotOpenForWrite = TRUE; - continue; - } - - // on target paths that support ADS also delete ADS on the target file (CREATE_ALWAYS should remove them, but on home W2K and XP they simply stay; no idea why, W2K and XP in VMWare delete ADS normally) - if (script->TargetPathSupADS && !DeleteAllADS(out, op->TargetName)) - { - HANDLES(CloseHandle(out)); - out = INVALID_HANDLE_VALUE; - if (chAttr) - SetFileAttributesUtf8(op->TargetName, attr); - targetCannotOpenForWrite = TRUE; - continue; - } - - // if we have not yet tested truncating the file to zero, obtain the new file size - if (mustDeleteFileBeforeOverwrite == 0 /* need test */) - { - HANDLES(CloseHandle(out)); - out = HANDLES_Q(CreateFileUtf8(op->TargetName, access, 0, NULL, OPEN_ALWAYS, asyncPar->GetOverlappedFlag() | FILE_FLAG_SEQUENTIAL_SCAN, NULL)); - if (out == INVALID_HANDLE_VALUE) // cannot reopen the target file we just opened, unlikely, try deleting and recreating it - { - targetCannotOpenForWrite = TRUE; - continue; - } - CQuadWord newFileSize(0, 0); // file size after truncation - newFileSize.LoDWord = GetFileSize(out, &newFileSize.HiDWord); - if ((newFileSize.LoDWord != INVALID_FILE_SIZE || GetLastError() == NO_ERROR) && // we have the new size - newFileSize == CQuadWord(0, 0)) // file really has 0 bytes - { - if (origFileSize != CQuadWord(0, 0)) // truncation can only be tested on a non-zero file - mustDeleteFileBeforeOverwrite = 2; /* no */ // success (not a SNAP server - NSA drive, truncation does not work there) - } - else - { - HANDLES(CloseHandle(out)); - out = INVALID_HANDLE_VALUE; - mustDeleteFileBeforeOverwrite = 1 /* yes */; // on error or when the size is non-zero, play it safe... - continue; - } - } - - if (script->CopyAttrs || !lossEncryptionAttr && copyAsEncrypted) - { - encryptionNotSupported = FALSE; - SetCompressAndEncryptedAttrs(op->TargetName, (!lossEncryptionAttr && copyAsEncrypted ? FILE_ATTRIBUTE_ENCRYPTED : 0) | (script->CopyAttrs ? (op->Attr & (FILE_ATTRIBUTE_COMPRESSED | (lossEncryptionAttr ? 0 : FILE_ATTRIBUTE_ENCRYPTED))) : 0), - &out, script->CopyAttrs, &encryptionNotSupported, asyncPar); - if (encryptionNotSupported) // unable to apply the Encrypted attribute, ask the user what to do... - { - if (dlgData.FileOutLossEncrAll) - lossEncryptionAttr = TRUE; - else - { - WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - if (*dlgData.CancelWorker) - goto CANCEL_ENCNOTSUP; - - if (dlgData.SkipAllFileOutLossEncr) - goto SKIP_ENCNOTSUP; - - int ret; - ret = IDCANCEL; - char* data[4]; - data[0] = (char*)&ret; - data[1] = (char*)TRUE; - data[2] = op->TargetName; - data[3] = (char*)(INT_PTR)isMove; - SendMessage(hProgressDlg, WM_USER_DIALOG, 12, (LPARAM)data); - switch (ret) - { - case IDB_ALL: - dlgData.FileOutLossEncrAll = TRUE; // the break; is intentionally missing here - case IDYES: - lossEncryptionAttr = TRUE; - break; - - case IDB_SKIPALL: - dlgData.SkipAllFileOutLossEncr = TRUE; - case IDB_SKIP: - goto SKIP_ENCNOTSUP; - - case IDCANCEL: - goto CANCEL_ENCNOTSUP; - } - } - } - } - } - break; - } - - goto COPY; - } - else // regular error - { - NORMAL_ERROR: - - WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - if (*dlgData.CancelWorker) - goto CANCEL_OPEN2; - - if (dlgData.SkipAllFileOpenOut) - goto SKIP_OPEN_OUT; - - int ret; - ret = IDCANCEL; - char* data[4]; - data[0] = (char*)&ret; - data[1] = LoadStr(errDeletingFile ? IDS_ERRORDELETINGFILE : IDS_ERROROPENINGFILE); - data[2] = op->TargetName; - data[3] = GetErrorText(err); - SendMessage(hProgressDlg, WM_USER_DIALOG, 0, (LPARAM)data); - switch (ret) - { - case IDRETRY: - break; - - case IDB_SKIPALL: - dlgData.SkipAllFileOpenOut = TRUE; - case IDB_SKIP: - goto SKIP_OPEN_OUT; - - case IDCANCEL: - goto CANCEL_OPEN2; - } - } - } - } - } - } - else - { - OPEN_IN_ERROR: - - DWORD err = GetLastError(); - if (invalidSrcName) - err = ERROR_INVALID_NAME; - if (asyncPar->Failed()) - err = ERROR_NOT_ENOUGH_MEMORY; // cannot create the synchronization event = lack of resources (will probably never happens, so we do not bother) - WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - if (*dlgData.CancelWorker) - return FALSE; - - if (dlgData.SkipAllFileOpenIn) - goto SKIP_OPEN_IN; - - int ret; - ret = IDCANCEL; - char* data[4]; - data[0] = (char*)&ret; - data[1] = LoadStr(IDS_ERROROPENINGFILE); - data[2] = op->SourceName; - data[3] = GetErrorText(err); - SendMessage(hProgressDlg, WM_USER_DIALOG, 0, (LPARAM)data); - switch (ret) - { - case IDRETRY: - break; - - case IDB_SKIPALL: - dlgData.SkipAllFileOpenIn = TRUE; - case IDB_SKIP: - { - SKIP_OPEN_IN: - - totalDone += op->Size; - SetTFSandPSforSkippedFile(op, lastTransferredFileSize, script, totalDone); - - SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); - if (skip != NULL) - *skip = TRUE; - return TRUE; - } - - case IDCANCEL: - return FALSE; - } - } - } -} - -BOOL DoMoveFile(COperation* op, HWND hProgressDlg, void* buffer, - COperations* script, CQuadWord& totalDone, BOOL dir, - DWORD clearReadonlyMask, BOOL* novellRenamePatch, BOOL lantasticCheck, - int& mustDeleteFileBeforeOverwrite, int& allocWholeFileOnStart, - CProgressDlgData& dlgData, BOOL copyADS, BOOL copyAsEncrypted, - BOOL* setDirTimeAfterMove, CAsyncCopyParams*& asyncPar, - BOOL ignInvalidName) -{ - char log_buffer[1024]; - _snprintf_s(log_buffer, sizeof(log_buffer), _TRUNCATE, "DoMoveFile: Source='%s', Target='%s'", op->SourceName ? op->SourceName : "NULL", op->TargetName ? op->TargetName : "NULL"); - OutputDebugStringA(log_buffer); - - if (script->CopyAttrs && copyAsEncrypted) - TRACE_E("DoMoveFile(): unexpected parameter value: copyAsEncrypted is TRUE when script->CopyAttrs is TRUE!"); - - // if the path ends with a space/dot, it is invalid and we must not move it, - // MoveFile would trim the spaces/dots and move a different file or under a different name, - // directories fare better: appending a backslash helps there, we block the move - // only when a new directory name would be invalid (when moving under the old - // name, 'ignInvalidName' is TRUE) - BOOL invalidName = FileNameIsInvalid(op->SourceName, TRUE, dir) || - FileNameIsInvalid(op->TargetName, TRUE, dir && ignInvalidName); - - if (!copyAsEncrypted && !script->SameRootButDiffVolume && HasTheSameRootPath(op->SourceName, op->TargetName)) - { - // if the path ends with a space or dot, we must append '\\', otherwise GetNamedSecurityInfo, - // GetDirTime, SetFileAttributes, and MoveFile trim the spaces/dots and operate on a different path - const char* sourceNameMvDir = op->SourceName; - char sourceNameMvDirCopy[3 * MAX_PATH]; - MakeCopyWithBackslashIfNeeded(sourceNameMvDir, sourceNameMvDirCopy); - const char* targetNameMvDir = op->TargetName; - char targetNameMvDirCopy[3 * MAX_PATH]; - MakeCopyWithBackslashIfNeeded(targetNameMvDir, targetNameMvDirCopy); - - int autoRetryAttempts = 0; - CSrcSecurity srcSecurity; - BOOL srcSecurityErr = FALSE; - if (!invalidName && script->CopySecurity) // should we copy NTFS security permissions? - { - CStrP sourceNameMvDirW(ConvertAllocUtf8ToWide(sourceNameMvDir, -1)); - if (sourceNameMvDirW != NULL) - { - srcSecurity.SrcError = GetNamedSecurityInfoW(sourceNameMvDirW, SE_FILE_OBJECT, - DACL_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION, - &srcSecurity.SrcOwner, &srcSecurity.SrcGroup, &srcSecurity.SrcDACL, - NULL, &srcSecurity.SrcSD); - } - else - { - srcSecurity.SrcError = ERROR_NO_UNICODE_TRANSLATION; - } - if (srcSecurity.SrcError != ERROR_SUCCESS) // failed to read security info from the source file -> nothing to apply on the target - { - srcSecurityErr = TRUE; - WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - if (*dlgData.CancelWorker) - return FALSE; - - int ret; - ret = IDCANCEL; - if (dlgData.IgnoreAllCopyPermErr) - ret = IDB_IGNORE; - else - { - char* data[4]; - data[0] = (char*)&ret; - data[1] = op->SourceName; - data[2] = op->TargetName; - data[3] = (char*)(DWORD_PTR)srcSecurity.SrcError; - SendMessage(hProgressDlg, WM_USER_DIALOG, 10, (LPARAM)data); - } - switch (ret) - { - case IDB_IGNOREALL: - dlgData.IgnoreAllCopyPermErr = TRUE; // the break; is intentionally missing here - case IDB_IGNORE: - break; - - case IDCANCEL: - return FALSE; - } - } - } - FILETIME dirTimeModified; - BOOL dirTimeModifiedIsValid = FALSE; - if (!invalidName && dir && !*novellRenamePatch && *setDirTimeAfterMove != 2 /* no */) // the issue apparently does not apply to Novell Netware, so ignore it there (affects e.g. Samba) - dirTimeModifiedIsValid = GetDirTime(sourceNameMvDir, &dirTimeModified); - while (1) - { - if (!invalidName && !*novellRenamePatch && SalMoveFile(sourceNameMvDir, targetNameMvDir)) - { - if (script->CopyAttrs && (op->Attr & FILE_ATTRIBUTE_ARCHIVE) == 0) // Archive attribute was not set, MoveFile turned it on, clear it again - SetFileAttributesUtf8(targetNameMvDir, op->Attr); // leave without handling or retry, not important (it normally toggles chaotically) - - OPERATION_DONE: - - if (script->CopyAttrs) // check whether the source file attributes were preserved - { - DWORD curAttrs; - curAttrs = SalGetFileAttributes(targetNameMvDir); - if (curAttrs == INVALID_FILE_ATTRIBUTES || (curAttrs & DISPLAYED_ATTRIBUTES) != (op->Attr & DISPLAYED_ATTRIBUTES)) - { // attributes probably were not preserved, warn the user - WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - if (*dlgData.CancelWorker) - goto MOVE_ERROR_2; - - int ret; - ret = IDCANCEL; - if (dlgData.IgnoreAllSetAttrsErr) - ret = IDB_IGNORE; - else - { - char* data[4]; - data[0] = (char*)&ret; - data[1] = op->TargetName; - data[2] = (char*)(DWORD_PTR)(op->Attr & DISPLAYED_ATTRIBUTES); - data[3] = (char*)(DWORD_PTR)(curAttrs == INVALID_FILE_ATTRIBUTES ? 0 : (curAttrs & DISPLAYED_ATTRIBUTES)); - SendMessage(hProgressDlg, WM_USER_DIALOG, 9, (LPARAM)data); - } - switch (ret) - { - case IDB_IGNOREALL: - dlgData.IgnoreAllSetAttrsErr = TRUE; // the break; is intentionally missing here - case IDB_IGNORE: - break; - - case IDCANCEL: - { - MOVE_ERROR_2: - - return FALSE; // the file was moved to the target + cancel occurred; but we would rather not move it back, nobody should mind much - } - } - } - } - - if (script->CopySecurity && !srcSecurityErr) // should we copy NTFS security permissions? - { - DWORD err; - if (!DoCopySecurity(sourceNameMvDir, targetNameMvDir, &err, &srcSecurity)) - { - WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - if (*dlgData.CancelWorker) - goto MOVE_ERROR_2; - - int ret; - ret = IDCANCEL; - if (dlgData.IgnoreAllCopyPermErr) - ret = IDB_IGNORE; - else - { - char* data[4]; - data[0] = (char*)&ret; - data[1] = op->SourceName; - data[2] = op->TargetName; - data[3] = (char*)(DWORD_PTR)err; - SendMessage(hProgressDlg, WM_USER_DIALOG, 10, (LPARAM)data); - } - switch (ret) - { - case IDB_IGNOREALL: - dlgData.IgnoreAllCopyPermErr = TRUE; // the break; is intentionally missing here - case IDB_IGNORE: - break; - - case IDCANCEL: - goto MOVE_ERROR_2; - } - } - } - - if (dir && dirTimeModifiedIsValid && *setDirTimeAfterMove != 2 /* no */) - { - FILETIME movedDirTimeModified; - if (GetDirTime(targetNameMvDir, &movedDirTimeModified)) - { - if (CompareFileTime(&dirTimeModified, &movedDirTimeModified) == 0) - { - if (*setDirTimeAfterMove == 0 /* need test */) - *setDirTimeAfterMove = 2 /* no */; - } - else - { - if (*setDirTimeAfterMove == 0 /* need test */) - *setDirTimeAfterMove = 1 /* yes */; - DoCopyDirTime(hProgressDlg, targetNameMvDir, &dirTimeModified, dlgData, TRUE); // ignore any failure, this is just a hack (we already ignore time read errors from the directory); MoveFile should not change times - } - } - } - - script->AddBytesToSpeedMetersAndTFSandPS((DWORD)op->Size.Value, TRUE, 0, NULL, MAX_OP_FILESIZE); - - totalDone += op->Size; - SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); - return TRUE; - } - else - { - DWORD err = GetLastError(); - if (invalidName) - err = ERROR_INVALID_NAME; - // Novell patch - before calling MoveFile we need to drop the read-only attribute - if (!invalidName && *novellRenamePatch || err == ERROR_ACCESS_DENIED) - { - DWORD attr = SalGetFileAttributes(sourceNameMvDir); - BOOL setAttr = ClearReadOnlyAttr(sourceNameMvDir, attr); - if (SalMoveFile(sourceNameMvDir, targetNameMvDir)) - { - if (!*novellRenamePatch) - *novellRenamePatch = TRUE; // the next operations will go straight through here - if (setAttr || script->CopyAttrs && (attr & FILE_ATTRIBUTE_ARCHIVE) == 0) - { - CStrP targetNameW(ConvertAllocUtf8ToWide(targetNameMvDir, -1)); - if (targetNameW != NULL) - SetFileAttributesW(targetNameW, attr); - } - - goto OPERATION_DONE; - } - err = GetLastError(); - if (setAttr) - { - CStrP sourceNameW(ConvertAllocUtf8ToWide(sourceNameMvDir, -1)); - if (sourceNameW != NULL) - SetFileAttributesW(sourceNameW, attr); - } - } - - if (StrICmp(op->SourceName, op->TargetName) != 0 && // provided this is not just a change of case - (err == ERROR_FILE_EXISTS || // verify whether this is only overwriting the DOS name of the file/directory - err == ERROR_ALREADY_EXISTS) && - targetNameMvDir == op->TargetName) // no invalid names are allowed here - { - WIN32_FIND_DATAW findData; - CStrP targetNameW(ConvertAllocUtf8ToWide(op->TargetName, -1)); - HANDLE find = targetNameW != NULL ? HANDLES_Q(FindFirstFileW(targetNameW, &findData)) : INVALID_HANDLE_VALUE; - if (find != INVALID_HANDLE_VALUE) - { - HANDLES(FindClose(find)); - const char* tgtName = SalPathFindFileName(op->TargetName); - char altName[MAX_PATH]; - char fullName[MAX_PATH]; - if (ConvertWideToUtf8(findData.cAlternateFileName, -1, altName, _countof(altName)) == 0) - altName[0] = 0; - if (ConvertWideToUtf8(findData.cFileName, -1, fullName, _countof(fullName)) == 0) - fullName[0] = 0; - if (StrICmp(tgtName, altName) == 0 && // match only on the DOS name - StrICmp(tgtName, fullName) != 0) // (the full name is different) - { - // rename ("tidy up") the file/directory with the conflicting DOS name to a temporary 8.3 name (does not need an extra DOS name) - char tmpName[MAX_PATH + 20]; - if (strlen(op->TargetName) >= _countof(tmpName)) - { - TRACE_E("DoMoveFile(): target path too long for DOS-name collision workaround: " << op->TargetName); - } - else - { - lstrcpyn(tmpName, op->TargetName, _countof(tmpName)); - CutDirectory(tmpName); - SalPathAddBackslash(tmpName, _countof(tmpName)); - char* tmpNamePart = tmpName + strlen(tmpName); - char origFullName[MAX_PATH + 20]; - if (SalPathAppend(tmpName, fullName, _countof(tmpName))) - { - strcpy(origFullName, tmpName); - DWORD num = (GetTickCount() / 10) % 0xFFF; - DWORD origFullNameAttr = SalGetFileAttributes(origFullName); - while (1) - { - sprintf(tmpNamePart, "sal%03X", num++); - if (SalMoveFile(origFullName, tmpName)) - break; - DWORD e = GetLastError(); - if (e != ERROR_FILE_EXISTS && e != ERROR_ALREADY_EXISTS) - { - tmpName[0] = 0; - break; - } - } - if (tmpName[0] != 0) // if we managed to "tidy up" the conflicting file/directory, try moving it again - { // then restore the original name of the "tidied" file/directory - BOOL moveDone = SalMoveFile(sourceNameMvDir, op->TargetName); - if (script->CopyAttrs && (op->Attr & FILE_ATTRIBUTE_ARCHIVE) == 0) // the Archive attribute was not set; MoveFile turned it on, clear it again - { - CStrP targetNameW2(ConvertAllocUtf8ToWide(op->TargetName, -1)); - if (targetNameW2 != NULL) - SetFileAttributesW(targetNameW2, op->Attr); // leave without handling or retry, not important (it normally toggles chaotically) - } - if (!SalMoveFile(tmpName, origFullName)) - { // this apparently can happen; inexplicably, Windows creates a file named origFullName instead of op->TargetName (the DOS name) - TRACE_I("DoMoveFile(): Unexpected situation: unable to rename file/dir from tmp-name to original long file name! " << origFullName); - if (moveDone) - { - if (SalMoveFile(op->TargetName, sourceNameMvDir)) - moveDone = FALSE; - if (!SalMoveFile(tmpName, origFullName)) - TRACE_E("DoMoveFile(): Fatal unexpected situation: unable to rename file/dir from tmp-name to original long file name! " << origFullName); - } - } - else - { - if ((origFullNameAttr & FILE_ATTRIBUTE_ARCHIVE) == 0) - SetFileAttributesUtf8(origFullName, origFullNameAttr); // leave without handling or retry, not important (it normally toggles chaotically) - } - - if (moveDone) - goto OPERATION_DONE; - } - } - else - TRACE_E("DoMoveFile(): Original full file/dir name is too long, unable to bypass only-dos-name-overwrite problem!"); - } - } - } - } - - if ((err == ERROR_ALREADY_EXISTS || // theoretically can happen for directories; prevent that (overwrite prompt is only for files) - err == ERROR_FILE_EXISTS) && - !dir && StrICmp(op->SourceName, op->TargetName) != 0 && - sourceNameMvDir == op->SourceName && targetNameMvDir == op->TargetName) // no invalid names allowed here (files only, and their names are validated) - { - HANDLE in, out; - in = HANDLES_Q(CreateFileUtf8(op->SourceName, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, - OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)); - if (in == INVALID_HANDLE_VALUE) - { - err = GetLastError(); - goto NORMAL_ERROR; - } - out = HANDLES_Q(CreateFileUtf8(op->TargetName, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, - OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)); - if (out == INVALID_HANDLE_VALUE) - { - err = GetLastError(); - HANDLES(CloseHandle(in)); - goto NORMAL_ERROR; - } - - if (!dlgData.OverwriteAll && (dlgData.CnfrmFileOver || script->OverwriteOlder)) - { - char sAttr[101], tAttr[101]; - BOOL getTimeFailed; - getTimeFailed = FALSE; - FILETIME sFileTime, tFileTime; - GetFileOverwriteInfo(sAttr, _countof(sAttr), in, op->SourceName, &sFileTime, &getTimeFailed); - GetFileOverwriteInfo(tAttr, _countof(tAttr), out, op->TargetName, &tFileTime, &getTimeFailed); - HANDLES(CloseHandle(in)); - HANDLES(CloseHandle(out)); - - WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - if (*dlgData.CancelWorker) - goto CANCEL_OPEN; - - if (dir) - TRACE_E("Error in script."); - - if (dlgData.SkipAllOverwrite) - goto SKIP_OPEN; - - int ret; - ret = IDCANCEL; - - if (!getTimeFailed && script->OverwriteOlder) // option from the Copy/Move dialog - { - // trim timestamps to seconds (different file systems store times with different precision, leading to "differences" even between "matching" times) - *(unsigned __int64*)&sFileTime = *(unsigned __int64*)&sFileTime - (*(unsigned __int64*)&sFileTime % 10000000); - *(unsigned __int64*)&tFileTime = *(unsigned __int64*)&tFileTime - (*(unsigned __int64*)&tFileTime % 10000000); - - if (CompareFileTime(&sFileTime, &tFileTime) > 0) - ret = IDYES; // older ones should be overwritten without asking - else - ret = IDB_SKIP; // skip the other existing ones - } - else - { - // display the prompt - char* data[5]; - data[0] = (char*)&ret; - data[1] = op->TargetName; - data[2] = tAttr; - data[3] = op->SourceName; - data[4] = sAttr; - SendMessage(hProgressDlg, WM_USER_DIALOG, 1, (LPARAM)data); - } - switch (ret) - { - case IDB_ALL: - dlgData.OverwriteAll = TRUE; - case IDYES: - break; - - case IDB_SKIPALL: - dlgData.SkipAllOverwrite = TRUE; - case IDB_SKIP: - { - SKIP_OPEN: - - totalDone += op->Size; - script->SetProgressSize(totalDone); - SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); - return TRUE; - } - - case IDCANCEL: - { - CANCEL_OPEN: - - return FALSE; - } - } - } - else - { - HANDLES(CloseHandle(in)); - HANDLES(CloseHandle(out)); - } - - DWORD attr = SalGetFileAttributes(op->TargetName); - if (attr != INVALID_FILE_ATTRIBUTES && (attr & (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM))) - { - if (!dlgData.OverwriteHiddenAll && dlgData.CnfrmSHFileOver) // ignore script->OverwriteOlder here; user wants to see that this is a SYSTEM or HIDDEN file even with the option enabled - { - WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - if (*dlgData.CancelWorker) - goto CANCEL_OPEN; - - if (dir) - TRACE_E("Error in script."); - - if (dlgData.SkipAllSystemOrHidden) - goto SKIP_OPEN; - - int ret = IDCANCEL; - char* data[4]; - data[0] = (char*)&ret; - data[1] = LoadStr(IDS_CONFIRMFILEOVERWRITING); - data[2] = op->TargetName; - data[3] = LoadStr(IDS_WANTOVERWRITESHFILE); - SendMessage(hProgressDlg, WM_USER_DIALOG, 2, (LPARAM)data); - switch (ret) - { - case IDB_ALL: - dlgData.OverwriteHiddenAll = TRUE; - case IDYES: - break; - - case IDB_SKIPALL: - dlgData.SkipAllSystemOrHidden = TRUE; - case IDB_SKIP: - goto SKIP_OPEN; - - case IDCANCEL: - goto CANCEL_OPEN; - } - attr = SalGetFileAttributes(op->TargetName); // may also fail (returns INVALID_FILE_ATTRIBUTES) - } - } - - ClearReadOnlyAttr(op->TargetName, attr); // make sure it can be deleted ... - while (1) - { - if (DeleteFileUtf8(op->TargetName)) - break; - else - { - DWORD err2 = GetLastError(); - if (err2 == ERROR_FILE_NOT_FOUND) - break; // if the user already deleted the file manually, everything is fine - - WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - if (*dlgData.CancelWorker) - return FALSE; - - if (dir) - TRACE_E("Error in script."); - - if (dlgData.SkipAllOverwriteErr) - goto SKIP_OVERWRITE_ERROR; - - int ret; - ret = IDCANCEL; - char* data[4]; - data[0] = (char*)&ret; - data[1] = LoadStr(IDS_ERROROVERWRITINGFILE); - data[2] = op->TargetName; - data[3] = GetErrorText(err2); - SendMessage(hProgressDlg, WM_USER_DIALOG, 0, (LPARAM)data); - switch (ret) - { - case IDRETRY: - break; - - case IDB_SKIPALL: - dlgData.SkipAllOverwriteErr = TRUE; - case IDB_SKIP: - { - SKIP_OVERWRITE_ERROR: - - totalDone += op->Size; - script->SetProgressSize(totalDone); - SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); - return TRUE; - } - - case IDCANCEL: - return FALSE; - } - } - } - } - else - { - NORMAL_ERROR: - - WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - if (*dlgData.CancelWorker) - return FALSE; - - if (dlgData.SkipAllMoveErrors) - goto SKIP_MOVE_ERROR; - - if (err == ERROR_SHARING_VIOLATION && ++autoRetryAttempts <= 2) - { // auto-retry added to handle move errors while directory icons are being read (SHGetFileInfo running in parallel with MoveFile) - Sleep(100); // wait a moment before the next attempt - } - else - { - int ret; - ret = IDCANCEL; - char* data[4]; - data[0] = (char*)&ret; - data[1] = op->SourceName; - data[2] = op->TargetName; - data[3] = GetErrorText(err); - SendMessage(hProgressDlg, WM_USER_DIALOG, dir ? 4 : 3, (LPARAM)data); - switch (ret) - { - case IDRETRY: - break; - - case IDB_SKIPALL: - dlgData.SkipAllMoveErrors = TRUE; - case IDB_SKIP: - { - SKIP_MOVE_ERROR: - - totalDone += op->Size; - script->SetProgressSize(totalDone); - SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); - return TRUE; - } - - case IDCANCEL: - return FALSE; - } - } - } - } - } - } - else - { - if (dir) - { - TRACE_E("Error in script."); - return FALSE; - } - - BOOL skip; - BOOL notError = DoCopyFile(op, hProgressDlg, buffer, script, totalDone, - clearReadonlyMask, &skip, lantasticCheck, - mustDeleteFileBeforeOverwrite, allocWholeFileOnStart, - dlgData, copyADS, copyAsEncrypted, TRUE, asyncPar); - if (notError && !skip) // still need to clean up the file from the source - { - ClearReadOnlyAttr(op->SourceName); // ensure it can be deleted - while (1) - { - if (DeleteFileUtf8(op->SourceName)) - break; - { - DWORD err = GetLastError(); - - WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - if (*dlgData.CancelWorker) - return FALSE; - - if (dlgData.SkipAllDeleteErr) - return TRUE; - - int ret = IDCANCEL; - char* data[4]; - data[0] = (char*)&ret; - data[1] = LoadStr(IDS_ERRORDELETINGFILE); - data[2] = op->SourceName; - data[3] = GetErrorText(err); - SendMessage(hProgressDlg, WM_USER_DIALOG, 0, (LPARAM)data); - switch (ret) - { - case IDRETRY: - break; - case IDB_SKIPALL: - dlgData.SkipAllDeleteErr = TRUE; - case IDB_SKIP: - return TRUE; - case IDCANCEL: - return FALSE; - } - } - } - } - return notError; - } -} - -BOOL DoDeleteFile(HWND hProgressDlg, char* name, const CQuadWord& size, COperations* script, - CQuadWord& totalDone, DWORD attr, CProgressDlgData& dlgData) -{ - // if the path ends with a space/dot it is invalid and we must not delete it, - // DeleteFile would trim the spaces/dots and remove a different file - BOOL invalidName = FileNameIsInvalid(name, TRUE); - - DWORD err; - while (1) - { - if (!invalidName) - { - if (attr & (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM)) - { - if (!dlgData.DeleteHiddenAll && dlgData.CnfrmSHFileDel) - { - WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - if (*dlgData.CancelWorker) - return FALSE; - - if (dlgData.SkipAllSystemOrHidden) - goto SKIP_DELETE; - - int ret = IDCANCEL; - char* data[4]; - data[0] = (char*)&ret; - data[1] = LoadStr(IDS_CONFIRMSHFILEDELETE); - data[2] = name; - data[3] = LoadStr(IDS_DELETESHFILE); - SendMessage(hProgressDlg, WM_USER_DIALOG, 2, (LPARAM)data); - switch (ret) - { - case IDB_ALL: - dlgData.DeleteHiddenAll = TRUE; - case IDYES: - break; - - case IDB_SKIPALL: - dlgData.SkipAllSystemOrHidden = TRUE; - case IDB_SKIP: - goto SKIP_DELETE; - - case IDCANCEL: - return FALSE; - } - } - } - ClearReadOnlyAttr(name, attr); // ensure it can be deleted - - err = ERROR_SUCCESS; - BOOL useRecycleBin; - switch (dlgData.UseRecycleBin) - { - case 0: - useRecycleBin = script->CanUseRecycleBin && script->InvertRecycleBin; - break; - case 1: - useRecycleBin = script->CanUseRecycleBin && !script->InvertRecycleBin; - break; - case 2: - { - if (!script->CanUseRecycleBin || script->InvertRecycleBin) - useRecycleBin = FALSE; - else - { - const char* fileName = strrchr(name, '\\'); - if (fileName != NULL) // "always true" - { - fileName++; - int tmpLen = lstrlen(fileName); - const char* ext = fileName + tmpLen; - // while (ext > fileName && *ext != '.') ext--; - while (--ext >= fileName && *ext != '.') - ; - // if (ext == fileName) // ".cvspass" is treated as an extension in Windows ... - if (ext < fileName) - ext = fileName + tmpLen; - else - ext++; - useRecycleBin = dlgData.AgreeRecycleMasks(fileName, ext); - } - else - { - useRecycleBin = TRUE; // choose the safe option on error and delete via the Recycle Bin - TRACE_E("DoDeleteFile(): unexpected situation: filename does not contain backslash: " << name); - } - } - break; - } - } - if (useRecycleBin) - { - if (!PathContainsValidComponents((char*)name, FALSE)) - { - err = ERROR_INVALID_NAME; - } - else - { - CStrP nameW(ConvertAllocUtf8ToWide(name, -1)); - if (nameW == NULL) - { - err = ERROR_NO_UNICODE_TRANSLATION; - } - else - { - int nameLen = lstrlenW(nameW); - WCHAR* nameListW = (WCHAR*)malloc((nameLen + 2) * sizeof(WCHAR)); - if (nameListW == NULL) - { - err = ERROR_NOT_ENOUGH_MEMORY; - } - else - { - memcpy(nameListW, nameW, (nameLen + 1) * sizeof(WCHAR)); - nameListW[nameLen + 1] = 0; // double null - - CShellExecuteWnd shellExecuteWnd; - SHFILEOPSTRUCTW opCode; - memset(&opCode, 0, sizeof(opCode)); - - opCode.hwnd = shellExecuteWnd.Create(hProgressDlg, "SEW: DoDeleteFile"); - - opCode.wFunc = FO_DELETE; - opCode.pFrom = nameListW; - opCode.fFlags = FOF_ALLOWUNDO | FOF_SILENT | FOF_NOCONFIRMATION; - opCode.lpszProgressTitle = L""; - err = SHFileOperationW(&opCode); - free(nameListW); - } - } - } - } - else - { - if (DeleteFileUtf8(name) == 0) - err = GetLastError(); - } - } - else - { - err = ERROR_INVALID_NAME; - } - if (err == ERROR_SUCCESS) - { - totalDone += size; - SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); - return TRUE; - } - else - { - WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - if (*dlgData.CancelWorker) - return FALSE; - - if (dlgData.SkipAllDeleteErr) - goto SKIP_DELETE; - - int ret; - ret = IDCANCEL; - char* data[4]; - data[0] = (char*)&ret; - data[1] = LoadStr(IDS_ERRORDELETINGFILE); - data[2] = name; - data[3] = GetErrorText(err); - SendMessage(hProgressDlg, WM_USER_DIALOG, 0, (LPARAM)data); - switch (ret) - { - case IDRETRY: - break; - - case IDB_SKIPALL: - dlgData.SkipAllDeleteErr = TRUE; - case IDB_SKIP: - { - SKIP_DELETE: - - totalDone += size; - SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); - return TRUE; - } - - case IDCANCEL: - return FALSE; - } - } - if (!invalidName) - { - DWORD attr2 = SalGetFileAttributes(name); // get the current attribute state - if (attr2 != INVALID_FILE_ATTRIBUTES) - attr = attr2; - } - } -} - -BOOL SalCreateDirectoryEx(const char* name, DWORD* err) -{ - if (err != NULL) - *err = 0; - // if the name ends with a space/dot we must append '\\', otherwise CreateDirectory - // quietly trims the trailing spaces/dots and creates a different directory - const char* nameCrDir = name; - char nameCrDirBuf[3 * MAX_PATH]; - MakeCopyWithBackslashIfNeeded(nameCrDir, nameCrDirBuf); - CStrP nameCrDirW(ConvertAllocUtf8ToWide(nameCrDir, -1)); - if (nameCrDirW == NULL) - { - if (err != NULL) - *err = ERROR_NO_UNICODE_TRANSLATION; - SetLastError(ERROR_NO_UNICODE_TRANSLATION); - return FALSE; - } - if (CreateDirectoryW(nameCrDirW, NULL)) - return TRUE; - else - { - DWORD errLoc = GetLastError(); - if (name == nameCrDir && // a name ending with a space/dot cannot collide with a DOS name - (errLoc == ERROR_FILE_EXISTS || // check whether this is only overwriting the file's DOS name - errLoc == ERROR_ALREADY_EXISTS)) - { - WIN32_FIND_DATAW data; - CStrP nameW(ConvertAllocUtf8ToWide(name, -1)); - HANDLE find = nameW != NULL ? HANDLES_Q(FindFirstFileW(nameW, &data)) : INVALID_HANDLE_VALUE; - if (find != INVALID_HANDLE_VALUE) - { - HANDLES(FindClose(find)); - const char* tgtName = SalPathFindFileName(name); - char altName[MAX_PATH]; - char fullName[MAX_PATH]; - if (ConvertWideToUtf8(data.cAlternateFileName, -1, altName, _countof(altName)) == 0) - altName[0] = 0; - if (ConvertWideToUtf8(data.cFileName, -1, fullName, _countof(fullName)) == 0) - fullName[0] = 0; - if (StrICmp(tgtName, altName) == 0 && // match only for the DOS name - StrICmp(tgtName, fullName) != 0) // (the full name differs) - { - // rename ("tidy up") the file/directory whose DOS name conflicts to a temporary 8.3 name (no extra DOS name needed) - char tmpName[MAX_PATH + 20]; - if (strlen(name) >= _countof(tmpName)) - { - TRACE_E("SalCreateDirectoryEx(): path too long for DOS-name collision workaround: " << name); - } - else - { - lstrcpyn(tmpName, name, _countof(tmpName)); - CutDirectory(tmpName); - SalPathAddBackslash(tmpName, _countof(tmpName)); - char* tmpNamePart = tmpName + strlen(tmpName); - char origFullName[MAX_PATH + 20]; - if (SalPathAppend(tmpName, fullName, _countof(tmpName))) - { - strcpy(origFullName, tmpName); - DWORD num = (GetTickCount() / 10) % 0xFFF; - DWORD origFullNameAttr = SalGetFileAttributes(origFullName); - while (1) - { - sprintf(tmpNamePart, "sal%03X", num++); - if (SalMoveFile(origFullName, tmpName)) - break; - DWORD e = GetLastError(); - if (e != ERROR_FILE_EXISTS && e != ERROR_ALREADY_EXISTS) - { - tmpName[0] = 0; - break; - } - } - if (tmpName[0] != 0) // if we managed to "tidy up" the conflicting file, retry the move - { // and then restore the original name of the "tidied" file - BOOL createDirDone = nameW != NULL ? CreateDirectoryW(nameW, NULL) : FALSE; - if (!SalMoveFile(tmpName, origFullName)) - { // this can apparently happen: inexplicably Windows creates a file named origFullName instead of name (the DOS name) - TRACE_I("Unexpected situation: unable to rename file from tmp-name to original long file name! " << origFullName); - if (createDirDone) - { - if (nameW != NULL && RemoveDirectoryW(nameW)) - createDirDone = FALSE; - if (!SalMoveFile(tmpName, origFullName)) - TRACE_E("Fatal unexpected situation: unable to rename file from tmp-name to original long file name! " << origFullName); - } - } - else - { - if ((origFullNameAttr & FILE_ATTRIBUTE_ARCHIVE) == 0) - { - CStrP origFullNameW(ConvertAllocUtf8ToWide(origFullName, -1)); - if (origFullNameW != NULL) - SetFileAttributesW(origFullNameW, origFullNameAttr); // leave it without extra handling or retries; not important (normally toggles unpredictably) - } - } - - if (createDirDone) - return TRUE; - } - } - else - TRACE_E("Original full file name is too long, unable to bypass only-dos-name-overwrite problem!"); - } - } - } - } - if (err != NULL) - *err = errLoc; - } - return FALSE; -} - -BOOL GetDirTime(const char* dirName, FILETIME* ftModified) -{ - HANDLE dir; - CStrP dirNameW(ConvertAllocUtf8ToWide(dirName, -1)); - dir = dirNameW != NULL - ? HANDLES_Q(CreateFileW(dirNameW, GENERIC_READ, - FILE_SHARE_READ | FILE_SHARE_WRITE, - NULL, OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS, - NULL)) - : INVALID_HANDLE_VALUE; - if (dir != INVALID_HANDLE_VALUE) - { - BOOL ret = GetFileTime(dir, NULL /*ftCreated*/, NULL /*ftAccessed*/, ftModified); - HANDLES(CloseHandle(dir)); - return ret; - } - return FALSE; -} - -BOOL DoCopyDirTime(HWND hProgressDlg, const char* targetName, FILETIME* modified, CProgressDlgData& dlgData, BOOL quiet) -{ - // if the path ends with a space/dot, we must append '\\', otherwise CreateFile - // trims the spaces/dots and works with a different path - const char* targetNameCrFile = targetName; - char targetNameCrFileCopy[3 * MAX_PATH]; - MakeCopyWithBackslashIfNeeded(targetNameCrFile, targetNameCrFileCopy); - CStrP targetNameW(ConvertAllocUtf8ToWide(targetNameCrFile, -1)); - - BOOL showError = !quiet; - DWORD error = NO_ERROR; - DWORD attr = targetNameW != NULL ? GetFileAttributesW(targetNameW) : INVALID_FILE_ATTRIBUTES; - BOOL setAttr = FALSE; - if (attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_READONLY)) - { - if (targetNameW != NULL) - SetFileAttributesW(targetNameW, attr & ~FILE_ATTRIBUTE_READONLY); - setAttr = TRUE; - } - HANDLE file; - file = targetNameW != NULL - ? HANDLES_Q(CreateFileW(targetNameW, GENERIC_WRITE, - FILE_SHARE_READ | FILE_SHARE_WRITE, - NULL, OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS, - NULL)) - : INVALID_HANDLE_VALUE; - if (file != INVALID_HANDLE_VALUE) - { - if (SetFileTime(file, NULL /*&ftCreated*/, NULL /*&ftAccessed*/, modified)) - showError = FALSE; // success! - else - error = GetLastError(); - HANDLES(CloseHandle(file)); - } - else - error = GetLastError(); - if (setAttr) - { - if (targetNameW != NULL) - SetFileAttributesW(targetNameW, attr); - } - - if (showError) - { - WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - if (*dlgData.CancelWorker) - return FALSE; - - int ret; - ret = IDCANCEL; - if (dlgData.IgnoreAllCopyDirTimeErr) - ret = IDB_IGNORE; - else - { - char* data[4]; - data[0] = (char*)&ret; - data[1] = (char*)targetNameCrFile; - data[2] = (char*)(DWORD_PTR)error; - SendMessage(hProgressDlg, WM_USER_DIALOG, 11, (LPARAM)data); - } - switch (ret) - { - case IDB_IGNOREALL: - dlgData.IgnoreAllCopyDirTimeErr = TRUE; // break intentionally omitted here - case IDB_IGNORE: - break; - - case IDCANCEL: - return FALSE; - } - } - return TRUE; -} - -BOOL DoCreateDir(HWND hProgressDlg, char* name, DWORD attr, - DWORD clearReadonlyMask, CProgressDlgData& dlgData, - CQuadWord& totalDone, CQuadWord& operTotal, - const char* sourceDir, BOOL adsCopy, COperations* script, - void* buffer, BOOL& skip, BOOL& alreadyExisted, - BOOL createAsEncrypted, BOOL ignInvalidName) -{ - if (script->CopyAttrs && createAsEncrypted) - TRACE_E("DoCreateDir(): unexpected parameter value: createAsEncrypted is TRUE when script->CopyAttrs is TRUE!"); - - skip = FALSE; - alreadyExisted = FALSE; - CQuadWord lastTransferredFileSize; - script->GetTFS(&lastTransferredFileSize); - - BOOL invalidName = FileNameIsInvalid(name, TRUE, ignInvalidName); - - // if the path ends with a space/dot, we must append '\\'; otherwise SetFileAttributes - // and RemoveDirectory trim the spaces/dots and operate on a different path - const char* nameCrDir = name; - char nameCrDirCopy[3 * MAX_PATH]; - MakeCopyWithBackslashIfNeeded(nameCrDir, nameCrDirCopy); - const char* sourceDirCrDir = sourceDir; - char sourceDirCrDirCopy[3 * MAX_PATH]; - if (sourceDirCrDir != NULL) - MakeCopyWithBackslashIfNeeded(sourceDirCrDir, sourceDirCrDirCopy); - - while (1) - { - DWORD err; - if (!invalidName && SalCreateDirectoryEx(name, &err)) - { - script->AddBytesToSpeedMetersAndTFSandPS((DWORD)CREATE_DIR_SIZE.Value, TRUE, 0, NULL, MAX_OP_FILESIZE); // directory already created - - DWORD newAttr = attr & clearReadonlyMask; - if (sourceDir != NULL && adsCopy) // copy ADS when required - { - CQuadWord operDone = CREATE_DIR_SIZE; // directory already created - BOOL adsSkip = FALSE; - if (!DoCopyADS(hProgressDlg, sourceDir, TRUE, name, totalDone, - operDone, operTotal, dlgData, script, &adsSkip, buffer) || - adsSkip) // user cancelled or skipped at least one ADS - { - if (RemoveDirectoryUtf8(nameCrDir) == 0) - { - DWORD err2 = GetLastError(); - TRACE_E("Unable to remove newly created directory: " << name << ", error: " << GetErrorText(err2)); - } - if (!adsSkip) - return FALSE; // cancel the entire operation (Skip must return TRUE) - skip = TRUE; - newAttr = -1; // the directory should no longer exist, so do not apply attributes - } - } - if (newAttr != -1) - { - if (script->CopyAttrs || createAsEncrypted) // set Compressed & Encrypted attributes based on the source directory - { - if (createAsEncrypted) - { - newAttr &= ~FILE_ATTRIBUTE_COMPRESSED; - newAttr |= FILE_ATTRIBUTE_ENCRYPTED; - } - DWORD changeAttrErr = NO_ERROR; - DWORD currentAttrs = SalGetFileAttributes(name); - if (currentAttrs != INVALID_FILE_ATTRIBUTES) - { - if ((newAttr & FILE_ATTRIBUTE_COMPRESSED) != (currentAttrs & FILE_ATTRIBUTE_COMPRESSED) && - (newAttr & FILE_ATTRIBUTE_COMPRESSED) == 0) - { - changeAttrErr = UncompressFile(name, currentAttrs); - } - if (changeAttrErr == NO_ERROR && - (newAttr & FILE_ATTRIBUTE_ENCRYPTED) != (currentAttrs & FILE_ATTRIBUTE_ENCRYPTED)) - { - BOOL dummyCancelOper = FALSE; - if (newAttr & FILE_ATTRIBUTE_ENCRYPTED) - { - changeAttrErr = MyEncryptFile(hProgressDlg, name, currentAttrs, 0 /* allow encrypting directories with the SYSTEM attribute */, - dlgData, dummyCancelOper, FALSE); - - if ( //(WindowsVistaAndLater || script->TargetPathSupEFS) && // complain regardless of OS version and EFS support; originally directories on FAT could not be encrypted before Vista, we behave the same (to match Explorer, the Encrypted attribute is not that important) - !dlgData.DirCrLossEncrAll && changeAttrErr != ERROR_SUCCESS) - { // failed to set the Encrypted attribute on the directory, ask the user what to do - WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - if (*dlgData.CancelWorker) - goto CANCEL_CRDIR; - - int ret; - if (dlgData.SkipAllDirCrLossEncr) - ret = IDB_SKIP; - else - { - ret = IDCANCEL; - char* data[4]; - data[0] = (char*)&ret; - data[1] = (char*)FALSE; - data[2] = name; - data[3] = (char*)(!script->IsCopyOperation); - SendMessage(hProgressDlg, WM_USER_DIALOG, 12, (LPARAM)data); - } - switch (ret) - { - case IDB_ALL: - dlgData.DirCrLossEncrAll = TRUE; // break intentionally omitted here - case IDYES: - break; - - case IDB_SKIPALL: - dlgData.SkipAllDirCrLossEncr = TRUE; - case IDB_SKIP: - { - ClearReadOnlyAttr(nameCrDir); // remove read-only attribute so the file can be deleted - RemoveDirectoryUtf8(nameCrDir); - script->SetTFS(lastTransferredFileSize); // add TFS only after the directory is fully outside; ProgressSize will be synced outside (no point in adjusting it here) - skip = TRUE; - return TRUE; - } - - case IDCANCEL: - goto CANCEL_CRDIR; - } - } - } - else - changeAttrErr = MyDecryptFile(name, currentAttrs, FALSE); - } - if (changeAttrErr == NO_ERROR && - (newAttr & FILE_ATTRIBUTE_COMPRESSED) != (currentAttrs & FILE_ATTRIBUTE_COMPRESSED) && - (newAttr & FILE_ATTRIBUTE_COMPRESSED) != 0) - { - changeAttrErr = CompressFile(name, currentAttrs); - } - } - else - changeAttrErr = GetLastError(); - if (changeAttrErr != NO_ERROR) - { - TRACE_I("DoCreateDir(): Unable to set Encrypted or Compressed attributes for " << name << "! error=" << GetErrorText(changeAttrErr)); - } - } - SetFileAttributesUtf8(nameCrDir, newAttr); - - if (script->CopyAttrs) // verify whether the source file attributes were preserved - { - DWORD curAttrs; - curAttrs = SalGetFileAttributes(name); - if (curAttrs == INVALID_FILE_ATTRIBUTES || (curAttrs & DISPLAYED_ATTRIBUTES) != (newAttr & DISPLAYED_ATTRIBUTES)) - { // attributes probably did not transfer; warn the user - WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - if (*dlgData.CancelWorker) - goto CANCEL_CRDIR; - - int ret; - ret = IDCANCEL; - if (dlgData.IgnoreAllSetAttrsErr) - ret = IDB_IGNORE; - else - { - char* data[4]; - data[0] = (char*)&ret; - data[1] = name; - data[2] = (char*)(DWORD_PTR)(newAttr & DISPLAYED_ATTRIBUTES); - data[3] = (char*)(DWORD_PTR)(curAttrs == INVALID_FILE_ATTRIBUTES ? 0 : (curAttrs & DISPLAYED_ATTRIBUTES)); - SendMessage(hProgressDlg, WM_USER_DIALOG, 9, (LPARAM)data); - } - switch (ret) - { - case IDB_IGNOREALL: - dlgData.IgnoreAllSetAttrsErr = TRUE; // break intentionally omitted here - case IDB_IGNORE: - break; - - case IDCANCEL: - { - CANCEL_CRDIR: - - ClearReadOnlyAttr(nameCrDir); // remove read-only so the file can be deleted - RemoveDirectoryUtf8(nameCrDir); - return FALSE; - } - } - } - } - - if (sourceDir != NULL && script->CopySecurity) // should NTFS security permissions be copied? - { - DWORD err2; - if (!DoCopySecurity(sourceDir, name, &err2, NULL)) - { - WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - if (*dlgData.CancelWorker) - goto CANCEL_CRDIR; - - int ret; - ret = IDCANCEL; - if (dlgData.IgnoreAllCopyPermErr) - ret = IDB_IGNORE; - else - { - char* data[4]; - data[0] = (char*)&ret; - data[1] = (char*)sourceDir; - data[2] = name; - data[3] = (char*)(DWORD_PTR)err2; - SendMessage(hProgressDlg, WM_USER_DIALOG, 10, (LPARAM)data); - } - switch (ret) - { - case IDB_IGNOREALL: - dlgData.IgnoreAllCopyPermErr = TRUE; // break intentionally omitted here - case IDB_IGNORE: - break; - - case IDCANCEL: - goto CANCEL_CRDIR; - } - } - } - } - return TRUE; - } - else - { - if (invalidName) - err = ERROR_INVALID_NAME; - if (err == ERROR_ALREADY_EXISTS || - err == ERROR_FILE_EXISTS) - { - DWORD attr2 = SalGetFileAttributes(name); - if (attr2 & FILE_ATTRIBUTE_DIRECTORY) // "directory overwrite" - { - if (dlgData.CnfrmDirOver && !dlgData.DirOverwriteAll) // should we ask the user about overwriting the directory? - { - char sAttr[101], tAttr[101]; - GetDirInfo(sAttr, _countof(sAttr), sourceDir); - GetDirInfo(tAttr, _countof(tAttr), name); - - WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - if (*dlgData.CancelWorker) - return FALSE; - - if (dlgData.SkipAllDirOver) - goto SKIP_CREATE_ERROR; - - int ret = IDCANCEL; - char* data[5]; - data[0] = (char*)&ret; - data[1] = name; - data[2] = tAttr; - data[3] = (char*)sourceDir; - data[4] = sAttr; - SendMessage(hProgressDlg, WM_USER_DIALOG, 7, (LPARAM)data); - switch (ret) - { - case IDB_ALL: - dlgData.DirOverwriteAll = TRUE; - case IDYES: - break; - - case IDB_SKIPALL: - dlgData.SkipAllDirOver = TRUE; - case IDB_SKIP: - goto SKIP_CREATE_ERROR; - - case IDCANCEL: - return FALSE; - } - } - alreadyExisted = TRUE; - return TRUE; // o.k. - } - - WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - if (*dlgData.CancelWorker) - return FALSE; - - if (dlgData.SkipAllDirCreate) - goto SKIP_CREATE_ERROR; - - int ret = IDCANCEL; - char* data[4]; - data[0] = (char*)&ret; - data[1] = LoadStr(IDS_ERRORCREATINGDIR); - data[2] = name; - data[3] = LoadStr(IDS_NAMEALREADYUSED); - SendMessage(hProgressDlg, WM_USER_DIALOG, 0, (LPARAM)data); - switch (ret) - { - case IDRETRY: - break; - - case IDB_SKIPALL: - dlgData.SkipAllDirCreate = TRUE; - case IDB_SKIP: - goto SKIP_CREATE_ERROR; - - case IDCANCEL: - return FALSE; - } - continue; - } - - WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - if (*dlgData.CancelWorker) - return FALSE; - - if (dlgData.SkipAllDirCreateErr) - goto SKIP_CREATE_ERROR; - - int ret; - ret = IDCANCEL; - char* data[4]; - data[0] = (char*)&ret; - data[1] = LoadStr(IDS_ERRORCREATINGDIR); - data[2] = name; - data[3] = GetErrorText(err); - SendMessage(hProgressDlg, WM_USER_DIALOG, 0, (LPARAM)data); - switch (ret) - { - case IDRETRY: - break; - - case IDB_SKIPALL: - dlgData.SkipAllDirCreateErr = TRUE; - case IDB_SKIP: - { - SKIP_CREATE_ERROR: - - skip = TRUE; // this is a skip (all operations within the directory must be skipped) - return TRUE; - } - case IDCANCEL: - return FALSE; - } - } - } -} - -BOOL DoDeleteDir(HWND hProgressDlg, char* name, const CQuadWord& size, COperations* script, - CQuadWord& totalDone, DWORD attr, BOOL dontUseRecycleBin, CProgressDlgData& dlgData) -{ - DWORD err; - int AutoRetryCounter = 0; - DWORD startTime = GetTickCount(); - - // if the path ends with a space/dot, we must append '\\'; otherwise SetFileAttributes - // and RemoveDirectory trim the spaces/dots and operate on a different path - const char* nameRmDir = name; - char nameRmDirCopy[3 * MAX_PATH]; - MakeCopyWithBackslashIfNeeded(nameRmDir, nameRmDirCopy); - - while (1) - { - ClearReadOnlyAttr(nameRmDir, attr); // ensure it can be deleted - - err = ERROR_SUCCESS; - if (script->CanUseRecycleBin && !dontUseRecycleBin && - (script->InvertRecycleBin && dlgData.UseRecycleBin == 0 || - !script->InvertRecycleBin && dlgData.UseRecycleBin == 1) && - IsDirectoryEmpty(name)) // subdirectory must not contain any files!!! - { - if (!PathContainsValidComponents((char*)name, FALSE)) - { - err = ERROR_INVALID_NAME; - } - else - { - CStrP nameW(ConvertAllocUtf8ToWide(name, -1)); - if (nameW == NULL) - { - err = ERROR_NO_UNICODE_TRANSLATION; - } - else - { - int nameLen = lstrlenW(nameW); - WCHAR* nameListW = (WCHAR*)malloc((nameLen + 2) * sizeof(WCHAR)); - if (nameListW == NULL) - { - err = ERROR_NOT_ENOUGH_MEMORY; - } - else - { - memcpy(nameListW, nameW, (nameLen + 1) * sizeof(WCHAR)); - nameListW[nameLen + 1] = 0; // double null - - CShellExecuteWnd shellExecuteWnd; - SHFILEOPSTRUCTW opCode; - memset(&opCode, 0, sizeof(opCode)); - - opCode.hwnd = shellExecuteWnd.Create(hProgressDlg, "SEW: DoDeleteDir"); - - opCode.wFunc = FO_DELETE; - opCode.pFrom = nameListW; - opCode.fFlags = FOF_ALLOWUNDO | FOF_SILENT | FOF_NOCONFIRMATION; - opCode.lpszProgressTitle = L""; - err = SHFileOperationW(&opCode); - free(nameListW); - } - } - } - } - else - { - if (RemoveDirectoryUtf8(nameRmDir) == 0) - err = GetLastError(); - } - - if (err == ERROR_SUCCESS) - { - script->AddBytesToSpeedMetersAndTFSandPS((DWORD)size.Value, TRUE, 0, NULL, MAX_OP_FILESIZE); - - totalDone += size; - SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); - return TRUE; - } - else - { - WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - if (*dlgData.CancelWorker) - return FALSE; - - if (dlgData.SkipAllDeleteErr) - goto SKIP_DELETE; - - if (AutoRetryCounter < 4 && GetTickCount() - startTime + (AutoRetryCounter + 1) * 100 <= 2000 && - (err == ERROR_DIR_NOT_EMPTY || err == ERROR_SHARING_VIOLATION)) - { // add auto-retry to handle this case: I have directories 1\2\3, deleting 1 including subdirectories while 3 is shown in a panel (watching for changes) -> removing 2 reports "directory not empty" because 3 stays in a transitional state due to change notifications (it is deleted, so it cannot be listed, but it still exists on disk briefly; quite a mess) - // TRACE_I("DoDeleteDir(): err: " << GetErrorText(err)); - AutoRetryCounter++; - Sleep(AutoRetryCounter * 100); - // TRACE_I("DoDeleteDir(): " << AutoRetryCounter << ". retry, delay is " << AutoRetryCounter * 100 << "ms"); - } - else - { - int ret; - ret = IDCANCEL; - char* data[4]; - data[0] = (char*)&ret; - data[1] = LoadStr(IDS_ERRORDELETINGDIR); - data[2] = (char*)nameRmDir; - data[3] = GetErrorText(err); - SendMessage(hProgressDlg, WM_USER_DIALOG, 0, (LPARAM)data); - switch (ret) - { - case IDRETRY: - break; - - case IDB_SKIPALL: - dlgData.SkipAllDeleteErr = TRUE; - case IDB_SKIP: - { - SKIP_DELETE: - - totalDone += size; - script->SetProgressSize(totalDone); - SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); - return TRUE; - } - - case IDCANCEL: - return FALSE; - } - } - } - - DWORD attr2 = SalGetFileAttributes(nameRmDir); // get the current attribute state - if (attr2 != INVALID_FILE_ATTRIBUTES) - attr = attr2; - } -} - -#define FSCTL_GET_REPARSE_POINT CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 42, METHOD_BUFFERED, FILE_ANY_ACCESS) // REPARSE_DATA_BUFFER -#define FSCTL_DELETE_REPARSE_POINT CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 43, METHOD_BUFFERED, FILE_SPECIAL_ACCESS) // REPARSE_DATA_BUFFER, - -#define IO_REPARSE_TAG_SYMLINK (0xA000000CL) - -/* This code copies a junction point into an empty directory (the directory must be created in - advance � to keep it simple we always use "D:\\ZUMPA\\link" here). - - People sometimes want to copy the contents of the junction, sometimes they want to copy only the junction as a link, - and sometimes they want to skip it (unclear whether that should create an empty junction directory)... - if we ever implement it properly, the script builder will need a comprehensive dialog asking what to do. - -#define FSCTL_SET_REPARSE_POINT CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 41, METHOD_BUFFERED, FILE_SPECIAL_ACCESS) // REPARSE_DATA_BUFFER, -#define FSCTL_GET_REPARSE_POINT CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 42, METHOD_BUFFERED, FILE_ANY_ACCESS) // REPARSE_DATA_BUFFER - -// Structure for FSCTL_SET_REPARSE_POINT, FSCTL_GET_REPARSE_POINT, and -// FSCTL_DELETE_REPARSE_POINT. -// This version of the reparse data buffer is only for Microsoft tags. - -struct TMN_REPARSE_DATA_BUFFER -{ - DWORD ReparseTag; - WORD ReparseDataLength; - WORD Reserved; - WORD SubstituteNameOffset; - WORD SubstituteNameLength; - WORD PrintNameOffset; - WORD PrintNameLength; - WCHAR PathBuffer[1]; -}; - -#define IO_REPARSE_TAG_VALID_VALUES 0xE000FFFF -#define IsReparseTagValid(x) (!((x)&~IO_REPARSE_TAG_VALID_VALUES)&&((x)>IO_REPARSE_TAG_RESERVED_RANGE)) -#define MAXIMUM_REPARSE_DATA_BUFFER_SIZE ( 16 * 1024 ) - - - HANDLE srcDir = HANDLES_Q(CreateFileUtf8(name, GENERIC_READ, 0, 0, OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, NULL)); - if (srcDir != INVALID_HANDLE_VALUE) - { - HANDLE tgtDir = HANDLES_Q(CreateFileUtf8("D:\\ZUMPA\\link", GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, NULL)); - if (tgtDir != INVALID_HANDLE_VALUE) - { - char szBuff[MAXIMUM_REPARSE_DATA_BUFFER_SIZE]; - TMN_REPARSE_DATA_BUFFER& rdb = *(TMN_REPARSE_DATA_BUFFER*)szBuff; - - DWORD dwBytesReturned; - if (DeviceIoControl(srcDir, FSCTL_GET_REPARSE_POINT, NULL, 0, (LPVOID)&rdb, - MAXIMUM_REPARSE_DATA_BUFFER_SIZE, &dwBytesReturned, 0) && - IsReparseTagValid(rdb.ReparseTag)) - { - DWORD dwBytesReturnedDummy; - if (DeviceIoControl(tgtDir, FSCTL_SET_REPARSE_POINT, (LPVOID)&rdb, dwBytesReturned, - NULL, 0, &dwBytesReturnedDummy, 0)) - { - TRACE_I("eureka?"); - } - } - HANDLES(CloseHandle(tgtDir)); - } - HANDLES(CloseHandle(srcDir)); - } - return FALSE; -*/ - -BOOL DoDeleteDirLinkAux(const char* nameDelLink, DWORD* err) -{ - // remove the reparse point from directory 'nameDelLink' - if (err != NULL) - *err = ERROR_SUCCESS; - BOOL ok = FALSE; - DWORD attr = GetFileAttributesUtf8(nameDelLink); - if (attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_REPARSE_POINT)) - { - HANDLE dir = HANDLES_Q(CreateFileUtf8(nameDelLink, GENERIC_WRITE /* | GENERIC_READ */, 0, 0, OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, NULL)); - if (dir != INVALID_HANDLE_VALUE) - { - DWORD dummy; - char buf[MAXIMUM_REPARSE_DATA_BUFFER_SIZE]; - REPARSE_GUID_DATA_BUFFER* juncData = (REPARSE_GUID_DATA_BUFFER*)buf; - if (DeviceIoControl(dir, FSCTL_GET_REPARSE_POINT, NULL, 0, juncData, - MAXIMUM_REPARSE_DATA_BUFFER_SIZE, &dummy, NULL) == 0) - { - if (err != NULL) - *err = GetLastError(); - } - else - { - if (juncData->ReparseTag != IO_REPARSE_TAG_MOUNT_POINT && - juncData->ReparseTag != IO_REPARSE_TAG_SYMLINK) - { // if this is not a volume mount point, junction, or symlink, report an error (we could probably delete it, but better refuse than break something...) - TRACE_E("DoDeleteDirLinkAux(): Unknown type of reparse point (tag is 0x" << std::hex << juncData->ReparseTag << std::dec << "): " << nameDelLink); - if (err != NULL) - *err = 4394 /* ERROR_REPARSE_TAG_MISMATCH */; - } - else - { - REPARSE_GUID_DATA_BUFFER rgdb = {0}; - rgdb.ReparseTag = juncData->ReparseTag; - - DWORD dwBytes; - if (DeviceIoControl(dir, FSCTL_DELETE_REPARSE_POINT, &rgdb, REPARSE_GUID_DATA_BUFFER_HEADER_SIZE, - NULL, 0, &dwBytes, 0) != 0) - { - ok = TRUE; - } - else - { - if (err != NULL) - *err = GetLastError(); - } - } - } - HANDLES(CloseHandle(dir)); - } - else - { - if (err != NULL) - *err = GetLastError(); - } - } - else - ok = TRUE; // the reparse point is apparently gone; all that remains is to delete the empty directory... - // remove the empty directory (that remained after deleting the reparse point) - if (ok) - ClearReadOnlyAttr(nameDelLink, attr); // ensure it can be deleted even with the read-only attribute - if (ok && !RemoveDirectoryUtf8(nameDelLink)) - { - ok = FALSE; - if (err != NULL) - *err = GetLastError(); - } - return ok; -} - -BOOL DeleteDirLink(const char* name, DWORD* err) -{ - // if the path ends with a space/dot, we must append '\\'; otherwise CreateFile - // and RemoveDirectory trim the spaces/dots and operate on a different path - const char* nameDelLink = name; - char nameDelLinkCopy[3 * MAX_PATH]; - MakeCopyWithBackslashIfNeeded(nameDelLink, nameDelLinkCopy); - - return DoDeleteDirLinkAux(nameDelLink, err); -} - -BOOL DoDeleteDirLink(HWND hProgressDlg, char* name, const CQuadWord& size, COperations* script, - CQuadWord& totalDone, CProgressDlgData& dlgData) -{ - // if the path ends with a space/dot, we must append '\\'; otherwise CreateFile - // and RemoveDirectory trim the spaces/dots and operate on a different path - const char* nameDelLink = name; - char nameDelLinkCopy[3 * MAX_PATH]; - MakeCopyWithBackslashIfNeeded(nameDelLink, nameDelLinkCopy); - - while (1) - { - DWORD err; - BOOL ok = DoDeleteDirLinkAux(nameDelLink, &err); - - if (ok) - { - script->AddBytesToSpeedMetersAndTFSandPS((DWORD)size.Value, TRUE, 0, NULL, MAX_OP_FILESIZE); - - totalDone += size; - SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); - return TRUE; - } - else - { - WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - if (*dlgData.CancelWorker) - return FALSE; - - if (dlgData.SkipAllDeleteErr) - goto SKIP_DELETE_LINK; - - int ret; - ret = IDCANCEL; - char* data[4]; - data[0] = (char*)&ret; - data[1] = LoadStr(IDS_ERRORDELETINGDIRLINK); - data[2] = (char*)nameDelLink; - data[3] = GetErrorText(err); - SendMessage(hProgressDlg, WM_USER_DIALOG, 0, (LPARAM)data); - switch (ret) - { - case IDRETRY: - break; - - case IDB_SKIPALL: - dlgData.SkipAllDeleteErr = TRUE; - case IDB_SKIP: - { - SKIP_DELETE_LINK: - - totalDone += size; - script->SetProgressSize(totalDone); - SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); - return TRUE; - } - - case IDCANCEL: - return FALSE; - } - } - } -} - -// a) create a temporary file in the same directory as file 'name' -// b) transfer the contents of 'name' into the temporary file while applying the -// conversions specified by convertData.CodeType and convertData.EOFType -// c) overwrite file 'name' with the temporary file -// -// convertData.EOFType - determines how line endings are replaced -// CR, LF, and CRLF are all considered end-of-line markers -// 0: leave line endings unchanged -// 1: replace line endings with CRLF (DOS, Windows, OS/2) -// 2: replace line endings with LF (UNIX) -// 3: replace line endings with CR (MAC) -BOOL DoConvert(HWND hProgressDlg, char* name, char* sourceBuffer, char* targetBuffer, - const CQuadWord& size, COperations* script, CQuadWord& totalDone, - CConvertData& convertData, CProgressDlgData& dlgData) -{ - // if the path ends with a space/dot it is invalid and we must not run the conversion, - // CreateFile would trim the spaces/dots and convert a different file - BOOL invalidName = FileNameIsInvalid(name, TRUE); - -CONVERT_AGAIN: - - CQuadWord operationDone; - operationDone = CQuadWord(0, 0); - while (1) - { - // attempt to open the source file - HANDLE hSource; - if (!invalidName) - { - hSource = HANDLES_Q(CreateFileUtf8(name, GENERIC_READ, - FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, - OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL)); - } - else - { - hSource = INVALID_HANDLE_VALUE; - } - if (hSource != INVALID_HANDLE_VALUE) - { - // derive the path for the temporary file - char tmpPath[3 * MAX_PATH]; - if (strlen(name) >= _countof(tmpPath)) - { - TRACE_E("DoConvert(): source path is too long for temporary path handling: " << name); - HANDLES(CloseHandle(hSource)); - return FALSE; - } - strcpy(tmpPath, name); - char* terminator = strrchr(tmpPath, '\\'); - if (terminator == NULL) - { - // sanity check - TRACE_E("Parameter 'name' must be full path to file (including path)"); - HANDLES(CloseHandle(hSource)); - return FALSE; - } - *(terminator + 1) = 0; - - // find a name for the temporary file and let the system create it - char tmpFileName[MAX_PATH]; - BOOL tmpFileExists = FALSE; - while (1) - { - if (SalGetTempFileName(tmpPath, "cnv", tmpFileName, _countof(tmpFileName), TRUE)) - { - tmpFileExists = TRUE; - - // align the temp file attributes with the source file - DWORD srcAttrs = SalGetFileAttributes(name); - DWORD tgtAttrs = SalGetFileAttributes(tmpFileName); - BOOL changeAttrs = FALSE; - if (srcAttrs != INVALID_FILE_ATTRIBUTES && tgtAttrs != INVALID_FILE_ATTRIBUTES && srcAttrs != tgtAttrs) - { - changeAttrs = TRUE; // SetFileAttributes will be called later... - // does the NTFS compression flag differ? - if ((srcAttrs & FILE_ATTRIBUTE_COMPRESSED) != (tgtAttrs & FILE_ATTRIBUTE_COMPRESSED) && - (srcAttrs & FILE_ATTRIBUTE_COMPRESSED) == 0) - { - UncompressFile(tmpFileName, tgtAttrs); - } - if ((srcAttrs & FILE_ATTRIBUTE_ENCRYPTED) != (tgtAttrs & FILE_ATTRIBUTE_ENCRYPTED)) - { - BOOL cancelOper = FALSE; - if (srcAttrs & FILE_ATTRIBUTE_ENCRYPTED) - { - MyEncryptFile(hProgressDlg, tmpFileName, tgtAttrs, 0 /* allow encrypting files with the SYSTEM attribute */, - dlgData, cancelOper, FALSE); - } - else - MyDecryptFile(tmpFileName, tgtAttrs, FALSE); - if (*dlgData.CancelWorker || cancelOper) - { - HANDLES(CloseHandle(hSource)); - ClearReadOnlyAttr(tmpFileName); // ensure it can be deleted - DeleteFileUtf8(tmpFileName); - return FALSE; - } - } - if ((srcAttrs & FILE_ATTRIBUTE_COMPRESSED) != (tgtAttrs & FILE_ATTRIBUTE_COMPRESSED) && - (srcAttrs & FILE_ATTRIBUTE_COMPRESSED) != 0) - { - CompressFile(tmpFileName, tgtAttrs); - } - } - - // open the empty temporary file - HANDLE hTarget = HANDLES_Q(CreateFileUtf8(tmpFileName, GENERIC_WRITE, 0, NULL, - OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL)); - if (hTarget != INVALID_HANDLE_VALUE) - { - DWORD read; - BOOL crlfBreak = FALSE; - while (1) - { - if (ReadFile(hSource, sourceBuffer, OPERATION_BUFFER, &read, NULL)) - { - DWORD written; - if (read == 0) - break; // EOF - WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - if (*dlgData.CancelWorker) - { - CONVERT_ERROR: - - if (hSource != NULL) - HANDLES(CloseHandle(hSource)); - if (hTarget != NULL) - HANDLES(CloseHandle(hTarget)); - ClearReadOnlyAttr(tmpFileName); // ensure it can be deleted - DeleteFileUtf8(tmpFileName); - return FALSE; - } - - // translate sourceBuffer -> targetBuffer - char* sourceIterator; - char* targetIterator; - sourceIterator = sourceBuffer; - targetIterator = targetBuffer; - while (sourceIterator - sourceBuffer < (int)read) - { - // lastChar is TRUE when sourceIterator points to the final character in the buffer - BOOL lastChar = (sourceIterator - sourceBuffer == (int)read - 1); - - if (convertData.EOFType != 0) - { - if (crlfBreak && sourceIterator == sourceBuffer && *sourceIterator == '\n') - { - // we already processed this CRLF, leave the LF as is now - crlfBreak = FALSE; - } - else - { - if (*sourceIterator == '\r' || *sourceIterator == '\n') - { - switch (convertData.EOFType) - { - case 2: - *targetIterator++ = convertData.CodeTable['\n']; - break; - case 3: - *targetIterator++ = convertData.CodeTable['\r']; - break; - default: - { - *targetIterator++ = convertData.CodeTable['\r']; - *targetIterator++ = convertData.CodeTable['\n']; - break; - } - } - // capture CRLF which splits across the buffer boundary - if (lastChar && *sourceIterator == '\r') - crlfBreak = TRUE; - // capture CRLF that is contiguous � skip the LF - if (!lastChar && - *sourceIterator == '\r' && *(sourceIterator + 1) == '\n') - sourceIterator++; - } - else - { - *targetIterator = convertData.CodeTable[(unsigned char)*sourceIterator]; - targetIterator++; - } - } - } - else - { - *targetIterator = convertData.CodeTable[(unsigned char)*sourceIterator]; - targetIterator++; - } - sourceIterator++; - } - - // write the data to the temp file - while (1) - { - if (WriteFile(hTarget, targetBuffer, (DWORD)(targetIterator - targetBuffer), &written, NULL) && - targetIterator - targetBuffer == (int)written) - break; - - WRITE_ERROR_CONVERT: - - DWORD err; - err = GetLastError(); - - WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - if (*dlgData.CancelWorker) - goto CONVERT_ERROR; - - if (dlgData.SkipAllFileWrite) - goto SKIP_CONVERT; - - int ret; - ret = IDCANCEL; - char* data[4]; - data[0] = (char*)&ret; - data[1] = LoadStr(IDS_ERRORWRITINGFILE); - data[2] = tmpFileName; - if (hTarget != NULL && err == NO_ERROR && targetIterator - targetBuffer != (int)written) - err = ERROR_DISK_FULL; - data[3] = GetErrorText(err); - SendMessage(hProgressDlg, WM_USER_DIALOG, 0, (LPARAM)data); - switch (ret) - { - case IDRETRY: - { - if (hSource == NULL && hTarget == NULL) - { - ClearReadOnlyAttr(tmpFileName); // ensure it can be deleted - DeleteFileUtf8(tmpFileName); - SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); - goto CONVERT_AGAIN; - } - break; - } - - case IDB_SKIPALL: - dlgData.SkipAllFileWrite = TRUE; - case IDB_SKIP: - { - SKIP_CONVERT: - - totalDone += size; - if (hSource != NULL) - HANDLES(CloseHandle(hSource)); - if (hTarget != NULL) - HANDLES(CloseHandle(hTarget)); - ClearReadOnlyAttr(tmpFileName); // ensure it can be deleted - DeleteFileUtf8(tmpFileName); - SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); - return TRUE; - } - - case IDCANCEL: - goto CONVERT_ERROR; - } - } - WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - if (*dlgData.CancelWorker) - goto CONVERT_ERROR; - - operationDone += CQuadWord(read, 0); - SetProgress(hProgressDlg, - CaclProg(operationDone, size), - CaclProg(totalDone + operationDone, script->TotalSize), dlgData); - } - else - { - DWORD err = GetLastError(); - WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - if (*dlgData.CancelWorker) - goto CONVERT_ERROR; - - if (dlgData.SkipAllFileRead) - goto SKIP_CONVERT; - - int ret = IDCANCEL; - char* data[4]; - data[0] = (char*)&ret; - data[1] = LoadStr(IDS_ERRORREADINGFILE); - data[2] = name; - data[3] = GetErrorText(err); - SendMessage(hProgressDlg, WM_USER_DIALOG, 0, (LPARAM)data); - switch (ret) - { - case IDRETRY: - break; - case IDB_SKIPALL: - dlgData.SkipAllFileRead = TRUE; - case IDB_SKIP: - goto SKIP_CONVERT; - case IDCANCEL: - goto CONVERT_ERROR; - } - } - } - // close the files and update the global progress - // do not reuse operationDone so the progress stays correct even if the file changes "under our feet" - HANDLES(CloseHandle(hSource)); - if (!HANDLES(CloseHandle(hTarget))) // even after a failed call we assume the handle is closed, - { // see /viewtopic.php?f=6&t=8455 - hSource = hTarget = NULL; // (it states that the target file can be deleted, so the handle was not left open) - goto WRITE_ERROR_CONVERT; - } - totalDone += size; - // restore attributes (write operations have trouble with read-only) - if (changeAttrs) - SetFileAttributesUtf8(tmpFileName, srcAttrs); - // overwrite the original file with the temp file - while (1) - { - ClearReadOnlyAttr(name); // ensure it can be deleted - if (DeleteFileUtf8(name)) - { - while (1) - { - if (SalMoveFile(tmpFileName, name)) - return TRUE; // success - else - { - DWORD err = GetLastError(); - - WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - if (*dlgData.CancelWorker) - { - ClearReadOnlyAttr(tmpFileName); // ensure it can be deleted - DeleteFileUtf8(tmpFileName); - return FALSE; - } - - if (dlgData.SkipAllMoveErrors) - { - ClearReadOnlyAttr(tmpFileName); // ensure it can be deleted - DeleteFileUtf8(tmpFileName); - return TRUE; - } - - int ret = IDCANCEL; - char* data[4]; - data[0] = (char*)&ret; - data[1] = tmpFileName; - data[2] = name; - data[3] = GetErrorText(err); - SendMessage(hProgressDlg, WM_USER_DIALOG, 3, (LPARAM)data); - switch (ret) - { - case IDRETRY: - break; - - case IDB_SKIPALL: - dlgData.SkipAllMoveErrors = TRUE; - case IDB_SKIP: - ClearReadOnlyAttr(tmpFileName); // ensure it can be deleted - DeleteFileUtf8(tmpFileName); - return TRUE; - - case IDCANCEL: - ClearReadOnlyAttr(tmpFileName); // ensure it can be deleted - DeleteFileUtf8(tmpFileName); - return FALSE; - } - } - } - } - else - { - DWORD err = GetLastError(); - - WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - if (*dlgData.CancelWorker) - { - CANCEL_CONVERT: - - ClearReadOnlyAttr(tmpFileName); // ensure it can be deleted - DeleteFileUtf8(tmpFileName); - return FALSE; - } - - if (dlgData.SkipAllOverwriteErr) - goto SKIP_OVERWRITE_ERROR; - - int ret; - ret = IDCANCEL; - char* data[4]; - data[0] = (char*)&ret; - data[1] = LoadStr(IDS_ERROROVERWRITINGFILE); - data[2] = name; - data[3] = GetErrorText(err); - SendMessage(hProgressDlg, WM_USER_DIALOG, 0, (LPARAM)data); - switch (ret) - { - case IDRETRY: - break; - - case IDB_SKIPALL: - dlgData.SkipAllOverwriteErr = TRUE; - case IDB_SKIP: - { - SKIP_OVERWRITE_ERROR: - - ClearReadOnlyAttr(tmpFileName); // ensure it can be deleted - DeleteFileUtf8(tmpFileName); - return TRUE; - } - - case IDCANCEL: - goto CANCEL_CONVERT; - } - } - } - } - else - goto TMP_OPEN_ERROR; - } - else - { - TMP_OPEN_ERROR: - - DWORD err = GetLastError(); - - char fakeName[3 * MAX_PATH]; // name of the temp file that cannot be created/opened - if (tmpFileExists) - { - strcpy(fakeName, tmpFileName); - ClearReadOnlyAttr(tmpFileName); // ensure it can be deleted - DeleteFileUtf8(tmpFileName); // the temp file exists, try to remove it - tmpFileExists = FALSE; - } - else - { - // assemble a fictitious temp-file name for the failed creation attempt - char* s = tmpPath + strlen(tmpPath); - if (s > tmpPath && *(s - 1) == '\\') - s--; - size_t fakeNamePrefixLen = (size_t)(s - tmpPath); - const char* fakeNameSuffix = "\\cnv0000.tmp"; - size_t fakeNameSuffixLen = strlen(fakeNameSuffix); - if (fakeNamePrefixLen + fakeNameSuffixLen < _countof(fakeName)) - { - memcpy(fakeName, tmpPath, fakeNamePrefixLen); - memcpy(fakeName + fakeNamePrefixLen, fakeNameSuffix, fakeNameSuffixLen + 1); - } - else - lstrcpyn(fakeName, tmpPath, _countof(fakeName)); - } - - WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - if (*dlgData.CancelWorker) - goto CANCEL_OPEN2; - - if (dlgData.SkipAllFileOpenOut) - goto SKIP_OPEN_OUT; - - int ret; - ret = IDCANCEL; - char* data[4]; - data[0] = (char*)&ret; - data[1] = LoadStr(IDS_ERRORCREATINGTMPFILE); - data[2] = fakeName; - data[3] = GetErrorText(err); - SendMessage(hProgressDlg, WM_USER_DIALOG, 0, (LPARAM)data); - switch (ret) - { - case IDRETRY: - break; - - case IDB_SKIPALL: - dlgData.SkipAllFileOpenOut = TRUE; - case IDB_SKIP: - { - SKIP_OPEN_OUT: - - HANDLES(CloseHandle(hSource)); - totalDone += size; - SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); - return TRUE; - } - - case IDCANCEL: - { - CANCEL_OPEN2: - - HANDLES(CloseHandle(hSource)); - return FALSE; - } - } - } - } - } - else - { - DWORD err = GetLastError(); - if (invalidName) - err = ERROR_INVALID_NAME; - WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - if (*dlgData.CancelWorker) - return FALSE; - - if (dlgData.SkipAllFileOpenIn) - goto SKIP_OPEN_IN; - - int ret; - ret = IDCANCEL; - char* data[4]; - data[0] = (char*)&ret; - data[1] = LoadStr(IDS_ERROROPENINGFILE); - data[2] = name; - data[3] = GetErrorText(err); - SendMessage(hProgressDlg, WM_USER_DIALOG, 0, (LPARAM)data); - switch (ret) - { - case IDRETRY: - break; - - case IDB_SKIPALL: - dlgData.SkipAllFileOpenIn = TRUE; - case IDB_SKIP: - { - SKIP_OPEN_IN: - - totalDone += size; - SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); - return TRUE; - } - - case IDCANCEL: - return FALSE; - } - } - } -} - -BOOL DoChangeAttrs(HWND hProgressDlg, char* name, const CQuadWord& size, DWORD attrs, - COperations* script, CQuadWord& totalDone, - FILETIME* timeModified, FILETIME* timeCreated, FILETIME* timeAccessed, - BOOL& changeCompression, BOOL& changeEncryption, DWORD fileAttr, - CProgressDlgData& dlgData) -{ - // if the path ends with a space/dot, we must append '\\'; otherwise - // SetFileAttributes (and others) trims the spaces/dots and operates - // on a different path - const char* nameSetAttrs = name; - char nameSetAttrsCopy[3 * MAX_PATH]; - MakeCopyWithBackslashIfNeeded(nameSetAttrs, nameSetAttrsCopy); - - while (1) - { - DWORD error = ERROR_SUCCESS; - BOOL showCompressErr = FALSE; - BOOL showEncryptErr = FALSE; - char* errTitle = NULL; - if (changeCompression && (attrs & FILE_ATTRIBUTE_COMPRESSED) == 0) - { - error = UncompressFile(name, fileAttr); - if (error != ERROR_SUCCESS) - { - errTitle = LoadStr(IDS_ERRORCOMPRESSING); - if (error == ERROR_INVALID_FUNCTION) - showCompressErr = TRUE; // not supported - } - } - if (error == ERROR_SUCCESS && changeEncryption && (attrs & FILE_ATTRIBUTE_ENCRYPTED) == 0) - { - error = MyDecryptFile(name, fileAttr, TRUE); - if (error != ERROR_SUCCESS) - { - errTitle = LoadStr(IDS_ERRORENCRYPTING); - if (error == ERROR_INVALID_FUNCTION) - showEncryptErr = TRUE; // not supported - } - } - if (error == ERROR_SUCCESS && changeCompression && (attrs & FILE_ATTRIBUTE_COMPRESSED)) - { - error = CompressFile(name, fileAttr); - if (error != ERROR_SUCCESS) - { - errTitle = LoadStr(IDS_ERRORCOMPRESSING); - if (error == ERROR_INVALID_FUNCTION) - showCompressErr = TRUE; // not supported - } - } - if (error == ERROR_SUCCESS && changeEncryption && (attrs & FILE_ATTRIBUTE_ENCRYPTED)) - { - BOOL cancelOper = FALSE; - error = MyEncryptFile(hProgressDlg, name, fileAttr, attrs, dlgData, cancelOper, TRUE); - if (*dlgData.CancelWorker || cancelOper) - return FALSE; - if (error != ERROR_SUCCESS) - { - errTitle = LoadStr(IDS_ERRORENCRYPTING); - if (error == ERROR_INVALID_FUNCTION) - showEncryptErr = TRUE; // not supported - } - } - if (showCompressErr || showEncryptErr) - { - WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - if (*dlgData.CancelWorker) - return FALSE; - - if (showCompressErr) - changeCompression = FALSE; - if (showEncryptErr) - changeEncryption = FALSE; - char* data[3]; - data[0] = LoadStr((showCompressErr && (attrs & FILE_ATTRIBUTE_COMPRESSED) || !showEncryptErr) ? IDS_ERRORCOMPRESSING : IDS_ERRORENCRYPTING); - data[1] = name; - data[2] = LoadStr((showCompressErr && (attrs & FILE_ATTRIBUTE_COMPRESSED) || !showEncryptErr) ? IDS_COMPRNOTSUPPORTED : IDS_ENCRYPNOTSUPPORTED); - SendMessage(hProgressDlg, WM_USER_DIALOG, 5, (LPARAM)data); - error = ERROR_SUCCESS; - } - if (error == ERROR_SUCCESS && SetFileAttributesUtf8(nameSetAttrs, attrs)) - { - BOOL isDir = ((attrs & FILE_ATTRIBUTE_DIRECTORY) != 0); - // if any of the timestamps need to be set - if (timeModified != NULL || timeCreated != NULL || timeAccessed != NULL) - { - HANDLE file; - if (attrs & FILE_ATTRIBUTE_READONLY) - SetFileAttributesUtf8(nameSetAttrs, attrs & (~FILE_ATTRIBUTE_READONLY)); - file = HANDLES_Q(CreateFileUtf8(nameSetAttrs, GENERIC_READ | GENERIC_WRITE, - FILE_SHARE_READ | FILE_SHARE_WRITE, - NULL, OPEN_EXISTING, isDir ? FILE_FLAG_BACKUP_SEMANTICS : 0, NULL)); - if (file != INVALID_HANDLE_VALUE) - { - FILETIME ftCreated, ftAccessed, ftModified; - GetFileTime(file, &ftCreated, &ftAccessed, &ftModified); - if (timeCreated != NULL) - ftCreated = *timeCreated; - if (timeAccessed != NULL) - ftAccessed = *timeAccessed; - if (timeModified != NULL) - ftModified = *timeModified; - SetFileTime(file, &ftCreated, &ftAccessed, &ftModified); - HANDLES(CloseHandle(file)); - if (attrs & FILE_ATTRIBUTE_READONLY) - SetFileAttributesUtf8(nameSetAttrs, attrs); - } - else - { - if (attrs & FILE_ATTRIBUTE_READONLY) - SetFileAttributesUtf8(nameSetAttrs, attrs); - goto SHOW_ERROR; - } - } - totalDone += size; - SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); - return TRUE; - } - else - { - SHOW_ERROR: - - if (error == ERROR_SUCCESS) - error = GetLastError(); - if (errTitle == NULL) - errTitle = LoadStr(IDS_ERRORCHANGINGATTRS); - - WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - if (*dlgData.CancelWorker) - return FALSE; - - if (dlgData.SkipAllChangeAttrs) - goto SKIP_ATTRS_ERROR; - - int ret; - ret = IDCANCEL; - char* data[4]; - data[0] = (char*)&ret; - data[1] = errTitle; - data[2] = name; - data[3] = GetErrorText(error); - SendMessage(hProgressDlg, WM_USER_DIALOG, 0, (LPARAM)data); - switch (ret) - { - case IDRETRY: - break; - - case IDB_SKIPALL: - dlgData.SkipAllChangeAttrs = TRUE; - case IDB_SKIP: - { - SKIP_ATTRS_ERROR: - - totalDone += size; - SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); - return TRUE; - } - - case IDCANCEL: - return FALSE; - } - } - } -} - -unsigned ThreadWorkerBody(void* parameter) -{ - CALL_STACK_MESSAGE1("ThreadWorkerBody()"); - SetThreadNameInVCAndTrace("Worker"); - TRACE_I("Begin"); - - CWorkerData* data = (CWorkerData*)parameter; - //--- create a local copy of the data - HANDLE wContinue = data->WContinue; - CProgressDlgData dlgData; - dlgData.WorkerNotSuspended = data->WorkerNotSuspended; - dlgData.CancelWorker = data->CancelWorker; - dlgData.OperationProgress = data->OperationProgress; - dlgData.SummaryProgress = data->SummaryProgress; - dlgData.OverwriteAll = dlgData.OverwriteHiddenAll = dlgData.DeleteHiddenAll = - dlgData.SkipAllFileWrite = dlgData.SkipAllFileRead = - dlgData.SkipAllOverwrite = dlgData.SkipAllSystemOrHidden = - dlgData.SkipAllFileOpenIn = dlgData.SkipAllFileOpenOut = - dlgData.SkipAllOverwriteErr = dlgData.SkipAllMoveErrors = - dlgData.SkipAllDeleteErr = dlgData.SkipAllDirCreate = - dlgData.SkipAllDirCreateErr = dlgData.SkipAllChangeAttrs = - dlgData.EncryptSystemAll = dlgData.SkipAllEncryptSystem = - dlgData.IgnoreAllADSReadErr = dlgData.SkipAllFileADSOpenIn = - dlgData.SkipAllFileADSOpenOut = dlgData.SkipAllFileADSRead = - dlgData.SkipAllFileADSWrite = dlgData.DirOverwriteAll = - dlgData.SkipAllDirOver = dlgData.IgnoreAllADSOpenOutErr = - dlgData.IgnoreAllSetAttrsErr = dlgData.IgnoreAllCopyPermErr = - dlgData.IgnoreAllCopyDirTimeErr = dlgData.SkipAllFileOutLossEncr = - dlgData.FileOutLossEncrAll = dlgData.SkipAllDirCrLossEncr = - dlgData.DirCrLossEncrAll = dlgData.IgnoreAllGetFileTimeErr = - dlgData.IgnoreAllSetFileTimeErr = dlgData.SkipAllGetFileTime = - dlgData.SkipAllSetFileTime = FALSE; - dlgData.CnfrmFileOver = Configuration.CnfrmFileOver; - dlgData.CnfrmDirOver = Configuration.CnfrmDirOver; - dlgData.CnfrmSHFileOver = Configuration.CnfrmSHFileOver; - dlgData.CnfrmSHFileDel = Configuration.CnfrmSHFileDel; - dlgData.UseRecycleBin = Configuration.UseRecycleBin; - dlgData.RecycleMasks.SetMasksString(Configuration.RecycleMasks.GetMasksString(), - Configuration.RecycleMasks.GetExtendedMode()); - int errorPos; - if (dlgData.UseRecycleBin == 2 && - !dlgData.PrepareRecycleMasks(errorPos)) - TRACE_E("Error in recycle-bin group mask."); - COperations* script = data->Script; - if (script->TotalSize == CQuadWord(0, 0)) - { - script->TotalSize = CQuadWord(1, 0); // guard against division by zero - // TRACE_E("ThreadWorkerBody(): script->TotalSize may not be zero!"); // when building the script we do not set the "synchronizing one", which caused issues in Calculate Occupied Space - } - - if (script->CopySecurity) - GainWriteOwnerAccess(); - - HWND hProgressDlg = data->HProgressDlg; - void* buffer = data->Buffer; - BOOL bufferIsAllocated = data->BufferIsAllocated; - CChangeAttrsData* attrsData = (CChangeAttrsData*)data->Buffer; - DWORD clearReadonlyMask = data->ClearReadonlyMask; - CConvertData convertData; - if (data->ConvertData != NULL) // make a copy of the data for Convert - { - convertData = *data->ConvertData; - } - SetEvent(wContinue); // data ready; resume the main thread or the progress-dialog thread - //--- - SetProgress(hProgressDlg, 0, 0, dlgData); - script->InitSpeedMeters(FALSE); - - char lastLantasticCheckRoot[MAX_PATH]; // last path root checked for Lantastic ("" = nothing checked yet) - lastLantasticCheckRoot[0] = 0; - BOOL lastIsLantasticPath = FALSE; // result of checking root lastLantasticCheckRoot - int mustDeleteFileBeforeOverwrite = 0; /* need test */ // (added for SNAP server - NSA drive - SetEndOfFile fails - 0/1/2 = need-test/yes/no - int allocWholeFileOnStart = 0; /* need test */ // safety measure (e.g. SNAP servers - NSA drives - may fail); cannot risk a broken Copy - 0/1/2 = need-test/yes/no - int setDirTimeAfterMove = script->PreserveDirTime && script->SourcePathIsNetwork ? 0 /* need test */ : 2 /* no */; // e.g. on Samba, moving/renaming a directory changes its date and time - 0/1/2 = need-test/yes/no - - BOOL Error = FALSE; - CQuadWord totalDone; - totalDone = CQuadWord(0, 0); - CProgressData pd; - BOOL novellRenamePatch = FALSE; // TRUE when the read-only attribute must be cleared before MoveFile (required on Novell) - char* tgtBuffer = NULL; // conversion buffer for ocConvert - CAsyncCopyParams* asyncPar = NULL; - if (buffer != NULL) - { - // prefetch strings so we do not load them for every operation individually (fills the LoadStr buffer quickly + throttles) - char opStrCopying[50]; - lstrcpyn(opStrCopying, LoadStr(IDS_COPYING), 50); - char opStrCopyingPrep[50]; - lstrcpyn(opStrCopyingPrep, LoadStr(IDS_COPYINGPREP), 50); - char opStrMoving[50]; - lstrcpyn(opStrMoving, LoadStr(IDS_MOVING), 50); - char opStrMovingPrep[50]; - lstrcpyn(opStrMovingPrep, LoadStr(IDS_MOVINGPREP), 50); - char opStrCreatingDir[50]; - lstrcpyn(opStrCreatingDir, LoadStr(IDS_CREATINGDIR), 50); - char opStrDeleting[50]; - lstrcpyn(opStrDeleting, LoadStr(IDS_DELETING), 50); - char opStrConverting[50]; - lstrcpyn(opStrConverting, LoadStr(IDS_CONVERTING), 50); - char opChangAttrs[50]; - lstrcpyn(opChangAttrs, LoadStr(IDS_CHANGINGATTRS), 50); - - int i; - for (i = 0; !*dlgData.CancelWorker && i < script->Count; i++) - { - COperation* op = &script->At(i); - - switch (op->Opcode) - { - case ocCopyFile: - { - const char* opName = "copy file"; - ExecLogFileOperationStart(opName, op->SourceName, op->TargetName); - pd.Operation = opStrCopying; - pd.Source = op->SourceName; - pd.Preposition = opStrCopyingPrep; - pd.Target = op->TargetName; - SetProgressDialog(hProgressDlg, &pd, dlgData); - - SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); - - BOOL lantasticCheck = IsLantasticDrive(op->TargetName, lastLantasticCheckRoot, _countof(lastLantasticCheckRoot), lastIsLantasticPath); - - Error = !DoCopyFile(op, hProgressDlg, buffer, script, totalDone, - clearReadonlyMask, NULL, lantasticCheck, mustDeleteFileBeforeOverwrite, - allocWholeFileOnStart, dlgData, - (op->OpFlags & OPFL_COPY_ADS) != 0, - (op->OpFlags & OPFL_AS_ENCRYPTED) != 0, - FALSE, asyncPar); - ExecLogFileOperationResult(opName, op->SourceName, op->TargetName, !Error); - break; - } - - case ocMoveDir: - case ocMoveFile: - { - const char* opName = op->Opcode == ocMoveDir ? "move dir" : "move file"; - ExecLogFileOperationStart(opName, op->SourceName, op->TargetName); - pd.Operation = opStrMoving; - pd.Source = op->SourceName; - pd.Preposition = opStrMovingPrep; - pd.Target = op->TargetName; - SetProgressDialog(hProgressDlg, &pd, dlgData); - - SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); - - BOOL lantasticCheck = IsLantasticDrive(op->TargetName, lastLantasticCheckRoot, _countof(lastLantasticCheckRoot), lastIsLantasticPath); - BOOL ignInvalidName = op->Opcode == ocMoveDir && (op->OpFlags & OPFL_IGNORE_INVALID_NAME) != 0; - - Error = !DoMoveFile(op, hProgressDlg, buffer, script, totalDone, - op->Opcode == ocMoveDir, clearReadonlyMask, &novellRenamePatch, - lantasticCheck, mustDeleteFileBeforeOverwrite, - allocWholeFileOnStart, dlgData, - (op->OpFlags & OPFL_COPY_ADS) != 0, - (op->OpFlags & OPFL_AS_ENCRYPTED) != 0, - &setDirTimeAfterMove, asyncPar, ignInvalidName); - ExecLogFileOperationResult(opName, op->SourceName, op->TargetName, !Error); - break; - } - - case ocCreateDir: - { - const char* opName = "create dir"; - ExecLogFileOperationStart(opName, op->TargetName, ""); - BOOL copyADS = (op->OpFlags & OPFL_COPY_ADS) != 0; - BOOL crAsEncrypted = (op->OpFlags & OPFL_AS_ENCRYPTED) != 0; - BOOL ignInvalidName = (op->OpFlags & OPFL_IGNORE_INVALID_NAME) != 0; - pd.Operation = opStrCreatingDir; - pd.Source = op->TargetName; - pd.Preposition = ""; - pd.Target = ""; - SetProgressDialog(hProgressDlg, &pd, dlgData); - - SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); - - BOOL skip, alreadyExisted; - Error = !DoCreateDir(hProgressDlg, op->TargetName, op->Attr, clearReadonlyMask, dlgData, - totalDone, op->Size, op->SourceName, copyADS, script, buffer, skip, - alreadyExisted, crAsEncrypted, ignInvalidName); - ExecLogFileOperationResult(opName, op->TargetName, "", !Error); - if (!Error) - { - if (skip) // skip directory creation - { - // skip all script operations up to the label that closes this directory - CQuadWord skipTotal(0, 0); - int createDirIndex = i; - while (++i < script->Count) - { - COperation* oper = &script->At(i); - if (oper->Opcode == ocLabelForSkipOfCreateDir && (int)oper->Attr == createDirIndex) - { - script->AddBytesToTFS(CQuadWord((DWORD)(DWORD_PTR)oper->SourceName, (DWORD)(DWORD_PTR)oper->TargetName)); - break; - } - skipTotal += oper->Size; - } - if (i == script->Count) - { - i = createDirIndex; - TRACE_E("ThreadWorkerBody(): unable to find end-label for dir-create operation: opcode=" << op->Opcode << ", index=" << i); - } - else - totalDone += skipTotal; - } - else - { - if (alreadyExisted) - op->Attr = 0x10000000 /* dir already existed */; - else - op->Attr = 0x01000000 /* dir was created */; - } - totalDone += op->Size; - script->SetProgressSize(totalDone); - SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); - } - break; - } - - case ocCopyDirTime: - { - BOOL skipSetDirTime = FALSE; - // locate the skip-label; it stores the index of the create-dir operation along with - // whether the target directory already existed or was created (date/time are copied - // only when we created the directory) - COperation* skipLabel = NULL; - if (i + 1 < script->Count && script->At(i + 1).Opcode == ocLabelForSkipOfCreateDir) - skipLabel = &script->At(i + 1); - else - { - if (i + 2 < script->Count && script->At(i + 2).Opcode == ocLabelForSkipOfCreateDir) - skipLabel = &script->At(i + 2); - } - if (skipLabel != NULL) - { - if (skipLabel->Attr < (DWORD)script->Count) - { - COperation* crDir = &script->At(skipLabel->Attr); - if (crDir->Opcode == ocCreateDir && (crDir->OpFlags & OPFL_AS_ENCRYPTED) == 0) - { - if (crDir->Attr == 0x10000000 /* dir already existed */) - skipSetDirTime = TRUE; - else - { - if (crDir->Attr != 0x01000000 /* dir was created */) - TRACE_E("ThreadWorkerBody(): unexpected value of Attr in create-dir operation (not 'existed' nor 'created')!"); - } - } - else - TRACE_E("ThreadWorkerBody(): unexpected opcode or flags of create-dir operation! Opcode=" << crDir->Opcode << ", OpFlags=" << crDir->OpFlags); - } - else - TRACE_E("ThreadWorkerBody(): unexpected index of create-dir operation! index=" << skipLabel->Attr); - } - else - TRACE_E("ThreadWorkerBody(): unable to find end-label for dir-create operation (not in first following item nor in second following item)!"); - - if (!skipSetDirTime) - { - pd.Operation = opChangAttrs; - pd.Source = op->TargetName; - pd.Preposition = ""; - pd.Target = ""; - SetProgressDialog(hProgressDlg, &pd, dlgData); - - SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); - - FILETIME modified; - modified.dwLowDateTime = (DWORD)(DWORD_PTR)op->SourceName; - modified.dwHighDateTime = op->Attr; - Error = !DoCopyDirTime(hProgressDlg, op->TargetName, &modified, dlgData, FALSE); - } - if (!Error) - { - script->AddBytesToSpeedMetersAndTFSandPS((DWORD)op->Size.Value, TRUE, 0, NULL, MAX_OP_FILESIZE); - - totalDone += op->Size; - SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); - } - break; - } - - case ocDeleteFile: - case ocDeleteDir: - case ocDeleteDirLink: - { - const char* opName = op->Opcode == ocDeleteFile ? "delete file" : (op->Opcode == ocDeleteDir ? "delete dir" : "delete dir link"); - ExecLogFileOperationStart(opName, op->SourceName, ""); - pd.Operation = opStrDeleting; - pd.Source = op->SourceName; - pd.Preposition = ""; - pd.Target = ""; - SetProgressDialog(hProgressDlg, &pd, dlgData); - - SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); - - if (op->Opcode == ocDeleteFile) - { - Error = !DoDeleteFile(hProgressDlg, op->SourceName, op->Size, - script, totalDone, op->Attr, dlgData); - } - else - { - if (op->Opcode == ocDeleteDir) - { - Error = !DoDeleteDir(hProgressDlg, op->SourceName, op->Size, - script, totalDone, op->Attr, (DWORD)(DWORD_PTR)op->TargetName != -1, - dlgData); - } - else - { - Error = !DoDeleteDirLink(hProgressDlg, op->SourceName, op->Size, - script, totalDone, dlgData); - } - } - ExecLogFileOperationResult(opName, op->SourceName, "", !Error); - break; - } - - case ocConvert: - { - const char* opName = "convert file"; - ExecLogFileOperationStart(opName, op->SourceName, ""); - // output buffer - the conversion will be performed in it (in the worst case, - // when the input file contains only CR or LF and we translate them to CRLF, - // this buffer is twice the size of sourceBuffer) and afterwards we will write from it - // to the temporary file - if (tgtBuffer == NULL) // first pass? - { - tgtBuffer = (char*)malloc(FAST_LOCAL_COPY_BUFFER * 2); - if (tgtBuffer == NULL) - { - TRACE_E(LOW_MEMORY); - Error = TRUE; - break; // error ... - } - } - pd.Operation = opStrConverting; - pd.Source = op->SourceName; - pd.Preposition = ""; - pd.Target = ""; - SetProgressDialog(hProgressDlg, &pd, dlgData); - - SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); - - Error = !DoConvert(hProgressDlg, op->SourceName, (char*)buffer, tgtBuffer, op->Size, script, - totalDone, convertData, dlgData); - ExecLogFileOperationResult(opName, op->SourceName, "", !Error); - break; - } - - case ocChangeAttrs: - { - const char* opName = "change attrs"; - ExecLogFileOperationStart(opName, op->SourceName, ""); - pd.Operation = opChangAttrs; - pd.Source = op->SourceName; - pd.Preposition = ""; - pd.Target = ""; - SetProgressDialog(hProgressDlg, &pd, dlgData); - - SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); - - Error = !DoChangeAttrs(hProgressDlg, op->SourceName, op->Size, (DWORD)(DWORD_PTR)op->TargetName, - script, totalDone, - attrsData->ChangeTimeModified ? &attrsData->TimeModified : NULL, - attrsData->ChangeTimeCreated ? &attrsData->TimeCreated : NULL, - attrsData->ChangeTimeAccessed ? &attrsData->TimeAccessed : NULL, - attrsData->ChangeCompression, attrsData->ChangeEncryption, - op->Attr, dlgData); - ExecLogFileOperationResult(opName, op->SourceName, "", !Error); - break; - } - - case ocLabelForSkipOfCreateDir: - break; // no action - } - if (Error) - break; - WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... - } - if (!Error && !*dlgData.CancelWorker && i == script->Count && totalDone != script->TotalSize && - (totalDone != CQuadWord(0, 0) || script->TotalSize != CQuadWord(1, 0))) // intentional change of script->TotalSize to one (prevents division by zero) - { - TRACE_E("ThreadWorkerBody(): operation done: totalDone != script->TotalSize (" << totalDone.Value << " != " << script->TotalSize.Value << ")"); - } - CQuadWord transferredFileSize, progressSize; - if (!Error && !*dlgData.CancelWorker && i == script->Count && - script->GetTFSandProgressSize(&transferredFileSize, &progressSize) && - (transferredFileSize != script->TotalFileSize || - progressSize != script->TotalSize && - (progressSize != CQuadWord(0, 0) || script->TotalSize != CQuadWord(1, 0)))) // intentional change of script->TotalSize to one (prevents division by zero) - { - if (transferredFileSize != script->TotalFileSize) - { - TRACE_E("ThreadWorkerBody(): operation done: transferredFileSize != script->TotalFileSize (" << transferredFileSize.Value << " != " << script->TotalFileSize.Value << ")"); - } - if (progressSize != script->TotalSize && - (progressSize != CQuadWord(0, 0) || script->TotalSize != CQuadWord(1, 0))) - { - TRACE_E("ThreadWorkerBody(): operation done: progressSize != script->TotalSize (" << progressSize.Value << " != " << script->TotalSize.Value << ")"); - } - } - } - if (asyncPar != NULL) - delete asyncPar; - if (tgtBuffer != NULL) - free(tgtBuffer); - if (bufferIsAllocated) - free(buffer); - *dlgData.CancelWorker = Error; // if this was triggered by Cancel, make that obvious ... - SendMessage(hProgressDlg, WM_COMMAND, IDOK, 0); // we're done ... - WaitForSingleObject(wContinue, INFINITE); // we need to stop the main thread - - FreeScript(script); // calls delete, so the main thread cannot be running - - TRACE_I("End"); - return 0; -} - -unsigned ThreadWorkerEH(void* param) -{ -#ifndef CALLSTK_DISABLE - __try - { -#endif // CALLSTK_DISABLE - return ThreadWorkerBody(param); -#ifndef CALLSTK_DISABLE - } - __except (CCallStack::HandleException(GetExceptionInformation())) - { - TRACE_I("Thread Worker: calling ExitProcess(1)."); - // ExitProcess(1); - TerminateProcess(GetCurrentProcess(), 1); // harsher exit (this one still invokes something) - return 1; - } -#endif // CALLSTK_DISABLE -} - -DWORD WINAPI ThreadWorker(void* param) -{ - CCallStack stack; - return ThreadWorkerEH(param); -} - -HANDLE StartWorker(COperations* script, HWND hDlg, CChangeAttrsData* attrsData, - CConvertData* convertData, HANDLE wContinue, HANDLE workerNotSuspended, - BOOL* cancelWorker, int* operationProgress, int* summaryProgress) -{ - CWorkerData data; - data.WorkerNotSuspended = workerNotSuspended; - data.CancelWorker = cancelWorker; - data.OperationProgress = operationProgress; - data.SummaryProgress = summaryProgress; - data.WContinue = wContinue; - data.ConvertData = convertData; - data.Script = script; - data.HProgressDlg = hDlg; - data.ClearReadonlyMask = script->ClearReadonlyMask; - if (attrsData != NULL) - { - data.Buffer = attrsData; - data.BufferIsAllocated = FALSE; - } - else - { - data.BufferIsAllocated = TRUE; - data.Buffer = malloc(FAST_LOCAL_COPY_BUFFER); - if (data.Buffer == NULL) - { - TRACE_E(LOW_MEMORY); - return NULL; - } - } - DWORD threadID; - ResetEvent(wContinue); - *cancelWorker = FALSE; - - // if (Worker != NULL) HANDLES(CloseHandle(Worker)); // was probably unnecessary - HANDLE worker = HANDLES(CreateThread(NULL, 0, ThreadWorker, &data, 0, &threadID)); - if (worker == NULL) - { - if (data.BufferIsAllocated) - free(data.Buffer); - TRACE_E("Unable to start Worker thread."); - return NULL; - } - // SetThreadPriority(Worker, THREAD_PRIORITY_HIGHEST); - WaitForSingleObject(wContinue, INFINITE); // wait until it copies the data (they are on the stack) - return worker; -} - -void FreeScript(COperations* script) -{ - if (script == NULL) - return; - int i; - for (i = 0; i < script->Count; i++) - { - COperation* op = &script->At(i); - if (op->SourceName != NULL && op->Opcode != ocCopyDirTime && op->Opcode != ocLabelForSkipOfCreateDir) - free(op->SourceName); - if (op->TargetName != NULL && op->Opcode != ocChangeAttrs && op->Opcode != ocLabelForSkipOfCreateDir) - free(op->TargetName); - } - if (script->WaitInQueueSubject != NULL) - free(script->WaitInQueueSubject); - if (script->WaitInQueueFrom != NULL) - free(script->WaitInQueueFrom); - if (script->WaitInQueueTo != NULL) - free(script->WaitInQueueTo); - delete script; -} - -BOOL COperationsQueue::AddOperation(HWND dlg, BOOL startOnIdle, BOOL* startPaused) -{ - CALL_STACK_MESSAGE1("COperationsQueue::AddOperation()"); - - HANDLES(EnterCriticalSection(&QueueCritSect)); - - int i; - for (i = 0; i < OperDlgs.Count; i++) // ensure uniqueness (an operation can be added only once) - if (OperDlgs[i] == dlg) - break; - - BOOL ret = FALSE; - if (i == OperDlgs.Count) // the operation can be added - { - OperDlgs.Add(dlg); - if (OperDlgs.IsGood()) - { - if (startOnIdle) - { - int j; - for (j = 0; j < OperPaused.Count && OperPaused[j] == 1 /* auto-paused */; j++) - ; // if another operation is already running or was paused manually, start this one as "auto-paused" - *startPaused = j < OperPaused.Count; - } - else - *startPaused = FALSE; - OperPaused.Add(*startPaused ? 1 /* auto-paused */ : 0 /* running */); - if (!OperPaused.IsGood()) - { - OperPaused.ResetState(); - OperDlgs.Delete(OperDlgs.Count - 1); - if (!OperDlgs.IsGood()) - OperDlgs.ResetState(); - } - else - ret = TRUE; - } - else - OperDlgs.ResetState(); - } - else - TRACE_E("COperationsQueue::AddOperation(): this operation has already been added!"); - - HANDLES(LeaveCriticalSection(&QueueCritSect)); - - return ret; -} - -void COperationsQueue::OperationEnded(HWND dlg, BOOL doNotResume, HWND* foregroundWnd) -{ - CALL_STACK_MESSAGE1("COperationsQueue::OperationEnded()"); - - HANDLES(EnterCriticalSection(&QueueCritSect)); - - BOOL found = FALSE; - int i; - for (i = 0; i < OperDlgs.Count; i++) - { - if (OperDlgs[i] == dlg) - { - found = TRUE; - OperDlgs.Delete(i); - if (!OperDlgs.IsGood()) - OperDlgs.ResetState(); - OperPaused.Delete(i); - if (!OperPaused.IsGood()) - OperPaused.ResetState(); - break; - } - } - if (!found) - TRACE_E("COperationsQueue::OperationEnded(): unexpected situation: operation was not found!"); - else - { - if (!doNotResume) - { - int j; - for (j = 0; j < OperPaused.Count && OperPaused[j] == 1 /* auto-paused */; j++) - ; // if no operation is running and none was paused manually, resume the first one in the queue - if (j == OperPaused.Count && OperDlgs.Count > 0) - { - PostMessage(OperDlgs[0], WM_COMMAND, CM_RESUMEOPER, 0); - if (foregroundWnd != NULL && GetForegroundWindow() == dlg) - *foregroundWnd = OperDlgs[0]; - } - } - } - - HANDLES(LeaveCriticalSection(&QueueCritSect)); -} - -void COperationsQueue::SetPaused(HWND dlg, int paused) -{ - CALL_STACK_MESSAGE1("COperationsQueue::SetPaused()"); - - HANDLES(EnterCriticalSection(&QueueCritSect)); - - int i; - for (i = 0; i < OperDlgs.Count; i++) - { - if (OperDlgs[i] == dlg) - { - OperPaused[i] = paused; - break; - } - } - if (i == OperDlgs.Count) - TRACE_E("COperationsQueue::SetPaused(): operation was not found!"); - - HANDLES(LeaveCriticalSection(&QueueCritSect)); -} - -BOOL COperationsQueue::IsEmpty() -{ - CALL_STACK_MESSAGE1("COperationsQueue::IsEmpty()"); - - HANDLES(EnterCriticalSection(&QueueCritSect)); - BOOL ret = OperDlgs.Count == 0; - HANDLES(LeaveCriticalSection(&QueueCritSect)); - return ret; -} - -void COperationsQueue::AutoPauseOperation(HWND dlg, HWND* foregroundWnd) -{ - CALL_STACK_MESSAGE1("COperationsQueue::AutoPauseOperation()"); - - HANDLES(EnterCriticalSection(&QueueCritSect)); - - int i; - for (i = 0; i < OperDlgs.Count; i++) - { - if (OperDlgs[i] == dlg) - { - int j; - for (j = i; j + 1 < OperDlgs.Count; j++) - OperDlgs[j] = OperDlgs[j + 1]; - for (j = i; j + 1 < OperPaused.Count; j++) - OperPaused[j] = OperPaused[j + 1]; - OperDlgs[j] = dlg; - OperPaused[j] = 1 /* auto-paused */; - break; - } - } - if (i == OperDlgs.Count) - TRACE_E("COperationsQueue::AutoPauseOperation(): operation was not found!"); - - // if no operation is running and none was paused manually, resume the first one in the queue - int j; - for (j = 0; j < OperPaused.Count && OperPaused[j] == 1 /* auto-paused */; j++) - ; - if (j == OperPaused.Count && OperDlgs.Count > 0) - { - PostMessage(OperDlgs[0], WM_COMMAND, CM_RESUMEOPER, 0); - if (foregroundWnd != NULL && GetForegroundWindow() == dlg) - *foregroundWnd = OperDlgs[0]; - } - - HANDLES(LeaveCriticalSection(&QueueCritSect)); -} - -int COperationsQueue::GetNumOfOperations() -{ - CALL_STACK_MESSAGE1("COperationsQueue::GetNumOfOperations()"); - - HANDLES(EnterCriticalSection(&QueueCritSect)); - int c = OperDlgs.Count; - HANDLES(LeaveCriticalSection(&QueueCritSect)); - return c; -} - - - diff --git a/src/worker.h b/src/worker.h index 6c689c56..1ceb4f96 100644 --- a/src/worker.h +++ b/src/worker.h @@ -526,3 +526,134 @@ BOOL CheckFileOrDirADS(const char* fileName, BOOL isDir, CQuadWord* adsSize, wch int* streamNamesCount, BOOL* lowMemory, DWORD* winError, DWORD bytesPerCluster, CQuadWord* adsOccupiedSpace, BOOL* onlyDiscardableStreams); + +// Shared worker data structures (defined in async_copy.cpp, used in operations_core.cpp) + +struct CWorkerData +{ + COperations* Script; + HWND HProgressDlg; + void* Buffer; + BOOL BufferIsAllocated; + DWORD ClearReadonlyMask; + CConvertData* ConvertData; + HANDLE WContinue; + + HANDLE WorkerNotSuspended; + BOOL* CancelWorker; + int* OperationProgress; + int* SummaryProgress; +}; + +struct CProgressDlgData +{ + HANDLE WorkerNotSuspended; + BOOL* CancelWorker; + int* OperationProgress; + int* SummaryProgress; + + BOOL OverwriteAll; + BOOL OverwriteHiddenAll; + BOOL DeleteHiddenAll; + BOOL EncryptSystemAll; + BOOL DirOverwriteAll; + BOOL FileOutLossEncrAll; + BOOL DirCrLossEncrAll; + + BOOL SkipAllFileWrite; + BOOL SkipAllFileRead; + BOOL SkipAllOverwrite; + BOOL SkipAllSystemOrHidden; + BOOL SkipAllFileOpenIn; + BOOL SkipAllFileOpenOut; + BOOL SkipAllOverwriteErr; + BOOL SkipAllMoveErrors; + BOOL SkipAllDeleteErr; + BOOL SkipAllDirCreate; + BOOL SkipAllDirCreateErr; + BOOL SkipAllChangeAttrs; + BOOL SkipAllEncryptSystem; + BOOL SkipAllFileADSOpenIn; + BOOL SkipAllFileADSOpenOut; + BOOL SkipAllGetFileTime; + BOOL SkipAllSetFileTime; + BOOL SkipAllFileADSRead; + BOOL SkipAllFileADSWrite; + BOOL SkipAllDirOver; + BOOL SkipAllFileOutLossEncr; + BOOL SkipAllDirCrLossEncr; + + BOOL IgnoreAllADSReadErr; + BOOL IgnoreAllADSOpenOutErr; + BOOL IgnoreAllGetFileTimeErr; + BOOL IgnoreAllSetFileTimeErr; + BOOL IgnoreAllSetAttrsErr; + BOOL IgnoreAllCopyPermErr; + BOOL IgnoreAllCopyDirTimeErr; + + int CnfrmFileOver; + int CnfrmDirOver; + int CnfrmSHFileOver; + int CnfrmSHFileDel; + int UseRecycleBin; + CMaskGroup RecycleMasks; + + BOOL PrepareRecycleMasks(int& errorPos) + { + return RecycleMasks.PrepareMasks(errorPos); + } + + BOOL AgreeRecycleMasks(const char* fileName, const char* fileExt) + { + return RecycleMasks.AgreeMasks(fileName, fileExt); + } +}; + +// Async copy parameter block (defined in async_copy.cpp, used in operations_core.cpp) +struct CAsyncCopyParams +{ + void* Buffers[8]; // allocated buffers of size ASYNC_COPY_BUF_SIZE bytes + OVERLAPPED Overlapped[8]; // structures for asynchronous operations + + BOOL UseAsyncAlg; // TRUE = use the asynchronous algorithm (data must be allocated), FALSE = old synchronous algorithm (allocate nothing) + + BOOL HasFailed; // TRUE = failed to create an event for the Overlapped array, the structure is unusable + + CAsyncCopyParams(); + ~CAsyncCopyParams(); + + void Init(BOOL useAsyncAlg); + + BOOL Failed() { return HasFailed; } + + DWORD GetOverlappedFlag() { return UseAsyncAlg ? FILE_FLAG_OVERLAPPED : 0; } + + OVERLAPPED* InitOverlapped(int i); + OVERLAPPED* InitOverlappedWithOffset(int i, const CQuadWord& offset); + OVERLAPPED* GetOverlapped(int i) { return &Overlapped[i]; } + void SetOverlappedToEOF(int i, const CQuadWord& offset); +}; + +int CaclProg(const CQuadWord& progressCurrent, const CQuadWord& progressTotal); +void SetProgress(HWND hProgressDlg, int operation, int summary, CProgressDlgData& dlgData); +void SetProgressDialog(HWND hProgressDlg, CProgressData* data, CProgressDlgData& dlgData); + +// File operation helpers (defined in async_copy.cpp, called from operations_core.cpp) +BOOL DoCopyDirTime(HWND hProgressDlg, const char* targetName, FILETIME* modified, CProgressDlgData& dlgData, BOOL quiet); +BOOL DoMoveFile(COperation* op, HWND hProgressDlg, void* buffer, + COperations* script, CQuadWord& totalDone, BOOL dir, + DWORD clearReadonlyMask, BOOL* novellRenamePatch, BOOL lantasticCheck, + int& mustDeleteFileBeforeOverwrite, int& allocWholeFileOnStart, + CProgressDlgData& dlgData, BOOL copyADS, BOOL copyAsEncrypted, + BOOL* setDirTimeAfterMove, CAsyncCopyParams*& asyncPar, + BOOL ignInvalidName); +BOOL DoDeleteFile(HWND hProgressDlg, char* name, const CQuadWord& size, COperations* script, + CQuadWord& totalDone, DWORD attr, CProgressDlgData& dlgData); +BOOL DoCreateDir(HWND hProgressDlg, char* name, DWORD attr, + DWORD clearReadonlyMask, CProgressDlgData& dlgData, + CQuadWord& totalDone, CQuadWord& operTotal, + const char* sourceDir, BOOL adsCopy, COperations* script, + void* buffer, BOOL& skip, BOOL& alreadyExisted, + BOOL createAsEncrypted, BOOL ignInvalidName); +BOOL DoDeleteDir(HWND hProgressDlg, char* name, const CQuadWord& size, COperations* script, + CQuadWord& totalDone, DWORD attr, BOOL dontUseRecycleBin, CProgressDlgData& dlgData); diff --git a/src/zip.cpp b/src/zip.cpp index 6caaf045..e53506de 100644 --- a/src/zip.cpp +++ b/src/zip.cpp @@ -1,6499 +1,12 @@ -// SPDX-FileCopyrightText: 2023 Taskscape Ltd +// SPDX-FileCopyrightText: 2023 Taskscape Ltd // SPDX-License-Identifier: GPL-2.0-or-later // CommentsTranslationProject: TRANSLATED -#include "precomp.h" - -#include "menu.h" -#include "cfgdlg.h" -#include "dialogs.h" -#include "mainwnd.h" -#include "plugins.h" -#include "filesbox.h" -#include "fileswnd.h" -#include "stswnd.h" -#include "editwnd.h" -#include "zip.h" -#include "cache.h" -#include "viewer.h" -#include "codetbl.h" -#include "shellib.h" -#include "gui.h" -#include "tasklist.h" -#include "olespy.h" -#include "md5.h" -#include "geticon.h" -#include "pack.h" -extern "C" -{ -#include "shexreg.h" -} -#include "salshlib.h" -#include "crypt\fileenc.h" -#include "crypt\sha1.h" -#include "pwdmngr.h" - -CPackerConfig PackerConfig; -CUnpackerConfig UnpackerConfig; - -const char* STR_NONE = "(none)"; - -CSalamanderDirectory GlobalEmptySalDir(FALSE); // returned as an empty sal-dir (instead of NULL) - only for archives - -HWND ProgressDialogActivateDrop = NULL; - -// -// **************************************************************************** -// CZIPUnpackProgress -// - -CZIPUnpackProgress::CZIPUnpackProgress() : CCommonDialog(HLanguage, IDD_ZIPUNPACKPROG, NULL, ooStatic) -{ - Init(); -} - -void CZIPUnpackProgress::Init() -{ - SetTotal(CQuadWord(0, 0), CQuadWord(0, 0)); - ActualSize = CQuadWord(0, 0); - ActualSize2 = CQuadWord(0, 0); - Title = NULL; - Cancel = FALSE; - SetRemapNames(NULL, NULL); - int i; - for (i = 0; i < ZIP_UNPACK_NUMLINES; i++) - LinesCache[i][0] = 0; - CacheIndex = 0; - CacheIsDirty = FALSE; - SizeIsDirty = FALSE; - Size2IsDirty = FALSE; - LastTickCount = 0; - FileProgress = FALSE; - TaskBarList3 = NULL; -} - -CZIPUnpackProgress::CZIPUnpackProgress(const char* title, HWND parent, const CQuadWord& totalSize, CITaskBarList3* taskBarList3) - : CCommonDialog(HLanguage, IDD_ZIPUNPACKPROG, parent, ooStatic) -{ - SetTotal(totalSize, CQuadWord(0, 0)); - ActualSize = CQuadWord(0, 0); - ActualSize2 = CQuadWord(0, 0); - Title = title; - Cancel = FALSE; - SetRemapNames(NULL, NULL); - int i; - for (i = 0; i < ZIP_UNPACK_NUMLINES; i++) - LinesCache[i][0] = 0; - CacheIndex = 0; - CacheIsDirty = FALSE; - SizeIsDirty = FALSE; - Size2IsDirty = FALSE; - LastTickCount = 0; - FileProgress = FALSE; - TaskBarList3 = taskBarList3; -} - -void CZIPUnpackProgress::Set(const char* title, HWND parent, const CQuadWord& totalSize, BOOL fileProgress) -{ - ResID = IDD_ZIPUNPACKPROG; - SetTotal(totalSize, CQuadWord(0, 0)); - SetParent(parent); - Title = title; - ActualSize = CQuadWord(0, 0); - ActualSize2 = CQuadWord(0, 0); - FileProgress = fileProgress; -} - -void CZIPUnpackProgress::Set(const char* title, HWND parent, const CQuadWord& totalSize1, - const CQuadWord& totalSize2) -{ - ResID = IDD_ZIPUNPACKPROG2; - SetTotal(totalSize1, totalSize2); - SetParent(parent); - Title = title; - ActualSize = CQuadWord(0, 0); - ActualSize2 = CQuadWord(0, 0); - FileProgress = FALSE; -} - -void CZIPUnpackProgress::SetTotal(const CQuadWord& total1, const CQuadWord& total2) -{ - if (total1 != CQuadWord(-1, -1)) - { - TotalSize = max(CQuadWord(1, 0), total1); - SizeIsDirty = FALSE; - } - if (total2 != CQuadWord(-1, -1)) - { - TotalSize2 = max(CQuadWord(1, 0), total2); - Size2IsDirty = FALSE; - } -} - -void CZIPUnpackProgress::SetRemapNames(const char* nameFrom, const char* nameTo) -{ - RemapNameFrom = nameFrom; - RemapNameTo = nameTo; -} - -void CZIPUnpackProgress::DoRemapNames(char* txt, int bufLen) -{ - if (RemapNameFrom != NULL && RemapNameTo != NULL) - { - char* s = strstr(txt, RemapNameFrom); - if (s != NULL) - { - int len = (int)strlen(txt); - int lenFrom = (int)strlen(RemapNameFrom); - int lenTo = (int)strlen(RemapNameTo); - if (len - lenFrom + lenTo < bufLen) - { - memmove(s + lenTo, s + lenFrom, len - ((s + lenFrom) - txt) + 1); - memcpy(s, RemapNameTo, lenTo); - } - else - TRACE_E("Remap: too long name."); - } - } -} - -void CZIPUnpackProgress::SetTaskBarList3(CITaskBarList3* taskBarList3) -{ - TaskBarList3 = taskBarList3; -} - -void CZIPUnpackProgress::DispatchMessages() -{ - // pump the message queue - MSG msg; - while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) - { - if (!IsWindow(HWindow) || !IsDialogMessage(HWindow, &msg)) - { - TranslateMessage(&msg); - DispatchMessage(&msg); - } - } -} - -BOOL CZIPUnpackProgress::HasTwoProgress() -{ - return ResID == IDD_ZIPUNPACKPROG2; -} - -int CZIPUnpackProgress::SetSize(const CQuadWord& size1, const CQuadWord& size2, BOOL delayedPaint) -{ - // does the user want to set size1? - if (size1 != CQuadWord(-1, -1)) - { - CQuadWord newSize = max(CQuadWord(0, 0), size1); - if (newSize != ActualSize) - { - ActualSize = newSize; - SizeIsDirty = TRUE; - } - } - // does the user want to set size2? - if (size2 != CQuadWord(-1, -1)) - { - CQuadWord newSize = max(CQuadWord(0, 0), size2); - if (newSize != ActualSize2) - { - ActualSize2 = newSize; - Size2IsDirty = TRUE; - } - } - return AddSize(0, delayedPaint); -} - -int CZIPUnpackProgress::AddSize(int size, BOOL delayedPaint) -{ - // time-critical function - // CALL_STACK_MESSAGE2("CZIPUnpackProgress::AddSize(%d)", size); - - ActualSize += CQuadWord(size, 0); - if (size != 0) - SizeIsDirty = TRUE; - - if (HasTwoProgress()) - { - ActualSize2 += CQuadWord(size, 0); - if (size != 0) - Size2IsDirty = TRUE; - } - - if (!delayedPaint) - { - // should we draw the text immediately - FlushDataToControls(); - } - - // every 100 ms redraw the changed data (text + progress bars) - DWORD ticks = GetTickCount(); - if (ticks - LastTickCount > 100) - { - LastTickCount = ticks; - // if we have not repainted a moment ago, do it now - if (delayedPaint) - FlushDataToControls(); - } - - DispatchMessages(); // give the user a moment ... - - return !Cancel; -} - -void CZIPUnpackProgress::NewLine(const char* txt, BOOL delayedPaint) -{ - // time-critical function - // CALL_STACK_MESSAGE2("CZIPUnpackProgress::NewLine(%s)", txt); - if (txt == NULL) - return; - - while (1) // output even multiple lines into the dialog - { - while (*txt != 0 && (*txt == '\r' || *txt == '\n' || *txt == ' ' || *txt == '\t')) - txt++; - if (*txt == 0) - break; - - // store it in the cache that we display on WM_TIMER - - // the cache index cycles through the items - CacheIndex++; - if (CacheIndex >= ZIP_UNPACK_NUMLINES) - CacheIndex = 0; - - char* s = LinesCache[CacheIndex]; - char* sEnd = s + 300 - 1; - while (*txt != 0 && *txt != '\r' && *txt != '\n') // read one line + convert '/' -> '\\' - { - if (*txt == '/') - { - if (s < sEnd) - *s++ = '\\'; - txt++; - } - else - { - if (s < sEnd) - *s++ = *txt++; - else - txt++; // simply ignore the rest of the text (it would not fit in the dialog anyway) - } - } - *s = 0; - DoRemapNames(LinesCache[CacheIndex], 300); - - // we dirtied the cache - CacheIsDirty = TRUE; - } - - if (!delayedPaint) - { - // should we draw the text immediately - FlushDataToControls(); - } - - // every 100 ms redraw the changed data (text + progress bars) - DWORD ticks = GetTickCount(); - if (ticks - LastTickCount > 100) - { - LastTickCount = ticks; - // if we have not repainted a moment ago, do it now - if (delayedPaint) - FlushDataToControls(); - } - - // be careful, do not call here - DispatchMessages(); // give the user a moment ... -} - -void CZIPUnpackProgress::EnableCancel(BOOL enable) -{ - if (HWindow != NULL) - { - HWND cancel = GetDlgItem(HWindow, IDCANCEL); - if (IsWindowEnabled(cancel) != enable) - { - EnableWindow(cancel, enable); - if (enable) - SetFocus(cancel); - PostMessage(cancel, BM_SETSTYLE, enable ? BS_DEFPUSHBUTTON : BS_PUSHBUTTON, TRUE); - - DispatchMessages(); // give the user a moment ... - } - } -} - -void CZIPUnpackProgress::FlushDataToControls() -{ - // texts - if (CacheIsDirty) - { - int index = CacheIndex; - int i; - for (i = ZIP_UNPACK_NUMLINES - 1; i >= 0; i--) - { - if (Lines[i] != NULL) - Lines[i]->SetText(LinesCache[index]); - index--; - if (index < 0) - index = ZIP_UNPACK_NUMLINES - 1; - } - CacheIsDirty = FALSE; - } - - if (TaskBarList3 != NULL) - { - if (HasTwoProgress()) - { - if (Size2IsDirty) - TaskBarList3->SetProgress2(ActualSize2, TotalSize2); - } - else - { - if (SizeIsDirty) - TaskBarList3->SetProgress2(ActualSize, TotalSize); - } - } - - // size - if (SizeIsDirty) - { - if (Summary != NULL) - { - Summary->SetProgress2(ActualSize, TotalSize); - } - SizeIsDirty = FALSE; - } - - // size2 - if (Size2IsDirty) - { - if (Summary2 != NULL) - { - Summary2->SetProgress2(ActualSize2, TotalSize2); - } - Size2IsDirty = FALSE; - } -} - -INT_PTR -CZIPUnpackProgress::DialogProc(UINT uMsg, WPARAM wParam, LPARAM lParam) -{ - switch (uMsg) - { - case WM_INITDIALOG: - { - if (ResID == IDD_ZIPUNPACKPROG && FileProgress) // it is necessary to replace the text "Total:" with "File:" - SetDlgItemText(HWindow, IDT_PROGTITLE, LoadStr(IDS_UNPACKFILEPROGRESS)); - - SetWindowText(HWindow, Title); - // the entire object assumes that the allocations may have failed - int i; - for (i = 0; i < ZIP_UNPACK_NUMLINES; i++) - { - if ((Lines[i] = new CStaticText(HWindow, IDS_ZIPLINE1 + i, STF_PATH_ELLIPSIS | STF_CACHED_PAINT)) == NULL) - TRACE_E(LOW_MEMORY); - } - if ((Summary = new CProgressBar(HWindow, IDC_ZIPSUMMARY)) == NULL) - TRACE_E(LOW_MEMORY); - if (HasTwoProgress()) - { - if ((Summary2 = new CProgressBar(HWindow, IDC_ZIPSUMMARY2)) == NULL) - TRACE_E(LOW_MEMORY); - } - break; - } - - case WM_DESTROY: - { - if (TaskBarList3 != NULL) - TaskBarList3->SetProgressState(TBPF_NOPROGRESS); - break; - } - - case WM_COMMAND: - { - // if the user clicked the Cancel button and has not confirmed it earlier, ask again - if (LOWORD(wParam) == IDCANCEL && !Cancel) - { - // the Cancel button must be enabled - if (IsWindowEnabled(GetDlgItem(HWindow, IDCANCEL))) - { - // to avoid repainting under the message box, repaint explicitly now - FlushDataToControls(); - - // ask the user whether they want to abort the operation - Cancel = (SalMessageBox(HWindow, LoadStr(IDS_CANCELOPERATION), LoadStr(IDS_QUESTION), - MB_YESNO | MB_ICONQUESTION) == IDYES); - } - } - // do not let the command fall through, otherwise the dialog would close - return TRUE; - } - } - return CCommonDialog::DialogProc(uMsg, wParam, lParam); -} - -// -// **************************************************************************** -// CSalamanderGeneral -// - -CSalamanderGeneral::CSalamanderGeneral() -{ - Plugin = NULL; - LanguageModule = NULL; - HelpFileName[0] = 0; -} - -CSalamanderGeneral::~CSalamanderGeneral() -{ - if (LanguageModule != NULL) - { - TRACE_E("CSalamanderGeneral::~CSalamanderGeneral(): unexpected situation!"); - HANDLES(FreeLibrary(LanguageModule)); - } -} - -void CSalamanderGeneral::Clear() -{ - if (LanguageModule != NULL) - HANDLES(FreeLibrary(LanguageModule)); - LanguageModule = NULL; - HelpFileName[0] = 0; -} - -int CSalamanderGeneral::ShowMessageBox(const char* text, const char* title, int type) -{ - if (MainThreadID != GetCurrentThreadId()) // Petr: just close; I do not have the energy to track down every wrong call - TRACE_E("You can call CSalamanderGeneral::ShowMessageBox() only from main thread!"); - HWND parent = GetMsgBoxParent(); - switch (type) - { - case MSGBOX_INFO: - { - return SalMessageBox(parent, text, title, MB_OK | MB_ICONINFORMATION); - } - - case MSGBOX_ERROR: - { - return SalMessageBox(parent, text, title, MB_OK | MB_ICONEXCLAMATION); - } - - case MSGBOX_EX_ERROR: - { - return SalMessageBox(parent, text, title, MB_OKCANCEL | MB_ICONEXCLAMATION); - } - - case MSGBOX_QUESTION: - { - return SalMessageBox(parent, text, title, MB_YESNO | MB_ICONQUESTION); - } - - case MSGBOX_EX_QUESTION: - { - return SalMessageBox(parent, text, title, MB_YESNOCANCEL | MB_ICONQUESTION); - } - - case MSGBOX_WARNING: - { - return SalMessageBox(parent, text, title, MB_OK | MB_ICONWARNING); - } - - case MSGBOX_EX_WARNING: - { - return SalMessageBox(parent, text, title, MB_YESNOCANCEL | MB_ICONWARNING); - } - - default: - { - TRACE_E("Unknown type of box in CSalamanderGeneral::ShowMessageBox()."); - return 0; - } - } -} - -int CSalamanderGeneral::SalMessageBox(HWND hParent, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType) -{ - return ::SalMessageBox(hParent, lpText, lpCaption, uType); -} - -int CSalamanderGeneral::SalMessageBoxEx(const MSGBOXEX_PARAMS* params) -{ - return ::SalMessageBoxEx(params); -} - -HWND CSalamanderGeneral::GetMsgBoxParent() -{ - if (MainThreadID != GetCurrentThreadId()) // Petr: just close; I do not have the energy to track down every wrong call - TRACE_E("You can call CSalamanderGeneral::GetMsgBoxParent() only from main thread!"); - // if the following code should change, it must also be updated in EnterPlugin - so the check keeps working - return PluginProgressDialog != NULL ? PluginProgressDialog : PluginMsgBoxParent; -} - -int DialogError(HWND parent, DWORD flags, const char* fileName, - const char* error, const char* title) -{ - HWND mainWnd = GetWndToFlash(parent); - DWORD resID; - BOOL noSkip; - switch (flags & BUTTONS_MASK) - { - case BUTTONS_OK: - { - resID = IDD_ERROR3; - noSkip = FALSE; // does not apply; something about resID == 0 - break; - } - - case BUTTONS_RETRYCANCEL: - { - resID = 0; - noSkip = TRUE; - break; - } - - case BUTTONS_SKIPCANCEL: - { - resID = IDD_ERROR2; - noSkip = FALSE; // does not apply; something about resID == 0 - break; - } - - case BUTTONS_RETRYSKIPCANCEL: - { - resID = 0; - noSkip = FALSE; - break; - } - - default: - { - TRACE_E("CSalamanderGeneral::DialogError: unknow flags=0x" << std::hex << flags << std::dec); - return DIALOG_FAIL; - } - } - int res = (int)CFileErrorDlg(parent, title != NULL ? title : ::LoadStr(IDS_ERRORTITLE), - fileName, error, noSkip, resID) - .Execute(); - if (mainWnd != NULL) - FlashWindow(mainWnd, FALSE); - switch (res) - { - case IDOK: - return DIALOG_OK; - case IDRETRY: - return DIALOG_RETRY; - case IDB_SKIP: - return DIALOG_SKIP; - case IDB_SKIPALL: - return DIALOG_SKIPALL; - case IDCANCEL: - return DIALOG_CANCEL; - default: - return DIALOG_FAIL; - } -} - -int CSalamanderGeneral::DialogError(HWND parent, DWORD flags, const char* fileName, - const char* error, const char* title) -{ - if (fileName == NULL || error == NULL) - { - TRACE_E("Invalid parametr (fileName == NULL || error == NULL) in CSalamanderGeneral::DialogError!"); - if (fileName == NULL) - fileName = ""; - if (error == NULL) - error = ""; - } - return ::DialogError(parent, flags, fileName, error, title); -} - -int DialogOverwrite(HWND parent, DWORD flags, const char* fileName1, const char* fileData1, - const char* fileName2, const char* fileData2) -{ - HWND mainWnd = GetWndToFlash(parent); - BOOL yesnocancel; - switch (flags & BUTTONS_MASK) - { - case BUTTONS_YESALLSKIPCANCEL: - { - yesnocancel = FALSE; - break; - } - - case BUTTONS_YESNOCANCEL: - { - yesnocancel = TRUE; - break; - } - - default: - { - TRACE_E("CSalamanderGeneral::DialogOverwrite: unknow flags=0x" << std::hex << flags << std::dec); - return DIALOG_FAIL; - } - } - - int res = (int)COverwriteDlg(parent, fileName1, fileData1, fileName2, fileData2, yesnocancel).Execute(); - if (mainWnd != NULL) - FlashWindow(mainWnd, FALSE); - switch (res) - { - case IDYES: - return DIALOG_YES; - case IDNO: - return DIALOG_NO; - case IDB_ALL: - return DIALOG_ALL; - case IDB_SKIP: - return DIALOG_SKIP; - case IDB_SKIPALL: - return DIALOG_SKIPALL; - case IDCANCEL: - return DIALOG_CANCEL; - default: - return DIALOG_FAIL; - } -} - -int CSalamanderGeneral::DialogOverwrite(HWND parent, DWORD flags, const char* fileName1, const char* fileData1, - const char* fileName2, const char* fileData2) -{ - if (fileName1 == NULL || fileData1 == NULL || fileName2 == NULL || fileData2 == NULL) - { - TRACE_E("Invalid parametr (fileName1 == NULL || fileData1 == NULL || fileName2 == NULL || fileData2 == NULL) in CSalamanderGeneral::DialogOverwrite!"); - if (fileName1 == NULL) - fileName1 = ""; - if (fileData1 == NULL) - fileData1 = ""; - if (fileName2 == NULL) - fileName2 = ""; - if (fileData2 == NULL) - fileData2 = ""; - } - return ::DialogOverwrite(parent, flags, fileName1, fileData1, fileName2, fileData2); -} - -int DialogQuestion(HWND parent, DWORD flags, const char* fileName, - const char* question, const char* title) -{ - HWND mainWnd = GetWndToFlash(parent); - BOOL yesnocancel, yesallcancel; - switch (flags & BUTTONS_MASK) - { - case BUTTONS_YESALLSKIPCANCEL: - { - yesnocancel = FALSE; - yesallcancel = FALSE; - break; - } - - case BUTTONS_YESNOCANCEL: - { - yesnocancel = TRUE; - yesallcancel = FALSE; - break; - } - - case BUTTONS_YESALLCANCEL: - { - yesnocancel = TRUE; - yesallcancel = TRUE; - break; - } - - default: - { - TRACE_E("CSalamanderGeneral::DialogQuestion: unknow flags=0x" << std::hex << flags << std::dec); - return DIALOG_FAIL; - } - } - int res = (int)CHiddenOrSystemDlg(parent, title != NULL ? title : ::LoadStr(IDS_QUESTION), fileName, - question, yesnocancel, yesallcancel) - .Execute(); - if (mainWnd != NULL) - FlashWindow(mainWnd, FALSE); - switch (res) - { - case IDYES: - return DIALOG_YES; - case IDNO: - return DIALOG_NO; - case IDB_ALL: - return DIALOG_ALL; - case IDB_SKIP: - return DIALOG_SKIP; - case IDB_SKIPALL: - return DIALOG_SKIPALL; - case IDCANCEL: - return DIALOG_CANCEL; - default: - return DIALOG_FAIL; - } -} - -int CSalamanderGeneral::DialogQuestion(HWND parent, DWORD flags, const char* fileName, - const char* question, const char* title) -{ - if (fileName == NULL || question == NULL) - { - TRACE_E("Invalid parametr (fileName == NULL || question == NULL) in CSalamanderGeneral::DialogQuestion!"); - if (fileName == NULL) - fileName = ""; - if (question == NULL) - question = ""; - } - return ::DialogQuestion(parent, flags, fileName, question, title); -} - -HWND CSalamanderGeneral::GetMainWindowHWND() -{ - return MainWindow != NULL ? MainWindow->HWindow : NULL; -} - -void RestoreFocusInSourcePanel() -{ - if (MainWindow != NULL) - { - CFilesWindow* p1 = MainWindow->GetActivePanel(); - if (p1 != NULL) - { - if (!MainWindow->EditMode) - MainWindow->FocusPanel(p1); - else - { - if (MainWindow->EditWindow != NULL && MainWindow->EditWindow->HWindow != NULL) - SetFocus(MainWindow->EditWindow->HWindow); - } - } - } -} - -void CSalamanderGeneral::RestoreFocusInSourcePanel() -{ - ::RestoreFocusInSourcePanel(); -} - -BOOL CSalamanderGeneral::CheckAndCreateDirectory(const char* dir, HWND parent, BOOL quiet, - char* errBuf, int errBufSize, char* firstCreatedDir, - BOOL manualCrDir) -{ - return ::CheckAndCreateDirectory(dir, parent, quiet, errBuf, errBufSize, firstCreatedDir, FALSE, manualCrDir); -} - -BOOL CSalamanderGeneral::TestFreeSpace(HWND parent, const char* path, const CQuadWord& totalSize, - const char* messageTitle) -{ - return ::TestFreeSpace(parent, path, totalSize, messageTitle); -} - -void CSalamanderGeneral::GetDiskFreeSpace(CQuadWord* retValue, const char* path, CQuadWord* total) -{ - if (retValue == NULL) - { - TRACE_E("Unexpected situation in CSalamanderGeneral::GetDiskFreeSpace(): retValue is NULL!"); - return; - } - *retValue = MyGetDiskFreeSpace(path, total); -} - -BOOL CSalamanderGeneral::SalGetDiskFreeSpace(const char* path, LPDWORD lpSectorsPerCluster, - LPDWORD lpBytesPerSector, LPDWORD lpNumberOfFreeClusters, - LPDWORD lpTotalNumberOfClusters) -{ - return MyGetDiskFreeSpace(path, lpSectorsPerCluster, lpBytesPerSector, - lpNumberOfFreeClusters, lpTotalNumberOfClusters); -} - -BOOL CSalamanderGeneral::SalGetVolumeInformation(const char* path, char* rootOrCurReparsePoint, LPTSTR lpVolumeNameBuffer, - DWORD nVolumeNameSize, LPDWORD lpVolumeSerialNumber, - LPDWORD lpMaximumComponentLength, LPDWORD lpFileSystemFlags, - LPTSTR lpFileSystemNameBuffer, DWORD nFileSystemNameSize) -{ - return MyGetVolumeInformation(path, rootOrCurReparsePoint, NULL, NULL, lpVolumeNameBuffer, nVolumeNameSize, - lpVolumeSerialNumber, lpMaximumComponentLength, lpFileSystemFlags, - lpFileSystemNameBuffer, nFileSystemNameSize); -} - -UINT CSalamanderGeneral::SalGetDriveType(const char* path) -{ - return MyGetDriveType(path); -} - -void CSalamanderGeneral::RemoveTemporaryDir(const char* dir) -{ - ::RemoveTemporaryDir(dir); -} - -void CSalamanderGeneral::PrepareMask(char* mask, const char* src) -{ - ::PrepareMask(mask, src); -} - -BOOL CSalamanderGeneral::AgreeMask(const char* filename, const char* mask, BOOL hasExtension) -{ - return ::AgreeMask(filename, mask, hasExtension, FALSE); -} - -char* CSalamanderGeneral::MaskName(char* buffer, int bufSize, const char* name, const char* mask) -{ - return ::MaskName(buffer, bufSize, name, mask); -} - -void CSalamanderGeneral::PrepareExtMask(char* mask, const char* src) -{ - ::PrepareMask(mask, src); -} - -BOOL CSalamanderGeneral::AgreeExtMask(const char* filename, const char* mask, BOOL hasExtension) -{ - return ::AgreeMask(filename, mask, hasExtension, TRUE); -} - -void* CSalamanderGeneral::Alloc(int size) -{ - return malloc(size); -} - -void* CSalamanderGeneral::Realloc(void* ptr, int size) -{ - return realloc(ptr, size); -} - -void CSalamanderGeneral::Free(void* ptr) -{ - free(ptr); -} - -char* CSalamanderGeneral::DupStr(const char* str) -{ - return ::DupStr(str); -} - -void CSalamanderGeneral::GetLowerAndUpperCase(unsigned char** lowerCase, unsigned char** upperCase) -{ - CALL_STACK_MESSAGE1("CSalamanderGeneral::GetLowerAndUpperCase(,)"); - if (lowerCase != NULL) - *lowerCase = LowerCase; - if (upperCase != NULL) - *upperCase = UpperCase; -} - -void CSalamanderGeneral::ToLowerCase(char* str) -{ - CALL_STACK_MESSAGE1("CSalamanderGeneral::ToLowerCase()"); - char* toLow = str; - while (*toLow != 0) - { - *toLow = LowerCase[*toLow]; - toLow++; - } -} - -void CSalamanderGeneral::ToUpperCase(char* str) -{ - CALL_STACK_MESSAGE1("CSalamanderGeneral::ToUpperCase()"); - char* toUpp = str; - while (*toUpp != 0) - { - *toUpp = UpperCase[*toUpp]; - toUpp++; - } -} - -int CSalamanderGeneral::StrCmpEx(const char* s1, int l1, const char* s2, int l2) -{ - return ::StrCmpEx(s1, l1, s2, l2); -} - -int CSalamanderGeneral::StrICpy(char* dest, const char* src) -{ - return ::StrICpy(dest, src); -} - -int CSalamanderGeneral::StrICmp(const char* s1, const char* s2) -{ - return ::StrICmp(s1, s2); -} - -int CSalamanderGeneral::StrICmpEx(const char* s1, int l1, const char* s2, int l2) -{ - return ::StrICmpEx(s1, l1, s2, l2); -} - -int CSalamanderGeneral::StrNICmp(const char* s1, const char* s2, int n) -{ - return ::StrNICmp(s1, s2, n); -} - -int CSalamanderGeneral::MemICmp(const void* buf1, const void* buf2, int n) -{ - return ::MemICmp(buf1, buf2, n); -} - -int CSalamanderGeneral::RegSetStrICmp(const char* s1, const char* s2) -{ - return ::RegSetStrICmp(s1, s2); -} - -int CSalamanderGeneral::RegSetStrICmpEx(const char* s1, int l1, const char* s2, int l2, BOOL* numericalyEqual) -{ - return ::RegSetStrICmpEx(s1, l1, s2, l2, numericalyEqual); -} - -int CSalamanderGeneral::RegSetStrCmp(const char* s1, const char* s2) -{ - return ::RegSetStrCmp(s1, s2); -} - -int CSalamanderGeneral::RegSetStrCmpEx(const char* s1, int l1, const char* s2, int l2, BOOL* numericalyEqual) -{ - return ::RegSetStrCmpEx(s1, l1, s2, l2, numericalyEqual); -} - -CFilesWindow* -CSalamanderGeneral::GetPanel(int panel) -{ - return MainWindow->GetPanel(panel); -} - -BOOL CSalamanderGeneral::GetPanelPath(int panel, char* buffer, int bufferSize, int* type, - char** archiveOrFS, BOOL convertFSPathToExternal) -{ - CALL_STACK_MESSAGE3("CSalamanderGeneral::GetPanelPath(%d, , %d, ,)", panel, bufferSize); - if (type != NULL) - *type = 0; // unknown - if (archiveOrFS != NULL) - *archiveOrFS = NULL; - if (bufferSize > 0) - buffer[0] = 0; - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::GetPanelPath() only from main thread!"); - if (type != NULL) - *type = 0; - return FALSE; - } - CFilesWindow* p = GetPanel(panel); - if (p != NULL) - { - char buf[2 * MAX_PATH]; - int offset = -1; // offset into the buffer for computing archiveOrFS (-1 means NULL) - if (p->Is(ptZIPArchive)) - { - if (type != NULL) - *type = PATH_TYPE_ARCHIVE; - offset = (int)strlen(p->GetZIPArchive()); - memcpy(buf, p->GetZIPArchive(), offset + 1); - if (p->GetZIPPath()[0] != 0) - { - if (p->GetZIPPath()[0] != '\\') - strcpy(buf + offset, "\\"); - strcat(buf + offset, p->GetZIPPath()); - } - } - else - { - if (p->Is(ptPluginFS)) - { - if (type != NULL) - *type = PATH_TYPE_FS; - offset = (int)strlen(p->GetPluginFS()->GetPluginFSName()); - memcpy(buf, p->GetPluginFS()->GetPluginFSName(), offset); - buf[offset] = ':'; - if (!p->GetPluginFS()->NotEmpty() || !p->GetPluginFS()->GetCurrentPath(buf + offset + 1)) - { - return FALSE; // error - } - if (convertFSPathToExternal) - { - p->GetPluginFS()->GetPluginInterfaceForFS()->ConvertPathToExternal(p->GetPluginFS()->GetPluginFSName(), - p->GetPluginFS()->GetPluginFSNameIndex(), - buf + offset + 1); - } - } - else - { - if (p->Is(ptDisk)) - { - if (type != NULL) - *type = PATH_TYPE_WINDOWS; - strcpy(buf, p->GetPath()); - } - else - { - TRACE_E("Unexpected situation in CSalamanderGeneral::GetPanelPath()"); - return FALSE; - } - } - } - - int l = (int)strlen(buf) + 1; - if (l > bufferSize) - return bufferSize == 0; // if the user does not want the path back, we do not treat it as an error - memcpy(buffer, buf, l); - - if (archiveOrFS != NULL && offset != -1) - *archiveOrFS = buffer + offset; - - return TRUE; - } - return FALSE; -} - -BOOL CSalamanderGeneral::GetLastWindowsPanelPath(int panel, char* buffer, int bufferSize) -{ - CALL_STACK_MESSAGE3("CSalamanderGeneral::GetLastWindowsPanelPath(%d, , %d)", panel, bufferSize); - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::GetLastWindowsPanelPath() only from main thread!"); - return FALSE; - } - CFilesWindow* p = GetPanel(panel); - if (p != NULL && buffer != NULL) - { - int l = (int)strlen(p->GetPath()) + 1; - if (l > bufferSize) - return FALSE; - memcpy(buffer, p->GetPath(), l); - return TRUE; - } - return FALSE; -} - -CPluginDataInterfaceAbstract* -CSalamanderGeneral::GetPanelPluginData(int panel) -{ - CALL_STACK_MESSAGE2("CSalamanderGeneral::GetPanelPluginData(%d)", panel); - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::GetPanelPluginData() only from main thread!"); - return NULL; - } - CFilesWindow* p = GetPanel(panel); - if (p != NULL) - { - CPluginDataInterfaceAbstract* iface = p->PluginData.GetInterface(); - if (iface != NULL && p->PluginData.GetPluginInterface() != Plugin) - iface = NULL; // the object is not from this plugin -> it gets nothing - return iface; - } - return NULL; -} - -CPluginFSInterfaceAbstract* -CSalamanderGeneral::GetPanelPluginFS(int panel) -{ - CALL_STACK_MESSAGE2("CSalamanderGeneral::GetPanelPluginFS(%d)", panel); - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::GetPanelPluginFS() only from main thread!"); - return NULL; - } - CFilesWindow* p = GetPanel(panel); - if (p != NULL && p->Is(ptPluginFS)) - { - CPluginFSInterfaceAbstract* iface = p->GetPluginFS()->GetInterface(); - if (iface != NULL && p->GetPluginFS()->GetPluginInterface() != Plugin) - iface = NULL; // the object is not from this plugin -> it gets nothing - return iface; - } - return NULL; -} - -const CFileData* -CSalamanderGeneral::GetPanelFocusedItem(int panel, BOOL* isDir) -{ - CALL_STACK_MESSAGE2("CSalamanderGeneral::GetPanelFocusedItem(%d,)", panel); - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::GetPanelFocusedItem() only from main thread!"); - return NULL; - } - CFilesWindow* p = GetPanel(panel); - if (p != NULL) - { - int caret = p->GetCaretIndex(); - if (caret >= 0 && caret < p->Files->Count + p->Dirs->Count) - { - if (isDir != NULL) - *isDir = caret < p->Dirs->Count; - return (caret < p->Dirs->Count) ? &p->Dirs->At(caret) : &p->Files->At(caret - p->Dirs->Count); - } - } - return NULL; -} - -const CFileData* -CSalamanderGeneral::GetPanelItem(int panel, int* index, BOOL* isDir) -{ - CALL_STACK_MESSAGE2("CSalamanderGeneral::GetPanelItem(%d,)", panel); - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::GetPanelItem() only from main thread!"); - return NULL; - } - CFilesWindow* p = GetPanel(panel); - if (p != NULL && index != NULL) - { - int i = *index; - if (i < 0) - return NULL; // enumeration already finished - if (i < p->Files->Count + p->Dirs->Count) // enumerate more items - { - *index = i + 1; // next time move to the following item - if (isDir != NULL) - *isDir = i < p->Dirs->Count; - return (i < p->Dirs->Count) ? &p->Dirs->At(i) : &p->Files->At(i - p->Dirs->Count); - } - else - { - *index = -1; // end of enumeration - return NULL; - } - } - return NULL; -} - -BOOL CSalamanderGeneral::GetPanelSelection(int panel, int* selectedFiles, int* selectedDirs) -{ - CALL_STACK_MESSAGE2("CSalamanderGeneral::GetPanelSelection(%d, ,)", panel); - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::GetPanelSelection() only from main thread!"); - return FALSE; - } - CFilesWindow* p = GetPanel(panel); - if (p != NULL) - { - int count = p->GetSelCount(); - int selDirs = 0; - if (count > 0) - { - CFilesArray* dirs = p->Dirs; - // count how many directories are selected (the rest of the selected items are files) - int i; - for (i = 0; i < dirs->Count; i++) // ".." cannot be selected; the test would be pointless - { - if (dirs->At(i).Selected) - selDirs++; - } - } - else - count = 0; - - if (selectedDirs != NULL) - *selectedDirs = selDirs; - if (selectedFiles != NULL) - *selectedFiles = count - selDirs; - - int i = p->GetCaretIndex(); - return p->Dirs->Count + p->Files->Count > 0 && // the panel is not empty - (i != 0 || count > 0 || p->Dirs->Count == 0 || - strcmp(p->Dirs->At(0).Name, "..") != 0); // the focus is not on the up-dir, or at least one item is selected - } - return FALSE; -} - -const CFileData* -CSalamanderGeneral::GetPanelSelectedItem(int panel, int* index, BOOL* isDir) -{ - SLOW_CALL_STACK_MESSAGE2("CSalamanderGeneral::GetPanelSelectedItem(%d, ,)", panel); - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::GetPanelSelectedItem() only from main thread!"); - return NULL; - } - CFilesWindow* p = GetPanel(panel); - if (p != NULL && index != NULL) - { - int i = *index; - if (i < 0) - return NULL; // enumeration already finished - while (i < p->Files->Count + p->Dirs->Count) // searching for the next selected item - { - CFileData* data = (i < p->Dirs->Count) ? &p->Dirs->At(i) : &p->Files->At(i - p->Dirs->Count); - if (data->Selected) // selected item? - { - *index = i + 1; // next time start searching from the following item - if (isDir != NULL) - *isDir = i < p->Dirs->Count; - return data; // return the found selected item - } - i++; - } - *index = -1; // end of enumeration; no selected items remain - } - return NULL; -} - -void CSalamanderGeneral::SelectPanelItem(int panel, const CFileData* file, BOOL select) -{ - CALL_STACK_MESSAGE3("CSalamanderGeneral::SelectPanelItem(%d, , %d)", panel, select); - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::SelectPanelItem() only from main thread!"); - return; - } - CFilesWindow* p = GetPanel(panel); - if (p != NULL) - { - int index = -1; // index of 'file' in the panel - if (p->Dirs->Count > 0) - { - CFileData* first = &p->Dirs->At(0); - CFileData* last = &p->Dirs->At(p->Dirs->Count - 1); - if (first <= file && file <= last) - index = (int)(file - first); // it is a directory - } - if (index == -1 && p->Files->Count > 0) - { - CFileData* first = &p->Files->At(0); - CFileData* last = &p->Files->At(p->Files->Count - 1); - if (first <= file && file <= last) - index = p->Dirs->Count + (int)(file - first); // it is a directory - } - if (index != -1) - p->SetSel(select, index, FALSE); // change selection - else - TRACE_E("Invalid parameter 'file' in CSalamanderGeneral::SelectPanelItem()."); - } -} - -void CSalamanderGeneral::RepaintChangedItems(int panel) -{ - CALL_STACK_MESSAGE2("CSalamanderGeneral::RepaintChangedItems(%d)", panel); - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::RepaintChangedItems() only from main thread!"); - return; - } - CFilesWindow* p = GetPanel(panel); - if (p != NULL) - { - p->RepaintListBox(DRAWFLAG_DIRTY_ONLY | DRAWFLAG_SKIP_VISTEST); - PostMessage(p->HWindow, WM_USER_SELCHANGED, 0, 0); // sel-change notify - } -} - -void CSalamanderGeneral::SelectAllPanelItems(int panel, BOOL select, BOOL repaint) -{ - CALL_STACK_MESSAGE4("CSalamanderGeneral::SelectAllPanelItems(%d, %d, %d)", panel, select, repaint); - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::SelectAllPanelItems() only from main thread!"); - return; - } - CFilesWindow* p = GetPanel(panel); - if (p != NULL) - { - p->SetSel(select, -1, repaint); // change selection - if (repaint) - PostMessage(p->HWindow, WM_USER_SELCHANGED, 0, 0); // sel-change notify - } -} - -void CSalamanderGeneral::SetPanelFocusedItem(int panel, const CFileData* file, BOOL partVis) -{ - CALL_STACK_MESSAGE3("CSalamanderGeneral::SetPanelFocusedItem(%d, , %d)", panel, partVis); - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::SetPanelFocusedItem() only from main thread!"); - return; - } - CFilesWindow* p = GetPanel(panel); - if (p != NULL) - { - int index = -1; // index of 'file' in the panel - if (p->Dirs->Count > 0) - { - CFileData* first = &p->Dirs->At(0); - CFileData* last = &p->Dirs->At(p->Dirs->Count - 1); - if (first <= file && file <= last) - index = (int)(file - first); // it is a directory - } - if (index == -1 && p->Files->Count > 0) - { - CFileData* first = &p->Files->At(0); - CFileData* last = &p->Files->At(p->Files->Count - 1); - if (first <= file && file <= last) - index = p->Dirs->Count + (int)(file - first); // it is a directory - } - if (index != -1) - p->SetCaretIndex(index, partVis); // change focus - else - TRACE_E("Invalid parameter 'file' in CSalamanderGeneral::SetPanelFocusedItem()."); - } -} - -BOOL CSalamanderGeneral::GetFilterFromPanel(int panel, char* masks, int masksBufSize) -{ - CALL_STACK_MESSAGE3("CSalamanderGeneral::GetFilterFromPanel(%d, , %d)", panel, masksBufSize); - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::GetFilterFromPanel() only from main thread!"); - return FALSE; - } - CFilesWindow* p = GetPanel(panel); - BOOL ret = FALSE; - if (p != NULL && p->FilterEnabled) - { - int len = (int)strlen(p->Filter.GetMasksString()); - if (len < masksBufSize) - { - memcpy(masks, p->Filter.GetMasksString(), len + 1); - ret = TRUE; - } - } - return ret; -} - -// returns the position of the source panel (is it on the left or on the right?), returns PANEL_LEFT or PANEL_RIGHT -int CSalamanderGeneral::GetSourcePanel() -{ - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::GetSourcePanel() only from main thread!"); - return PANEL_LEFT; - } - if (MainWindow->GetActivePanel() == MainWindow->LeftPanel) - return PANEL_LEFT; - else - return PANEL_RIGHT; -} - -// activates the other panel (like the TAB key); panels marked through PANEL_SOURCE and PANEL_TARGET -// swap naturally as a result -void CSalamanderGeneral::ChangePanel() -{ - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::ChangePanel() only from main thread!"); - return; - } - MainWindow->ChangePanel(); -} - -void CSalamanderGeneral::SkipOneActivateRefresh() -{ - ::SkipOneActivateRefresh = TRUE; - PostMessage(MainWindow->HWindow, WM_USER_SKIPONEREFRESH, 0, 0); -} - -BOOL CSalamanderGeneral::SalGetTempFileName(const char* path, const char* prefix, char* tmpName, BOOL file, DWORD* err) -{ - CALL_STACK_MESSAGE1("CSalamanderGeneral::SalGetTempFileName()"); - BOOL ret = ::SalGetTempFileName(path, prefix, tmpName, file); - if (err != NULL) - *err = GetLastError(); - return ret; -} - -char* CSalamanderGeneral::NumberToStr(char* buffer, const CQuadWord& number) -{ - return ::NumberToStr(buffer, number); -} - -char* CSalamanderGeneral::PrintDiskSize(char* buf, const CQuadWord& size, int mode) -{ - return ::PrintDiskSize(buf, size, mode); -} - -char* CSalamanderGeneral::PrintTimeLeft(char* buf, const CQuadWord& secs) -{ - return ::PrintTimeLeft(buf, secs); -} - -BOOL CSalamanderGeneral::HasTheSameRootPath(const char* path1, const char* path2) -{ - return ::HasTheSameRootPath(path1, path2); -} - -int CSalamanderGeneral::CommonPrefixLength(const char* path1, const char* path2) -{ - return ::CommonPrefixLength(path1, path2); -} - -BOOL CSalamanderGeneral::PathIsPrefix(const char* prefix, const char* path) -{ - return ::SalPathIsPrefix(prefix, path); -} - -BOOL CSalamanderGeneral::IsTheSamePath(const char* path1, const char* path2) -{ - return ::IsTheSamePath(path1, path2); -} - -int CSalamanderGeneral::GetRootPath(char* root, const char* path) -{ - return ::GetRootPath(root, path); -} - -BOOL CSalamanderGeneral::CutDirectory(char* path, char** cutDir) -{ - return ::CutDirectory(path, cutDir); -} - -BOOL CSalamanderGeneral::SalPathAppend(char* path, const char* name, int pathSize) -{ - return ::SalPathAppend(path, name, pathSize); -} - -BOOL CSalamanderGeneral::SalPathAddBackslash(char* path, int pathSize) -{ - return ::SalPathAddBackslash(path, pathSize); -} - -void CSalamanderGeneral::SalPathRemoveBackslash(char* path) -{ - ::SalPathRemoveBackslash(path); -} - -void CSalamanderGeneral::SalPathStripPath(char* path) -{ - ::SalPathStripPath(path); -} - -void CSalamanderGeneral::SalPathRemoveExtension(char* path) -{ - ::SalPathRemoveExtension(path); -} - -BOOL CSalamanderGeneral::SalPathAddExtension(char* path, const char* extension, int pathSize) -{ - return ::SalPathAddExtension(path, extension, pathSize); -} - -BOOL CSalamanderGeneral::SalPathRenameExtension(char* path, const char* extension, int pathSize) -{ - return ::SalPathRenameExtension(path, extension, pathSize); -} - -const char* -CSalamanderGeneral::SalPathFindFileName(const char* path) -{ - return ::SalPathFindFileName(path); -} - -BOOL CSalamanderGeneral::SalGetFullName(char* name, int* errTextID, const char* curDir, - char* nextFocus, int nameBufSize) -{ - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::SalGetFullName() only from main thread!"); - if (errTextID != NULL) - *errTextID = GFN_PATHISINVALID; - return FALSE; - } - BOOL ret = ::SalGetFullName(name, errTextID, curDir, nextFocus, NULL, nameBufSize); - if (errTextID != NULL) - { - switch (*errTextID) - { - case IDS_SERVERNAMEMISSING: - *errTextID = GFN_SERVERNAMEMISSING; - break; - case IDS_SHARENAMEMISSING: - *errTextID = GFN_SHARENAMEMISSING; - break; - case IDS_TOOLONGPATH: - *errTextID = GFN_TOOLONGPATH; - break; - case IDS_INVALIDDRIVE: - *errTextID = GFN_INVALIDDRIVE; - break; - case IDS_INCOMLETEFILENAME: - *errTextID = GFN_INCOMLETEFILENAME; - break; - case IDS_EMPTYNAMENOTALLOWED: - *errTextID = GFN_EMPTYNAMENOTALLOWED; - break; - case IDS_PATHISINVALID: - *errTextID = GFN_PATHISINVALID; - break; - } - } - - return ret; -} - -void CSalamanderGeneral::SalUpdateDefaultDir(BOOL activePrefered) -{ - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::SalUpdateDefaultDir() only from main thread!"); - return; - } - if (MainWindow != NULL) - MainWindow->UpdateDefaultDir(activePrefered); -} - -char* CSalamanderGeneral::GetGFNErrorText(int GFN, char* buf, int bufSize) -{ - char* s = NULL; - switch (GFN) - { - case GFN_SERVERNAMEMISSING: - s = ::LoadStr(IDS_SERVERNAMEMISSING); - break; - case GFN_SHARENAMEMISSING: - s = ::LoadStr(IDS_SHARENAMEMISSING); - break; - case GFN_TOOLONGPATH: - s = ::LoadStr(IDS_TOOLONGPATH); - break; - case GFN_INVALIDDRIVE: - s = ::LoadStr(IDS_INVALIDDRIVE); - break; - case GFN_INCOMLETEFILENAME: - s = ::LoadStr(IDS_INCOMLETEFILENAME); - break; - case GFN_EMPTYNAMENOTALLOWED: - s = ::LoadStr(IDS_EMPTYNAMENOTALLOWED); - break; - case GFN_PATHISINVALID: - s = ::LoadStr(IDS_PATHISINVALID); - break; - } - if (s != NULL) - lstrcpyn(buf, s, bufSize); - else - buf[0] = 0; - return buf; -} - -char* CSalamanderGeneral::GetErrorText(int err, char* buf, int bufSize) -{ - if (buf == NULL || bufSize == 0) - return ::GetErrorText(err); - - int l = 0; - if (bufSize > 20) - l = sprintf(buf, "(%d) ", err); - if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, - NULL, - err, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - buf + l, - bufSize - l, - NULL) == 0 || - bufSize > l && *(buf + l) == 0) - { - char txt[100]; - sprintf(txt, "System error %d, text description is not available.", err); - lstrcpyn(buf, txt, bufSize); - } - return buf; -} - -char* CSalamanderGeneral::LoadStr(HINSTANCE module, int resID) -{ - if (module == NULL) - { - TRACE_E("CSalamanderGeneral::LoadStr(): module == NULL"); - static char buffEmpty[] = "ERROR LOADING STRING"; - return buffEmpty; - } - return ::LoadStr(resID, module); -} - -WCHAR* -CSalamanderGeneral::LoadStrW(HINSTANCE module, int resID) -{ - if (module == NULL) - { - TRACE_E("CSalamanderGeneral::LoadStrW(): module == NULL"); - static wchar_t buffEmpty[] = L"ERROR LOADING WIDE STRING"; - return buffEmpty; - } - return ::LoadStrW(resID, module); -} - -COLORREF -CSalamanderGeneral::GetCurrentColor(int color) -{ - int index; - SALCOLOR* arr = CurrentColors; - switch (color) - { - // CurrentColors - case SALCOL_FOCUS_ACTIVE_NORMAL: - index = FOCUS_ACTIVE_NORMAL; - break; - case SALCOL_FOCUS_ACTIVE_SELECTED: - index = FOCUS_ACTIVE_SELECTED; - break; - case SALCOL_FOCUS_FG_INACTIVE_NORMAL: - index = FOCUS_FG_INACTIVE_NORMAL; - break; - case SALCOL_FOCUS_FG_INACTIVE_SELECTED: - index = FOCUS_FG_INACTIVE_SELECTED; - break; - case SALCOL_FOCUS_BK_INACTIVE_NORMAL: - index = FOCUS_BK_INACTIVE_NORMAL; - break; - case SALCOL_FOCUS_BK_INACTIVE_SELECTED: - index = FOCUS_BK_INACTIVE_SELECTED; - break; - case SALCOL_ITEM_FG_NORMAL: - index = ITEM_FG_NORMAL; - break; - case SALCOL_ITEM_FG_SELECTED: - index = ITEM_FG_SELECTED; - break; - case SALCOL_ITEM_FG_FOCUSED: - index = ITEM_FG_FOCUSED; - break; - case SALCOL_ITEM_FG_FOCSEL: - index = ITEM_FG_FOCSEL; - break; - case SALCOL_ITEM_FG_HIGHLIGHT: - index = ITEM_FG_HIGHLIGHT; - break; - case SALCOL_ITEM_BK_NORMAL: - index = ITEM_BK_NORMAL; - break; - case SALCOL_ITEM_BK_SELECTED: - index = ITEM_BK_SELECTED; - break; - case SALCOL_ITEM_BK_FOCUSED: - index = ITEM_BK_FOCUSED; - break; - case SALCOL_ITEM_BK_FOCSEL: - index = ITEM_BK_FOCSEL; - break; - case SALCOL_ITEM_BK_HIGHLIGHT: - index = ITEM_BK_HIGHLIGHT; - break; - case SALCOL_ICON_BLEND_SELECTED: - index = ICON_BLEND_SELECTED; - break; - case SALCOL_ICON_BLEND_FOCUSED: - index = ICON_BLEND_FOCUSED; - break; - case SALCOL_ICON_BLEND_FOCSEL: - index = ICON_BLEND_FOCSEL; - break; - case SALCOL_PROGRESS_FG_NORMAL: - index = PROGRESS_FG_NORMAL; - break; - case SALCOL_PROGRESS_FG_SELECTED: - index = PROGRESS_FG_SELECTED; - break; - case SALCOL_PROGRESS_BK_NORMAL: - index = PROGRESS_BK_NORMAL; - break; - case SALCOL_PROGRESS_BK_SELECTED: - index = PROGRESS_BK_SELECTED; - break; - case SALCOL_HOT_PANEL: - index = HOT_PANEL; - break; - case SALCOL_HOT_ACTIVE: - index = HOT_ACTIVE; - break; - case SALCOL_HOT_INACTIVE: - index = HOT_INACTIVE; - break; - case SALCOL_ACTIVE_CAPTION_FG: - index = ACTIVE_CAPTION_FG; - break; - case SALCOL_ACTIVE_CAPTION_BK: - index = ACTIVE_CAPTION_BK; - break; - case SALCOL_INACTIVE_CAPTION_FG: - index = INACTIVE_CAPTION_FG; - break; - case SALCOL_INACTIVE_CAPTION_BK: - index = INACTIVE_CAPTION_BK; - break; - case SALCOL_THUMBNAIL_NORMAL: - index = THUMBNAIL_FRAME_NORMAL; - break; - case SALCOL_THUMBNAIL_SELECTED: - index = THUMBNAIL_FRAME_FOCUSED; - break; - case SALCOL_THUMBNAIL_FOCUSED: - index = THUMBNAIL_FRAME_SELECTED; - break; - case SALCOL_THUMBNAIL_FOCSEL: - index = THUMBNAIL_FRAME_FOCSEL; - break; - // ViewerColors - case SALCOL_VIEWER_FG_NORMAL: - index = VIEWER_FG_NORMAL; - arr = ViewerColors; - break; - case SALCOL_VIEWER_BK_NORMAL: - index = VIEWER_BK_NORMAL; - arr = ViewerColors; - break; - case SALCOL_VIEWER_FG_SELECTED: - index = VIEWER_FG_SELECTED; - arr = ViewerColors; - break; - case SALCOL_VIEWER_BK_SELECTED: - index = VIEWER_BK_SELECTED; - arr = ViewerColors; - break; - - default: - { - TRACE_E("Invalid color constant!"); - return COLORREF(0); - } - } - return GetCOLORREF(arr[index]); -} - -void CSalamanderGeneral::GetPluginFSName(char* buf, int fsNameIndex) -{ - CALL_STACK_MESSAGE2("CSalamanderGeneral::GetPluginFSName(, %d)", fsNameIndex); - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::GetPluginFSName() only from main thread!"); - buf[0] = 0; - return; - } - CPluginData* data = Plugins.GetPluginData(Plugin); - if (data != NULL && data->SupportFS && fsNameIndex >= 0 && fsNameIndex < data->FSNames.Count) - lstrcpyn(buf, data->FSNames[fsNameIndex], MAX_PATH); - else - { - TRACE_E("CSalamanderGeneral::GetPluginFSName(): incorrect call (not supporting FS or 'fsNameIndex' is out of range)!"); - buf[0] = 0; - } -} - -BOOL CSalamanderGeneral::SetFlagLoadOnSalamanderStart(BOOL start) -{ - CALL_STACK_MESSAGE2("CSalamanderGeneral::SetFlagLoadOnSalamanderStart(%d)", start); - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::SetFlagLoadOnSalamanderStart() only from main thread!"); - return FALSE; - } - CPluginData* data = Plugins.GetPluginData(Plugin); - if (data != NULL) - { - BOOL prev = data->LoadOnStart != 0; - data->LoadOnStart = start != 0; - return prev; - } - else - { - TRACE_E("Unexpected situation in CSalamanderGeneral::SetFlagLoadOnSalamanderStart()."); - return FALSE; - } -} - -void CSalamanderGeneral::PostUnloadThisPlugin() -{ - CALL_STACK_MESSAGE1("CSalamanderGeneral::PostUnloadThisPlugin()"); - if (MainThreadID == GetCurrentThreadId()) - { // because of calls from the entry point where Plugin is set to -1 (just to look up plugin data) - // before WM_USER_POSTCMDORUNLOADPLUGIN would arrive, Plugin would be reset (according to the entry point's return value) - CPluginData* data = Plugins.GetPluginData(Plugin); - if (data != NULL) - { - data->ShouldUnload = TRUE; - ExecCmdsOrUnloadMarkedPlugins = TRUE; - } - else - { - TRACE_E("Unexpected situation in CSalamanderGeneral::PostUnloadThisPlugin()."); - } - } - else // outside the entry point the Plugin is certainly set... - { - if (MainWindow != NULL && MainWindow->HWindow != NULL) - { - // check for a call while the entry point is starting (Plugin is set to -1) - if ((INT_PTR)Plugin == -1) - { - TRACE_E("You can call CSalamanderGeneral::PostUnloadThisPlugin only from main " - "thread when plugin entry-point is not finished yet!"); - } - else - { - PostMessage(MainWindow->HWindow, WM_USER_POSTCMDORUNLOADPLUGIN, (WPARAM)Plugin, 0); - } - } - else - { - TRACE_E("Unexpected situation (2) in CSalamanderGeneral::PostUnloadThisPlugin()."); - } - } -} - -void CSalamanderGeneral::PostPluginMenuChanged() -{ - CALL_STACK_MESSAGE1("CSalamanderGeneral::PostPluginMenuChanged()"); - if (MainThreadID == GetCurrentThreadId()) - { // because of calls from the entry point where Plugin is set to -1 (just to look up plugin data) - // before WM_USER_POSTCMDORUNLOADPLUGIN would arrive, Plugin would be reset (according to the entry point's return value) - CPluginData* data = Plugins.GetPluginData(Plugin); - if (data != NULL) - { - data->ShouldRebuildMenu = TRUE; - ExecCmdsOrUnloadMarkedPlugins = TRUE; - } - else - { - TRACE_E("Unexpected situation in CSalamanderGeneral::PostPluginMenuChanged()."); - } - } - else // outside the entry point the Plugin is certainly set... - { - if (MainWindow != NULL && MainWindow->HWindow != NULL) - { - // check for a call while the entry point is starting (Plugin is set to -1) - if ((INT_PTR)Plugin == -1) - { - TRACE_E("You can call CSalamanderGeneral::PostPluginMenuChanged only from main " - "thread when plugin entry-point is not finished yet!"); - } - else - { - PostMessage(MainWindow->HWindow, WM_USER_POSTCMDORUNLOADPLUGIN, (WPARAM)Plugin, 1); - } - } - else - { - TRACE_E("Unexpected situation (2) in CSalamanderGeneral::PostPluginMenuChanged()."); - } - } -} - -void CSalamanderGeneral::PostMenuExtCommand(int id, BOOL waitForSalIdle) -{ - CALL_STACK_MESSAGE3("CSalamanderGeneral::PostMenuExtCommand(%d, %d)", id, waitForSalIdle); - if (waitForSalIdle) - { - if (id < 0 || id >= 1000000) - { - TRACE_E("CSalamanderGeneral::PostMenuExtCommand: id is invalid (" << id << " is not in range 0-999999)."); - return; - } - - if (MainThreadID == GetCurrentThreadId()) - { // because of calls from the entry point where Plugin is set to -1 (just to look up plugin data) - // before WM_USER_POSTCMDORUNLOADPLUGIN would arrive, Plugin would be reset (according to the entry point's return value) - CPluginData* data = Plugins.GetPluginData(Plugin); - if (data != NULL) - { - data->Commands.Add(500 + id); // salCmd values are in the <0, 499> range; 500 is the first free number - ExecCmdsOrUnloadMarkedPlugins = TRUE; - } - else - { - TRACE_E("Unexpected situation in CSalamanderGeneral::PostMenuExtCommand()."); - } - } - else // outside the entry point the Plugin is certainly set... - { - if (MainWindow != NULL && MainWindow->HWindow != NULL) - { - // check for a call while the entry point is starting (Plugin is set to -1) - if ((INT_PTR)Plugin == -1) - { - TRACE_E("You can call CSalamanderGeneral::PostMenuExtCommand only from main " - "thread when plugin entry-point is not finished yet!"); - } - else - { // 0 - unload, 1 - rebuild menu, 2-501 salCmd, 502-1000501 menuCmd - PostMessage(MainWindow->HWindow, WM_USER_POSTCMDORUNLOADPLUGIN, (WPARAM)Plugin, 502 + id); - } - } - else - { - TRACE_E("Unexpected situation (2) in CSalamanderGeneral::PostMenuExtCommand()."); - } - } - } - else - { - if (MainThreadID == GetCurrentThreadId()) - { // check for a call from the entry point (Plugin is set to -1) - if ((INT_PTR)Plugin == -1) - { - TRACE_E("You may not call CSalamanderGeneral::PostMenuExtCommand from entry-point!"); - return; - } - } - else - { - // check for a call while the entry point is starting (Plugin is set to -1) - if ((INT_PTR)Plugin == -1) - { - TRACE_E("You may not call CSalamanderGeneral::PostMenuExtCommand when " - "entry-point is not finished yet!"); - return; - } - } - if (MainWindow != NULL && MainWindow->HWindow != NULL) - { - PostMessage(MainWindow->HWindow, WM_USER_POSTMENUEXTCMD, (WPARAM)Plugin, (LPARAM)id); - } - else - { - TRACE_E("Unexpected situation in CSalamanderGeneral::PostMenuExtCommand()."); - } - } -} - -BOOL CSalamanderGeneral::SalamanderIsNotBusy(DWORD* lastIdleTime) -{ - CALL_STACK_MESSAGE1("CSalamanderGeneral::SalamanderIsNotBusy()"); - return ::SalamanderIsNotBusy(lastIdleTime); -} - -void CSalamanderGeneral::CallLoadOrSaveConfiguration(BOOL load, - FSalLoadOrSaveConfiguration loadOrSaveFunc, - void* param) -{ - CALL_STACK_MESSAGE2("CSalamanderGeneral::CallLoadOrSaveConfiguration(%d, ,)", load); - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::CallLoadOrSaveConfiguration() only from main thread!"); - return; - } - CPluginData* data = Plugins.GetPluginData(Plugin); - if (data != NULL) - { - data->CallLoadOrSaveConfiguration(load, loadOrSaveFunc, param); - } - else - { - TRACE_E("Unexpected situation in CSalamanderGeneral::CallLoadOrSaveConfiguration()."); - } -} - -void CSalamanderGeneral::SetPluginBugReportInfo(const char* message, const char* email) -{ - CALL_STACK_MESSAGE2("CSalamanderGeneral::SetPluginBugReportInfo(%s)", message); - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::SetPluginBugReportInfo() only from main thread!"); - return; - } - CPluginData* data = Plugins.GetPluginData(Plugin); - if (data != NULL) - { - if (data->BugReportMessage != NULL) - free(data->BugReportMessage); - if (message != NULL) - data->BugReportMessage = ::DupStr(message); - else - data->BugReportMessage = NULL; - if (data->BugReportEMail != NULL) - free(data->BugReportEMail); - if (email != NULL) - { - data->BugReportEMail = ::DupStr(email); - if (data->BugReportEMail != NULL && strlen(data->BugReportEMail) > 100) - { - data->BugReportEMail[100] = 0; - } - } - else - data->BugReportEMail = NULL; - } - else - { - TRACE_E("Unexpected situation in CSalamanderGeneral::SetPluginBugReportInfo()."); - } -} - -void CSalamanderGeneral::FocusNameInPanel(int panel, const char* path, const char* name) -{ - CALL_STACK_MESSAGE4("CSalamanderGeneral::FocusNameInPanel(%d, %s, %s)", panel, path, name); - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::FocusNameInPanel() only from main thread!"); - return; - } - if (name == NULL || path == NULL) - { - TRACE_E("CSalamanderGeneral::FocusNameInPanel(): incorrect parameters (name == NULL || path == NULL)!"); - return; - } - CFilesWindow* p = GetPanel(panel); - char pathBackup[MAX_PATH + 200]; - char nameBackup[MAX_PATH + 200]; - lstrcpyn(pathBackup, path, MAX_PATH + 200); - lstrcpyn(nameBackup, name, MAX_PATH + 200); - if (p != NULL) - SendMessage(p->HWindow, WM_USER_FOCUSFILE, (WPARAM)nameBackup, (LPARAM)pathBackup); -} - -BOOL CSalamanderGeneral::ChangePanelPath(int panel, const char* path, int* failReason, - int suggestedTopIndex, const char* suggestedFocusName, - BOOL convertFSPathToInternal) -{ - CALL_STACK_MESSAGE6("CSalamanderGeneral::ChangePanelPath(%d, %s, , %d, %s, %d)", - panel, path, suggestedTopIndex, suggestedFocusName, convertFSPathToInternal); - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::ChangePanelPath() only from main thread!"); - if (failReason != NULL) - *failReason = CHPPFR_INVALIDPATH; - return FALSE; - } - CFilesWindow* p = GetPanel(panel); - if (p != NULL) - { - return p->ChangeDir(path, suggestedTopIndex, suggestedFocusName, 3 /*change-dir*/, - failReason, convertFSPathToInternal); - } - if (failReason != NULL) - *failReason = CHPPFR_INVALIDPATH; - return FALSE; -} - -BOOL CSalamanderGeneral::ChangePanelPathToDisk(int panel, const char* path, int* failReason, - int suggestedTopIndex, const char* suggestedFocusName) -{ - CALL_STACK_MESSAGE5("CSalamanderGeneral::ChangePanelPathToDisk(%d, %s, , %d, %s)", - panel, path, suggestedTopIndex, suggestedFocusName); - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::ChangePanelPathToDisk() only from main thread!"); - if (failReason != NULL) - *failReason = CHPPFR_INVALIDPATH; - return FALSE; - } - CFilesWindow* p = GetPanel(panel); - if (p != NULL) - { - return p->ChangePathToDisk(GetMsgBoxParent(), path, suggestedTopIndex, suggestedFocusName, - NULL, TRUE, FALSE, FALSE, failReason); - } - if (failReason != NULL) - *failReason = CHPPFR_INVALIDPATH; - return FALSE; -} - -BOOL CSalamanderGeneral::ChangePanelPathToArchive(int panel, const char* archive, const char* archivePath, - int* failReason, int suggestedTopIndex, - const char* suggestedFocusName, BOOL forceUpdate) -{ - CALL_STACK_MESSAGE7("CSalamanderGeneral::ChangePanelPathToArchive(%d, %s, %s, , %d, %s, %d)", - panel, archive, archivePath, suggestedTopIndex, suggestedFocusName, forceUpdate); - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::ChangePanelPathToArchive() only from main thread!"); - if (failReason != NULL) - *failReason = CHPPFR_INVALIDPATH; - return FALSE; - } - CFilesWindow* p = GetPanel(panel); - if (p != NULL) - { - return p->ChangePathToArchive(archive, archivePath, suggestedTopIndex, suggestedFocusName, - forceUpdate, NULL, TRUE, failReason); - } - if (failReason != NULL) - *failReason = CHPPFR_INVALIDPATH; - return FALSE; -} - -BOOL CSalamanderGeneral::ChangePanelPathToPluginFS(int panel, const char* fsName, const char* fsUserPart, - int* failReason, int suggestedTopIndex, - const char* suggestedFocusName, BOOL forceUpdate, - BOOL convertPathToInternal) -{ - CALL_STACK_MESSAGE8("CSalamanderGeneral::ChangePanelPathToPluginFS(%d, %s, %s, , %d, %s, %d, %d)", - panel, fsName, fsUserPart, suggestedTopIndex, suggestedFocusName, forceUpdate, - convertPathToInternal); - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::ChangePanelPathToPluginFS() only from main thread!"); - if (failReason != NULL) - *failReason = CHPPFR_INVALIDPATH; - return FALSE; - } - CFilesWindow* p = GetPanel(panel); - if (p != NULL) - { - return p->ChangePathToPluginFS(fsName, fsUserPart, suggestedTopIndex, suggestedFocusName, - forceUpdate, 2 /*report all errors*/, NULL, TRUE, failReason, - FALSE, FALSE, convertPathToInternal); - } - if (failReason != NULL) - *failReason = CHPPFR_INVALIDPATH; - return FALSE; -} - -BOOL CSalamanderGeneral::ChangePanelPathToDetachedFS(int panel, CPluginFSInterfaceAbstract* detachedFS, - int* failReason, int suggestedTopIndex, - const char* suggestedFocusName) -{ - CALL_STACK_MESSAGE4("CSalamanderGeneral::ChangePanelPathToDetachedFS(%d, , , %d, %s)", - panel, suggestedTopIndex, suggestedFocusName); - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::ChangePanelPathToDetachedFS() only from main thread!"); - if (failReason != NULL) - *failReason = CHPPFR_INVALIDPATH; - return FALSE; - } - CFilesWindow* p = GetPanel(panel); - if (p != NULL) - { - int fsIndex = -1; - CDetachedFSList* list = MainWindow->DetachedFSList; - int i; - for (i = 0; i < list->Count; i++) - { - if (list->At(i)->GetInterface() == detachedFS) - { - fsIndex = i; - break; - } - } - if (fsIndex != -1) - { - return p->ChangePathToDetachedFS(fsIndex, suggestedTopIndex, suggestedFocusName, TRUE, failReason); - } - else - { - TRACE_E("Parameter 'detachedFS' is not detached FS in " - "CSalamanderGeneral::ChangePanelPathToDetachedFS()."); - } - } - if (failReason != NULL) - *failReason = CHPPFR_INVALIDPATH; - return FALSE; -} - -BOOL CSalamanderGeneral::ChangePanelPathToFixedDrive(int panel, int* failReason) -{ - CALL_STACK_MESSAGE2("CSalamanderGeneral::ChangePanelPathToFixedDrive(%d,)", panel); - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::ChangePanelPathToFixedDrive() only from main thread!"); - if (failReason != NULL) - *failReason = CHPPFR_INVALIDPATH; - return FALSE; - } - CFilesWindow* p = GetPanel(panel); - if (p != NULL) - { - return p->ChangeToFixedDrive(GetMsgBoxParent(), NULL, TRUE, FALSE, failReason); - } - if (failReason != NULL) - *failReason = CHPPFR_INVALIDPATH; - return FALSE; -} - -BOOL CSalamanderGeneral::ChangePanelPathToRescuePathOrFixedDrive(int panel, int* failReason) -{ - CALL_STACK_MESSAGE2("CSalamanderGeneral::ChangePanelPathToRescuePathOrFixedDrive(%d,)", panel); - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::ChangePanelPathToRescuePathOrFixedDrive() only from main thread!"); - if (failReason != NULL) - *failReason = CHPPFR_INVALIDPATH; - return FALSE; - } - CFilesWindow* p = GetPanel(panel); - if (p != NULL) - { - return p->ChangeToRescuePathOrFixedDrive(GetMsgBoxParent(), NULL, TRUE, FALSE, FSTRYCLOSE_CHANGEPATH, failReason); - } - if (failReason != NULL) - *failReason = CHPPFR_INVALIDPATH; - return FALSE; -} - -void CSalamanderGeneral::RefreshPanelPath(int panel, BOOL forceRefresh, BOOL focusFirstNewItem) -{ - CALL_STACK_MESSAGE4("CSalamanderGeneral::RefreshPanelPath(%d, %d, %d)", - panel, forceRefresh, focusFirstNewItem); - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::RefreshPanelPath() only from main thread!"); - return; - } - CFilesWindow* p = GetPanel(panel); - if (p != NULL) - { - if (forceRefresh && p->Is(ptZIPArchive)) - { // for archives ensure a hard refresh by invalidating the archive stamp - p->SetZIPArchiveSize(CQuadWord(-1, -1)); - } - p->FocusFirstNewItem = focusFirstNewItem; - p->RefreshDirectory(FALSE, forceRefresh); - } -} - -void CSalamanderGeneral::PostRefreshPanelPath(int panel, BOOL focusFirstNewItem) -{ - CALL_STACK_MESSAGE3("CSalamanderGeneral::PostRefreshPanelPath(%d, %d)", panel, focusFirstNewItem); - CFilesWindow* p = GetPanel(panel); - if (p != NULL) - { - // post a hard refresh - HANDLES(EnterCriticalSection(&TimeCounterSection)); - int t1 = MyTimeCounter++; - HANDLES(LeaveCriticalSection(&TimeCounterSection)); - p->FocusFirstNewItem = focusFirstNewItem; // not synchronized (may be called outside the main thread) but should not matter - PostMessage(p->HWindow, WM_USER_REFRESH_DIR, 0, t1); - } -} - -void CSalamanderGeneral::PostRefreshPanelFS(CPluginFSInterfaceAbstract* modifiedFS, BOOL focusFirstNewItem) -{ - CALL_STACK_MESSAGE2("CSalamanderGeneral::PostRefreshPanelFS(, %d)", focusFirstNewItem); - PostRefreshPanelFS2(modifiedFS, focusFirstNewItem); -} - -BOOL CSalamanderGeneral::PostRefreshPanelFS2(CPluginFSInterfaceAbstract* modifiedFS, BOOL focusFirstNewItem) -{ - CALL_STACK_MESSAGE2("CSalamanderGeneral::PostRefreshPanelFS2(, %d)", focusFirstNewItem); - CFilesWindow* p = NULL; - if (MainWindow != NULL) - { - // no synchronization issue, because PluginFS is cleared only after CloseFS, which - // should terminate the thread monitoring FS changes (after CloseFS there should be no call to - // PostRefreshPanelFS2) - if (MainWindow->LeftPanel != NULL && MainWindow->LeftPanel->Is(ptPluginFS) && - MainWindow->LeftPanel->GetPluginFS()->Contains(modifiedFS)) - { - p = MainWindow->LeftPanel; - } - if (MainWindow->RightPanel != NULL && MainWindow->RightPanel->Is(ptPluginFS) && - MainWindow->RightPanel->GetPluginFS()->Contains(modifiedFS)) - { - p = MainWindow->RightPanel; - } - } - if (p != NULL) - { - // post a hard refresh - HANDLES(EnterCriticalSection(&TimeCounterSection)); - int t1 = MyTimeCounter++; - HANDLES(LeaveCriticalSection(&TimeCounterSection)); - p->FocusFirstNewItem = focusFirstNewItem; // not synchronized (may be called outside the main thread) but should not matter - PostMessage(p->HWindow, WM_USER_REFRESH_DIR, 0, t1); - return TRUE; - } - else - return FALSE; -} - -BOOL CSalamanderGeneral::CloseDetachedFS(HWND parent, CPluginFSInterfaceAbstract* detachedFS) -{ - CALL_STACK_MESSAGE1("CSalamanderGeneral::CloseDetachedFS(,)"); - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::CloseDetachedFS() only from main thread!"); - return FALSE; - } - if (MainWindow->DetachedFSList->IsGood()) // to guarantee Delete succeeds - { - CDetachedFSList* list = MainWindow->DetachedFSList; - int i; - for (i = 0; i < list->Count; i++) - { - if (list->At(i)->GetInterface() == detachedFS) - { - CPluginFSInterfaceEncapsulation* fs = list->At(i); - BOOL dummy; - if (fs->TryCloseOrDetach(FALSE, FALSE, dummy, FSTRYCLOSE_PLUGINCLOSEDETACHEDFS)) // the FS has no objection to closing - { - CPluginInterfaceForFSEncapsulation plugin(fs->GetPluginInterfaceForFS()->GetInterface(), - fs->GetPluginInterfaceForFS()->GetBuiltForVersion()); - if (plugin.NotEmpty()) - { - fs->ReleaseObject(parent); - plugin.CloseFS(fs->GetInterface()); - list->Delete(i); - if (!list->IsGood()) - list->ResetState(); - return TRUE; - } - else - TRACE_E("Unexpected situation in CSalamanderGeneral::CloseDetachedFS()"); - } - break; - } - } - } - return FALSE; -} - -BOOL CSalamanderGeneral::DuplicateAmpersands(char* buffer, int bufferSize) -{ - CALL_STACK_MESSAGE3("CSalamanderGeneral::DuplicateAmpersands(%s, %d)", buffer, bufferSize); - return ::DuplicateAmpersands(buffer, bufferSize); -} - -void CSalamanderGeneral::RemoveAmpersands(char* text) -{ - CALL_STACK_MESSAGE2("CSalamanderGeneral::RemoveAmpersands(%s)", text); - ::RemoveAmpersands(text); -} - -BOOL CSalamanderGeneral::ValidateVarString(HWND msgParent, const char* varText, int& errorPos1, int& errorPos2, - const CSalamanderVarStrEntry* variables) -{ - CALL_STACK_MESSAGE2("CSalamanderGeneral::ValidateVarString(, %s, , ,)", varText); - if (varText == NULL || variables == NULL) - { - TRACE_E("CSalamanderGeneral::ValidateVarString(): invalid parameters!"); - return FALSE; - } - return ::ValidateVarString(msgParent, varText, errorPos1, errorPos2, variables); -} - -BOOL CSalamanderGeneral::ExpandVarString(HWND msgParent, const char* varText, char* buffer, int bufferLen, - const CSalamanderVarStrEntry* variables, void* param, - BOOL ignoreEnvVarNotFoundOrTooLong, - DWORD* varPlacements, int* varPlacementsCount, - BOOL detectMaxVarWidths, int* maxVarWidths, - int maxVarWidthsCount) -{ - CALL_STACK_MESSAGE6("CSalamanderGeneral::ExpandVarString(, %s, , %d, , , %d, , , %d, , %d)", - varText, bufferLen, ignoreEnvVarNotFoundOrTooLong, detectMaxVarWidths, - maxVarWidthsCount); - if (bufferLen <= 0 || buffer == NULL || varText == NULL || variables == NULL) - { - TRACE_E("CSalamanderGeneral::ExpandVarString(): invalid parameters!"); - return FALSE; - } - return ::ExpandVarString(msgParent, varText, buffer, bufferLen, variables, param, - ignoreEnvVarNotFoundOrTooLong, varPlacements, varPlacementsCount, - detectMaxVarWidths, maxVarWidths, maxVarWidthsCount); -} - -BOOL CSalamanderGeneral::EnumInstalledModules(int* index, char* module, char* version) -{ - CALL_STACK_MESSAGE1("CSalamanderGeneral::EnumInstalledModules(, ,)"); - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::EnumInstalledModules() only from main thread!"); - return FALSE; - } - return Plugins.EnumInstalledModules(index, module, version); -} - -BOOL CSalamanderGeneral::CopyTextToClipboard(const char* text, int textLen, BOOL showEcho, HWND echoParent) -{ - CALL_STACK_MESSAGE3("CSalamanderGeneral::CopyTextToClipboard(, %d, %d,)", textLen, showEcho); - // j.r. threw the text parameter, which did not have to be null-terminated - if (text == NULL) - { - TRACE_E("Unexpected parameter (NULL) in CSalamanderGeneral::CopyTextToClipboard()."); - return FALSE; - } - return ::CopyTextToClipboard(text, textLen, showEcho, echoParent); -} - -BOOL CSalamanderGeneral::CopyTextToClipboardW(const wchar_t* text, int textLen, BOOL showEcho, HWND echoParent) -{ - CALL_STACK_MESSAGE3("CSalamanderGeneral::CopyTextToClipboardW(, %d, %d,)", textLen, showEcho); - // j.r. threw the text parameter, which did not have to be null-terminated - if (text == NULL) - { - TRACE_E("Unexpected parameter (NULL) in CSalamanderGeneral::CopyTextToClipboardW()."); - return FALSE; - } - return ::CopyTextToClipboardW(text, textLen, showEcho, echoParent); -} - -BOOL CSalamanderGeneral::IsPluginInstalled(const char* pluginSPL) -{ - CALL_STACK_MESSAGE2("CSalamanderGeneral::IsPluginInstalled(%s)", pluginSPL); - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::IsPluginInstalled() only from main thread!"); - return FALSE; - } - if (pluginSPL != NULL) - { - CPluginData* data = Plugins.GetPluginDataFromSuffix(pluginSPL); - return data != NULL; - } - else - { - TRACE_E("Unexpected parameter 'pluginSPL' (NULL) in CSalamanderGeneral::IsPluginInstalled()."); - return FALSE; - } -} - -BOOL ViewFileInPluginViewer(const char* pluginSPL, - CSalamanderPluginViewerData* pluginData, - BOOL useCache, const char* rootTmpPath, - const char* fileNameInCache, int& error) -{ - error = -1; // unknown - if (pluginData == NULL || pluginData->Size < sizeof(CSalamanderPluginViewerData) || - pluginData->FileName == NULL || pluginData->FileName[0] == 0) - { - TRACE_E("Unexpected value of 'pluginData' in CSalamanderGeneral::ViewFileInPluginViewer!"); - return FALSE; - } - - CALL_STACK_MESSAGE7("CSalamanderGeneral::ViewFileInPluginViewer(%s, %d, %s, %d, %s, %s,)", - pluginSPL, pluginData->Size, pluginData->FileName, useCache, - (useCache ? rootTmpPath : "(ignored)"), - (useCache ? fileNameInCache : "(ignored)")); - - char viewUniqueName[50]; // we need a unique name for the viewed file in the cache - viewUniqueName[0] = 0; - const char* fileName; // name of the file we will pass to the viewer - if (useCache) - { - // verify that 'fileNameInCache' is valid (a name without path) - const char* s = NULL; - if (fileNameInCache != NULL) - { - s = fileNameInCache; - while (*s != 0 && *s != '\\' && *s != '/' && *s != ':' && - *s >= 32 && *s != '<' && *s != '>' && *s != '|' && *s != '"') - s++; - } - if (fileNameInCache == NULL || fileNameInCache[0] == 0 || *s != 0) - { - TRACE_E("Unexpected value of 'fileNameInCache' in CSalamanderGeneral::ViewFileInPluginViewer!"); - error = 3; - ::DeleteFileUtf8(pluginData->FileName); - return FALSE; - } - - // insert the file 'pluginData->FileName' into the disk cache under the name 'fileNameInCache' - while (1) - { - sprintf(viewUniqueName, "ViewFile %X", GetTickCount()); - BOOL exists; - fileName = DiskCache.GetName(viewUniqueName, fileNameInCache, &exists, TRUE, rootTmpPath, FALSE, NULL, NULL); - if (fileName == NULL) // error (if 'exists' is TRUE -> fatal, otherwise "file already exists") - { - if (!exists) - Sleep(100); // the file exists -> almost impossible, still handle it - else // fatal error - { - error = 3; - ::DeleteFileUtf8(pluginData->FileName); - return FALSE; // fatal error - } - } - else - break; // we have the name in the disk cache, all OK - } - if (!::SalMoveFile(pluginData->FileName, fileName)) - { - DWORD err = GetLastError(); - TRACE_E("Unable to move file to disk cache! (error " << ::GetErrorText(err) << ")"); - ::DeleteFileUtf8(pluginData->FileName); - DiskCache.ReleaseName(viewUniqueName, FALSE); - error = 3; - return FALSE; - } - else // successfully obtained a temp file; we must call NamePrepared() - { - CQuadWord size(0, 0); - HANDLE file = HANDLES_Q(CreateFileUtf8(fileName, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, - NULL, OPEN_EXISTING, 0, NULL)); - if (file != INVALID_HANDLE_VALUE) - { // ignore the error; the file size is not that important - DWORD err; - ::SalGetFileSize(file, size, err); - HANDLES(CloseHandle(file)); - } - - DiskCache.NamePrepared(viewUniqueName, size); - } - } - else - fileName = pluginData->FileName; - - // position for viewers - WINDOWPLACEMENT place; - place.length = sizeof(WINDOWPLACEMENT); - GetWindowPlacement(MainWindow->HWindow, &place); - // GetWindowPlacement accounts for the taskbar, so if the taskbar is at the top or left, - // the values are shifted by its dimensions. Apply a correction. - RECT monitorRect; - RECT workRect; - MultiMonGetClipRectByRect(&place.rcNormalPosition, &workRect, &monitorRect); - OffsetRect(&place.rcNormalPosition, workRect.left - monitorRect.left, - workRect.top - monitorRect.top); - //we do not want a minimized viewer, even if the main window is minimized - if (place.showCmd == SW_MINIMIZE || place.showCmd == SW_SHOWMINIMIZED || - place.showCmd == SW_SHOWMINNOACTIVE) - place.showCmd = SW_SHOWNORMAL; - - // finally open the viewer itself - BOOL diskCacheNameClosed = FALSE; - error = 0; - if (pluginSPL != NULL) // viewer from a plug-in - { - CPluginData* data = Plugins.GetPluginDataFromSuffix(pluginSPL); - if (data != NULL && data->SupportViewer) - { - if (data->InitDLL(MainWindow->HWindow) - /*&& PluginIfaceForViewer.NotEmpty()*/) // redundant, because downgrade is impossible and InitDLL checks the interfaces - { - HANDLE lock = NULL; - BOOL lockOwner = FALSE; - BOOL ret = data->GetPluginInterfaceForViewer()->ViewFile(fileName, place.rcNormalPosition.left, - place.rcNormalPosition.top, - place.rcNormalPosition.right - place.rcNormalPosition.left, - place.rcNormalPosition.bottom - place.rcNormalPosition.top, - place.showCmd, Configuration.AlwaysOnTop, - useCache, &lock, &lockOwner, pluginData, -1, -1); - if (!ret) - { - TRACE_E("PluginIfaceForViewer.ViewFile() returns error."); - error = 2; - } - else - { - if (useCache && lock != NULL) - { - if (lockOwner) // add the handle for 'lock' to HANDLES (the disk cache will want to close it and will look for it) - HANDLES_ADD(__htEvent, __hoCreateEvent, lock); - DiskCache.AssignName(viewUniqueName, lock, lockOwner, crtDirect); - diskCacheNameClosed = TRUE; - } - } - } - else - error = 1; - } - else - error = 1; - if (error == 1) - TRACE_E("Unable to load plugin."); - } - else // internal viewer - { - if (Configuration.SavePosition && - Configuration.WindowPlacement.length != 0) - { - place = Configuration.WindowPlacement; - // GetWindowPlacement accounts for the taskbar, so if the taskbar is at the top or left, - // the values are shifted by its dimensions. Apply a correction. - RECT monitorRect2; - RECT workRect2; - MultiMonGetClipRectByRect(&place.rcNormalPosition, &workRect2, &monitorRect2); - OffsetRect(&place.rcNormalPosition, workRect2.left - monitorRect2.left, - workRect2.top - monitorRect2.top); - MultiMonEnsureRectVisible(&place.rcNormalPosition, TRUE); - } - - HANDLE lock = NULL; - BOOL lockOwner = FALSE; - if (OpenViewer(fileName, vtText, - place.rcNormalPosition.left, - place.rcNormalPosition.top, - place.rcNormalPosition.right - place.rcNormalPosition.left, - place.rcNormalPosition.bottom - place.rcNormalPosition.top, - place.showCmd, useCache, &lock, &lockOwner, pluginData, -1, -1)) - { - if (useCache && lock != NULL) - { - DiskCache.AssignName(viewUniqueName, lock, lockOwner, crtDirect); - diskCacheNameClosed = TRUE; - } - } - else - { - TRACE_E("OpenViewer() returns error."); - error = 2; - } - } - - // if we did not assign a name in the disk cache, release the record... - if (useCache && !diskCacheNameClosed) - { - DiskCache.ReleaseName(viewUniqueName, FALSE); - // ::DeleteFileUtf8(fileName); // the cache already removed the file and deallocated fileName - } - return error == 0; // returning success? -} - -BOOL CSalamanderGeneral::ViewFileInPluginViewer(const char* pluginSPL, - CSalamanderPluginViewerData* pluginData, - BOOL useCache, const char* rootTmpPath, - const char* fileNameInCache, int& error) -{ - error = -1; // unknown - - // guard against calls from outside the main thread and from the entry point - if (MainThreadID != GetCurrentThreadId() || (INT_PTR)Plugin == -1) - { - if (MainThreadID == GetCurrentThreadId()) // if both errors occur (different thread + unfinished entry point), the entry point takes priority - TRACE_E("You may not call CSalamanderGeneral::ViewFileInPluginViewer from entry-point!"); - else - TRACE_E("You can call CSalamanderGeneral::ViewFileInPluginViewer only from main thread!"); - return FALSE; - } - - return ::ViewFileInPluginViewer(pluginSPL, pluginData, useCache, - rootTmpPath, fileNameInCache, error); -} - -void CSalamanderGeneral::ExecuteAssociation(HWND parent, const char* path, const char* name) -{ - CALL_STACK_MESSAGE4("CSalamanderGeneral::ExecuteAssociation(0x%p, %s, %s)", parent, path, name); - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::ExecuteAssociation() only from main thread!"); - return; - } - MainWindow->SetDefaultDirectories(); // so the starting process inherits the correct current directories - ::ExecuteAssociation(parent, path, name); -} - -int CSalamanderGeneral::GetPanelTopIndex(int panel) -{ - CALL_STACK_MESSAGE2("CSalamanderGeneral::GetPanelTopIndex(%d)", panel); - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::GetPanelTopIndex() only from main thread!"); - return 0; - } - CFilesWindow* p = GetPanel(panel); - if (p != NULL) - return p->ListBox->GetTopIndex(); - return 0; // error; should not happen... -} - -void CSalamanderGeneral::GetPanelEnumFilesParams(int panel, int* enumFilesSourceUID, int* enumFilesCurrentIndex) -{ - CALL_STACK_MESSAGE2("CSalamanderGeneral::GetPanelEnumFilesParams(%d, ,)", panel); - if (enumFilesCurrentIndex != NULL) - *enumFilesCurrentIndex = -1; - if (enumFilesSourceUID != NULL) - *enumFilesSourceUID = -1; - else - { - TRACE_E("CSalamanderGeneral::GetPanelEnumFilesParams(): 'enumFilesSourceUID' cannot be NULL!"); - return; - } - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::GetPanelEnumFilesParams() only from main thread!"); - return; - } - - CFilesWindow* p = GetPanel(panel); - if (p != NULL && p->Is(ptDisk)) - { - *enumFilesSourceUID = p->EnumFileNamesSourceUID; - if (enumFilesCurrentIndex != NULL) - { - int i = p->GetCaretIndex(); - if (i >= p->Dirs->Count && i < p->Dirs->Count + p->Files->Count) - *enumFilesCurrentIndex = i - p->Dirs->Count; - } - } -} - -BOOL CSalamanderGeneral::GetPanelWithPluginFS(CPluginFSInterfaceAbstract* pluginFS, int& panel) -{ - CALL_STACK_MESSAGE1("CSalamanderGeneral::GetPanelWithPluginFS(, )"); - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::GetPanelWithPluginFS() only from main thread!"); - return FALSE; - } - if (pluginFS == NULL) - return FALSE; - if (MainWindow->LeftPanel->Is(ptPluginFS) && - MainWindow->LeftPanel->GetPluginFS()->GetInterface() == pluginFS) - { - panel = PANEL_LEFT; - return TRUE; - } - if (MainWindow->RightPanel->Is(ptPluginFS) && - MainWindow->RightPanel->GetPluginFS()->GetInterface() == pluginFS) - { - panel = PANEL_RIGHT; - return TRUE; - } - return FALSE; -} - -void CSalamanderGeneral::PostChangeOnPathNotification(const char* path, BOOL includingSubdirs) -{ - CALL_STACK_MESSAGE3("CSalamanderGeneral::PostChangeOnPathNotification(%s, %d)", path, includingSubdirs); - MainWindow->PostChangeOnPathNotification(path, includingSubdirs); -} - -DWORD -CSalamanderGeneral::SalCheckPath(BOOL echo, const char* path, DWORD err, HWND parent) -{ - CALL_STACK_MESSAGE4("CSalamanderGeneral::SalCheckPath(%d, %s, %u,)", echo, path, err); - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::SalCheckPath() only from main thread!"); - return ERROR_SUCCESS; - } - return ::SalCheckPath(echo, path, err, TRUE, parent); // the value of 'postRefresh' does not matter (StopRefresh is surely > 0) -} - -BOOL CSalamanderGeneral::SalCheckAndRestorePath(HWND parent, const char* path, BOOL tryNet) -{ - CALL_STACK_MESSAGE3("CSalamanderGeneral::SalCheckAndRestorePath(, %s, %d)", path, tryNet); - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::SalCheckAndRestorePath() only from main thread!"); - return FALSE; - } - return ::SalCheckAndRestorePath(parent, path, tryNet); -} - -BOOL CSalamanderGeneral::SalCheckAndRestorePathWithCut(HWND parent, char* path, BOOL& tryNet, DWORD& err, - DWORD& lastErr, BOOL& pathInvalid, BOOL& cut, - BOOL donotReconnect) -{ - CALL_STACK_MESSAGE4("CSalamanderGeneral::SalCheckAndRestorePathWithCut(, %s, %d, , , , , %d)", - path, tryNet, donotReconnect); - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::SalCheckAndRestorePathWithCut() only from main thread!"); - lastErr = err = ERROR_SUCCESS; - pathInvalid = TRUE; - cut = FALSE; - return FALSE; - } - return ::SalCheckAndRestorePathWithCut(parent, path, tryNet, err, lastErr, pathInvalid, cut, - donotReconnect); -} - -BOOL CSalamanderGeneral::SalParsePath(HWND parent, char* path, int& type, BOOL& isDir, char*& secondPart, - const char* errorTitle, char* nextFocus, BOOL curPathIsDiskOrArchive, - const char* curPath, const char* curArchivePath, int* error, - int pathBufSize) -{ - CALL_STACK_MESSAGE7("CSalamanderGeneral::SalParsePath(, %s, , , , %s, , %d, %s, %s, , %d)", - path, errorTitle, curPathIsDiskOrArchive, curPath, curArchivePath, - pathBufSize); - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::SalParsePath() only from main thread!"); - if (error != NULL) - *error = SPP_WINDOWSPATHERROR; - return FALSE; - } - return ::SalParsePath(parent, path, type, isDir, secondPart, errorTitle, nextFocus, - curPathIsDiskOrArchive, curPath, curArchivePath, error, pathBufSize); -} - -BOOL CSalamanderGeneral::SalSplitWindowsPath(HWND parent, const char* title, const char* errorTitle, - int selCount, char* path, char* secondPart, BOOL pathIsDir, - BOOL backslashAtEnd, const char* dirName, - const char* curDiskPath, char*& mask) -{ - CALL_STACK_MESSAGE10("CSalamanderGeneral::SalSplitWindowsPath(, %s, %s, %d, %s, %s, %d, %d, %s, %s,)", - title, errorTitle, selCount, path, secondPart, pathIsDir, backslashAtEnd, - dirName, curDiskPath); - return ::SalSplitWindowsPath(parent, title, errorTitle, selCount, path, secondPart, - pathIsDir, backslashAtEnd, dirName, curDiskPath, mask); -} - -BOOL CSalamanderGeneral::SalSplitGeneralPath(HWND parent, const char* title, const char* errorTitle, - int selCount, char* path, char* afterRoot, char* secondPart, - BOOL pathIsDir, BOOL backslashAtEnd, const char* dirName, - const char* curPath, char*& mask, char* newDirs, - SGP_IsTheSamePathF isTheSamePathF) -{ - CALL_STACK_MESSAGE11("CSalamanderGeneral::SalSplitGeneralPath(, %s, %s, %d, %s, %s, %s, %d, %d, %s, %s, , ,)", - title, errorTitle, selCount, path, afterRoot, secondPart, pathIsDir, backslashAtEnd, - dirName, curPath); - return ::SalSplitGeneralPath(parent, title, errorTitle, selCount, path, afterRoot, secondPart, - pathIsDir, backslashAtEnd, dirName, curPath, mask, newDirs, - isTheSamePathF); -} - -BOOL CSalamanderGeneral::SalRemovePointsFromPath(char* afterRoot) -{ - CALL_STACK_MESSAGE2("CSalamanderGeneral::SalRemovePointsFromPath(%s)", afterRoot); - return ::SalRemovePointsFromPath(afterRoot); -} - -BOOL CSalamanderGeneral::GetConfigParameter(int paramID, void* buffer, int bufferSize, int* type) -{ - SLOW_CALL_STACK_MESSAGE3("CSalamanderGeneral::GetConfigParameter(%d, , %d,)", paramID, bufferSize); - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::GetConfigParameter() only from main thread!"); - if (type != NULL) - *type = SALCFGTYPE_NOTFOUND; - return FALSE; - } - char auxBuf[500]; - int auxType = SALCFGTYPE_BOOL; - int auxDataSize = 4; - BOOL ret = TRUE; - switch (paramID) - { - case SALCFG_SELOPINCLUDEDIRS: - *((DWORD*)auxBuf) = (DWORD)Configuration.IncludeDirs; - break; - case SALCFG_SAVEONEXIT: - *((DWORD*)auxBuf) = (DWORD)Configuration.AutoSave; - break; - case SALCFG_MINBEEPWHENDONE: - *((DWORD*)auxBuf) = (DWORD)Configuration.MinBeepWhenDone; - break; - case SALCFG_HIDEHIDDENORSYSTEMFILES: - *((DWORD*)auxBuf) = (DWORD)Configuration.NotHiddenSystemFiles; - break; - case SALCFG_ALWAYSONTOP: - *((DWORD*)auxBuf) = (DWORD)Configuration.AlwaysOnTop; - break; - // case SALCFG_FASTDIRMOVE: *((DWORD *)auxBuf) = (DWORD)Configuration.FastDirectoryMove; break; - case SALCFG_SORTUSESLOCALE: - *((DWORD*)auxBuf) = (DWORD)Configuration.SortUsesLocale; - break; - case SALCFG_SORTDETECTNUMBERS: - *((DWORD*)auxBuf) = (DWORD)Configuration.SortDetectNumbers; - break; - case SALCFG_SORTBYEXTDIRSASFILES: - *((DWORD*)auxBuf) = (DWORD)Configuration.SortDirsByExt; - break; - case SALCFG_SINGLECLICK: - *((DWORD*)auxBuf) = (DWORD)Configuration.SingleClick; - break; - case SALCFG_TOPTOOLBARVISIBLE: - *((DWORD*)auxBuf) = (DWORD)Configuration.TopToolBarVisible; - break; - case SALCFG_MIDDLETOOLBARVISIBLE: - *((DWORD*)auxBuf) = (DWORD)Configuration.MiddleToolBarVisible; - break; - case SALCFG_BOTTOMTOOLBARVISIBLE: - *((DWORD*)auxBuf) = (DWORD)Configuration.BottomToolBarVisible; - break; - case SALCFG_USERMENUTOOLBARVISIBLE: - *((DWORD*)auxBuf) = (DWORD)Configuration.UserMenuToolBarVisible; - break; - case SALCFG_SAVEHISTORY: - *((DWORD*)auxBuf) = (DWORD)Configuration.SaveHistory; - break; - case SALCFG_ENABLECMDLINEHISTORY: - *((DWORD*)auxBuf) = (DWORD)Configuration.EnableCmdLineHistory; - break; - case SALCFG_SAVECMDLINEHISTORY: - *((DWORD*)auxBuf) = (DWORD)Configuration.SaveCmdLineHistory; - break; - case SALCFG_SIZEFORMAT: - *((DWORD*)auxBuf) = (DWORD)Configuration.SizeFormat; - break; - case SALCFG_SELECTWHOLENAME: - *((DWORD*)auxBuf) = (DWORD)Configuration.QuickRenameSelectAll; - break; - - case SALCFG_FILENAMEFORMAT: - { - auxType = SALCFGTYPE_INT; - auxDataSize = 4; - *((DWORD*)auxBuf) = (DWORD)Configuration.FileNameFormat; - break; - } - - case SALCFG_INFOLINECONTENT: - { - auxType = SALCFGTYPE_STRING; - auxDataSize = (int)strlen(Configuration.InfoLineContent) + 1; - if (auxDataSize > 200) - auxDataSize = 200; // we limited the required buffer to 200 characters - memcpy(auxBuf, Configuration.InfoLineContent, auxDataSize); - auxBuf[auxDataSize - 1] = 0; - break; - } - - case SALCFG_USERECYCLEBIN: - { - auxType = SALCFGTYPE_INT; - auxDataSize = 4; - *((DWORD*)auxBuf) = (DWORD)Configuration.UseRecycleBin; - break; - } - - case SALCFG_RECYCLEBINMASKS: - { - auxType = SALCFGTYPE_STRING; - auxDataSize = (int)strlen(Configuration.RecycleMasks.GetMasksString()) + 1; - if (auxDataSize > MAX_PATH) - auxDataSize = MAX_PATH; // we limited the required buffer to MAX_PATH characters - memcpy(auxBuf, Configuration.RecycleMasks.GetMasksString(), auxDataSize); - auxBuf[auxDataSize - 1] = 0; - break; - } - - case SALCFG_COMPDIRSUSETIMERES: - *((DWORD*)auxBuf) = (DWORD)Configuration.UseTimeResolution; - break; - - case SALCFG_COMPDIRTIMERES: - { - auxType = SALCFGTYPE_INT; - auxDataSize = 4; - *((DWORD*)auxBuf) = (DWORD)Configuration.TimeResolution; - break; - } - - case SALCFG_CNFRMFILEDIRDEL: - *((DWORD*)auxBuf) = (DWORD)Configuration.CnfrmFileDirDel; - break; - case SALCFG_CNFRMNEDIRDEL: - *((DWORD*)auxBuf) = (DWORD)Configuration.CnfrmNEDirDel; - break; - case SALCFG_CNFRMFILEOVER: - *((DWORD*)auxBuf) = (DWORD)Configuration.CnfrmFileOver; - break; - case SALCFG_CNFRMDIROVER: - *((DWORD*)auxBuf) = (DWORD)Configuration.CnfrmDirOver; - break; - case SALCFG_CNFRMSHFILEDEL: - *((DWORD*)auxBuf) = (DWORD)Configuration.CnfrmSHFileDel; - break; - case SALCFG_CNFRMSHDIRDEL: - *((DWORD*)auxBuf) = (DWORD)Configuration.CnfrmSHDirDel; - break; - case SALCFG_CNFRMSHFILEOVER: - *((DWORD*)auxBuf) = (DWORD)Configuration.CnfrmSHFileOver; - break; - case SALCFG_CNFRMCREATEPATH: - *((DWORD*)auxBuf) = (DWORD)Configuration.CnfrmCreatePath; - break; - case SALCFG_DRVSPECFLOPPYMON: - *((DWORD*)auxBuf) = (DWORD)Configuration.DrvSpecFloppyMon; - break; - case SALCFG_DRVSPECFLOPPYSIM: - *((DWORD*)auxBuf) = (DWORD)Configuration.DrvSpecFloppySimple; - break; - case SALCFG_DRVSPECREMOVABLEMON: - *((DWORD*)auxBuf) = (DWORD)Configuration.DrvSpecRemovableMon; - break; - case SALCFG_DRVSPECREMOVABLESIM: - *((DWORD*)auxBuf) = (DWORD)Configuration.DrvSpecRemovableSimple; - break; - case SALCFG_DRVSPECFIXEDMON: - *((DWORD*)auxBuf) = (DWORD)Configuration.DrvSpecFixedMon; - break; - case SALCFG_DRVSPECFIXEDSIMPLE: - *((DWORD*)auxBuf) = (DWORD)Configuration.DrvSpecFixedSimple; - break; - case SALCFG_DRVSPECREMOTEMON: - *((DWORD*)auxBuf) = (DWORD)Configuration.DrvSpecRemoteMon; - break; - case SALCFG_DRVSPECREMOTESIMPLE: - *((DWORD*)auxBuf) = (DWORD)Configuration.DrvSpecRemoteSimple; - break; - case SALCFG_DRVSPECREMOTEDONOTREF: - *((DWORD*)auxBuf) = (DWORD)Configuration.DrvSpecRemoteDoNotRefreshOnAct; - break; - case SALCFG_DRVSPECCDROMMON: - *((DWORD*)auxBuf) = (DWORD)Configuration.DrvSpecCDROMMon; - break; - case SALCFG_DRVSPECCDROMSIMPLE: - *((DWORD*)auxBuf) = (DWORD)Configuration.DrvSpecCDROMSimple; - break; - - case SALCFG_IFPATHISINACCESSIBLEGOTO: - { - auxType = SALCFGTYPE_STRING; - char ifPathIsInaccessibleGoTo[MAX_PATH]; - GetIfPathIsInaccessibleGoTo(ifPathIsInaccessibleGoTo, _countof(ifPathIsInaccessibleGoTo), FALSE); - auxDataSize = (int)strlen(ifPathIsInaccessibleGoTo) + 1; - if (auxDataSize > MAX_PATH) - auxDataSize = MAX_PATH; // we limited the required buffer to MAX_PATH characters - memcpy(auxBuf, ifPathIsInaccessibleGoTo, auxDataSize); - auxBuf[auxDataSize - 1] = 0; - break; - } - - case SALCFG_VIEWEREOLCRLF: - *((DWORD*)auxBuf) = (DWORD)Configuration.EOL_CRLF; - break; - case SALCFG_VIEWEREOLCR: - *((DWORD*)auxBuf) = (DWORD)Configuration.EOL_CR; - break; - case SALCFG_VIEWEREOLLF: - *((DWORD*)auxBuf) = (DWORD)Configuration.EOL_LF; - break; - case SALCFG_VIEWEREOLNULL: - *((DWORD*)auxBuf) = (DWORD)Configuration.EOL_NULL; - break; - case SALCFG_VIEWERSAVEPOSITION: - *((DWORD*)auxBuf) = (DWORD)Configuration.SavePosition; - break; - case SALCFG_VIEWERWRAPTEXT: - *((DWORD*)auxBuf) = (DWORD)Configuration.WrapText; - break; - case SALCFG_AUTOCOPYSELTOCLIPBOARD: - *((DWORD*)auxBuf) = (DWORD)Configuration.AutoCopySelection; - break; - - case SALCFG_VIEWERTABSIZE: - { - auxType = SALCFGTYPE_INT; - auxDataSize = 4; - *((DWORD*)auxBuf) = (DWORD)Configuration.TabSize; - break; - } - - case SALCFG_VIEWERFONT: - { - auxType = SALCFGTYPE_LOGFONT; - auxDataSize = sizeof(LOGFONT); - if (UseCustomViewerFont) - *((LOGFONT*)auxBuf) = ViewerLogFont; - else - GetDefaultViewerLogFont((LOGFONT*)auxBuf); - break; - } - - case SALCFG_ARCOTHERPANELFORPACK: - *((DWORD*)auxBuf) = (DWORD)Configuration.UseAnotherPanelForPack; - break; - case SALCFG_ARCOTHERPANELFORUNPACK: - *((DWORD*)auxBuf) = (DWORD)Configuration.UseAnotherPanelForUnpack; - break; - case SALCFG_ARCSUBDIRBYARCFORUNPACK: - *((DWORD*)auxBuf) = (DWORD)Configuration.UseSubdirNameByArchiveForUnpack; - break; - case SALCFG_ARCUSESIMPLEICONS: - *((DWORD*)auxBuf) = (DWORD)Configuration.UseSimpleIconsInArchives; - break; - - default: - { - auxType = SALCFGTYPE_NOTFOUND; - auxDataSize = 0; - TRACE_E("Unknown parameter ID (" << paramID << ") in CSalamanderGeneral::GetConfigParameter()."); - ret = FALSE; - } - } - if (type != NULL) - *type = auxType; - if (auxDataSize > 0 && auxDataSize <= bufferSize) - memcpy(buffer, auxBuf, auxDataSize); - else - { - if (bufferSize > 0 && auxDataSize > 0) // copy at least what fits - { - memcpy(buffer, auxBuf, bufferSize); - if (auxType == SALCFGTYPE_STRING) - ((char*)buffer)[bufferSize - 1] = 0; // trim the string with a zero - } - ret = FALSE; - } - return ret; -} - -void CSalamanderGeneral::AlterFileName(char* tgtName, char* srcName, int format, int changedParts, - BOOL isDir) -{ - CALL_STACK_MESSAGE5("CSalamanderGeneral::AlterFileName(, %s, %d, %d, %d)", - srcName, format, changedParts, isDir); - ::AlterFileName(tgtName, srcName, -1, format, changedParts, isDir); -} - -void CSalamanderGeneral::CreateSafeWaitWindow(const char* message, const char* caption, - int delay, BOOL showCloseButton, HWND hForegroundWnd) -{ - CALL_STACK_MESSAGE5("CSalamanderGeneral::CreateSafeWaitWindow(%s, , %d, %d, 0x%p)", message, delay, showCloseButton, hForegroundWnd); - ::CreateSafeWaitWindow(message, caption, delay, showCloseButton, hForegroundWnd); -} - -void CSalamanderGeneral::DestroySafeWaitWindow() -{ - CALL_STACK_MESSAGE1("CSalamanderGeneral::DestroySafeWaitWindow()"); - ::DestroySafeWaitWindow(); -} - -void CSalamanderGeneral::ShowSafeWaitWindow(BOOL show) -{ - CALL_STACK_MESSAGE2("CSalamanderGeneral::ShowSafeWaitWindow(%d)", show); - ::ShowSafeWaitWindow(show); -} - -BOOL CSalamanderGeneral::GetSafeWaitWindowClosePressed() -{ - CALL_STACK_MESSAGE1("CSalamanderGeneral::GetSafeWaitWindowClosePressed()"); - return ::GetSafeWaitWindowClosePressed(); -} - -void CSalamanderGeneral::SetSafeWaitWindowText(const char* message) -{ - CALL_STACK_MESSAGE2("CSalamanderGeneral::SetSafeWaitWindowText(%s)", message); - ::SetSafeWaitWindowText(message); -} - -BOOL CSalamanderGeneral::GetFileFromCache(const char* uniqueFileName, const char*& tmpName, - HANDLE fileLock) -{ - CALL_STACK_MESSAGE2("CSalamanderGeneral::GetFileFromCache(%s, ,)", uniqueFileName); - tmpName = NULL; - if (uniqueFileName == NULL || fileLock == NULL) - { - TRACE_E("Invalid parameter in CSalamanderGeneral::GetFileFromCache!"); - return FALSE; - } - - BOOL fileExists; - const char* name = DiskCache.GetName(uniqueFileName, NULL, &fileExists, FALSE, NULL, FALSE, NULL, NULL); - if (name != NULL) // file found - { - if (!fileExists) // some helpful soul deleted it straight from the disk - { - // cannot prepare the file; tell the disk cache we give up - DiskCache.ReleaseName(uniqueFileName, FALSE); - } - else - { - DiskCache.AssignName(uniqueFileName, fileLock, FALSE, crtCache); - tmpName = name; - return TRUE; - } - } - return FALSE; -} - -void CSalamanderGeneral::UnlockFileInCache(HANDLE fileLock) -{ - CALL_STACK_MESSAGE2("CSalamanderGeneral::UnlockFileInCache(0x%p)", fileLock); - - SetEvent(fileLock); // start cleaning up the file - DiskCache.WaitForIdle(); - ResetEvent(fileLock); // finish cleaning up the file -} - -BOOL CSalamanderGeneral::MoveFileToCache(const char* uniqueFileName, const char* nameInCache, - const char* rootTmpPath, const char* newFileName, - const CQuadWord& newFileSize, BOOL* alreadyExists) -{ - CALL_STACK_MESSAGE6("CSalamanderGeneral::MoveFileToCache(%s, %s, %s, %s, %g, )", - uniqueFileName, nameInCache, rootTmpPath, newFileName, newFileSize.GetDouble()); - if (alreadyExists != NULL) - *alreadyExists = FALSE; - if (uniqueFileName == NULL || newFileName == NULL || nameInCache == NULL) - { - TRACE_E("Invalid parameter in CSalamanderGeneral::GetFileFromCache!"); - return FALSE; - } - - // verify that 'nameInCache' is valid (a name without a path) - const char* s = nameInCache; - while (*s != 0 && *s != '\\' && *s != '/' && *s != ':' && - *s >= 32 && *s != '<' && *s != '>' && *s != '|' && *s != '"') - s++; - if (nameInCache[0] == 0 || *s != 0) - { - TRACE_E("Unexpected value of 'nameInCache' in CSalamanderGeneral::MoveFileToCache!"); - return FALSE; - } - - // add the file 'newFileName' to the disk cache under the name 'uniqueFileName' - BOOL exists; - const char* fileName = DiskCache.GetName(uniqueFileName, nameInCache, &exists, TRUE, rootTmpPath, FALSE, NULL, NULL); - if (fileName == NULL) // error (if 'exists' is TRUE -> fatal, otherwise "file already exists") - { - if (alreadyExists != NULL) - *alreadyExists = !exists; - return FALSE; - } - - if (!::SalMoveFile(newFileName, fileName)) - { - DWORD err = GetLastError(); - TRACE_E("Unable to move file to disk cache! (error " << ::GetErrorText(err) << ")"); - DiskCache.ReleaseName(uniqueFileName, FALSE); // nothing to keep in the cache - return FALSE; - } - else // successfully obtained a temp file; we must call NamePrepared() - { - DiskCache.NamePrepared(uniqueFileName, newFileSize); - DiskCache.ReleaseName(uniqueFileName, TRUE); // leave the prepared file in the cache (even if it is not locked) - return TRUE; - } -} - -void CSalamanderGeneral::RemoveOneFileFromCache(const char* uniqueFileName) -{ - CALL_STACK_MESSAGE2("CSalamanderGeneral::RemoveOneFileFromCache(%s)", uniqueFileName); - if (uniqueFileName == NULL) - { - TRACE_E("Invalid parametr (NULL) in CSalamanderGeneral::RemoveOneFileFromCache!"); - return; - } - DiskCache.FlushOneFile(uniqueFileName); -} - -void CSalamanderGeneral::RemoveFilesFromCache(const char* fileNamesRoot) -{ - CALL_STACK_MESSAGE2("CSalamanderGeneral::RemoveFilesFromCache(%s)", fileNamesRoot); - if (fileNamesRoot == NULL) - { - TRACE_E("Invalid parametr (NULL) in CSalamanderGeneral::RemoveFilesFromCache!"); - return; - } - DiskCache.FlushCache(fileNamesRoot); -} - -BOOL CSalamanderGeneral::EnumConversionTables(HWND parent, int* index, const char** name, const char** table) -{ - if (index == NULL) - { - TRACE_E("Unexpected value of 'index' (NULL) in CSalamanderGeneral::EnumConversionTables()."); - return FALSE; - } - CALL_STACK_MESSAGE2("CSalamanderGeneral::EnumConversionTables(, %d, ,)", *index); - parent = (parent == NULL ? MainWindow->HWindow : parent); - return CodeTables.EnumCodeTables(parent, index, name, table); -} - -BOOL CSalamanderGeneral::GetConversionTable(HWND parent, char* table, const char* conversion) -{ - CALL_STACK_MESSAGE2("CSalamanderGeneral::GetConversionTable(, , %s)", conversion); - if (table == NULL) - { - TRACE_E("Invalid parametr (table==NULL) in CSalamanderGeneral::GetConversionTable!"); - return FALSE; - } - parent = (parent == NULL ? MainWindow->HWindow : parent); - BOOL ret = CodeTables.Init(parent); - int codeType; - if (ret) - ret &= CodeTables.GetCodeType(conversion, codeType); - if (ret) - ret &= CodeTables.GetCode(table, codeType); - return ret; -} - -void CSalamanderGeneral::GetWindowsCodePage(HWND parent, char* codePage) -{ - CALL_STACK_MESSAGE1("CSalamanderGeneral::GetWindowsCodePage(,)"); - parent = (parent == NULL ? MainWindow->HWindow : parent); - CodeTables.Init(parent); - CodeTables.GetWinCodePage(codePage); -} - -void CSalamanderGeneral::RecognizeFileType(HWND parent, const char* pattern, int patternLen, BOOL forceText, - BOOL* isText, char* codePage) -{ - CALL_STACK_MESSAGE3("CSalamanderGeneral::RecognizeFileType(, , %d, %d, ,)", patternLen, forceText); - parent = (parent == NULL ? MainWindow->HWindow : parent); - ::RecognizeFileType(parent, pattern, patternLen, forceText, isText, codePage); -} - -BOOL CSalamanderGeneral::IsANSIText(const char* text, int textLen) -{ - CALL_STACK_MESSAGE2("CSalamanderGeneral::IsANSIText(, %d)", textLen); - - const unsigned char* s = (const unsigned char*)text; - const unsigned char* end = s + textLen; - while (s < end) - { - if (*s < ' ' && *s != '\a' && *s != '\b' && *s != '\r' && // *s != 0 must not be here because of Unicode files ("0A 00" cannot be expanded to "0D 0A 00") - *s != '\f' && *s != '\n' && *s != '\t' && *s != '\v' && - *s != '\x1a' && *s != '\x04' && *s != '\x06') - { // disallowed character - break; - } - s++; - } - return s == end; -} - -BOOL CSalamanderGeneral::SalMoveFile(const char* srcName, const char* destName, DWORD* err) -{ - CALL_STACK_MESSAGE3("CSalamanderGeneral::SalMoveFile(%s, %s,)", srcName, destName); - BOOL ret = ::SalMoveFile(srcName, destName); - if (err != NULL) - *err = GetLastError(); - return ret; -} - -BOOL CSalamanderGeneral::SalGetFileSize(HANDLE file, CQuadWord& size, DWORD& err) -{ - CALL_STACK_MESSAGE1("CSalamanderGeneral::SalGetFileSize(, ,)"); - return ::SalGetFileSize(file, size, err); -} - -BOOL CSalamanderGeneral::GetTargetDirectory(HWND parent, HWND hCenterWindow, const char* title, - const char* comment, char* path, BOOL onlyNet, - const char* initDir) -{ - CALL_STACK_MESSAGE5("CSalamanderGeneral::GetTargetDirectory(, , %s, %s, , %d, %s)", - title, comment, onlyNet, initDir); - return ::GetTargetDirectory(parent, hCenterWindow, title, comment, path, onlyNet, initDir); -} - -void CSalamanderGeneral::CallPluginOperationFromDisk(int panel, SalPluginOperationFromDisk callback, - void* param) -{ - CALL_STACK_MESSAGE2("CSalamanderGeneral::CallPluginOperationFromDisk(%d, ,)", panel); - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::CallPluginOperationFromDisk() only from main thread!"); - return; - } - if (callback == NULL) - { - TRACE_E("Unexpected value of parameter 'callback' (NULL) in CSalamanderGeneral::CallPluginOperationFromDisk()."); - return; - } - CFilesWindow* p = GetPanel(panel); - if (p != NULL) - { - if (!p->Is(ptDisk)) - { - TRACE_E("CSalamanderGeneral::CallPluginOperationFromDisk(): there must be windows (disk) path in panel!"); - return; - } - if (p->Files->Count + p->Dirs->Count <= 0) - { - TRACE_I("CSalamanderGeneral::CallPluginOperationFromDisk(): no items in panel!"); - return; - } - // prepare data for enumerating files and directories from the panel - CPanelTmpEnumData data; - int oneIndex = -1; - int count = p->GetSelCount(); - if (count > 0) // some files are selected - { - data.IndexesCount = count; - data.Indexes = new int[count]; - if (data.Indexes == NULL) - { - TRACE_E(LOW_MEMORY); - return; - } - else - p->GetSelItems(count, data.Indexes); - } - else // take the focus - { - oneIndex = p->GetCaretIndex(); - - BOOL subDir; - if (p->Dirs->Count > 0) - subDir = (strcmp(p->Dirs->At(0).Name, "..") == 0); - else - subDir = FALSE; - if (oneIndex == 0 && subDir) - { - TRACE_E("Unexpected situation in CSalamanderGeneral::CallPluginOperationFromDisk(): no files nor directories selected and focus is on up-dir symbol."); - return; - } - - data.IndexesCount = 1; - data.Indexes = &oneIndex; // not deallocated - } - data.CurrentIndex = 0; - data.ZIPPath = p->GetZIPPath(); - data.Dirs = p->Dirs; - data.Files = p->Files; - data.ArchiveDir = p->GetArchiveDir(); - lstrcpyn(data.WorkPath, p->GetPath(), MAX_PATH); - data.EnumLastDir = NULL; - data.EnumLastIndex = -1; - - callback(p->GetPath(), PanelEnumDiskSelection, &data, param); - - if (count > 0) - delete[] (data.Indexes); - } -} - -BYTE CSalamanderGeneral::GetUserDefaultCharset() -{ - return (BYTE)UserCharset; -} - -class CSalamanderBMSearchDataImp : public CSalamanderBMSearchData -{ -protected: - CSearchData Moore; - -public: - CSalamanderBMSearchDataImp() : Moore() {} - - virtual void WINAPI Set(const char* pattern, WORD flags) { Moore.Set(pattern, flags); } - virtual void WINAPI Set(const char* pattern, const int length, WORD flags) { Moore.Set(pattern, length, flags); } - virtual void WINAPI SetFlags(WORD flags) { Moore.SetFlags(flags); } - virtual int WINAPI GetLength() const { return Moore.GetLength(); } - virtual const char* WINAPI GetPattern() const { return Moore.GetPattern(); } - virtual BOOL WINAPI IsGood() const { return Moore.IsGood(); } - virtual int WINAPI SearchForward(const char* text, int length, int start) { return Moore.SearchForward(text, length, start); } - virtual int WINAPI SearchBackward(const char* text, int length) { return Moore.SearchBackward(text, length); } -}; - -CSalamanderBMSearchData* -CSalamanderGeneral::AllocSalamanderBMSearchData() -{ - CALL_STACK_MESSAGE1("CSalamanderGeneral::AllocSalamanderBMSearchData()"); - CSalamanderBMSearchData* ret = new CSalamanderBMSearchDataImp; - if (ret == NULL) - TRACE_E(LOW_MEMORY); - return ret; -} - -void CSalamanderGeneral::FreeSalamanderBMSearchData(CSalamanderBMSearchData* data) -{ - CALL_STACK_MESSAGE1("CSalamanderGeneral::FreeSalamanderBMSearchData()"); - if (data != NULL) - delete ((CSalamanderBMSearchDataImp*)data); -} - -class CSalamanderREGEXPSearchDataImp : public CSalamanderREGEXPSearchData -{ -protected: - CRegularExpression REGEXP; - -public: - CSalamanderREGEXPSearchDataImp() : REGEXP() {} - - virtual BOOL WINAPI Set(const char* pattern, WORD flags) { return REGEXP.Set(pattern, flags); } - virtual BOOL WINAPI SetFlags(WORD flags) { return REGEXP.SetFlags(flags); } - virtual const char* WINAPI GetLastErrorText() const { return REGEXP.GetLastErrorText(); } - virtual const char* WINAPI GetPattern() const { return REGEXP.GetPattern(); } - virtual BOOL WINAPI SetLine(const char* start, const char* end) - { - REGEXP.SetLine(start, end); - return TRUE; - } - virtual int WINAPI SearchForward(int start, int& foundLen) { return REGEXP.SearchForward(start, foundLen); } - virtual int WINAPI SearchBackward(int length, int& foundLen) { return REGEXP.SearchBackward(length, foundLen); } -}; - -CSalamanderREGEXPSearchData* -CSalamanderGeneral::AllocSalamanderREGEXPSearchData() -{ - CALL_STACK_MESSAGE1("CSalamanderGeneral::AllocSalamanderREGEXPSearchData()"); - CSalamanderREGEXPSearchData* ret = new CSalamanderREGEXPSearchDataImp; - return ret; -} - -void CSalamanderGeneral::FreeSalamanderREGEXPSearchData(CSalamanderREGEXPSearchData* data) -{ - CALL_STACK_MESSAGE1("CSalamanderGeneral::FreeSalamanderREGEXPSearchData()"); - if (data != NULL) - delete ((CSalamanderREGEXPSearchDataImp*)data); -} - -struct CSalCommandsAux -{ - int SalCmd; - int Cmd; - int TextID; - DWORD* Enabled; // NULL == "always TRUE" - int Type; -}; - -CSalCommandsAux SalCommandsArray[] = // ends with an item whose 'SalCmd' == -1 - { - {SALCMD_VIEW, CM_VIEW, IDS_MENU_FILES_VIEW, &EnablerViewFile, sctyForFocusedFile}, - {SALCMD_ALTVIEW, CM_ALTVIEW, IDS_MENU_FILES_ALTVIEW, &EnablerViewFile, sctyForFocusedFile}, - {SALCMD_VIEWWITH, CM_VIEW_WITH, IDS_SALCMD_VIEWWITH, &EnablerViewFile, sctyForFocusedFile}, - {SALCMD_EDIT, CM_EDIT, IDS_MENU_FILES_EDIT, &EnablerFileOnDiskOrArchive, sctyForFocusedFile}, - {SALCMD_EDITWITH, CM_EDIT_WITH, IDS_SALCMD_EDITWITH, &EnablerFileOnDiskOrArchive, sctyForFocusedFile}, - - {SALCMD_OPEN, CM_OPEN, IDS_SALCMD_OPEN, NULL, sctyForFocusedFileOrDirectory}, - {SALCMD_QUICKRENAME, CM_RENAMEFILE, IDS_MENU_FILES_RENAME, &EnablerQuickRename, sctyForFocusedFileOrDirectory}, - - {SALCMD_COPY, CM_COPYFILES, IDS_MENU_FILES_COPY, &EnablerFilesCopy, sctyForSelectedFilesAndDirectories}, - {SALCMD_MOVE, CM_MOVEFILES, IDS_MENU_FILES_MOVE, &EnablerFilesMove, sctyForSelectedFilesAndDirectories}, - {SALCMD_EMAIL, CM_EMAILFILES, IDS_MENU_FILES_EMAIL, &EnablerFilesOnDisk, sctyForSelectedFilesAndDirectories}, - {SALCMD_DELETE, CM_DELETEFILES, IDS_MENU_FILES_DELETE, &EnablerFilesDelete, sctyForSelectedFilesAndDirectories}, - {SALCMD_PROPERTIES, CM_PROPERTIES, IDS_MENU_FILES_PROPERTIES, &EnablerShowProperties, sctyForSelectedFilesAndDirectories}, - {SALCMD_CHANGECASE, CM_CHANGECASE, IDS_MENU_FILES_CHANGECASE, &EnablerFilesOnDisk, sctyForSelectedFilesAndDirectories}, - {SALCMD_CHANGEATTRS, CM_CHANGEATTR, IDS_MENU_FILES_CHANGEATTR, &EnablerChangeAttrs, sctyForSelectedFilesAndDirectories}, - {SALCMD_OCCUPIEDSPACE, CM_OCCUPIEDSPACE, IDS_MENU_CMD_OCCUPIED, &EnablerOccupiedSpace, sctyForSelectedFilesAndDirectories}, - - {SALCMD_EDITNEWFILE, CM_EDITNEW, IDS_MENU_FILES_EDITNEW, &EnablerOnDisk, sctyForCurrentPath}, - {SALCMD_REFRESH, CM_ACTIVEREFRESH, IDS_MENU_LEFT_REFRESH, NULL, sctyForCurrentPath}, - {SALCMD_CREATEDIRECTORY, CM_CREATEDIR, IDS_MENU_CMD_CREATEDIR, &EnablerCreateDir, sctyForCurrentPath}, - {SALCMD_DRIVEINFO, CM_DRIVEINFO, IDS_MENU_CMD_DRIVEINFO, &EnablerDriveInfo, sctyForCurrentPath}, - {SALCMD_CALCDIRSIZES, CM_CALCDIRSIZES, IDS_MENU_CMD_CALCDIRSIZES, &EnablerCalcDirSizes, sctyForCurrentPath}, - - {SALCMD_DISCONNECT, CM_DISCONNECTNET, IDS_MENU_CMD_DISCONNECTNET, NULL, sctyForConnectedDrivesAndFS}, - - {-1, -1, -1, NULL, sctyUnknown} // terminator -}; - -int GetWMCommandFromSalCmd(int salCmd) -{ - int index = 0; - while (SalCommandsArray[index].SalCmd != -1) - { - if (SalCommandsArray[index].SalCmd == salCmd) - return SalCommandsArray[index].Cmd; - index++; - } - TRACE_E("You have used CSalamanderGeneral::PostSalamanderCommand for invalid command (" << salCmd << ")!"); - return -1; -} - -BOOL CSalamanderGeneral::GetSalamanderCommand(int salCmd, char* nameBuf, int nameBufSize, BOOL* enabled, - int* type) -{ - CALL_STACK_MESSAGE3("CSalamanderGeneral::GetSalamanderCommand(%d, , %d, ,)", salCmd, nameBufSize); - int index = 0; - while (SalCommandsArray[index].SalCmd != -1) - { - if (SalCommandsArray[index].SalCmd == salCmd) // found it - { - // need to compute the command states; SalCommandsArray uses them - MainWindow->OnEnterIdle(); - - if (nameBuf != NULL && nameBufSize > 0) - { - lstrcpyn(nameBuf, ::LoadStr(SalCommandsArray[index].TextID), nameBufSize); - } - if (SalCommandsArray[index].Enabled != NULL) - { - if (enabled != NULL) - *enabled = *(SalCommandsArray[index].Enabled); - } - if (type != NULL) - *type = SalCommandsArray[index].Type; - - return TRUE; - } - index++; - } - return FALSE; -} - -BOOL CSalamanderGeneral::EnumSalamanderCommands(int* index, int* salCmd, char* nameBuf, int nameBufSize, - BOOL* enabled, int* type) -{ - CALL_STACK_MESSAGE2("CSalamanderGeneral::EnumSalamanderCommands(, , , %d, ,)", nameBufSize); - if (salCmd != NULL) - *salCmd = -1; - if (nameBuf != NULL && nameBufSize > 0) - nameBuf[0] = 0; - if (enabled != NULL) - *enabled = TRUE; - if (type != NULL) - *type = sctyUnknown; - - if (index != NULL && *index >= 0 && SalCommandsArray[*index].SalCmd != -1) - { - if (*index == 0) - { - // need to compute the command states; SalCommandsArray uses them - MainWindow->OnEnterIdle(); - } - - if (salCmd != NULL) - *salCmd = SalCommandsArray[*index].SalCmd; - if (nameBuf != NULL && nameBufSize > 0) - { - lstrcpyn(nameBuf, ::LoadStr(SalCommandsArray[*index].TextID), nameBufSize); - } - if (SalCommandsArray[*index].Enabled != NULL) - { - if (enabled != NULL) - *enabled = *(SalCommandsArray[*index].Enabled); - } - if (type != NULL) - *type = SalCommandsArray[*index].Type; - - (*index)++; - return TRUE; - } - return FALSE; -} - -void CSalamanderGeneral::PostSalamanderCommand(int salCmd) -{ - CALL_STACK_MESSAGE2("CSalamanderGeneral::PostSalamanderCommand(%d)", salCmd); - if (salCmd < 0 || salCmd >= 500) - { - TRACE_E("CSalamanderGeneral::PostSalamanderCommand: salCmd is invalid (" << salCmd << " is not in range 0-499)."); - return; - } - - if (MainThreadID == GetCurrentThreadId()) - { // because of calls from the entry point where Plugin is set to -1 (just to look up plugin data) - // before WM_USER_POSTCMDORUNLOADPLUGIN would arrive, Plugin would be reset (according to the entry point's return value) - CPluginData* data = Plugins.GetPluginData(Plugin); - if (data != NULL) - { - data->Commands.Add(salCmd); - ExecCmdsOrUnloadMarkedPlugins = TRUE; - } - else - { - TRACE_E("Unexpected situation in CSalamanderGeneral::PostSalamanderCommand()."); - } - } - else // outside the entry point the Plugin is certainly set... - { - if (MainWindow != NULL && MainWindow->HWindow != NULL) - { - // check for a call while the entry point is starting (Plugin is set to -1) - if ((INT_PTR)Plugin == -1) - { - TRACE_E("You can call CSalamanderGeneral::PostSalamanderCommand only from main " - "thread when plugin entry-point is not finished yet!"); - } - else - { // 0 - unload, 1 - rebuild menu, 2-501 salCmd, 502-1000501 menuCmd - PostMessage(MainWindow->HWindow, WM_USER_POSTCMDORUNLOADPLUGIN, (WPARAM)Plugin, 2 + salCmd); - } - } - else - { - TRACE_E("Unexpected situation (2) in CSalamanderGeneral::PostSalamanderCommand()."); - } - } -} - -void CSalamanderGeneral::SetUserWorkedOnPanelPath(int panel) -{ - CALL_STACK_MESSAGE2("CSalamanderGeneral::SetUserWorkedOnPanelPath(%d)", panel); - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::SetUserWorkedOnPanelPath() only from main thread!"); - return; - } - CFilesWindow* p = GetPanel(panel); - if (p != NULL) - p->UserWorkedOnThisPath = TRUE; -} - -void CSalamanderGeneral::StoreSelectionOnPanelPath(int panel) -{ - CALL_STACK_MESSAGE2("CSalamanderGeneral::StoreSelectionOnPanelPath(%d)", panel); - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::StoreSelectionOnPanelPath() only from main thread!"); - return; - } - CFilesWindow* p = GetPanel(panel); - if (p != NULL) - p->StoreSelection(); -} - -class CSalamanderMaskGroupImp : public CSalamanderMaskGroup -{ -protected: - CMaskGroup maskGroup; - -public: - CSalamanderMaskGroupImp() : maskGroup() {} - - virtual void WINAPI SetMasksString(const char* masks, BOOL extendedMode) { maskGroup.SetMasksString(masks, extendedMode); } - virtual void WINAPI GetMasksString(char* buffer) { lstrcpyn(buffer, maskGroup.GetMasksString(), MAX_GROUPMASK); } - virtual BOOL WINAPI GetExtendedMode() { return maskGroup.GetExtendedMode(); } - virtual BOOL WINAPI PrepareMasks(int& errorPos) { return maskGroup.PrepareMasks(errorPos); } - virtual BOOL WINAPI AgreeMasks(const char* fileName, const char* fileExt) { return maskGroup.AgreeMasks(fileName, fileExt); } -}; - -CSalamanderMaskGroup* -CSalamanderGeneral::AllocSalamanderMaskGroup() -{ - CALL_STACK_MESSAGE1("CSalamanderGeneral::AllocSalamanderMaskGroup()"); - CSalamanderMaskGroup* ret = new CSalamanderMaskGroupImp; - if (ret == NULL) - TRACE_E(LOW_MEMORY); - return ret; -} - -void CSalamanderGeneral::FreeSalamanderMaskGroup(CSalamanderMaskGroup* maskGroup) -{ - CALL_STACK_MESSAGE1("CSalamanderGeneral::FreeSalamanderMaskGroup()"); - if (maskGroup != NULL) - delete ((CSalamanderMaskGroupImp*)maskGroup); -} - -DWORD -CSalamanderGeneral::UpdateCrc32(const void* buffer, DWORD count, DWORD crcVal) -{ - CALL_STACK_MESSAGE_NONE - return ::UpdateCrc32(buffer, count, crcVal); -} - -class CSalamanderMD5Imp : public CSalamanderMD5 -{ -protected: - MD5 md5; - -public: - CSalamanderMD5Imp() : md5() {} - - virtual void WINAPI Init() { md5.init(); } - virtual void WINAPI Update(const void* input, DWORD input_length) { md5.update((unsigned char*)input, input_length); } - virtual void WINAPI Finalize() { md5.finalize(); } - virtual void WINAPI GetDigest(void* dest) { memcpy(dest, md5.digest, 16); } -}; - -CSalamanderMD5* -CSalamanderGeneral::AllocSalamanderMD5() -{ - CALL_STACK_MESSAGE1("CSalamanderGeneral::AllocSalamanderMD5()"); - CSalamanderMD5Imp* ret = new CSalamanderMD5Imp; - if (ret == NULL) - TRACE_E(LOW_MEMORY); - return ret; -} - -void CSalamanderGeneral::FreeSalamanderMD5(CSalamanderMD5* md5) -{ - CALL_STACK_MESSAGE1("CSalamanderGeneral::FreeSalamanderMD5()"); - if (md5 != NULL) - delete ((CSalamanderMD5Imp*)md5); -} - -BOOL CSalamanderGeneral::LookForSubTexts(char* text, DWORD* varPlacements, int* varPlacementsCount) -{ - CALL_STACK_MESSAGE_NONE - return ::LookForSubTexts(text, varPlacements, varPlacementsCount); -} - -void CSalamanderGeneral::WaitForESCRelease() -{ - CALL_STACK_MESSAGE_NONE - ::WaitForESCRelease(); -} - -DWORD -CSalamanderGeneral::GetMouseWheelScrollLines() -{ - CALL_STACK_MESSAGE_NONE - return ::GetMouseWheelScrollLines(); -} - -DWORD -CSalamanderGeneral::GetMouseWheelScrollChars() -{ - CALL_STACK_MESSAGE_NONE - return ::GetMouseWheelScrollChars(); -} - -HWND CSalamanderGeneral::GetTopVisibleParent(HWND hParent) -{ - CALL_STACK_MESSAGE_NONE - return ::GetTopVisibleParent(hParent); -} - -BOOL CSalamanderGeneral::MultiMonGetDefaultWindowPos(HWND hByWnd, POINT* p) -{ - CALL_STACK_MESSAGE_NONE - return ::MultiMonGetDefaultWindowPos(hByWnd, p); -} - -void CSalamanderGeneral::MultiMonGetClipRectByRect(const RECT* rect, RECT* workClipRect, RECT* monitorClipRect) -{ - CALL_STACK_MESSAGE_NONE - ::MultiMonGetClipRectByRect(rect, workClipRect, monitorClipRect); -} - -void CSalamanderGeneral::MultiMonGetClipRectByWindow(HWND hByWnd, RECT* workClipRect, RECT* monitorClipRect) -{ - CALL_STACK_MESSAGE_NONE - ::MultiMonGetClipRectByWindow(hByWnd, workClipRect, monitorClipRect); -} - -void CSalamanderGeneral::MultiMonCenterWindow(HWND hWindow, HWND hByWnd, BOOL findTopWindow) -{ - CALL_STACK_MESSAGE_NONE - ::MultiMonCenterWindow(hWindow, hByWnd, findTopWindow); -} - -BOOL CSalamanderGeneral::MultiMonEnsureRectVisible(RECT* rect, BOOL partialOK) -{ - CALL_STACK_MESSAGE_NONE - return ::MultiMonEnsureRectVisible(rect, partialOK); -} - -BOOL CSalamanderGeneral::InstallWordBreakProc(HWND hWindow) -{ - CALL_STACK_MESSAGE_NONE - return ::InstallWordBreakProc(hWindow); -} - -BOOL CSalamanderGeneral::IsFirstInstance3OrLater() -{ - CALL_STACK_MESSAGE_NONE - return FirstInstance_3_or_later; -} - -int CSalamanderGeneral::ExpandPluralString(char* buffer, int bufferSize, const char* format, - int parametersCount, const CQuadWord* parametersArray) -{ - CALL_STACK_MESSAGE4("CSalamanderGeneral::ExpandPluralString(, %d, %s, %d, )", - bufferSize, format, parametersCount); - return ::ExpandPluralString(buffer, bufferSize, format, parametersCount, parametersArray); -} - -int CSalamanderGeneral::ExpandPluralFilesDirs(char* buffer, int bufferSize, int files, int dirs, - int mode, BOOL forDlgCaption) -{ - CALL_STACK_MESSAGE6("CSalamanderGeneral::ExpandPluralFilesDirs(, %d, %d, %d, %d, %d)", - bufferSize, files, dirs, (int)mode, forDlgCaption); - return ::ExpandPluralFilesDirs(buffer, bufferSize, files, dirs, mode, forDlgCaption); -} - -int CSalamanderGeneral::ExpandPluralBytesFilesDirs(char* buffer, int bufferSize, - const CQuadWord& selectedBytes, int files, int dirs, - BOOL useSubTexts) -{ - CALL_STACK_MESSAGE6("CSalamanderGeneral::FreeSalamanderMaskGroup(, %d, %g, %d, %d, %d)", - bufferSize, selectedBytes.GetDouble(), files, dirs, useSubTexts); - return ::ExpandPluralBytesFilesDirs(buffer, bufferSize, selectedBytes, files, dirs, useSubTexts); -} - -void CSalamanderGeneral::GetCommonFSOperSourceDescr(char* sourceDescr, int sourceDescrSize, - int panel, int selectedFiles, int selectedDirs, - const char* fileOrDirName, BOOL isDir, - BOOL forDlgCaption) -{ - CALL_STACK_MESSAGE8("CSalamanderGeneral::GetCommonFSOperSourceDescr(, %d, %d, %d, %d, %s, %d, %d)", - sourceDescrSize, panel, selectedFiles, selectedDirs, fileOrDirName, - isDir, forDlgCaption); - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::GetCommonFSOperSourceDescr() only from main thread!"); - return; - } - if (sourceDescrSize <= 0) - return; - if (sourceDescr == NULL) - { - TRACE_E("CSalamanderGeneral::GetCommonFSOperSourceDescr(): 'sourceDescr' may not be NULL!"); - return; - } - if (selectedFiles + selectedDirs <= 1 && panel == -1 && fileOrDirName == NULL) - { - TRACE_E("CSalamanderGeneral::GetCommonFSOperSourceDescr(): 'fileOrDirName' may not be NULL!"); - sourceDescr[0] = 0; - return; - } - if (selectedFiles + selectedDirs <= 1) // one selected item or the focus - { - BOOL nameIsDir; - char* name; - char nameBuf[MAX_PATH]; - if (panel != -1) - { - const CFileData* f; - if (selectedFiles == 0 && selectedDirs == 0) - f = GetPanelFocusedItem(panel, &nameIsDir); - else - { - int index = 0; - f = GetPanelSelectedItem(panel, &index, &nameIsDir); - } - if (f != NULL && f->Name != NULL) - name = f->Name; - else - { - TRACE_E("Unexpected situation in CSalamanderGeneral::GetCommonFSOperSourceDescr()!"); - sourceDescr[0] = 0; - return; - } - } - else - { - lstrcpyn(nameBuf, fileOrDirName, MAX_PATH); - name = nameBuf; - nameIsDir = isDir; - } - int fileNameFormat; - GetConfigParameter(SALCFG_FILENAMEFORMAT, &fileNameFormat, - sizeof(fileNameFormat), NULL); - char formatedFileName[MAX_PATH]; // CFileData::Name is at most MAX_PATH-5 characters long - Salamander's limit - ::AlterFileName(formatedFileName, name, -1, fileNameFormat, 0, nameIsDir); - _snprintf_s(sourceDescr, sourceDescrSize, _TRUNCATE, - ::LoadStr(nameIsDir ? (forDlgCaption ? IDS_DLG_QUESTION_DIRECTORY : IDS_QUESTION_DIRECTORY) : (forDlgCaption ? IDS_DLG_QUESTION_FILE : IDS_QUESTION_FILE)), - formatedFileName); - } - else // multiple directories and files - { - ExpandPluralFilesDirs(sourceDescr, sourceDescrSize, selectedFiles, selectedDirs, - epfdmNormal, forDlgCaption); - } -} - -void CSalamanderGeneral::AddStrToStr(char* dstStr, int dstBufSize, const char* srcStr) -{ - CALL_STACK_MESSAGE1("CSalamanderGeneral::AddStrToStr(, ,)"); - if (dstBufSize < 2) - { - TRACE_E("CSalamanderGeneral::AddStrToStr(): dstBufSize must be greater or equal to 2"); - return; - } - ::AddStrToStr(dstStr, dstBufSize, srcStr); -} - -BOOL CSalamanderGeneral::SalIsValidFileNameComponent(const char* fileNameComponent) -{ - CALL_STACK_MESSAGE1("CSalamanderGeneral::SalIsValidFileNameComponent()"); - return ::SalIsValidFileNameComponent(fileNameComponent); -} - -void CSalamanderGeneral::SalMakeValidFileNameComponent(char* fileNameComponent) -{ - CALL_STACK_MESSAGE1("CSalamanderGeneral::SalMakeValidFileNameComponent()"); - ::SalMakeValidFileNameComponent(fileNameComponent); -} - -BOOL CSalamanderGeneral::IsFileEnumSourcePanel(int srcUID, int* panel) -{ - CALL_STACK_MESSAGE2("CSalamanderGeneral::IsFileEnumSourcePanel(%d,)", srcUID); - return ::IsFileEnumSourcePanel(srcUID, panel); -} - -BOOL CSalamanderGeneral::GetNextFileNameForViewer(int srcUID, int* lastFileIndex, const char* lastFileName, - BOOL preferSelected, BOOL onlyAssociatedExtensions, - char* fileName, BOOL* noMoreFiles, BOOL* srcBusy) -{ - CALL_STACK_MESSAGE5("CSalamanderGeneral::GetNextFileNameForViewer(%d, , , %d, %d, %s, ,)", - srcUID, preferSelected, onlyAssociatedExtensions, fileName); - if (fileName == NULL || lastFileIndex == NULL) - { - if (noMoreFiles != NULL) - *noMoreFiles = FALSE; - if (srcBusy != NULL) - *srcBusy = FALSE; - TRACE_E("CSalamanderGeneral::GetNextFileNameForViewer(): invalid parameters (fileName == NULL || lastFileIndex == NULL)!"); - return FALSE; - } - if (Plugin == NULL || (INT_PTR)Plugin == -1) - { - if (noMoreFiles != NULL) - *noMoreFiles = FALSE; - if (srcBusy != NULL) - *srcBusy = FALSE; - TRACE_E("CSalamanderGeneral::GetNextFileNameForViewer(): unexpected call, plugin is not initialized yet!"); - return FALSE; - } - return ::GetNextFileNameForViewer(srcUID, lastFileIndex, lastFileName, preferSelected, - onlyAssociatedExtensions, fileName, - noMoreFiles, srcBusy, Plugin); -} - -BOOL CSalamanderGeneral::GetPreviousFileNameForViewer(int srcUID, int* lastFileIndex, const char* lastFileName, - BOOL preferSelected, BOOL onlyAssociatedExtensions, - char* fileName, BOOL* noMoreFiles, BOOL* srcBusy) -{ - CALL_STACK_MESSAGE5("CSalamanderGeneral::GetPreviousFileNameForViewer(%d, , , %d, %d, %s, ,)", - srcUID, preferSelected, onlyAssociatedExtensions, fileName); - if (fileName == NULL || lastFileIndex == NULL) - { - if (noMoreFiles != NULL) - *noMoreFiles = FALSE; - if (srcBusy != NULL) - *srcBusy = FALSE; - TRACE_E("CSalamanderGeneral::GetPreviousFileNameForViewer(): invalid parameters (fileName == NULL || lastFileIndex == NULL)!"); - return FALSE; - } - if (Plugin == NULL || (INT_PTR)Plugin == -1) - { - if (noMoreFiles != NULL) - *noMoreFiles = FALSE; - if (srcBusy != NULL) - *srcBusy = FALSE; - TRACE_E("CSalamanderGeneral::GetPreviousFileNameForViewer(): unexpected call, plugin is not initialized yet!"); - return FALSE; - } - return ::GetPreviousFileNameForViewer(srcUID, lastFileIndex, lastFileName, preferSelected, - onlyAssociatedExtensions, fileName, - noMoreFiles, srcBusy, Plugin); -} - -BOOL CSalamanderGeneral::IsFileNameForViewerSelected(int srcUID, int lastFileIndex, - const char* lastFileName, - BOOL* isFileSelected, BOOL* srcBusy) -{ - CALL_STACK_MESSAGE3("CSalamanderGeneral::IsFileNameForViewerSelected(%d, %d, , , ,)", - srcUID, lastFileIndex); - if (isFileSelected == NULL) - { - if (srcBusy != NULL) - *srcBusy = FALSE; - TRACE_E("CSalamanderGeneral::IsFileNameForViewerSelected(): invalid parameters (isFileSelected == NULL)!"); - return FALSE; - } - return ::IsFileNameForViewerSelected(srcUID, lastFileIndex, lastFileName, - isFileSelected, srcBusy); -} - -BOOL CSalamanderGeneral::SetSelectionOnFileNameForViewer(int srcUID, int lastFileIndex, - const char* lastFileName, BOOL select, - BOOL* srcBusy) -{ - CALL_STACK_MESSAGE4("CSalamanderGeneral::SetSelectionOnFileNameForViewer(%d, %d, , %d,)", - srcUID, lastFileIndex, select); - return ::SetSelectionOnFileNameForViewer(srcUID, lastFileIndex, lastFileName, select, srcBusy); -} - -BOOL CSalamanderGeneral::GetStdHistoryValues(int historyID, char*** historyArr, int* historyItemsCount) -{ - CALL_STACK_MESSAGE2("CSalamanderGeneral::GetStdHistoryValues(%d, ,)", historyID); - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::GetStdHistoryValues() only from main thread!"); - if (historyArr != NULL) - *historyArr = NULL; - if (historyItemsCount != NULL) - *historyItemsCount = 0; - return FALSE; - } - if (historyArr == NULL || historyItemsCount == NULL) - { - TRACE_E("CSalamanderGeneral::GetStdHistoryValues(): invalid parameters!"); - return FALSE; - } - switch (historyID) - { - case SALHIST_COPYMOVETGT: - { - *historyArr = Configuration.CopyHistory; - *historyItemsCount = COPY_HISTORY_SIZE; - return TRUE; - } - - case SALHIST_CREATEDIR: - { - *historyArr = Configuration.CreateDirHistory; - *historyItemsCount = CREATEDIR_HISTORY_SIZE; - return TRUE; - } - - case SALHIST_CHANGEDIR: - { - *historyArr = Configuration.ChangeDirHistory; - *historyItemsCount = CHANGEDIR_HISTORY_SIZE; - return TRUE; - } - - case SALHIST_QUICKRENAME: - { - *historyArr = Configuration.QuickRenameHistory; - *historyItemsCount = QUICKRENAME_HISTORY_SIZE; - return TRUE; - } - - case SALHIST_EDITNEW: - { - *historyArr = Configuration.EditNewHistory; - *historyItemsCount = EDITNEW_HISTORY_SIZE; - return TRUE; - } - - case SALHIST_CONVERT: - { - *historyArr = Configuration.ConvertHistory; - *historyItemsCount = CONVERT_HISTORY_SIZE; - return TRUE; - } - - default: - { - *historyArr = NULL; - *historyItemsCount = 0; - return FALSE; - } - } -} - -void CSalamanderGeneral::AddValueToStdHistoryValues(char** historyArr, int historyItemsCount, - const char* value, BOOL caseSensitiveValue) -{ - CALL_STACK_MESSAGE4("CSalamanderGeneral::AddValueToStdHistoryValues(, %d, %s, %d)", - historyItemsCount, value, caseSensitiveValue); - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::AddValueToStdHistoryValues() only from main thread!"); - return; - } - if (historyArr == NULL || value == NULL) - { - TRACE_E("CSalamanderGeneral::AddValueToStdHistoryValues(): 'historyArr' and 'value' may not be NULL!"); - return; - } - ::AddValueToStdHistoryValues(historyArr, historyItemsCount, value, caseSensitiveValue); -} - -void CSalamanderGeneral::LoadComboFromStdHistoryValues(HWND combo, char** historyArr, int historyItemsCount) -{ - CALL_STACK_MESSAGE2("CSalamanderGeneral::LoadComboFromStdHistoryValues(, , %d)", - historyItemsCount); - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::LoadComboFromStdHistoryValues() only from main thread!"); - return; - } - if (historyArr == NULL) - { - TRACE_E("CSalamanderGeneral::LoadComboFromStdHistoryValues(): 'historyArr' may not be NULL!"); - return; - } - ::LoadComboFromStdHistoryValues(combo, historyArr, historyItemsCount); -} - -BOOL CSalamanderGeneral::CanUse256ColorsBitmap() -{ - CALL_STACK_MESSAGE1("CSalamanderGeneral::CanUse256ColorsBitmap()"); - return ::Use256ColorsBitmap(); -} - -HWND CSalamanderGeneral::GetWndToFlash(HWND parent) -{ - CALL_STACK_MESSAGE1("CSalamanderGeneral::GetWndToFlash()"); - return ::GetWndToFlash(parent); -} - -void ActivateDropTarget(HWND dropTarget, HWND progressWnd) -{ - if (dropTarget != NULL) - { // this dirty hack removes the activated state without an active application (visually inactive, but WM_ACTIVATEAPP with "activate" already arrived) - HWND tgtWnd = dropTarget; - HWND tmp; - while ((tmp = ::GetParent(tgtWnd)) != NULL && IsWindowEnabled(tmp)) - tgtWnd = tmp; - if (MainWindow != NULL && tgtWnd != MainWindow->HWindow) - { // perform it only if it is not an operation inside our Salamander - SetForegroundWindow(progressWnd); - SetForegroundWindow(tgtWnd); - // TRACE_I("SetForegroundWindow: " << hex << tgtWnd); - } - } -} - -void CSalamanderGeneral::ActivateDropTarget(HWND dropTarget, HWND progressWnd) -{ - CALL_STACK_MESSAGE1("CSalamanderGeneral::ActivateDropTarget(,)"); - ::ActivateDropTarget(dropTarget, progressWnd); -} - -void CSalamanderGeneral::PostOpenPackDlgForThisPlugin(int delFilesAfterPacking) -{ - CALL_STACK_MESSAGE2("CSalamanderGeneral::PostOpenPackDlgForThisPlugin(%d)", delFilesAfterPacking); - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::PostOpenPackDlgForThisPlugin() only from main thread!"); - return; - } - - CPluginData* data = Plugins.GetPluginData(Plugin); - if (data != NULL) - { - data->OpenPackDlg = TRUE; - data->PackDlgDelFilesAfterPacking = delFilesAfterPacking; - OpenPackOrUnpackDlgForMarkedPlugins = TRUE; - } - else - { - TRACE_E("Unexpected situation in CSalamanderGeneral::PostOpenPackDlgForThisPlugin()."); - } -} - -void CSalamanderGeneral::PostOpenUnpackDlgForThisPlugin(const char* unpackMask) -{ - CALL_STACK_MESSAGE2("CSalamanderGeneral::PostOpenUnpackDlgForThisPlugin(%s)", unpackMask); - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::PostOpenUnpackDlgForThisPlugin() only from main thread!"); - return; - } - - CPluginData* data = Plugins.GetPluginData(Plugin); - if (data != NULL) - { - data->OpenUnpackDlg = TRUE; - if (data->UnpackDlgUnpackMask != NULL) - free(data->UnpackDlgUnpackMask); - if (unpackMask != NULL) - data->UnpackDlgUnpackMask = DupStr(unpackMask); - else - data->UnpackDlgUnpackMask = NULL; - OpenPackOrUnpackDlgForMarkedPlugins = TRUE; - } - else - { - TRACE_E("Unexpected situation in CSalamanderGeneral::PostOpenUnpackDlgForThisPlugin()."); - } -} - -HANDLE -CSalamanderGeneral::SalCreateFileEx(const char* fileName, DWORD desiredAccess, DWORD shareMode, - DWORD flagsAndAttributes, DWORD* err) -{ - CALL_STACK_MESSAGE1("CSalamanderGeneral::SalCreateFileEx()"); - HANDLE ret = ::SalCreateFileEx(fileName, desiredAccess, shareMode, flagsAndAttributes, NULL); - if (err != NULL) - *err = GetLastError(); - return ret; -} - -BOOL CSalamanderGeneral::SalCreateDirectoryEx(const char* name, DWORD* err) -{ - CALL_STACK_MESSAGE1("CSalamanderGeneral::SalCreateDirectoryEx()"); - return ::SalCreateDirectoryEx(name, err); -} - -void CSalamanderGeneral::PanelStopMonitoring(int panel, BOOL stopMonitoring) -{ - CALL_STACK_MESSAGE3("CSalamanderGeneral::PanelStopMonitoring(%d, %d)", panel, stopMonitoring); - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::PanelStopMonitoring() only from main thread!"); - return; - } - CFilesWindow* p = GetPanel(panel); - if (p != NULL) - p->HandsOff(stopMonitoring); -} - -CSalamanderDirectoryAbstract* -CSalamanderGeneral::AllocSalamanderDirectory(BOOL isForFS) -{ - CALL_STACK_MESSAGE2("CSalamanderGeneral::AllocSalamanderDirectory(%d)", isForFS); - CSalamanderDirectory* ret = new CSalamanderDirectory(isForFS); - if (ret == NULL) - TRACE_E(LOW_MEMORY); - else - ret->AllocAddCache(); - return ret; -} - -void CSalamanderGeneral::FreeSalamanderDirectory(CSalamanderDirectoryAbstract* salDir) -{ - CALL_STACK_MESSAGE1("CSalamanderGeneral::FreeSalamanderDirectory()"); - if (salDir != NULL) - delete ((CSalamanderDirectory*)salDir); -} - -BOOL CSalamanderGeneral::AddPluginFSTimer(int timeout, CPluginFSInterfaceAbstract* timerOwner, - DWORD timerParam) // FIXME_X64 - review the Salamander interface to ensure we do not pass parameters that should hold x64 pointers; for example here 'timerParam' -{ - CALL_STACK_MESSAGE3("CSalamanderGeneral::AddPluginFSTimer(%d, , 0x%X)", timeout, timerParam); - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::AddPluginFSTimer() only from main thread!"); - return FALSE; - } - if (timeout < 0) - { - TRACE_E("CSalamanderGeneral::AddPluginFSTimer(): invalid timeout value (" << timeout << ")!"); - return FALSE; - } - if (timerOwner == NULL) - { - TRACE_E("CSalamanderGeneral::AddPluginFSTimer(): invalid timerOwner (NULL)!"); - return FALSE; - } - return Plugins.AddPluginFSTimer(timeout, timerOwner, timerParam); -} - -int CSalamanderGeneral::KillPluginFSTimer(CPluginFSInterfaceAbstract* timerOwner, BOOL allTimers, - DWORD timerParam) -{ - CALL_STACK_MESSAGE3("CSalamanderGeneral::KillPluginFSTimer(, %d, 0x%X)", allTimers, timerParam); - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::KillPluginFSTimer() only from main thread!"); - return 0; - } - if (timerOwner == NULL) - { - TRACE_E("CSalamanderGeneral::KillPluginFSTimer(): invalid timer owner (NULL)!"); - return 0; - } - return Plugins.KillPluginFSTimer(timerOwner, allTimers, timerParam); -} - -BOOL CSalamanderGeneral::GetChangeDriveMenuItemVisibility() -{ - CALL_STACK_MESSAGE1("CSalamanderGeneral::GetChangeDriveMenuItemVisibility()"); - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::GetChangeDriveMenuItemVisibility() only from main thread!"); - return FALSE; - } - CPluginData* data = Plugins.GetPluginData(Plugin); - if (data != NULL) - return data->ChDrvMenuFSItemVisible; - else - TRACE_E("Unexpected situation in CSalamanderGeneral::GetChangeDriveMenuItemVisibility()."); - return FALSE; -} - -void CSalamanderGeneral::SetChangeDriveMenuItemVisibility(BOOL visible) -{ - CALL_STACK_MESSAGE2("CSalamanderGeneral::SetChangeDriveMenuItemVisibility(%d)", visible); - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::SetChangeDriveMenuItemVisibility() only from main thread!"); - return; - } - CPluginData* data = Plugins.GetPluginData(Plugin); - if (data != NULL) - data->ChDrvMenuFSItemVisible = (visible != FALSE); - else - TRACE_E("Unexpected situation in CSalamanderGeneral::SetChangeDriveMenuItemVisibility()."); -} - -void CSalamanderGeneral::OleSpySetBreak(int alloc) -{ - CALL_STACK_MESSAGE2("CSalamanderGeneral::OleSpySetBreak(%d)", alloc); - ::OleSpySetBreak(alloc); -} - -HICON -CSalamanderGeneral::GetSalamanderIcon(int icon, int iconSize) -{ - CALL_STACK_MESSAGE3("CSalamanderGeneral::GetSalamanderIcon(%d, %d)", icon, iconSize); - - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::GetSalamanderIcon() only from main thread!"); - return NULL; - } - - CSymbolsImageListIndexes iconIndex; - switch (icon) - { - case SALICON_EXECUTABLE: - iconIndex = symbolsExecutable; - break; - case SALICON_DIRECTORY: - iconIndex = symbolsDirectory; - break; - case SALICON_NONASSOCIATED: - iconIndex = symbolsNonAssociated; - break; - case SALICON_ASSOCIATED: - iconIndex = symbolsAssociated; - break; - case SALICON_UPDIR: - iconIndex = symbolsUpDir; - break; - case SALICON_ARCHIVE: - iconIndex = symbolsArchive; - break; - default: - { - TRACE_E("CSalamanderGeneral::GetSalamanderIcon: invalid icon=" << icon << " forcing SALICON_NONASSOCIATED"); - iconIndex = symbolsNonAssociated; - } - } - - CIconSizeEnum salIconSize; - switch (iconSize) - { - case SALICONSIZE_16: - salIconSize = ICONSIZE_16; - break; - case SALICONSIZE_32: - salIconSize = ICONSIZE_32; - break; - case SALICONSIZE_48: - salIconSize = ICONSIZE_48; - break; - default: - { - TRACE_E("CSalamanderGeneral::GetSalamanderIcon: invalid iconSize=" << iconSize << " forcing SALICONSIZE_16"); - salIconSize = ICONSIZE_16; - } - } - - CIconList* list = SimpleIconLists[salIconSize]; - if (list == NULL) - { - TRACE_E("CSalamanderGeneral::GetSalamanderIcon: list == NULL"); - return NULL; - } - - return list->GetIcon(iconIndex, FALSE); -} - -BOOL CSalamanderGeneral::GetFileIcon(const char* path, BOOL pathIsPIDL, HICON* hIcon, int iconSize, - BOOL fallbackToDefIcon, BOOL defIconIsDir) -{ - CALL_STACK_MESSAGE5("CSalamanderGeneral::GetFileIcon(, %d, , %d, %d, %d)", - pathIsPIDL, iconSize, fallbackToDefIcon, defIconIsDir); - CIconSizeEnum salIconSize; - switch (iconSize) - { - case SALICONSIZE_16: - salIconSize = ICONSIZE_16; - break; - case SALICONSIZE_32: - salIconSize = ICONSIZE_32; - break; - case SALICONSIZE_48: - salIconSize = ICONSIZE_48; - break; - default: - { - TRACE_E("CSalamanderGeneral::GetFileIcon: invalid iconSize=" << iconSize << " forcing SALICONSIZE_16"); - salIconSize = ICONSIZE_16; - } - } - return ::GetFileIcon(path, pathIsPIDL, hIcon, salIconSize, fallbackToDefIcon, defIconIsDir); -} - -class CSalamanderPNG : public CSalamanderPNGAbstract -{ -public: - virtual HBITMAP WINAPI LoadPNGBitmap(HINSTANCE hInstance, LPCTSTR lpBitmapName, DWORD flags, COLORREF unused) - { - HBITMAP hBitmap = ::LoadPNGBitmap(hInstance, lpBitmapName, flags); - if (hBitmap != NULL) // the handle is handed over to the plug-in; the plug-in is responsible for destroying it, remove it from Salamander HANDLES - HANDLES_REMOVE(hBitmap, __htHandle_comp_with_DeleteObject, "DeleteObject"); - return hBitmap; - } - - virtual HBITMAP WINAPI LoadRawPNGBitmap(const void* rawPNG, DWORD rawPNGSize, DWORD flags, COLORREF unused) - { - HBITMAP hBitmap = ::LoadRawPNGBitmap(rawPNG, rawPNGSize, flags); - if (hBitmap != NULL) // the handle is handed over to the plug-in; the plug-in is responsible for destroying it, remove it from Salamander HANDLES - HANDLES_REMOVE(hBitmap, __htHandle_comp_with_DeleteObject, "DeleteObject"); - return hBitmap; - } -}; - -CSalamanderPNG SalamanderPNG; - -CSalamanderPNGAbstract* -CSalamanderGeneral::GetSalamanderPNG() -{ - CALL_STACK_MESSAGE_NONE - return &SalamanderPNG; -} - -CSalamanderPasswordManagerAbstract* -CSalamanderGeneral::GetSalamanderPasswordManager() -{ - CALL_STACK_MESSAGE1("CSalamanderGeneral::GetSalamanderPasswordManager()"); - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::GetSalamanderPasswordManager() only from main thread!"); - return NULL; - } - CPluginData* data = Plugins.GetPluginData(Plugin); - if (data != NULL) - return &data->SalamanderPasswordManager; - else - TRACE_E("Unexpected situation in CSalamanderGeneral::GetSalamanderPasswordManager()."); - return NULL; -} - -class CSalamanderCrypt : public CSalamanderCryptAbstract -{ -public: - /* AES functions */ - virtual int WINAPI AESInit(CSalAES* aes, - int mode, /* Mode (key size) to be used (input) */ - LPCSTR pwd, /* User specified password (input) */ - size_t pwd_len, /* Password length (input) */ - LPBYTE salt, /* Salt (input) */ - LPWORD pwd_ver) /* 2 byte password verifier (output) */ - { - _ASSERT(sizeof(aes->nonce) == sizeof(((fcrypt_ctx*)aes)->nonce)); - _ASSERT(sizeof(aes->encr_bfr) == sizeof(((fcrypt_ctx*)aes)->encr_bfr)); - _ASSERT(sizeof(aes->encr_ctx) == sizeof(((fcrypt_ctx*)aes)->encr_ctx)); - _ASSERT(sizeof(aes->auth_ctx) == sizeof(((fcrypt_ctx*)aes)->auth_ctx)); - _ASSERT(sizeof(aes->nonce) == sizeof(((fcrypt_ctx*)aes)->nonce)); - _ASSERT(sizeof(CSalAES) == sizeof(fcrypt_ctx)); - return fcrypt_init(mode, (unsigned char*)pwd, (unsigned int)pwd_len, salt, (unsigned char*)pwd_ver, (fcrypt_ctx*)aes); - } - - virtual void WINAPI AESEncrypt(CSalAES* aes, LPVOID data, size_t dataLen) - { - fcrypt_encrypt((unsigned char*)data, (unsigned int)dataLen, (fcrypt_ctx*)aes); - } - - virtual void WINAPI AESDecrypt(CSalAES* aes, LPVOID data, size_t dataLen) - { - fcrypt_decrypt((unsigned char*)data, (unsigned int)dataLen, (fcrypt_ctx*)aes); - } - - virtual void WINAPI AESEnd(CSalAES* aes, LPBYTE mac, LPDWORD pMacLen) - { - if (pMacLen) - *pMacLen = SAL_AES_MAC_LENGTH(aes->mode); - fcrypt_end(mac, (fcrypt_ctx*)aes); - } - - /* SHA1 functions */ - virtual void WINAPI SHA1Init(CSalSHA1* sha1) - { - _ASSERT(sizeof(CSalSHA1) == sizeof(SHA1_Context)); - ::SHA1Init((SHA1_CTX*)sha1); - } - - virtual void WINAPI SHA1Update(CSalSHA1* sha1, const LPBYTE data, size_t dataLen) - { - ::SHA1Update((SHA1_CTX*)sha1, data, (unsigned int)dataLen); - } - - virtual void WINAPI SHA1Final(CSalSHA1* sha1, BYTE digest[20]) - { - ::SHA1Final(digest, (SHA1_CTX*)sha1); - } -}; - -CSalamanderCrypt SalamanderCrypt; - -CSalamanderCryptAbstract* GetSalamanderCrypt() // for Salamander's internal use -{ - CALL_STACK_MESSAGE_NONE - return &SalamanderCrypt; -} - -CSalamanderCryptAbstract* -CSalamanderGeneral::GetSalamanderCrypt() -{ - CALL_STACK_MESSAGE_NONE - return &SalamanderCrypt; -} - -BOOL CSalamanderGeneral::FileExists(const char* fileName) -{ - CALL_STACK_MESSAGE2("CSalamanderGeneral::FileExists(%s)", fileName); - return ::FileExists(fileName); -} - -void CSalamanderGeneral::DisconnectFSFromPanel(HWND parent, int panel) -{ - CALL_STACK_MESSAGE2("CSalamanderGeneral::DisconnectFSFromPanel(, %d)", panel); - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::DisconnectFSFromPanel() only from main thread!"); - return; - } - char buf[MAX_PATH]; - BOOL rescueOrFixed = TRUE; - if (GetLastWindowsPanelPath(panel, buf, MAX_PATH)) - { // change the path to the last visited Windows path - BOOL tryNet = FALSE; - DWORD err; - DWORD lastErr; - BOOL pathInvalid; - BOOL cut; - if (::SalCheckAndRestorePathWithCut(parent, buf, tryNet, err, lastErr, - pathInvalid, cut, TRUE)) - { - int failReason; - if (ChangePanelPathToDisk(panel, buf, &failReason) || - failReason != CHPPFR_INVALIDPATH) // except for the "bad path" error (closing the FS was refused, etc.) - { - rescueOrFixed = FALSE; - } - } - } - if (rescueOrFixed) - ChangePanelPathToRescuePathOrFixedDrive(panel); // "always false" -} - -BOOL CSalamanderGeneral::IsArchiveHandledByThisPlugin(const char* name) -{ - CALL_STACK_MESSAGE2("CSalamanderGeneral::IsArchiveHandledByThisPlugin(%s)", name); - if (Plugin == NULL || (INT_PTR)Plugin == -1) - { - TRACE_E("CSalamanderGeneral::IsArchiveHandledByThisPlugin() unexpected call, plugin is not initialized yet!"); - return FALSE; - } - CPluginData* data = Plugins.GetPluginData(Plugin); - if (data == NULL) - { - TRACE_E("Unexpected situation in CSalamanderGeneral::IsArchiveHandledByThisPlugin()"); - return FALSE; - } - if (!data->SupportPanelView) - return FALSE; - - int format = PackerFormatConfig.PackIsArchive(name); - if (format != 0) // found a supported archive - { - format--; - int index = PackerFormatConfig.GetUnpackerIndex(format); - if (index < 0) // view: is it internal processing (plug-in)? - { - CPluginData* foundData = Plugins.Get(-index - 1); - if (foundData == data) // is it us? - return TRUE; - } - } - return FALSE; -} - -DWORD -CSalamanderGeneral::GetIconLRFlags() -{ - return IconLRFlags; -} - -int CSalamanderGeneral::IsFileLink(const char* fileExtension) -{ - CALL_STACK_MESSAGE_NONE - // CALL_STACK_MESSAGE1("CSalamanderGeneral::IsFileLink()"); - if (fileExtension == NULL) - return 0; - return ::IsFileLink(fileExtension); -} - -DWORD -CSalamanderGeneral::GetImageListColorFlags() -{ - CALL_STACK_MESSAGE_NONE - return ::GetImageListColorFlags(); -} - -void CSalamanderGeneral::SetHelpFileName(const char* chmName) -{ - CALL_STACK_MESSAGE_NONE - if (chmName == NULL || *chmName == 0) - TRACE_E("CSalamanderGeneral::SetHelpFileName(): invalid parameter 'chmName'."); - else - lstrcpyn(HelpFileName, chmName, MAX_PATH); -} - -BOOL CSalamanderGeneral::OpenHtmlHelp(HWND parent, CHtmlHelpCommand command, DWORD_PTR dwData, BOOL quiet) -{ - CALL_STACK_MESSAGE4("CSalamanderGeneral::OpenHtmlHelp(, %d, %Iu, %d)", command, dwData, quiet); - if (HelpFileName[0] == 0) - { - TRACE_E("CSalamanderGeneral::OpenHtmlHelp(): plugin must call CSalamanderGeneral::SetHelpFileName() first!"); - return FALSE; - } - else - { - return ::OpenHtmlHelp(HelpFileName, parent, command, dwData, quiet); - } -} - -BOOL CSalamanderGeneral::OpenHtmlHelpForSalamander(HWND parent, CHtmlHelpCommand command, DWORD_PTR dwData, BOOL quiet) -{ - CALL_STACK_MESSAGE4("CSalamanderGeneral::OpenHtmlHelpForSalamander(, %d, %Iu, %d)", command, dwData, quiet); - DWORD_PTR newData = dwData; - if (command == HHCDisplayContext) - { - switch (dwData) - { - case HTMLHELP_SALID_PWDMANAGER: - newData = IDH_PWDMANAGER; - break; // password manager help - default: - { - TRACE_E("CSalamanderGeneral::OpenHtmlHelpForSalamander(): invalid dwData parameter, see allowed HTMLHELP_SALID_XXX constants."); - return FALSE; - } - } - } - return ::OpenHtmlHelp(NULL, parent, command, newData, quiet); -} - -BOOL CSalamanderGeneral::PathsAreOnTheSameVolume(const char* path1, const char* path2, - BOOL* resIsOnlyEstimation) -{ - CALL_STACK_MESSAGE1("CSalamanderGeneral::PathsAreOnTheSameVolume(, ,)"); - return ::PathsAreOnTheSameVolume(path1, path2, resIsOnlyEstimation); -} - -BOOL CSalamanderGeneral::SafeGetOpenFileName(LPOPENFILENAME lpofn) -{ - CALL_STACK_MESSAGE_NONE - return ::SafeGetOpenFileName(lpofn); -} - -BOOL CSalamanderGeneral::SafeGetSaveFileName(LPOPENFILENAME lpofn) -{ - CALL_STACK_MESSAGE_NONE - return ::SafeGetSaveFileName(lpofn); -} - -void CSalamanderGeneral::SetPluginIsNethood() -{ - CALL_STACK_MESSAGE_NONE - if (MainThreadID != GetCurrentThreadId() || (INT_PTR)Plugin != -1) - { - TRACE_E("You can call CSalamanderGeneral::SetPluginIsNethood() only from entry-point!"); - return; - } - CPluginData* data = Plugins.GetPluginData(Plugin); - if (data != NULL) - data->PluginIsNethood = TRUE; - else - TRACE_E("Unexpected situation in CSalamanderGeneral::SetPluginIsNethood()."); -} - -void CSalamanderGeneral::SetPluginUsesPasswordManager() -{ - CALL_STACK_MESSAGE_NONE - if (MainThreadID != GetCurrentThreadId() || (INT_PTR)Plugin != -1) - { - TRACE_E("You can call CSalamanderGeneral::SetPluginUsesPasswordManager() only from entry-point!"); - return; - } - CPluginData* data = Plugins.GetPluginData(Plugin); - if (data != NULL) - data->PluginUsesPasswordManager = TRUE; - else - TRACE_E("Unexpected situation in CSalamanderGeneral::SetPluginUsesPasswordManager()."); -} - -void CSalamanderGeneral::OpenNetworkContextMenu(HWND parent, int panel, BOOL forItems, int menuX, - int menuY, const char* netPath, char* newlyMappedDrive) -{ - CALL_STACK_MESSAGE6("CSalamanderGeneral::OpenNetworkContextMenu(, %d, %d, %d, %d, %s,)", - panel, forItems, menuX, menuY, netPath); - - if (newlyMappedDrive != NULL) - *newlyMappedDrive = 0; - - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::OpenNetworkContextMenu() only from main thread!"); - return; - } - - if (netPath == NULL || netPath[0] != '\\' || netPath[1] != '\\' || strchr(netPath + 2, '\\') != NULL) - { - TRACE_E("CSalamanderGeneral::OpenNetworkContextMenu(): invalid netPath: " << (netPath != NULL ? netPath : "(null)")); - return; - } - - CFilesWindow* p = GetPanel(panel); - if (p != NULL) - { - BeginStopRefresh(); // no refreshes needed (formality: the call comes from a plug-in, so refreshes are already disabled by EnterPlugin) - - int* indexes = NULL; - int index = 0; - int count = 0; - if (forItems) - { - BOOL subDir; - if (p->Dirs->Count > 0) - subDir = (strcmp(p->Dirs->At(0).Name, "..") == 0); - else - subDir = FALSE; - - count = p->GetSelCount(); - if (count != 0) - { - indexes = new int[count]; - p->GetSelItems(count, indexes, TRUE); // we stepped back from this (see GetSelItems): for context menus we start from the focused item and end with the item before the focus (there is an intermediate wrap to the start of the name list) (the system does the same, e.g., Add To Windows Media Player List on MP3 files) - } - else - { - index = p->GetCaretIndex(); - if (subDir && index == 0) - { - EndStopRefresh(); - return; - } - } - } - else - index = -1; - - if (p->ContextMenu != NULL) - { - TRACE_E("CSalamanderGeneral::OpenNetworkContextMenu: p->ContextMenu must be NULL (probably forbidden recursive call)!"); - } - else - { - if (forItems) - { - CTmpEnumData data; - data.Indexes = (count == 0) ? &index : indexes; - data.Panel = p; - p->ContextMenu = CreateIContextMenu2(MainWindow->HWindow, netPath, (count == 0) ? 1 : count, - EnumFileNames, &data); - } - else - { - p->ContextMenu = CreateIContextMenu2(MainWindow->HWindow, netPath); - } - - HMENU h = CreatePopupMenu(); - if (p->ContextMenu != NULL && h != NULL) - { - UINT flags = CMF_NORMAL | CMF_EXPLORE; - // handle the pressed Shift key - extended context menu; under W2K it contains, for example, Run as... -#define CMF_EXTENDEDVERBS 0x00000100 // rarely used verbs - BOOL shiftPressed = (GetKeyState(VK_SHIFT) & 0x8000) != 0; - if (shiftPressed) - flags |= CMF_EXTENDEDVERBS; - - ShellActionAux5(flags, p, h); - RemoveUselessSeparatorsFromMenu(h); - - int cmd = 0; - if (GetMenuItemCount(h) > 0) // guard against a completely trimmed menu - { - CMenuPopup contextPopup; - contextPopup.SetTemplateMenu(h); - cmd = contextPopup.Track(MENU_TRACK_RETURNCMD | MENU_TRACK_RIGHTBUTTON, - menuX, menuY, parent, NULL); - } - if (cmd != 0) - { - CALL_STACK_MESSAGE1("CSalamanderGeneral::OpenNetworkContextMenu::exec"); - - char cmdName[2000]; // deliberately 2000 instead of 200; shell extensions sometimes write double (considering: Unicode = 2 * "number of characters"), etc. - if (AuxGetCommandString(p->ContextMenu, cmd, GCS_VERB, NULL, cmdName, 200) != NOERROR) - cmdName[0] = 0; - - // the Map Network Drive command is 40 on XP, 43 on W2K, and only under Vista has a defined cmdName - if ((stricmp(cmdName, "connectNetworkDrive") == 0 || - !WindowsVistaAndLater && cmd == 40) && - forItems && netPath[2] != 0) - { - char root[MAX_PATH]; - strcpy(root, netPath); - int focus = p->GetCaretIndex(); - if (SalPathAppend(root, (focus < p->Dirs->Count ? p->Dirs->At(focus) : p->Files->At(focus - p->Dirs->Count)).Name, MAX_PATH)) - { - char newDrive = 0; - p->ConnectNet(TRUE, root, FALSE /* called from a plug-in; must not change the panel path, otherwise - we would return to a deallocated FS object */ - , - &newDrive); - if (newlyMappedDrive != NULL) - *newlyMappedDrive = newDrive; - } - } - else - { - CShellExecuteWnd shellExecuteWnd; - CMINVOKECOMMANDINFOEX ici; - ZeroMemory(&ici, sizeof(CMINVOKECOMMANDINFOEX)); - ici.cbSize = sizeof(CMINVOKECOMMANDINFOEX); - ici.fMask = CMIC_MASK_PTINVOKE; - if (CanUseShellExecuteWndAsParent(cmdName)) - ici.hwnd = shellExecuteWnd.Create(MainWindow->HWindow, "SEW: CSalamanderGeneral::OpenNetworkContextMenu cmd=%d", cmd); - else - ici.hwnd = MainWindow->HWindow; - ici.lpVerb = MAKEINTRESOURCE(cmd); - ici.nShow = SW_SHOWNORMAL; - ici.ptInvoke.x = menuX; - ici.ptInvoke.y = menuY; - - AuxInvokeCommand(p, (CMINVOKECOMMANDINFO*)&ici); - - IdleRefreshStates = TRUE; // during the next Idle force checking the status variables - IdleCheckClipboard = TRUE; // also request checking the clipboard - } - } - } - { - CALL_STACK_MESSAGE1("CSalamanderGeneral::OpenNetworkContextMenu::release"); - ShellActionAux6(p); - if (h != NULL) - DestroyMenu(h); - } - } - - if (count != 0) - delete[] (indexes); - - EndStopRefresh(); - } -} - -BOOL CSalamanderGeneral::DuplicateBackslashes(char* buffer, int bufferSize) -{ - CALL_STACK_MESSAGE3("CSalamanderGeneral::DuplicateBackslashes(%s, %d)", buffer, bufferSize); - return ::DuplicateBackslashes(buffer, bufferSize); -} - -int CSalamanderGeneral::StartThrobber(int panel, const char* tooltip, int delay) -{ - CALL_STACK_MESSAGE4("CSalamanderGeneral::StartThrobber(%d, %s, %d)", panel, tooltip, delay); - - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::StartThrobber() only from main thread!"); - return -1; - } - - CFilesWindow* p = GetPanel(panel); - if (p != NULL && p->DirectoryLine != NULL) - { - p->DirectoryLine->SetThrobber(TRUE, delay); - p->DirectoryLine->SetThrobberTooltip(tooltip); - return p->DirectoryLine->ChangeThrobberID(); - } - return -1; -} - -BOOL CSalamanderGeneral::StopThrobber(int id) -{ - CALL_STACK_MESSAGE2("CSalamanderGeneral::StopThrobber(%d)", id); - - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::StopThrobber() only from main thread!"); - return FALSE; - } - - if (MainWindow->LeftPanel->DirectoryLine != NULL && - MainWindow->LeftPanel->DirectoryLine->IsThrobberVisible(id)) - { - MainWindow->LeftPanel->DirectoryLine->SetThrobber(FALSE); - return TRUE; - } - if (MainWindow->RightPanel->DirectoryLine != NULL && - MainWindow->RightPanel->DirectoryLine->IsThrobberVisible(id)) - { - MainWindow->RightPanel->DirectoryLine->SetThrobber(FALSE); - return TRUE; - } - return FALSE; -} - -void CSalamanderGeneral::ShowSecurityIcon(int panel, BOOL showIcon, BOOL isLocked, - const char* tooltip) -{ - CALL_STACK_MESSAGE5("CSalamanderGeneral::ShowSecurityIcon(%d, %d, %d, %s)", - panel, showIcon, isLocked, tooltip); - - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::ShowSecurityIcon() only from main thread!"); - return; - } - - CFilesWindow* p = GetPanel(panel); - if (p != NULL && p->DirectoryLine != NULL) - { - p->DirectoryLine->SetSecurity(showIcon ? (isLocked ? sisSecured : sisUnsecured) : sisNone); - p->DirectoryLine->SetSecurityTooltip(tooltip); - } -} - -void CSalamanderGeneral::RemoveCurrentPathFromHistory(int panel) -{ - CALL_STACK_MESSAGE2("CSalamanderGeneral::RemoveCurrentPathFromHistory(%d)", panel); - - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::RemoveCurrentPathFromHistory() only from main thread!"); - return; - } - - CFilesWindow* p = GetPanel(panel); - if (p != NULL) - { - p->RemoveCurrentPathFromHistory(); - if (MainWindow != NULL) - MainWindow->DirHistoryRemoveActualPath(p); - p->UserWorkedOnThisPath = FALSE; - } -} - -BOOL CSalamanderGeneral::IsUserAdmin() -{ - CALL_STACK_MESSAGE1("CSalamanderGeneral::IsUserAdmin()"); - return ::IsUserAdmin(); -} - -BOOL CSalamanderGeneral::IsRemoteSession() -{ - CALL_STACK_MESSAGE1("CSalamanderGeneral::IsRemoteSession()"); - return ::IsRemoteSession(); -} - -DWORD -CSalamanderGeneral::SalWNetAddConnection2Interactive(LPNETRESOURCE lpNetResource) -{ - CALL_STACK_MESSAGE1("CSalamanderGeneral::SalWNetAddConnection2Interactive()"); - DWORD err; - RestoreNetworkConnection(NULL, NULL, NULL, &err, lpNetResource); - return err; -} - -void CSalamanderGeneral::GetFocusedItemMenuPos(POINT* pos) -{ - CALL_STACK_MESSAGE1("CSalamanderGeneral::GetFocusedItemMenuPos()"); - - if (pos == NULL) - { - TRACE_E("CSalamanderGeneral::GetFocusedItemMenuPos(): invalid 'pos' (NULL)!"); - return; - } - - pos->x = 0; - pos->y = 0; - - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::GetFocusedItemMenuPos() only from main thread!"); - return; - } - - if (MainWindow != NULL) - { - CFilesWindow* activePanel = MainWindow->GetActivePanel(); - if (activePanel != NULL) - { - activePanel->GetContextMenuPos(pos); - return; - } - } - - pos->x = 0; - pos->y = 0; -} - -void CSalamanderGeneral::LockMainWindow(BOOL lock, HWND hToolWnd, const char* lockReason) -{ - CALL_STACK_MESSAGE4("CSalamanderGeneral::LockMainWindow(%d, 0x%p, %s)", lock, hToolWnd, lockReason); - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::LockMainWindow() only from main thread!"); - return; - } - if (MainWindow != NULL) - MainWindow->LockUI(lock, hToolWnd, lockReason); -} - -BOOL CSalamanderGeneral::GetMenuItemHotKey(int id, WORD* hotKey, char* hotKeyText, int hotKeyTextSize) -{ - CALL_STACK_MESSAGE2("CSalamanderGeneral::GetMenuItemHotKey(%d, , , )", id); - if (MainThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderGeneral::GetMenuItemHotKey() only from main thread!"); - return FALSE; - } - BOOL ret = FALSE; - CPluginData* data = Plugins.GetPluginData(Plugin); - if (data != NULL) - ret = data->GetMenuItemHotKey(id, hotKey, hotKeyText, hotKeyTextSize); - else - TRACE_E("Unexpected situation in CSalamanderGeneral::GetMenuItemHotKey()."); - return ret; -} - -LONG CSalamanderGeneral::SalRegQueryValue(HKEY hKey, LPCSTR lpSubKey, LPSTR lpData, PLONG lpcbData) -{ - CALL_STACK_MESSAGE1("CSalamanderGeneral::SalRegQueryValue(, , ,)"); - return ::SalRegQueryValue(hKey, lpSubKey, lpData, lpcbData); -} - -LONG CSalamanderGeneral::SalRegQueryValueEx(HKEY hKey, LPCSTR lpValueName, LPDWORD lpReserved, - LPDWORD lpType, LPBYTE lpData, LPDWORD lpcbData) -{ - CALL_STACK_MESSAGE1("CSalamanderGeneral::SalRegQueryValueEx(, , , , ,)"); - return ::SalRegQueryValueEx(hKey, lpValueName, lpReserved, lpType, lpData, lpcbData); -} - -DWORD -CSalamanderGeneral::SalGetFileAttributes(const char* fileName) -{ - CALL_STACK_MESSAGE1("CSalamanderGeneral::SalGetFileAttributes()"); - return ::SalGetFileAttributes(fileName); -} - -BOOL CSalamanderGeneral::IsPathOnSSD(const char* path) -{ - CALL_STACK_MESSAGE1("CSalamanderGeneral::IsPathOnSSD()"); - return ::IsPathOnSSD(path); -} - -BOOL CSalamanderGeneral::IsUNCPath(const char* path) -{ - CALL_STACK_MESSAGE1("CSalamanderGeneral::IsUNCPath()"); - return ::IsUNCPath(path); -} - -BOOL CSalamanderGeneral::ResolveSubsts(char* resPath) -{ - CALL_STACK_MESSAGE1("CSalamanderGeneral::ResolveSubsts()"); - return ::ResolveSubsts(resPath); -} - -void CSalamanderGeneral::ResolveLocalPathWithReparsePoints(char* resPath, const char* path, BOOL* cutResPathIsPossible, - BOOL* rootOrCurReparsePointSet, char* rootOrCurReparsePoint, - char* junctionOrSymlinkTgt, int* linkType, char* netPath) -{ - CALL_STACK_MESSAGE1("CSalamanderGeneral::ResolveLocalPathWithReparsePoints()"); - ::ResolveLocalPathWithReparsePoints(resPath, path, cutResPathIsPossible, - rootOrCurReparsePointSet, rootOrCurReparsePoint, - junctionOrSymlinkTgt, linkType, netPath); -} - -BOOL CSalamanderGeneral::GetResolvedPathMountPointAndGUID(const char* path, char* mountPoint, char* guidPath) -{ - CALL_STACK_MESSAGE2("CSalamanderGeneral::GetResolvedPathMountPointAndGUID(%s, ,)", path); - return ::GetResolvedPathMountPointAndGUID(path, mountPoint, guidPath); -} - -BOOL CSalamanderGeneral::PointToLocalDecimalSeparator(char* buffer, int bufferSize) -{ - CALL_STACK_MESSAGE1("CSalamanderGeneral::PointToLocalDecimalSeparator()"); - return ::PointToLocalDecimalSeparator(buffer, bufferSize); -} - -void CSalamanderGeneral::SetPluginIconOverlays(int iconOverlaysCount, HICON* iconOverlays) -{ - CALL_STACK_MESSAGE2("CSalamanderGeneral::SetPluginIconOverlays(%d,)", iconOverlaysCount); - if (MainThreadID != GetCurrentThreadId()) - { // this is a call error; we do not release the icons, not interested... - TRACE_E("You can call CSalamanderGeneral::SetPluginIconOverlays() only from main thread!"); - return; - } - CPluginData* data = Plugins.GetPluginData(Plugin); - if (data != NULL) - { - data->ReleaseIconOverlays(); - if (iconOverlaysCount > 0) - { - if (iconOverlays != NULL) - { - data->IconOverlays = (HICON*)malloc(3 * iconOverlaysCount * sizeof(HICON)); - memcpy(data->IconOverlays, iconOverlays, 3 * iconOverlaysCount * sizeof(HICON)); - data->IconOverlaysCount = iconOverlaysCount; - - BOOL err = FALSE; - for (int i = 0; i < 3 * iconOverlaysCount; i++) - { - if (data->IconOverlays[i] == NULL) - { - if (!err) - TRACE_E("CSalamanderGeneral::SetPluginIconOverlays(): invalid 'iconOverlays' (contains at least one NULL instead of icon handle)!"); - err = TRUE; - } - else - HANDLES_ADD(__htIcon, __hoLoadImage, data->IconOverlays[i]); - } - if (err) - data->ReleaseIconOverlays(); - } - else - TRACE_E("CSalamanderGeneral::SetPluginIconOverlays(): invalid 'iconOverlays' (NULL)!"); - } - else - { - if (iconOverlaysCount < 0) - TRACE_E("CSalamanderGeneral::SetPluginIconOverlays(): invalid 'iconOverlaysCount' (negative value)!"); - } - } - else - TRACE_E("Unexpected situation in CSalamanderGeneral::SetPluginIconOverlays()."); -} - -BOOL CSalamanderGeneral::SalGetFileSize2(const char* fileName, CQuadWord& size, DWORD* err) -{ - CALL_STACK_MESSAGE1("CSalamanderGeneral::SalGetFileSize2()"); - return ::SalGetFileSize2(fileName, size, err); -} - -BOOL CSalamanderGeneral::GetLinkTgtFileSize(HWND parent, const char* fileName, CQuadWord* size, - BOOL* cancel, BOOL* ignoreAll) -{ - CALL_STACK_MESSAGE1("CSalamanderGeneral::GetLinkTgtFileSize()"); - return ::GetLinkTgtFileSize(parent, fileName, NULL, size, cancel, ignoreAll); -} - -BOOL CSalamanderGeneral::DeleteDirLink(const char* name, DWORD* err) -{ - CALL_STACK_MESSAGE1("CSalamanderGeneral::DeleteDirLink()"); - return ::DeleteDirLink(name, err); -} - -BOOL CSalamanderGeneral::ClearReadOnlyAttr(const char* name, DWORD attr) -{ - CALL_STACK_MESSAGE1("CSalamanderGeneral::ClearReadOnlyAttr()"); - return ::ClearReadOnlyAttr(name, attr); -} - -BOOL CSalamanderGeneral::IsCriticalShutdown() -{ - return CriticalShutdown; -} - -void CSalamanderGeneral::CloseAllOwnedEnabledDialogs(HWND parent, DWORD tid) -{ - CALL_STACK_MESSAGE2("CSalamanderGeneral::CloseAllOwnedEnabledDialogs(, %d)", tid); - ::CloseAllOwnedEnabledDialogs(parent, tid); -} - -// -// **************************************************************************** -// CSalamanderForOperations -// - -CSalamanderForOperations::CSalamanderForOperations(CFilesWindow* panel) -{ - Panel = panel; - FocusWnd = NULL; - ThreadID = GetCurrentThreadId(); - Destroyed = FALSE; -} - -CSalamanderForOperations::~CSalamanderForOperations() -{ - if (UnpackProgress.HWindow != NULL) - { - TRACE_E("Progress dialog remains opened."); - CloseProgressDialog(); - } - Destroyed = TRUE; -} - -void CSalamanderForOperations::OpenProgressDialog(const char* title, BOOL twoProgressBars, HWND parent, - BOOL fileProgress) -{ - if (ThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderForOperations::OpenProgressDialog() only from thread ID:" << ThreadID); - return; - } - if (Destroyed) - { - TRACE_E("You are calling CSalamanderForOperations::OpenProgressDialog() after destruction!"); - return; - } - if (UnpackProgress.HWindow == NULL) - { - if (parent == NULL) - { - parent = MainWindow->HWindow; - UnpackProgress.SetTaskBarList3(&MainWindow->TaskBarList3); - } - if (!twoProgressBars) - UnpackProgress.Set(title, parent, CQuadWord(0, 0), fileProgress); - else - UnpackProgress.Set(title, parent, CQuadWord(0, 0), CQuadWord(0, 0)); - FocusWnd = GetFocus(); - EnableWindow(UnpackProgress.GetParent(), FALSE); - UnpackProgress.Create(); - - ActivateDropTarget(ProgressDialogActivateDrop, UnpackProgress.HWindow); - - ProgressDialog2 = twoProgressBars; - PluginProgressDialog = UnpackProgress.HWindow; - } -} - -void CSalamanderForOperations::CloseProgressDialog() -{ - if (ThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderForOperations::CloseProgressDialog() only from thread ID:" << ThreadID); - return; - } - if (Destroyed) - { - TRACE_E("You are calling CSalamanderForOperations::CloseProgressDialog() after destruction!"); - return; - } - if (UnpackProgress.HWindow != NULL) - { - PluginProgressDialog = NULL; - EnableWindow(UnpackProgress.GetParent(), TRUE); - HWND actWnd = GetForegroundWindow(); - BOOL activate = actWnd == UnpackProgress.HWindow || actWnd == UnpackProgress.GetParent(); - DestroyWindow(UnpackProgress.HWindow); - if (activate && FocusWnd != NULL) - SetFocus(FocusWnd); - UnpackProgress.Init(); - } -} - -void CSalamanderForOperations::ProgressSetTotalSize(const CQuadWord& totalSize1, const CQuadWord& totalSize2) -{ - if (ThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderForOperations::ProgressSetTotalSize() only from thread ID:" << ThreadID); - return; - } - if (Destroyed) - { - TRACE_E("You are calling CSalamanderForOperations::ProgressSetTotalSize() after destruction!"); - return; - } - if (!ProgressDialog2 && totalSize2 != CQuadWord(-1, -1)) - { - TRACE_E("Incorrect call to CSalamanderForOperations::ProgressSetTotalSize(): progress dialog has only one progress!"); - return; - } - if (UnpackProgress.HWindow != NULL) - UnpackProgress.SetTotal(totalSize1, totalSize2); -} - -void CSalamanderForOperations::ProgressDialogAddText(const char* txt, BOOL delayedPaint) -{ - if (ThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderForOperations::ProgressDialogAddText() only from thread ID:" << ThreadID); - return; - } - if (Destroyed) - { - TRACE_E("You are calling CSalamanderForOperations::ProgressDialogAddText() after destruction!"); - return; - } - if (UnpackProgress.HWindow != NULL) - UnpackProgress.NewLine(txt, delayedPaint); -} - -BOOL CSalamanderForOperations::ProgressAddSize(int size, BOOL delayedPaint) -{ - if (ThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderForOperations::ProgressAddSize() only from thread ID:" << ThreadID); - return FALSE; - } - if (Destroyed) - { - TRACE_E("You are calling CSalamanderForOperations::ProgressAddSize() after destruction!"); - return FALSE; - } - if (UnpackProgress.HWindow != NULL) - return UnpackProgress.AddSize(size, delayedPaint) == 1; - return TRUE; -} - -BOOL CSalamanderForOperations::ProgressSetSize(const CQuadWord& size1, const CQuadWord& size2, BOOL delayedPaint) -{ - if (ThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderForOperations::ProgressSetSize() only from thread ID:" << ThreadID); - return FALSE; - } - if (Destroyed) - { - TRACE_E("You are calling CSalamanderForOperations::ProgressSetSize() after destruction!"); - return FALSE; - } - if (!ProgressDialog2 && size2 != CQuadWord(-1, -1)) - { - TRACE_E("Incorrect call to CSalamanderForOperations::ProgressSetSize(): progress dialog has only one progress!"); - return FALSE; - } - if (UnpackProgress.HWindow != NULL) - return UnpackProgress.SetSize(size1, size2, delayedPaint) == 1; - return TRUE; -} - -void CSalamanderForOperations::ProgressEnableCancel(BOOL enable) -{ - if (ThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderForOperations::ProgressEnableCancel() only from thread ID:" << ThreadID); - return; - } - if (Destroyed) - { - TRACE_E("You are calling CSalamanderForOperations::ProgressEnableCancel() after destruction!"); - return; - } - if (UnpackProgress.HWindow != NULL) - UnpackProgress.EnableCancel(enable); -} - -BOOL CSalamanderForOperations::MoveFiles(const char* source, const char* target, const char* remapNameFrom, - const char* remapNameTo) -{ - if (ThreadID != GetCurrentThreadId()) - { - TRACE_E("You can call CSalamanderForOperations::MoveFiles() only from thread ID:" << ThreadID); - return FALSE; - } - if (Destroyed) - { - TRACE_E("You are calling CSalamanderForOperations::MoveFiles() after destruction!"); - return FALSE; - } - if (Panel == NULL) - { - TRACE_E("Incorrect call to CSalamanderForOperations::MoveFiles"); - return FALSE; - } - return Panel->MoveFiles(source, target, remapNameFrom, remapNameTo); -} - -// -// **************************************************************************** -// CSalamanderForViewFileOnFS -// - -const char* -CSalamanderForViewFileOnFS::AllocFileNameInCache(HWND parent, const char* uniqueFileName, const char* nameInCache, - const char* rootTmpPath, BOOL& fileExists) -{ - CALL_STACK_MESSAGE4("CSalamanderForViewFileOnFS::AllocFileNameInCache(, %s, %s, %s, )", - uniqueFileName, nameInCache, rootTmpPath); - if (CallsCounter > 0) - { - TRACE_E("You are calling CSalamanderForViewFileOnFS::AllocFileNameInCache more than once! Is it O.K.?"); - } - - int errorCode; - const char* name = DiskCache.GetName(uniqueFileName, nameInCache, &fileExists, FALSE, rootTmpPath, FALSE, NULL, &errorCode); - if (name == NULL) - { - char buff[2000]; - strcpy(buff, LoadStr(IDS_VIEWFILEFAILED)); - if (errorCode == DCGNE_TOOLONGNAME) - { - strcat(buff, "\n"); - strcat(buff, LoadStr(IDS_VIEWFILETOOLONGNAME)); - } - SalMessageBox(parent, buff, LoadStr(IDS_ERRORTITLE), MB_OK | MB_ICONEXCLAMATION); - } - else - { - CallsCounter++; - } - - return name; -} - -BOOL CSalamanderForViewFileOnFS::OpenViewer(HWND parent, const char* fileName, HANDLE* fileLock, - BOOL* fileLockOwner) -{ - CALL_STACK_MESSAGE2("CSalamanderForViewFileOnFS::OpenViewer(, %s, ,)", fileName); - - HANDLE lock = NULL; - BOOL lockOwner = FALSE; - - BOOL ret = ViewFileInt(parent, fileName, AltView, HandlerID, - (fileLock != NULL && fileLockOwner != NULL), - lock, lockOwner, FALSE, -1, -1); - - if (fileLock != NULL) - *fileLock = lock; - if (fileLockOwner != NULL) - *fileLockOwner = lockOwner; - return ret; -} - -void CSalamanderForViewFileOnFS::FreeFileNameInCache(const char* uniqueFileName, BOOL fileExists, BOOL newFileOK, - const CQuadWord& newFileSize, HANDLE fileLock, - BOOL fileLockOwner, BOOL removeAsSoonAsPossible) -{ - CALL_STACK_MESSAGE8("CSalamanderForViewFileOnFS::FreeFileNameInCache(%s, %d, %d, %g, 0x%p, %d, %d)", - uniqueFileName, fileExists, newFileOK, newFileSize.GetDouble(), fileLock, - fileLockOwner, removeAsSoonAsPossible); - - if (CallsCounter == 0) - { - TRACE_E("Unmatched call to CSalamanderForViewFileOnFS::FreeFileNameInCache!"); - return; - } - CallsCounter--; - - if (!fileExists) // newly downloaded copy of the file - { - if (newFileOK) // the download was successful - { - DiskCache.NamePrepared(uniqueFileName, newFileSize); - } - else - { - DiskCache.ReleaseName(uniqueFileName, FALSE); // the download failed, nothing to cache - return; // nothing else to address - } - } - - if (fileLock != NULL) // we have the viewer's "lock" object; link the viewer and the disk cache - { - DiskCache.AssignName(uniqueFileName, fileLock, fileLockOwner, - (fileExists || removeAsSoonAsPossible) ? crtDirect : crtCache); // for files present in the disk cache we use crtDirect, because it does not affect the "lifetime" setting (it stays as the file's author requested) - } - else // the viewer did not open or simply does not have a "lock" object - { - DiskCache.ReleaseName(uniqueFileName, !fileExists && !removeAsSoonAsPossible); // if 'removeAsSoonAsPossible' is not TRUE, at least try to keep a copy of the file in the disk cache (if it was not an existing file, we do not change its "lifetime") - } -} - -// -// **************************************************************************** -// CSalamanderDirectory -// - -CSalamanderDirectory::CSalamanderDirectory(BOOL isForFS, DWORD validData, DWORD flags) - : Dirs(10, 200), SalamDirs(10, 200), Files(10, 200) -{ - ValidData = validData; - if (flags == -1) - flags = isForFS ? SALDIRFLAG_IGNOREDUPDIRS : 0; - Flags = flags; - IsForFS = isForFS; - AddCache = NULL; -} - -CSalamanderDirectory::~CSalamanderDirectory() -{ - Clear(NULL); // plug-in data are released only in the root sal-dir - FreeAddCache(); -} - -void CSalamanderDirectory::AllocAddCache() -{ - if (AddCache == NULL) - { - AddCache = (CSalamanderDirectoryAddCache*)malloc(sizeof(CSalamanderDirectoryAddCache)); - if (AddCache != NULL) - ZeroMemory(AddCache, sizeof(CSalamanderDirectoryAddCache)); - // if allocating the cache fails, it is fine; we are fully functional without it - } -} - -void CSalamanderDirectory::FreeAddCache() -{ - if (AddCache != NULL) - { - free(AddCache); - AddCache = NULL; - } -} - -int CSalamanderDirectory::SalDirStrCmp(const char* s1, const char* s2) -{ - if (Flags & SALDIRFLAG_CASESENSITIVE) - return strcmp(s1, s2); - else - return StrICmp(s1, s2); -} - -int CSalamanderDirectory::SalDirStrCmpEx(const char* s1, int l1, const char* s2, int l2) -{ - if (Flags & SALDIRFLAG_CASESENSITIVE) - return StrCmpEx(s1, l1, s2, l2); - else - return StrICmpEx(s1, l1, s2, l2); -} - -void CSalamanderDirectory::Clear(CPluginDataInterfaceAbstract* pluginData) -{ - if (pluginData != NULL) // release plug-in-specific data - { - CPluginDataInterfaceEncapsulation plugin(pluginData, STR_NONE, STR_NONE, NULL, 0); - BOOL releaseFiles = plugin.CallReleaseForFiles(); - BOOL releaseDirs = plugin.CallReleaseForDirs(); - if (releaseFiles || releaseDirs) - { - ReleasePluginData(plugin, releaseFiles, releaseDirs); - } - } - int i; - for (i = 0; i < SalamDirs.Count; i++) - { - CSalamanderDirectory* salDir = SalamDirs[i]; - if (salDir != NULL) - delete salDir; - } - SalamDirs.DestroyMembers(); - Dirs.DestroyMembers(); - Files.DestroyMembers(); - if (AddCache != NULL) - { - AddCache->PathLen = 0; - AddCache->Path[0] = 0; - AddCache->Dir = NULL; - } - ValidData = VALID_DATA_ALL_FS_ARC; - Flags = IsForFS ? SALDIRFLAG_IGNOREDUPDIRS : 0; -} - -void CSalamanderDirectory::SetValidData(DWORD validData) -{ - if (ValidData != validData) - { - ValidData = validData; - int i; - for (i = 0; i < SalamDirs.Count; i++) - { - CSalamanderDirectory* salDir = SalamDirs[i]; - if (salDir != NULL) - salDir->SetValidData(validData); - } - } -} - -void CSalamanderDirectory::SetFlags(DWORD flags) -{ - if (Flags != flags) - { - Flags = flags; - int i; - for (i = 0; i < SalamDirs.Count; i++) - { - CSalamanderDirectory* salDir = SalamDirs[i]; - if (salDir != NULL) - salDir->SetFlags(flags); - } - } -} - -CSalamanderDirectory* -CSalamanderDirectory::AllocSalamDir(int index) -{ - CALL_STACK_MESSAGE_NONE // time-critical method - - if (index < 0 || index >= SalamDirs.Count || SalamDirs[index] != NULL) - { - TRACE_E("Unexpected error in CSalamanderDirectory::AllocSalamDir()."); - return NULL; - } - CSalamanderDirectory* dir = new CSalamanderDirectory(IsForFS, ValidData, Flags); - if (dir == NULL) - { - TRACE_E(LOW_MEMORY); - return NULL; - } - SalamDirs[index] = dir; - return dir; -} - -// *************************************************************************** -// FindDir: -// -// 'path' - input: path in the archive (relative to this directory) -// 's' - output: points past the first name in the path 'path' -// 'i' - output: index of the found subdirectory (which should continue processing the path 's') -// 'file' - input: if the directory must be created, where to copy data from -// 'pluginData' - input: interface for creating plug-in-specific data for the new directory (if needed) -// 'archivePath' - input: full path in the archive ('path' and 's' both point into it) - -BOOL CSalamanderDirectory::FindDir(const char* path, const char*& s, int& i, const CFileData& file, - CPluginDataInterfaceAbstract* pluginData, const char* archivePath) -{ - CALL_STACK_MESSAGE_NONE // time-critical method - // CALL_STACK_MESSAGE2("CSalamanderDirectory::FindDir(%s, , , ,)", path); - s = path; - while (*s != 0 && *s != '\\') - s++; - - for (i = 0; i < Dirs.Count; i++) - { - if (SalDirStrCmpEx(Dirs[i].Name, Dirs[i].NameLen, path, (int)(s - path)) == 0) - break; - } - if (i == Dirs.Count) // we must create it - { - CFileData data; - //--- name - data.Name = (char*)malloc((s - path) + 1); // allocation - if (data.Name == NULL) - { - TRACE_E(LOW_MEMORY); - return FALSE; - } - memcpy(data.Name, path, s - path); // copy of the text - data.Name[s - path] = 0; - data.NameLen = s - path; - //--- extension - if (!Configuration.SortDirsByExt) - data.Ext = data.Name + data.NameLen; // directories have no extensions - else - { - const char* ss = s; - while (--ss >= path && *ss != '.') - ; - if (ss >= path) - data.Ext = data.Name + (ss - path + 1); // ".cvspass" is an extension in Windows... - // if (ss > path) data.Ext = data.Name + (ss - path + 1); - else - data.Ext = data.Name + data.NameLen; - } - //--- other fields - data.Size = CQuadWord(0, 0); - data.Attr = 0; - data.LastWrite = file.LastWrite; // take the date from the first file in the directory - data.DosName = NULL; - data.PluginData = 0; - data.Hidden = 0; - data.IsLink = 0; - data.IsOffline = 0; - // private Salamander data - data.Association = 0; - data.Selected = 0; - data.Shared = 0; - data.Archive = 0; - data.SizeValid = 0; - data.Dirty = 0; // optional, kept only for formality - data.CutToClip = 0; - data.IconOverlayIndex = ICONOVERLAYINDEX_NOTUSED; - data.IconOverlayDone = 0; - - if (pluginData != NULL) // let the plug-in add its specific data - { - char arcPath[MAX_PATH]; // name of the added directory inside the archive - memcpy(arcPath, archivePath, s - archivePath); - arcPath[s - archivePath] = 0; - CPluginDataInterfaceEncapsulation plugin(pluginData, STR_NONE, STR_NONE, NULL, 0); - if (!plugin.GetFileDataForNewDir(arcPath, data)) // cannot add the plug-in data - { - free(data.Name); - return FALSE; - } - } - - Dirs.Add(data); - if (!Dirs.IsGood()) - { - Dirs.ResetState(); - if (pluginData != NULL) // release plug-in-specific data - { - CPluginDataInterfaceEncapsulation plugin(pluginData, STR_NONE, STR_NONE, NULL, 0); - if (plugin.CallReleaseForDirs()) - plugin.ReleasePluginData2(data, TRUE); - } - free(data.Name); - return FALSE; - } - //--- adding the Salamander directory corresponding to the new directory - /* - CSalamanderDirectory *dir = new CSalamanderDirectory(IsForFS, ValidData, Flags); - if (dir != NULL) SalamDirs.Add((DWORD)dir); - else TRACE_E(LOW_MEMORY); - if (dir == NULL || !SalamDirs.IsGood()) - { - if (dir != NULL) delete dir; - SalamDirs.ResetState(); - if (pluginData != NULL) // release plug-in-specific data - { - CPluginDataInterfaceEncapsulation plugin(pluginData, STR_NONE, STR_NONE, NULL, 0); - if (plugin.CallReleaseForDirs()) plugin.ReleasePluginData2(Dirs[Dirs.Count - 1], TRUE); - } - Dirs.Delete(Dirs.Count - 1); - return FALSE; - } -*/ - SalamDirs.Add(NULL); // add NULL (the object will be allocated the first time it is needed) - if (!SalamDirs.IsGood()) - { - SalamDirs.ResetState(); - if (pluginData != NULL) // release plug-in-specific data - { - CPluginDataInterfaceEncapsulation plugin(pluginData, STR_NONE, STR_NONE, NULL, 0); - if (plugin.CallReleaseForDirs()) - plugin.ReleasePluginData2(Dirs[Dirs.Count - 1], TRUE); - } - Dirs.Delete(Dirs.Count - 1); - if (!Dirs.IsGood()) - Dirs.ResetState(); - return FALSE; - } - } - return TRUE; -} - -BOOL CSalamanderDirectory::AddFile(const char* path, CFileData& file, CPluginDataInterfaceAbstract* pluginData) -{ - CALL_STACK_MESSAGE_NONE // time-critical method - - int pathLen = 0; - if (path != NULL && ((pathLen = (int)strlen(path)) > MAX_PATH - 5 || file.NameLen > MAX_PATH - 5)) - { - TRACE_E("Too long path or file name!"); - return FALSE; - } - - // TRACE_I("AddFile path="< 0 && - pathLen == AddCache->PathLen && memcmp(path, AddCache->Path, pathLen) == 0) - { - // the cache already held our path, so we can insert the file immediately - AddCache->Dir->Files.Add(file); - if (!AddCache->Dir->Files.IsGood()) - { - AddCache->Dir->Files.ResetState(); - return FALSE; - } - return TRUE; - } - - CSalamanderDirectory* ret = AddFileInt(path, file, pluginData, path); - - // if the insertion succeeded and the cache is used, remember the path - if (ret != NULL && AddCache != NULL && pathLen > 0) - { - AddCache->PathLen = pathLen; - memcpy(AddCache->Path, path, pathLen); - AddCache->Dir = ret; - } - - return ret != NULL; -} - -BOOL CSalamanderDirectory::AddDir(const char* path, CFileData& dir, CPluginDataInterfaceAbstract* pluginData) -{ - CALL_STACK_MESSAGE_NONE // time-critical method - - if (path != NULL && (strlen(path) > MAX_PATH - 5 || dir.NameLen > MAX_PATH - 5)) - { - TRACE_E("Too long path or file name!"); - return FALSE; - } - - // TRACE_I("AddDir path="<= 0 && i < Files.Count) return &(*((CFilesArray*)&Files))[i]; - else return NULL; -} - -CFileData const* -CSalamanderDirectory::GetDir(int i) const -{ - CALL_STACK_MESSAGE_NONE // time-critical method - if (i >= 0 && i < Dirs.Count) return &(*((CFilesArray*)&Dirs))[i]; - else return NULL; -} - -CSalamanderDirectoryAbstract const* -CSalamanderDirectory::GetSalDir(int i) const -{ - CALL_STACK_MESSAGE_NONE // time-critical method - if (i >= 0 && i < SalamDirs.Count) - { - CSalamanderDirectoryAbstract const* salDir = (CSalamanderDirectoryAbstract const*)(*((TDirectArray*)&SalamDirs))[i]; - if (salDir == NULL) - salDir = &GlobalEmptySalDir; // it's an empty directory - return the global empty directory - return salDir; - } - else return NULL; -} - -CSalamanderDirectory* -CSalamanderDirectory::AddFileInt(const char* path, CFileData& file, - CPluginDataInterfaceAbstract* pluginData, const char* archivePath) -{ - CALL_STACK_MESSAGE_NONE // time-critical method; in addition, path may be NULL - // CALL_STACK_MESSAGE3("CSalamanderDirectory::AddFileInt(%s, , , %s)", path, archivePath); - - if (path != NULL) - { - if (*path == '\\') - path++; - if (*path != 0) // not this directory; find the subdirectory - { - const char* s; - int i; - if (!FindDir(path, s, i, file, pluginData, archivePath)) - return NULL; - - CSalamanderDirectory* salDir = SalamDirs[i]; - if (salDir != NULL || // already allocated - (salDir = AllocSalamDir(i)) != NULL) // or succeeded in allocating a new object - { - return salDir->AddFileInt(s, file, pluginData, archivePath); - } - else - return NULL; - } - } - - // note: if AddCache applies, the item is added directly in AddFile - Files.Add(file); - if (!Files.IsGood()) - { - Files.ResetState(); - return NULL; - } - return this; -} - -CSalamanderDirectory* -CSalamanderDirectory::AddDirInt(const char* path, CFileData& dir, - CPluginDataInterfaceAbstract* pluginData, const char* archivePath) -{ - CALL_STACK_MESSAGE_NONE // time-critical method; in addition, path may be NULL - // CALL_STACK_MESSAGE3("CSalamanderDirectory::AddDirInt(%s, , , %s)", path, archivePath); - - if (path != NULL) - { - if (*path == '\\') - path++; - if (*path != 0) // not this directory; find the subdirectory - { - const char* s; - int i; - if (!FindDir(path, s, i, dir, pluginData, archivePath)) - return NULL; - - CSalamanderDirectory* salDir = SalamDirs[i]; - if (salDir != NULL || // already allocated - (salDir = AllocSalamDir(i)) != NULL) // or succeeded in allocating a new object - { - return salDir->AddDirInt(s, dir, pluginData, archivePath); - } - else - return NULL; - } - } - - BOOL newDir = TRUE; - if ((Flags & SALDIRFLAG_IGNOREDUPDIRS) == 0) // if we should test for duplicate directories - { - int i; - for (i = 0; i < Dirs.Count; i++) - { - if (SalDirStrCmpEx(Dirs[i].Name, Dirs[i].NameLen, dir.Name, dir.NameLen) == 0) - break; - } - newDir = (i == Dirs.Count); // not created yet - if (!newDir) // updating existing data - { - if (pluginData != NULL) // release plug-in-specific data - { - CPluginDataInterfaceEncapsulation plugin(pluginData, STR_NONE, STR_NONE, NULL, 0); - if (plugin.CallReleaseForDirs()) - plugin.ReleasePluginData2(Dirs[i], TRUE); - } - - if (Dirs[i].Name != NULL) - free(Dirs[i].Name); - Dirs[i].Name = dir.Name; // rather take the new name (for possible data after '\0' in the string) - Dirs[i].Ext = dir.Ext; - Dirs[i].Size = dir.Size; - Dirs[i].Attr = dir.Attr; - Dirs[i].LastWrite = dir.LastWrite; - if (Dirs[i].DosName != NULL) - free(Dirs[i].DosName); - Dirs[i].DosName = dir.DosName; - Dirs[i].PluginData = dir.PluginData; - // Dirs[i].NameLen should be the same as dir.NameLen - Dirs[i].Hidden = dir.Hidden; - Dirs[i].IsLink = dir.IsLink; - Dirs[i].IsOffline = dir.IsOffline; - // the remainder of Dirs[i] should be zeroed just like the rest of dir - } - } - if (newDir) - { - //--- adding the Salamander directory corresponding to the new directory - /* - CSalamanderDirectory *SalamDir = new CSalamanderDirectory(IsForFS, ValidData, Flags); - if (SalamDir != NULL) SalamDirs.Add((DWORD)SalamDir); - else TRACE_E(LOW_MEMORY); - if (SalamDir == NULL || !SalamDirs.IsGood()) - { - if (SalamDir != NULL) delete SalamDir; - SalamDirs.ResetState(); - return FALSE; - } - - Dirs.Add(dir); - if (!Dirs.IsGood()) - { - Dirs.ResetState(); - SalamDirs.Delete(SalamDirs.Count - 1); - delete SalamDir; - return FALSE; - } -*/ - if (IsForFS && dir.NameLen == 2 && dir.Name[0] == '.' && dir.Name[1] == '.') - { - CFileData* firstDir = Dirs.Count > 0 ? &Dirs[0] : NULL; - if (firstDir != NULL && firstDir->NameLen == 2 && - firstDir->Name[0] == '.' && firstDir->Name[1] == '.') - { // an up-directory is already present - TRACE_E("CSalamanderDirectory::AddFile(): you can add up-dir (\"..\") at most once!"); - return NULL; - } - SalamDirs.Insert(0, NULL); // add NULL (the object will be allocated the first time it is needed) - if (!SalamDirs.IsGood()) - { - SalamDirs.ResetState(); - return NULL; - } - - Dirs.Insert(0, dir); - if (!Dirs.IsGood()) - { - Dirs.ResetState(); - SalamDirs.Delete(0); - if (!SalamDirs.IsGood()) - SalamDirs.ResetState(); - return NULL; - } - } - else - { - SalamDirs.Add(NULL); // add NULL (the object will be allocated the first time it is needed) - if (!SalamDirs.IsGood()) - { - SalamDirs.ResetState(); - return NULL; - } - - Dirs.Add(dir); - if (!Dirs.IsGood()) - { - Dirs.ResetState(); - SalamDirs.Delete(SalamDirs.Count - 1); - if (!SalamDirs.IsGood()) - SalamDirs.ResetState(); - return NULL; - } - } - } - return this; -} - -extern int DeltaForTotalCount(int total); - -void CSalamanderDirectory::SetApproximateCount(int files, int dirs) -{ - CALL_STACK_MESSAGE3("CSalamanderDirectory::SetApproximateCount(%d, %d)", files, dirs); - if (files > 1) - { - if (Files.Count == 0) - Files.SetDelta(DeltaForTotalCount(files)); - else - TRACE_E("CSalamanderDirectory::SetApproximateCount() Files.Count = " << Files.Count); - } - if (dirs > 1) - { - if (Dirs.Count == 0) - Dirs.SetDelta(DeltaForTotalCount(dirs)); - else - TRACE_E("CSalamanderDirectory::SetApproximateCount() Dirs.Count = " << Dirs.Count); - } -} - -void CSalamanderDirectory::ReleasePluginData(CPluginDataInterfaceEncapsulation& pluginData, - BOOL releaseFiles, BOOL releaseDirs) -{ - SLOW_CALL_STACK_MESSAGE3("CSalamanderDirectory::ReleasePluginData(, %d, %d)", - releaseFiles, releaseDirs); - if (releaseFiles) - pluginData.ReleaseFilesOrDirs(&Files, FALSE); - if (releaseDirs) - pluginData.ReleaseFilesOrDirs(&Dirs, TRUE); - int i; - for (i = 0; i < SalamDirs.Count; i++) - { - CSalamanderDirectory* salDir = SalamDirs[i]; - if (salDir != NULL) - salDir->ReleasePluginData(pluginData, releaseFiles, releaseDirs); - } -} - -CFilesArray* -CSalamanderDirectory::GetDirs(const char* path) -{ - CALL_STACK_MESSAGE2("CSalamanderDirectory::GetDirs(%s)", path); - if (path != NULL) - { - if (*path == '\\') - path++; - if (*path != 0) // some subdirectory - { - const char* s = path; - while (*s != 0 && *s != '\\') - s++; - - int i; - for (i = 0; i < Dirs.Count; i++) - { - if (SalDirStrCmpEx(Dirs[i].Name, Dirs[i].NameLen, path, (int)(s - path)) == 0) - { - CSalamanderDirectory* salDir = SalamDirs[i]; - if (salDir != NULL || // already allocated - (salDir = AllocSalamDir(i)) != NULL) // or succeeded in allocating a new object - { - return salDir->GetDirs(s); - } - else - return NULL; // low memory error (as if the directory did not exist) - } - } - } - else - return &Dirs; - } - return NULL; -} - -CFilesArray* -CSalamanderDirectory::GetFiles(const char* path) -{ - CALL_STACK_MESSAGE2("CSalamanderDirectory::GetFiles(%s)", path); - if (path != NULL) - { - if (*path == '\\') - path++; - if (*path != 0) // some subdirectory - { - const char* s = path; - while (*s != 0 && *s != '\\') - s++; - - int i; - for (i = 0; i < Dirs.Count; i++) - { - if (SalDirStrCmpEx(Dirs[i].Name, Dirs[i].NameLen, path, (int)(s - path)) == 0) - { - CSalamanderDirectory* salDir = SalamDirs[i]; - if (salDir != NULL || // already allocated - (salDir = AllocSalamDir(i)) != NULL) // or succeeded in allocating a new object - { - return salDir->GetFiles(s); - } - else - return NULL; // low memory error (as if the directory did not exist) - } - } - } - else - return &Files; - } - return NULL; -} - -const CFileData* -CSalamanderDirectory::GetUpperDir(const char* path) -{ - CALL_STACK_MESSAGE2("CSalamanderDirectory::GetUpperDir(%s)", path); - if (path != NULL) - { - if (*path == '\\') - path++; - if (*path != 0) // some subdirectory - { - const char* s = path; - while (*s != 0 && *s != '\\') - s++; - - int i; - for (i = 0; i < Dirs.Count; i++) - { - if (SalDirStrCmpEx(Dirs[i].Name, Dirs[i].NameLen, path, (int)(s - path)) == 0) - { - if (*s == 0 || *(s + 1) == 0) - return &Dirs[i]; // the last path component = the requested parent directory - else - { - CSalamanderDirectory* salDir = SalamDirs[i]; - if (salDir != NULL || // already allocated - (salDir = AllocSalamDir(i)) != NULL) // or succeeded in allocating a new object - { - return salDir->GetUpperDir(s); - } - else - return NULL; // low memory error (as if the directory did not exist) - } - } - } - } - else - return NULL; // for root return NULL - } - return NULL; // for root and unknown paths return NULL -} - -CQuadWord -CSalamanderDirectory::GetSize(int* dirsCount, int* filesCount, TDirectArray* sizes) -{ - CALL_STACK_MESSAGE1("CSalamanderDirectory::GetSize(,)"); - CQuadWord size(0, 0); - int i; - for (i = 0; i < Files.Count; i++) - { - size += Files[i].Size; - if (sizes != NULL) - sizes->Add(Files[i].Size); // addition failure is handled at the level of the output dialog - } - if (filesCount != NULL) - *filesCount += Files.Count; - for (i = 0; i < SalamDirs.Count; i++) - { - CSalamanderDirectory* salDir = SalamDirs[i]; - if (salDir != NULL) - size += salDir->GetSize(dirsCount, filesCount, sizes); - } - if (dirsCount != NULL) - *dirsCount += SalamDirs.Count; - return size; -} - -CQuadWord -CSalamanderDirectory::GetDirSize(const char* path, const char* dirName, int* dirsCount, - int* filesCount, TDirectArray* sizes) -{ - CALL_STACK_MESSAGE3("CSalamanderDirectory::GetDirSize(%s, %s, , ,)", path, dirName); - if (path != NULL) - { - if (*path == '\\') - path++; - if (*path != 0) // some subdirectory - { - const char* s = path; - while (*s != 0 && *s != '\\') - s++; - - int i; - for (i = 0; i < Dirs.Count; i++) - { - if (SalDirStrCmpEx(Dirs[i].Name, Dirs[i].NameLen, path, (int)(s - path)) == 0) - { - CSalamanderDirectory* salDir = SalamDirs[i]; - if (salDir != NULL) - return salDir->GetDirSize(s, dirName, dirsCount, filesCount, sizes); - else - return CQuadWord(0, 0); // contains nothing; otherwise it would already be allocated - } - } - } - else - { - int i; - for (i = 0; i < Dirs.Count; i++) - { - if (SalDirStrCmp(Dirs[i].Name, dirName) == 0) - { - CSalamanderDirectory* salDir = SalamDirs[i]; - if (salDir != NULL) - return salDir->GetSize(dirsCount, filesCount, sizes); - else - return CQuadWord(0, 0); // contains nothing; otherwise it would already be allocated - } - } - TRACE_E("Incorrect call to CSalamanderDirectory::GetDirSize() - directory does not exist!"); - return CQuadWord(0, 0); // not found - } - } - return CQuadWord(0, 0); -} - -CSalamanderDirectory* -CSalamanderDirectory::GetSalamanderDir(const char* path, BOOL readOnly) -{ - CALL_STACK_MESSAGE_NONE - // CALL_STACK_MESSAGE3("CSalamanderDirectory::GetSalamanderDir(%s, %d)", path, readOnly); - if (path != NULL) - { - if (*path == '\\') - path++; - if (*path != 0) // some subdirectory - { - const char* s = path; - while (*s != 0 && *s != '\\') - s++; - - int i; - for (i = 0; i < Dirs.Count; i++) - { - if (SalDirStrCmpEx(Dirs[i].Name, Dirs[i].NameLen, path, (int)(s - path)) == 0) - { - CSalamanderDirectory* salDir = SalamDirs[i]; - if (salDir != NULL) - return salDir->GetSalamanderDir(s, readOnly); - else // an empty directory - { - if (readOnly) - return &GlobalEmptySalDir; // read-only - return the global empty directory - else // for writing - { - if ((salDir = AllocSalamDir(i)) != NULL) // we must allocate a new object - { - return salDir->GetSalamanderDir(s, readOnly); - } - else - return NULL; // allocation error - } - } - } - } - } - else - return this; - } - return NULL; -} - -CSalamanderDirectory* -CSalamanderDirectory::GetSalamanderDir(int i) -{ - if (i >= 0 && i < SalamDirs.Count) - { - CSalamanderDirectory* salDir = SalamDirs[i]; - if (salDir == NULL) - salDir = &GlobalEmptySalDir; // it's an empty directory - return the global empty directory - return salDir; - } - else - return NULL; -} - -int CSalamanderDirectory::GetIndex(const char* dir) -{ - if (dir != NULL) - { - int i; - for (i = 0; i < Dirs.Count; i++) - { - if (SalDirStrCmp(Dirs[i].Name, dir) == 0) - return i; - } - } - return -1; // not found -} - -// **************************************************************************** - -BOOL TestFreeSpace(HWND parent, const char* path, const CQuadWord& totalSize, const char* messageTitle) -{ - CQuadWord freeSpace = MyGetDiskFreeSpace(path); - if (freeSpace != CQuadWord(-1, -1) && freeSpace < totalSize) - { - char buf1[50]; - char buf2[50]; - char buf3[200]; - sprintf(buf3, LoadStr(IDS_NOTENOUGHSPACE), - NumberToStr(buf1, totalSize), - NumberToStr(buf2, freeSpace)); - return SalMessageBox(parent, buf3, messageTitle, MB_YESNO | MB_ICONQUESTION | MSGBOXEX_ESCAPEENABLED) == IDYES; - } - return TRUE; -} +// This file has been split into: +// zip_progress.cpp - CZIPUnpackProgress +// zip_general_api.cpp - CSalamanderGeneral implementation +// zip_utilities.cpp - CSalamanderBMSearchDataImp, CSalamanderREGEXPSearchDataImp, +// CSalCommandsAux, CSalamanderMaskGroupImp, CSalamanderMD5Imp, +// CSalamanderPNG, CSalamanderCrypt +// zip_directory.cpp - CSalamanderForOperations, CSalamanderForViewFileOnFS, +// CSalamanderDirectory diff --git a/src/zip_directory.cpp b/src/zip_directory.cpp new file mode 100644 index 00000000..bb7e5b16 --- /dev/null +++ b/src/zip_directory.cpp @@ -0,0 +1,1316 @@ +// SPDX-FileCopyrightText: 2023 Taskscape Ltd +// SPDX-License-Identifier: GPL-2.0-or-later +// CommentsTranslationProject: TRANSLATED + +#include "precomp.h" + +#include "menu.h" +#include "cfgdlg.h" +#include "dialogs.h" +#include "mainwnd.h" +#include "plugins.h" +#include "filesbox.h" +#include "fileswnd.h" +#include "stswnd.h" +#include "editwnd.h" +#include "zip.h" +#include "cache.h" +#include "viewer.h" +#include "codetbl.h" +#include "shellib.h" +#include "gui.h" +#include "tasklist.h" +#include "olespy.h" +#include "md5.h" +#include "geticon.h" +#include "pack.h" +extern "C" +{ +#include "shexreg.h" +} +#include "salshlib.h" +#include "crypt\fileenc.h" +#include "crypt\sha1.h" +#include "pwdmngr.h" + +// Globals defined in zip_progress.cpp +extern const char* STR_NONE; +extern CSalamanderDirectory GlobalEmptySalDir; +extern HWND ProgressDialogActivateDrop; + +// Forward declaration for ActivateDropTarget defined in zip_utilities.cpp +void ActivateDropTarget(HWND dropTarget, HWND progressWnd); + +// **************************************************************************** +// CSalamanderForOperations +// + +CSalamanderForOperations::CSalamanderForOperations(CFilesWindow* panel) +{ + Panel = panel; + FocusWnd = NULL; + ThreadID = GetCurrentThreadId(); + Destroyed = FALSE; +} + +CSalamanderForOperations::~CSalamanderForOperations() +{ + if (UnpackProgress.HWindow != NULL) + { + TRACE_E("Progress dialog remains opened."); + CloseProgressDialog(); + } + Destroyed = TRUE; +} + +void CSalamanderForOperations::OpenProgressDialog(const char* title, BOOL twoProgressBars, HWND parent, + BOOL fileProgress) +{ + if (ThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderForOperations::OpenProgressDialog() only from thread ID:" << ThreadID); + return; + } + if (Destroyed) + { + TRACE_E("You are calling CSalamanderForOperations::OpenProgressDialog() after destruction!"); + return; + } + if (UnpackProgress.HWindow == NULL) + { + if (parent == NULL) + { + parent = MainWindow->HWindow; + UnpackProgress.SetTaskBarList3(&MainWindow->TaskBarList3); + } + if (!twoProgressBars) + UnpackProgress.Set(title, parent, CQuadWord(0, 0), fileProgress); + else + UnpackProgress.Set(title, parent, CQuadWord(0, 0), CQuadWord(0, 0)); + FocusWnd = GetFocus(); + EnableWindow(UnpackProgress.GetParent(), FALSE); + UnpackProgress.Create(); + + ActivateDropTarget(ProgressDialogActivateDrop, UnpackProgress.HWindow); + + ProgressDialog2 = twoProgressBars; + PluginProgressDialog = UnpackProgress.HWindow; + } +} + +void CSalamanderForOperations::CloseProgressDialog() +{ + if (ThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderForOperations::CloseProgressDialog() only from thread ID:" << ThreadID); + return; + } + if (Destroyed) + { + TRACE_E("You are calling CSalamanderForOperations::CloseProgressDialog() after destruction!"); + return; + } + if (UnpackProgress.HWindow != NULL) + { + PluginProgressDialog = NULL; + EnableWindow(UnpackProgress.GetParent(), TRUE); + HWND actWnd = GetForegroundWindow(); + BOOL activate = actWnd == UnpackProgress.HWindow || actWnd == UnpackProgress.GetParent(); + DestroyWindow(UnpackProgress.HWindow); + if (activate && FocusWnd != NULL) + SetFocus(FocusWnd); + UnpackProgress.Init(); + } +} + +void CSalamanderForOperations::ProgressSetTotalSize(const CQuadWord& totalSize1, const CQuadWord& totalSize2) +{ + if (ThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderForOperations::ProgressSetTotalSize() only from thread ID:" << ThreadID); + return; + } + if (Destroyed) + { + TRACE_E("You are calling CSalamanderForOperations::ProgressSetTotalSize() after destruction!"); + return; + } + if (!ProgressDialog2 && totalSize2 != CQuadWord(-1, -1)) + { + TRACE_E("Incorrect call to CSalamanderForOperations::ProgressSetTotalSize(): progress dialog has only one progress!"); + return; + } + if (UnpackProgress.HWindow != NULL) + UnpackProgress.SetTotal(totalSize1, totalSize2); +} + +void CSalamanderForOperations::ProgressDialogAddText(const char* txt, BOOL delayedPaint) +{ + if (ThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderForOperations::ProgressDialogAddText() only from thread ID:" << ThreadID); + return; + } + if (Destroyed) + { + TRACE_E("You are calling CSalamanderForOperations::ProgressDialogAddText() after destruction!"); + return; + } + if (UnpackProgress.HWindow != NULL) + UnpackProgress.NewLine(txt, delayedPaint); +} + +BOOL CSalamanderForOperations::ProgressAddSize(int size, BOOL delayedPaint) +{ + if (ThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderForOperations::ProgressAddSize() only from thread ID:" << ThreadID); + return FALSE; + } + if (Destroyed) + { + TRACE_E("You are calling CSalamanderForOperations::ProgressAddSize() after destruction!"); + return FALSE; + } + if (UnpackProgress.HWindow != NULL) + return UnpackProgress.AddSize(size, delayedPaint) == 1; + return TRUE; +} + +BOOL CSalamanderForOperations::ProgressSetSize(const CQuadWord& size1, const CQuadWord& size2, BOOL delayedPaint) +{ + if (ThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderForOperations::ProgressSetSize() only from thread ID:" << ThreadID); + return FALSE; + } + if (Destroyed) + { + TRACE_E("You are calling CSalamanderForOperations::ProgressSetSize() after destruction!"); + return FALSE; + } + if (!ProgressDialog2 && size2 != CQuadWord(-1, -1)) + { + TRACE_E("Incorrect call to CSalamanderForOperations::ProgressSetSize(): progress dialog has only one progress!"); + return FALSE; + } + if (UnpackProgress.HWindow != NULL) + return UnpackProgress.SetSize(size1, size2, delayedPaint) == 1; + return TRUE; +} + +void CSalamanderForOperations::ProgressEnableCancel(BOOL enable) +{ + if (ThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderForOperations::ProgressEnableCancel() only from thread ID:" << ThreadID); + return; + } + if (Destroyed) + { + TRACE_E("You are calling CSalamanderForOperations::ProgressEnableCancel() after destruction!"); + return; + } + if (UnpackProgress.HWindow != NULL) + UnpackProgress.EnableCancel(enable); +} + +BOOL CSalamanderForOperations::MoveFiles(const char* source, const char* target, const char* remapNameFrom, + const char* remapNameTo) +{ + if (ThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderForOperations::MoveFiles() only from thread ID:" << ThreadID); + return FALSE; + } + if (Destroyed) + { + TRACE_E("You are calling CSalamanderForOperations::MoveFiles() after destruction!"); + return FALSE; + } + if (Panel == NULL) + { + TRACE_E("Incorrect call to CSalamanderForOperations::MoveFiles"); + return FALSE; + } + return Panel->MoveFiles(source, target, remapNameFrom, remapNameTo); +} + +// +// **************************************************************************** +// CSalamanderForViewFileOnFS +// + +const char* +CSalamanderForViewFileOnFS::AllocFileNameInCache(HWND parent, const char* uniqueFileName, const char* nameInCache, + const char* rootTmpPath, BOOL& fileExists) +{ + CALL_STACK_MESSAGE4("CSalamanderForViewFileOnFS::AllocFileNameInCache(, %s, %s, %s, )", + uniqueFileName, nameInCache, rootTmpPath); + if (CallsCounter > 0) + { + TRACE_E("You are calling CSalamanderForViewFileOnFS::AllocFileNameInCache more than once! Is it O.K.?"); + } + + int errorCode; + const char* name = DiskCache.GetName(uniqueFileName, nameInCache, &fileExists, FALSE, rootTmpPath, FALSE, NULL, &errorCode); + if (name == NULL) + { + char buff[2000]; + strcpy(buff, LoadStr(IDS_VIEWFILEFAILED)); + if (errorCode == DCGNE_TOOLONGNAME) + { + strcat(buff, "\n"); + strcat(buff, LoadStr(IDS_VIEWFILETOOLONGNAME)); + } + SalMessageBox(parent, buff, LoadStr(IDS_ERRORTITLE), MB_OK | MB_ICONEXCLAMATION); + } + else + { + CallsCounter++; + } + + return name; +} + +BOOL CSalamanderForViewFileOnFS::OpenViewer(HWND parent, const char* fileName, HANDLE* fileLock, + BOOL* fileLockOwner) +{ + CALL_STACK_MESSAGE2("CSalamanderForViewFileOnFS::OpenViewer(, %s, ,)", fileName); + + HANDLE lock = NULL; + BOOL lockOwner = FALSE; + + BOOL ret = ViewFileInt(parent, fileName, AltView, HandlerID, + (fileLock != NULL && fileLockOwner != NULL), + lock, lockOwner, FALSE, -1, -1); + + if (fileLock != NULL) + *fileLock = lock; + if (fileLockOwner != NULL) + *fileLockOwner = lockOwner; + return ret; +} + +void CSalamanderForViewFileOnFS::FreeFileNameInCache(const char* uniqueFileName, BOOL fileExists, BOOL newFileOK, + const CQuadWord& newFileSize, HANDLE fileLock, + BOOL fileLockOwner, BOOL removeAsSoonAsPossible) +{ + CALL_STACK_MESSAGE8("CSalamanderForViewFileOnFS::FreeFileNameInCache(%s, %d, %d, %g, 0x%p, %d, %d)", + uniqueFileName, fileExists, newFileOK, newFileSize.GetDouble(), fileLock, + fileLockOwner, removeAsSoonAsPossible); + + if (CallsCounter == 0) + { + TRACE_E("Unmatched call to CSalamanderForViewFileOnFS::FreeFileNameInCache!"); + return; + } + CallsCounter--; + + if (!fileExists) // newly downloaded copy of the file + { + if (newFileOK) // the download was successful + { + DiskCache.NamePrepared(uniqueFileName, newFileSize); + } + else + { + DiskCache.ReleaseName(uniqueFileName, FALSE); // the download failed, nothing to cache + return; // nothing else to address + } + } + + if (fileLock != NULL) // we have the viewer's "lock" object; link the viewer and the disk cache + { + DiskCache.AssignName(uniqueFileName, fileLock, fileLockOwner, + (fileExists || removeAsSoonAsPossible) ? crtDirect : crtCache); // for files present in the disk cache we use crtDirect, because it does not affect the "lifetime" setting (it stays as the file's author requested) + } + else // the viewer did not open or simply does not have a "lock" object + { + DiskCache.ReleaseName(uniqueFileName, !fileExists && !removeAsSoonAsPossible); // if 'removeAsSoonAsPossible' is not TRUE, at least try to keep a copy of the file in the disk cache (if it was not an existing file, we do not change its "lifetime") + } +} + +// +// **************************************************************************** +// CSalamanderDirectory +// + +CSalamanderDirectory::CSalamanderDirectory(BOOL isForFS, DWORD validData, DWORD flags) + : Dirs(10, 200), SalamDirs(10, 200), Files(10, 200) +{ + ValidData = validData; + if (flags == -1) + flags = isForFS ? SALDIRFLAG_IGNOREDUPDIRS : 0; + Flags = flags; + IsForFS = isForFS; + AddCache = NULL; +} + +CSalamanderDirectory::~CSalamanderDirectory() +{ + Clear(NULL); // plug-in data are released only in the root sal-dir + FreeAddCache(); +} + +void CSalamanderDirectory::AllocAddCache() +{ + if (AddCache == NULL) + { + AddCache = (CSalamanderDirectoryAddCache*)malloc(sizeof(CSalamanderDirectoryAddCache)); + if (AddCache != NULL) + ZeroMemory(AddCache, sizeof(CSalamanderDirectoryAddCache)); + // if allocating the cache fails, it is fine; we are fully functional without it + } +} + +void CSalamanderDirectory::FreeAddCache() +{ + if (AddCache != NULL) + { + free(AddCache); + AddCache = NULL; + } +} + +int CSalamanderDirectory::SalDirStrCmp(const char* s1, const char* s2) +{ + if (Flags & SALDIRFLAG_CASESENSITIVE) + return strcmp(s1, s2); + else + return StrICmp(s1, s2); +} + +int CSalamanderDirectory::SalDirStrCmpEx(const char* s1, int l1, const char* s2, int l2) +{ + if (Flags & SALDIRFLAG_CASESENSITIVE) + return StrCmpEx(s1, l1, s2, l2); + else + return StrICmpEx(s1, l1, s2, l2); +} + +void CSalamanderDirectory::Clear(CPluginDataInterfaceAbstract* pluginData) +{ + if (pluginData != NULL) // release plug-in-specific data + { + CPluginDataInterfaceEncapsulation plugin(pluginData, STR_NONE, STR_NONE, NULL, 0); + BOOL releaseFiles = plugin.CallReleaseForFiles(); + BOOL releaseDirs = plugin.CallReleaseForDirs(); + if (releaseFiles || releaseDirs) + { + ReleasePluginData(plugin, releaseFiles, releaseDirs); + } + } + int i; + for (i = 0; i < SalamDirs.Count; i++) + { + CSalamanderDirectory* salDir = SalamDirs[i]; + if (salDir != NULL) + delete salDir; + } + SalamDirs.DestroyMembers(); + Dirs.DestroyMembers(); + Files.DestroyMembers(); + if (AddCache != NULL) + { + AddCache->PathLen = 0; + AddCache->Path[0] = 0; + AddCache->Dir = NULL; + } + ValidData = VALID_DATA_ALL_FS_ARC; + Flags = IsForFS ? SALDIRFLAG_IGNOREDUPDIRS : 0; +} + +void CSalamanderDirectory::SetValidData(DWORD validData) +{ + if (ValidData != validData) + { + ValidData = validData; + int i; + for (i = 0; i < SalamDirs.Count; i++) + { + CSalamanderDirectory* salDir = SalamDirs[i]; + if (salDir != NULL) + salDir->SetValidData(validData); + } + } +} + +void CSalamanderDirectory::SetFlags(DWORD flags) +{ + if (Flags != flags) + { + Flags = flags; + int i; + for (i = 0; i < SalamDirs.Count; i++) + { + CSalamanderDirectory* salDir = SalamDirs[i]; + if (salDir != NULL) + salDir->SetFlags(flags); + } + } +} + +CSalamanderDirectory* +CSalamanderDirectory::AllocSalamDir(int index) +{ + CALL_STACK_MESSAGE_NONE // time-critical method + + if (index < 0 || index >= SalamDirs.Count || SalamDirs[index] != NULL) + { + TRACE_E("Unexpected error in CSalamanderDirectory::AllocSalamDir()."); + return NULL; + } + CSalamanderDirectory* dir = new CSalamanderDirectory(IsForFS, ValidData, Flags); + if (dir == NULL) + { + TRACE_E(LOW_MEMORY); + return NULL; + } + SalamDirs[index] = dir; + return dir; +} + +// *************************************************************************** +// FindDir: +// +// 'path' - input: path in the archive (relative to this directory) +// 's' - output: points past the first name in the path 'path' +// 'i' - output: index of the found subdirectory (which should continue processing the path 's') +// 'file' - input: if the directory must be created, where to copy data from +// 'pluginData' - input: interface for creating plug-in-specific data for the new directory (if needed) +// 'archivePath' - input: full path in the archive ('path' and 's' both point into it) + +BOOL CSalamanderDirectory::FindDir(const char* path, const char*& s, int& i, const CFileData& file, + CPluginDataInterfaceAbstract* pluginData, const char* archivePath) +{ + CALL_STACK_MESSAGE_NONE // time-critical method + // CALL_STACK_MESSAGE2("CSalamanderDirectory::FindDir(%s, , , ,)", path); + s = path; + while (*s != 0 && *s != '\\') + s++; + + for (i = 0; i < Dirs.Count; i++) + { + if (SalDirStrCmpEx(Dirs[i].Name, Dirs[i].NameLen, path, (int)(s - path)) == 0) + break; + } + if (i == Dirs.Count) // we must create it + { + CFileData data; + //--- name + data.Name = (char*)malloc((s - path) + 1); // allocation + if (data.Name == NULL) + { + TRACE_E(LOW_MEMORY); + return FALSE; + } + memcpy(data.Name, path, s - path); // copy of the text + data.Name[s - path] = 0; + data.NameLen = s - path; + //--- extension + if (!Configuration.SortDirsByExt) + data.Ext = data.Name + data.NameLen; // directories have no extensions + else + { + const char* ss = s; + while (--ss >= path && *ss != '.') + ; + if (ss >= path) + data.Ext = data.Name + (ss - path + 1); // ".cvspass" is an extension in Windows... + // if (ss > path) data.Ext = data.Name + (ss - path + 1); + else + data.Ext = data.Name + data.NameLen; + } + //--- other fields + data.Size = CQuadWord(0, 0); + data.Attr = 0; + data.LastWrite = file.LastWrite; // take the date from the first file in the directory + data.DosName = NULL; + data.PluginData = 0; + data.Hidden = 0; + data.IsLink = 0; + data.IsOffline = 0; + // private Salamander data + data.Association = 0; + data.Selected = 0; + data.Shared = 0; + data.Archive = 0; + data.SizeValid = 0; + data.Dirty = 0; // optional, kept only for formality + data.CutToClip = 0; + data.IconOverlayIndex = ICONOVERLAYINDEX_NOTUSED; + data.IconOverlayDone = 0; + + if (pluginData != NULL) // let the plug-in add its specific data + { + char arcPath[MAX_PATH]; // name of the added directory inside the archive + memcpy(arcPath, archivePath, s - archivePath); + arcPath[s - archivePath] = 0; + CPluginDataInterfaceEncapsulation plugin(pluginData, STR_NONE, STR_NONE, NULL, 0); + if (!plugin.GetFileDataForNewDir(arcPath, data)) // cannot add the plug-in data + { + free(data.Name); + return FALSE; + } + } + + Dirs.Add(data); + if (!Dirs.IsGood()) + { + Dirs.ResetState(); + if (pluginData != NULL) // release plug-in-specific data + { + CPluginDataInterfaceEncapsulation plugin(pluginData, STR_NONE, STR_NONE, NULL, 0); + if (plugin.CallReleaseForDirs()) + plugin.ReleasePluginData2(data, TRUE); + } + free(data.Name); + return FALSE; + } + //--- adding the Salamander directory corresponding to the new directory + /* + CSalamanderDirectory *dir = new CSalamanderDirectory(IsForFS, ValidData, Flags); + if (dir != NULL) SalamDirs.Add((DWORD)dir); + else TRACE_E(LOW_MEMORY); + if (dir == NULL || !SalamDirs.IsGood()) + { + if (dir != NULL) delete dir; + SalamDirs.ResetState(); + if (pluginData != NULL) // release plug-in-specific data + { + CPluginDataInterfaceEncapsulation plugin(pluginData, STR_NONE, STR_NONE, NULL, 0); + if (plugin.CallReleaseForDirs()) plugin.ReleasePluginData2(Dirs[Dirs.Count - 1], TRUE); + } + Dirs.Delete(Dirs.Count - 1); + return FALSE; + } +*/ + SalamDirs.Add(NULL); // add NULL (the object will be allocated the first time it is needed) + if (!SalamDirs.IsGood()) + { + SalamDirs.ResetState(); + if (pluginData != NULL) // release plug-in-specific data + { + CPluginDataInterfaceEncapsulation plugin(pluginData, STR_NONE, STR_NONE, NULL, 0); + if (plugin.CallReleaseForDirs()) + plugin.ReleasePluginData2(Dirs[Dirs.Count - 1], TRUE); + } + Dirs.Delete(Dirs.Count - 1); + if (!Dirs.IsGood()) + Dirs.ResetState(); + return FALSE; + } + } + return TRUE; +} + +BOOL CSalamanderDirectory::AddFile(const char* path, CFileData& file, CPluginDataInterfaceAbstract* pluginData) +{ + CALL_STACK_MESSAGE_NONE // time-critical method + + int pathLen = 0; + if (path != NULL && ((pathLen = (int)strlen(path)) > MAX_PATH - 5 || file.NameLen > MAX_PATH - 5)) + { + TRACE_E("Too long path or file name!"); + return FALSE; + } + + // TRACE_I("AddFile path="< 0 && + pathLen == AddCache->PathLen && memcmp(path, AddCache->Path, pathLen) == 0) + { + // the cache already held our path, so we can insert the file immediately + AddCache->Dir->Files.Add(file); + if (!AddCache->Dir->Files.IsGood()) + { + AddCache->Dir->Files.ResetState(); + return FALSE; + } + return TRUE; + } + + CSalamanderDirectory* ret = AddFileInt(path, file, pluginData, path); + + // if the insertion succeeded and the cache is used, remember the path + if (ret != NULL && AddCache != NULL && pathLen > 0) + { + AddCache->PathLen = pathLen; + memcpy(AddCache->Path, path, pathLen); + AddCache->Dir = ret; + } + + return ret != NULL; +} + +BOOL CSalamanderDirectory::AddDir(const char* path, CFileData& dir, CPluginDataInterfaceAbstract* pluginData) +{ + CALL_STACK_MESSAGE_NONE // time-critical method + + if (path != NULL && (strlen(path) > MAX_PATH - 5 || dir.NameLen > MAX_PATH - 5)) + { + TRACE_E("Too long path or file name!"); + return FALSE; + } + + // TRACE_I("AddDir path="<= 0 && i < Files.Count) return &(*((CFilesArray*)&Files))[i]; + else return NULL; +} + +CFileData const* +CSalamanderDirectory::GetDir(int i) const +{ + CALL_STACK_MESSAGE_NONE // time-critical method + if (i >= 0 && i < Dirs.Count) return &(*((CFilesArray*)&Dirs))[i]; + else return NULL; +} + +CSalamanderDirectoryAbstract const* +CSalamanderDirectory::GetSalDir(int i) const +{ + CALL_STACK_MESSAGE_NONE // time-critical method + if (i >= 0 && i < SalamDirs.Count) + { + CSalamanderDirectoryAbstract const* salDir = (CSalamanderDirectoryAbstract const*)(*((TDirectArray*)&SalamDirs))[i]; + if (salDir == NULL) + salDir = &GlobalEmptySalDir; // it's an empty directory - return the global empty directory + return salDir; + } + else return NULL; +} + +CSalamanderDirectory* +CSalamanderDirectory::AddFileInt(const char* path, CFileData& file, + CPluginDataInterfaceAbstract* pluginData, const char* archivePath) +{ + CALL_STACK_MESSAGE_NONE // time-critical method; in addition, path may be NULL + // CALL_STACK_MESSAGE3("CSalamanderDirectory::AddFileInt(%s, , , %s)", path, archivePath); + + if (path != NULL) + { + if (*path == '\\') + path++; + if (*path != 0) // not this directory; find the subdirectory + { + const char* s; + int i; + if (!FindDir(path, s, i, file, pluginData, archivePath)) + return NULL; + + CSalamanderDirectory* salDir = SalamDirs[i]; + if (salDir != NULL || // already allocated + (salDir = AllocSalamDir(i)) != NULL) // or succeeded in allocating a new object + { + return salDir->AddFileInt(s, file, pluginData, archivePath); + } + else + return NULL; + } + } + + // note: if AddCache applies, the item is added directly in AddFile + Files.Add(file); + if (!Files.IsGood()) + { + Files.ResetState(); + return NULL; + } + return this; +} + +CSalamanderDirectory* +CSalamanderDirectory::AddDirInt(const char* path, CFileData& dir, + CPluginDataInterfaceAbstract* pluginData, const char* archivePath) +{ + CALL_STACK_MESSAGE_NONE // time-critical method; in addition, path may be NULL + // CALL_STACK_MESSAGE3("CSalamanderDirectory::AddDirInt(%s, , , %s)", path, archivePath); + + if (path != NULL) + { + if (*path == '\\') + path++; + if (*path != 0) // not this directory; find the subdirectory + { + const char* s; + int i; + if (!FindDir(path, s, i, dir, pluginData, archivePath)) + return NULL; + + CSalamanderDirectory* salDir = SalamDirs[i]; + if (salDir != NULL || // already allocated + (salDir = AllocSalamDir(i)) != NULL) // or succeeded in allocating a new object + { + return salDir->AddDirInt(s, dir, pluginData, archivePath); + } + else + return NULL; + } + } + + BOOL newDir = TRUE; + if ((Flags & SALDIRFLAG_IGNOREDUPDIRS) == 0) // if we should test for duplicate directories + { + int i; + for (i = 0; i < Dirs.Count; i++) + { + if (SalDirStrCmpEx(Dirs[i].Name, Dirs[i].NameLen, dir.Name, dir.NameLen) == 0) + break; + } + newDir = (i == Dirs.Count); // not created yet + if (!newDir) // updating existing data + { + if (pluginData != NULL) // release plug-in-specific data + { + CPluginDataInterfaceEncapsulation plugin(pluginData, STR_NONE, STR_NONE, NULL, 0); + if (plugin.CallReleaseForDirs()) + plugin.ReleasePluginData2(Dirs[i], TRUE); + } + + if (Dirs[i].Name != NULL) + free(Dirs[i].Name); + Dirs[i].Name = dir.Name; // rather take the new name (for possible data after '\0' in the string) + Dirs[i].Ext = dir.Ext; + Dirs[i].Size = dir.Size; + Dirs[i].Attr = dir.Attr; + Dirs[i].LastWrite = dir.LastWrite; + if (Dirs[i].DosName != NULL) + free(Dirs[i].DosName); + Dirs[i].DosName = dir.DosName; + Dirs[i].PluginData = dir.PluginData; + // Dirs[i].NameLen should be the same as dir.NameLen + Dirs[i].Hidden = dir.Hidden; + Dirs[i].IsLink = dir.IsLink; + Dirs[i].IsOffline = dir.IsOffline; + // the remainder of Dirs[i] should be zeroed just like the rest of dir + } + } + if (newDir) + { + //--- adding the Salamander directory corresponding to the new directory + /* + CSalamanderDirectory *SalamDir = new CSalamanderDirectory(IsForFS, ValidData, Flags); + if (SalamDir != NULL) SalamDirs.Add((DWORD)SalamDir); + else TRACE_E(LOW_MEMORY); + if (SalamDir == NULL || !SalamDirs.IsGood()) + { + if (SalamDir != NULL) delete SalamDir; + SalamDirs.ResetState(); + return FALSE; + } + + Dirs.Add(dir); + if (!Dirs.IsGood()) + { + Dirs.ResetState(); + SalamDirs.Delete(SalamDirs.Count - 1); + delete SalamDir; + return FALSE; + } +*/ + if (IsForFS && dir.NameLen == 2 && dir.Name[0] == '.' && dir.Name[1] == '.') + { + CFileData* firstDir = Dirs.Count > 0 ? &Dirs[0] : NULL; + if (firstDir != NULL && firstDir->NameLen == 2 && + firstDir->Name[0] == '.' && firstDir->Name[1] == '.') + { // an up-directory is already present + TRACE_E("CSalamanderDirectory::AddFile(): you can add up-dir (\"..\") at most once!"); + return NULL; + } + SalamDirs.Insert(0, NULL); // add NULL (the object will be allocated the first time it is needed) + if (!SalamDirs.IsGood()) + { + SalamDirs.ResetState(); + return NULL; + } + + Dirs.Insert(0, dir); + if (!Dirs.IsGood()) + { + Dirs.ResetState(); + SalamDirs.Delete(0); + if (!SalamDirs.IsGood()) + SalamDirs.ResetState(); + return NULL; + } + } + else + { + SalamDirs.Add(NULL); // add NULL (the object will be allocated the first time it is needed) + if (!SalamDirs.IsGood()) + { + SalamDirs.ResetState(); + return NULL; + } + + Dirs.Add(dir); + if (!Dirs.IsGood()) + { + Dirs.ResetState(); + SalamDirs.Delete(SalamDirs.Count - 1); + if (!SalamDirs.IsGood()) + SalamDirs.ResetState(); + return NULL; + } + } + } + return this; +} + +extern int DeltaForTotalCount(int total); + +void CSalamanderDirectory::SetApproximateCount(int files, int dirs) +{ + CALL_STACK_MESSAGE3("CSalamanderDirectory::SetApproximateCount(%d, %d)", files, dirs); + if (files > 1) + { + if (Files.Count == 0) + Files.SetDelta(DeltaForTotalCount(files)); + else + TRACE_E("CSalamanderDirectory::SetApproximateCount() Files.Count = " << Files.Count); + } + if (dirs > 1) + { + if (Dirs.Count == 0) + Dirs.SetDelta(DeltaForTotalCount(dirs)); + else + TRACE_E("CSalamanderDirectory::SetApproximateCount() Dirs.Count = " << Dirs.Count); + } +} + +void CSalamanderDirectory::ReleasePluginData(CPluginDataInterfaceEncapsulation& pluginData, + BOOL releaseFiles, BOOL releaseDirs) +{ + SLOW_CALL_STACK_MESSAGE3("CSalamanderDirectory::ReleasePluginData(, %d, %d)", + releaseFiles, releaseDirs); + if (releaseFiles) + pluginData.ReleaseFilesOrDirs(&Files, FALSE); + if (releaseDirs) + pluginData.ReleaseFilesOrDirs(&Dirs, TRUE); + int i; + for (i = 0; i < SalamDirs.Count; i++) + { + CSalamanderDirectory* salDir = SalamDirs[i]; + if (salDir != NULL) + salDir->ReleasePluginData(pluginData, releaseFiles, releaseDirs); + } +} + +CFilesArray* +CSalamanderDirectory::GetDirs(const char* path) +{ + CALL_STACK_MESSAGE2("CSalamanderDirectory::GetDirs(%s)", path); + if (path != NULL) + { + if (*path == '\\') + path++; + if (*path != 0) // some subdirectory + { + const char* s = path; + while (*s != 0 && *s != '\\') + s++; + + int i; + for (i = 0; i < Dirs.Count; i++) + { + if (SalDirStrCmpEx(Dirs[i].Name, Dirs[i].NameLen, path, (int)(s - path)) == 0) + { + CSalamanderDirectory* salDir = SalamDirs[i]; + if (salDir != NULL || // already allocated + (salDir = AllocSalamDir(i)) != NULL) // or succeeded in allocating a new object + { + return salDir->GetDirs(s); + } + else + return NULL; // low memory error (as if the directory did not exist) + } + } + } + else + return &Dirs; + } + return NULL; +} + +CFilesArray* +CSalamanderDirectory::GetFiles(const char* path) +{ + CALL_STACK_MESSAGE2("CSalamanderDirectory::GetFiles(%s)", path); + if (path != NULL) + { + if (*path == '\\') + path++; + if (*path != 0) // some subdirectory + { + const char* s = path; + while (*s != 0 && *s != '\\') + s++; + + int i; + for (i = 0; i < Dirs.Count; i++) + { + if (SalDirStrCmpEx(Dirs[i].Name, Dirs[i].NameLen, path, (int)(s - path)) == 0) + { + CSalamanderDirectory* salDir = SalamDirs[i]; + if (salDir != NULL || // already allocated + (salDir = AllocSalamDir(i)) != NULL) // or succeeded in allocating a new object + { + return salDir->GetFiles(s); + } + else + return NULL; // low memory error (as if the directory did not exist) + } + } + } + else + return &Files; + } + return NULL; +} + +const CFileData* +CSalamanderDirectory::GetUpperDir(const char* path) +{ + CALL_STACK_MESSAGE2("CSalamanderDirectory::GetUpperDir(%s)", path); + if (path != NULL) + { + if (*path == '\\') + path++; + if (*path != 0) // some subdirectory + { + const char* s = path; + while (*s != 0 && *s != '\\') + s++; + + int i; + for (i = 0; i < Dirs.Count; i++) + { + if (SalDirStrCmpEx(Dirs[i].Name, Dirs[i].NameLen, path, (int)(s - path)) == 0) + { + if (*s == 0 || *(s + 1) == 0) + return &Dirs[i]; // the last path component = the requested parent directory + else + { + CSalamanderDirectory* salDir = SalamDirs[i]; + if (salDir != NULL || // already allocated + (salDir = AllocSalamDir(i)) != NULL) // or succeeded in allocating a new object + { + return salDir->GetUpperDir(s); + } + else + return NULL; // low memory error (as if the directory did not exist) + } + } + } + } + else + return NULL; // for root return NULL + } + return NULL; // for root and unknown paths return NULL +} + +CQuadWord +CSalamanderDirectory::GetSize(int* dirsCount, int* filesCount, TDirectArray* sizes) +{ + CALL_STACK_MESSAGE1("CSalamanderDirectory::GetSize(,)"); + CQuadWord size(0, 0); + int i; + for (i = 0; i < Files.Count; i++) + { + size += Files[i].Size; + if (sizes != NULL) + sizes->Add(Files[i].Size); // addition failure is handled at the level of the output dialog + } + if (filesCount != NULL) + *filesCount += Files.Count; + for (i = 0; i < SalamDirs.Count; i++) + { + CSalamanderDirectory* salDir = SalamDirs[i]; + if (salDir != NULL) + size += salDir->GetSize(dirsCount, filesCount, sizes); + } + if (dirsCount != NULL) + *dirsCount += SalamDirs.Count; + return size; +} + +CQuadWord +CSalamanderDirectory::GetDirSize(const char* path, const char* dirName, int* dirsCount, + int* filesCount, TDirectArray* sizes) +{ + CALL_STACK_MESSAGE3("CSalamanderDirectory::GetDirSize(%s, %s, , ,)", path, dirName); + if (path != NULL) + { + if (*path == '\\') + path++; + if (*path != 0) // some subdirectory + { + const char* s = path; + while (*s != 0 && *s != '\\') + s++; + + int i; + for (i = 0; i < Dirs.Count; i++) + { + if (SalDirStrCmpEx(Dirs[i].Name, Dirs[i].NameLen, path, (int)(s - path)) == 0) + { + CSalamanderDirectory* salDir = SalamDirs[i]; + if (salDir != NULL) + return salDir->GetDirSize(s, dirName, dirsCount, filesCount, sizes); + else + return CQuadWord(0, 0); // contains nothing; otherwise it would already be allocated + } + } + } + else + { + int i; + for (i = 0; i < Dirs.Count; i++) + { + if (SalDirStrCmp(Dirs[i].Name, dirName) == 0) + { + CSalamanderDirectory* salDir = SalamDirs[i]; + if (salDir != NULL) + return salDir->GetSize(dirsCount, filesCount, sizes); + else + return CQuadWord(0, 0); // contains nothing; otherwise it would already be allocated + } + } + TRACE_E("Incorrect call to CSalamanderDirectory::GetDirSize() - directory does not exist!"); + return CQuadWord(0, 0); // not found + } + } + return CQuadWord(0, 0); +} + +CSalamanderDirectory* +CSalamanderDirectory::GetSalamanderDir(const char* path, BOOL readOnly) +{ + CALL_STACK_MESSAGE_NONE + // CALL_STACK_MESSAGE3("CSalamanderDirectory::GetSalamanderDir(%s, %d)", path, readOnly); + if (path != NULL) + { + if (*path == '\\') + path++; + if (*path != 0) // some subdirectory + { + const char* s = path; + while (*s != 0 && *s != '\\') + s++; + + int i; + for (i = 0; i < Dirs.Count; i++) + { + if (SalDirStrCmpEx(Dirs[i].Name, Dirs[i].NameLen, path, (int)(s - path)) == 0) + { + CSalamanderDirectory* salDir = SalamDirs[i]; + if (salDir != NULL) + return salDir->GetSalamanderDir(s, readOnly); + else // an empty directory + { + if (readOnly) + return &GlobalEmptySalDir; // read-only - return the global empty directory + else // for writing + { + if ((salDir = AllocSalamDir(i)) != NULL) // we must allocate a new object + { + return salDir->GetSalamanderDir(s, readOnly); + } + else + return NULL; // allocation error + } + } + } + } + } + else + return this; + } + return NULL; +} + +CSalamanderDirectory* +CSalamanderDirectory::GetSalamanderDir(int i) +{ + if (i >= 0 && i < SalamDirs.Count) + { + CSalamanderDirectory* salDir = SalamDirs[i]; + if (salDir == NULL) + salDir = &GlobalEmptySalDir; // it's an empty directory - return the global empty directory + return salDir; + } + else + return NULL; +} + +int CSalamanderDirectory::GetIndex(const char* dir) +{ + if (dir != NULL) + { + int i; + for (i = 0; i < Dirs.Count; i++) + { + if (SalDirStrCmp(Dirs[i].Name, dir) == 0) + return i; + } + } + return -1; // not found +} + +// **************************************************************************** + +BOOL TestFreeSpace(HWND parent, const char* path, const CQuadWord& totalSize, const char* messageTitle) +{ + CQuadWord freeSpace = MyGetDiskFreeSpace(path); + if (freeSpace != CQuadWord(-1, -1) && freeSpace < totalSize) + { + char buf1[50]; + char buf2[50]; + char buf3[200]; + sprintf(buf3, LoadStr(IDS_NOTENOUGHSPACE), + NumberToStr(buf1, totalSize), + NumberToStr(buf2, freeSpace)); + return SalMessageBox(parent, buf3, messageTitle, MB_YESNO | MB_ICONQUESTION | MSGBOXEX_ESCAPEENABLED) == IDYES; + } + return TRUE; +} diff --git a/src/zip_general_api.cpp b/src/zip_general_api.cpp new file mode 100644 index 00000000..ca103402 --- /dev/null +++ b/src/zip_general_api.cpp @@ -0,0 +1,3020 @@ +// SPDX-FileCopyrightText: 2023 Taskscape Ltd +// SPDX-License-Identifier: GPL-2.0-or-later +// CommentsTranslationProject: TRANSLATED + +#include "precomp.h" + +#include "menu.h" +#include "cfgdlg.h" +#include "dialogs.h" +#include "mainwnd.h" +#include "plugins.h" +#include "filesbox.h" +#include "fileswnd.h" +#include "stswnd.h" +#include "editwnd.h" +#include "zip.h" +#include "cache.h" +#include "viewer.h" +#include "codetbl.h" +#include "shellib.h" +#include "gui.h" +#include "tasklist.h" +#include "olespy.h" +#include "md5.h" +#include "geticon.h" +#include "pack.h" +extern "C" +{ +#include "shexreg.h" +} +#include "salshlib.h" +#include "crypt\fileenc.h" +#include "crypt\sha1.h" +#include "pwdmngr.h" + +// Globals defined in zip_progress.cpp +extern const char* STR_NONE; +extern CSalamanderDirectory GlobalEmptySalDir; +extern HWND ProgressDialogActivateDrop; + +// Forward declaration for TestFreeSpace defined in zip_directory.cpp +BOOL TestFreeSpace(HWND parent, const char* path, const CQuadWord& totalSize, const char* messageTitle); + +// +// **************************************************************************** +// CSalamanderGeneral +// + +CSalamanderGeneral::CSalamanderGeneral() +{ + Plugin = NULL; + LanguageModule = NULL; + HelpFileName[0] = 0; +} + +CSalamanderGeneral::~CSalamanderGeneral() +{ + if (LanguageModule != NULL) + { + TRACE_E("CSalamanderGeneral::~CSalamanderGeneral(): unexpected situation!"); + HANDLES(FreeLibrary(LanguageModule)); + } +} + +void CSalamanderGeneral::Clear() +{ + if (LanguageModule != NULL) + HANDLES(FreeLibrary(LanguageModule)); + LanguageModule = NULL; + HelpFileName[0] = 0; +} + +int CSalamanderGeneral::ShowMessageBox(const char* text, const char* title, int type) +{ + if (MainThreadID != GetCurrentThreadId()) // Petr: just close; I do not have the energy to track down every wrong call + TRACE_E("You can call CSalamanderGeneral::ShowMessageBox() only from main thread!"); + HWND parent = GetMsgBoxParent(); + switch (type) + { + case MSGBOX_INFO: + { + return SalMessageBox(parent, text, title, MB_OK | MB_ICONINFORMATION); + } + + case MSGBOX_ERROR: + { + return SalMessageBox(parent, text, title, MB_OK | MB_ICONEXCLAMATION); + } + + case MSGBOX_EX_ERROR: + { + return SalMessageBox(parent, text, title, MB_OKCANCEL | MB_ICONEXCLAMATION); + } + + case MSGBOX_QUESTION: + { + return SalMessageBox(parent, text, title, MB_YESNO | MB_ICONQUESTION); + } + + case MSGBOX_EX_QUESTION: + { + return SalMessageBox(parent, text, title, MB_YESNOCANCEL | MB_ICONQUESTION); + } + + case MSGBOX_WARNING: + { + return SalMessageBox(parent, text, title, MB_OK | MB_ICONWARNING); + } + + case MSGBOX_EX_WARNING: + { + return SalMessageBox(parent, text, title, MB_YESNOCANCEL | MB_ICONWARNING); + } + + default: + { + TRACE_E("Unknown type of box in CSalamanderGeneral::ShowMessageBox()."); + return 0; + } + } +} + +int CSalamanderGeneral::SalMessageBox(HWND hParent, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType) +{ + return ::SalMessageBox(hParent, lpText, lpCaption, uType); +} + +int CSalamanderGeneral::SalMessageBoxEx(const MSGBOXEX_PARAMS* params) +{ + return ::SalMessageBoxEx(params); +} + +HWND CSalamanderGeneral::GetMsgBoxParent() +{ + if (MainThreadID != GetCurrentThreadId()) // Petr: just close; I do not have the energy to track down every wrong call + TRACE_E("You can call CSalamanderGeneral::GetMsgBoxParent() only from main thread!"); + // if the following code should change, it must also be updated in EnterPlugin - so the check keeps working + return PluginProgressDialog != NULL ? PluginProgressDialog : PluginMsgBoxParent; +} + +int DialogError(HWND parent, DWORD flags, const char* fileName, + const char* error, const char* title) +{ + HWND mainWnd = GetWndToFlash(parent); + DWORD resID; + BOOL noSkip; + switch (flags & BUTTONS_MASK) + { + case BUTTONS_OK: + { + resID = IDD_ERROR3; + noSkip = FALSE; // does not apply; something about resID == 0 + break; + } + + case BUTTONS_RETRYCANCEL: + { + resID = 0; + noSkip = TRUE; + break; + } + + case BUTTONS_SKIPCANCEL: + { + resID = IDD_ERROR2; + noSkip = FALSE; // does not apply; something about resID == 0 + break; + } + + case BUTTONS_RETRYSKIPCANCEL: + { + resID = 0; + noSkip = FALSE; + break; + } + + default: + { + TRACE_E("CSalamanderGeneral::DialogError: unknow flags=0x" << std::hex << flags << std::dec); + return DIALOG_FAIL; + } + } + int res = (int)CFileErrorDlg(parent, title != NULL ? title : ::LoadStr(IDS_ERRORTITLE), + fileName, error, noSkip, resID) + .Execute(); + if (mainWnd != NULL) + FlashWindow(mainWnd, FALSE); + switch (res) + { + case IDOK: + return DIALOG_OK; + case IDRETRY: + return DIALOG_RETRY; + case IDB_SKIP: + return DIALOG_SKIP; + case IDB_SKIPALL: + return DIALOG_SKIPALL; + case IDCANCEL: + return DIALOG_CANCEL; + default: + return DIALOG_FAIL; + } +} + +int CSalamanderGeneral::DialogError(HWND parent, DWORD flags, const char* fileName, + const char* error, const char* title) +{ + if (fileName == NULL || error == NULL) + { + TRACE_E("Invalid parametr (fileName == NULL || error == NULL) in CSalamanderGeneral::DialogError!"); + if (fileName == NULL) + fileName = ""; + if (error == NULL) + error = ""; + } + return ::DialogError(parent, flags, fileName, error, title); +} + +int DialogOverwrite(HWND parent, DWORD flags, const char* fileName1, const char* fileData1, + const char* fileName2, const char* fileData2) +{ + HWND mainWnd = GetWndToFlash(parent); + BOOL yesnocancel; + switch (flags & BUTTONS_MASK) + { + case BUTTONS_YESALLSKIPCANCEL: + { + yesnocancel = FALSE; + break; + } + + case BUTTONS_YESNOCANCEL: + { + yesnocancel = TRUE; + break; + } + + default: + { + TRACE_E("CSalamanderGeneral::DialogOverwrite: unknow flags=0x" << std::hex << flags << std::dec); + return DIALOG_FAIL; + } + } + + int res = (int)COverwriteDlg(parent, fileName1, fileData1, fileName2, fileData2, yesnocancel).Execute(); + if (mainWnd != NULL) + FlashWindow(mainWnd, FALSE); + switch (res) + { + case IDYES: + return DIALOG_YES; + case IDNO: + return DIALOG_NO; + case IDB_ALL: + return DIALOG_ALL; + case IDB_SKIP: + return DIALOG_SKIP; + case IDB_SKIPALL: + return DIALOG_SKIPALL; + case IDCANCEL: + return DIALOG_CANCEL; + default: + return DIALOG_FAIL; + } +} + +int CSalamanderGeneral::DialogOverwrite(HWND parent, DWORD flags, const char* fileName1, const char* fileData1, + const char* fileName2, const char* fileData2) +{ + if (fileName1 == NULL || fileData1 == NULL || fileName2 == NULL || fileData2 == NULL) + { + TRACE_E("Invalid parametr (fileName1 == NULL || fileData1 == NULL || fileName2 == NULL || fileData2 == NULL) in CSalamanderGeneral::DialogOverwrite!"); + if (fileName1 == NULL) + fileName1 = ""; + if (fileData1 == NULL) + fileData1 = ""; + if (fileName2 == NULL) + fileName2 = ""; + if (fileData2 == NULL) + fileData2 = ""; + } + return ::DialogOverwrite(parent, flags, fileName1, fileData1, fileName2, fileData2); +} + +int DialogQuestion(HWND parent, DWORD flags, const char* fileName, + const char* question, const char* title) +{ + HWND mainWnd = GetWndToFlash(parent); + BOOL yesnocancel, yesallcancel; + switch (flags & BUTTONS_MASK) + { + case BUTTONS_YESALLSKIPCANCEL: + { + yesnocancel = FALSE; + yesallcancel = FALSE; + break; + } + + case BUTTONS_YESNOCANCEL: + { + yesnocancel = TRUE; + yesallcancel = FALSE; + break; + } + + case BUTTONS_YESALLCANCEL: + { + yesnocancel = TRUE; + yesallcancel = TRUE; + break; + } + + default: + { + TRACE_E("CSalamanderGeneral::DialogQuestion: unknow flags=0x" << std::hex << flags << std::dec); + return DIALOG_FAIL; + } + } + int res = (int)CHiddenOrSystemDlg(parent, title != NULL ? title : ::LoadStr(IDS_QUESTION), fileName, + question, yesnocancel, yesallcancel) + .Execute(); + if (mainWnd != NULL) + FlashWindow(mainWnd, FALSE); + switch (res) + { + case IDYES: + return DIALOG_YES; + case IDNO: + return DIALOG_NO; + case IDB_ALL: + return DIALOG_ALL; + case IDB_SKIP: + return DIALOG_SKIP; + case IDB_SKIPALL: + return DIALOG_SKIPALL; + case IDCANCEL: + return DIALOG_CANCEL; + default: + return DIALOG_FAIL; + } +} + +int CSalamanderGeneral::DialogQuestion(HWND parent, DWORD flags, const char* fileName, + const char* question, const char* title) +{ + if (fileName == NULL || question == NULL) + { + TRACE_E("Invalid parametr (fileName == NULL || question == NULL) in CSalamanderGeneral::DialogQuestion!"); + if (fileName == NULL) + fileName = ""; + if (question == NULL) + question = ""; + } + return ::DialogQuestion(parent, flags, fileName, question, title); +} + +HWND CSalamanderGeneral::GetMainWindowHWND() +{ + return MainWindow != NULL ? MainWindow->HWindow : NULL; +} + +void RestoreFocusInSourcePanel() +{ + if (MainWindow != NULL) + { + CFilesWindow* p1 = MainWindow->GetActivePanel(); + if (p1 != NULL) + { + if (!MainWindow->EditMode) + MainWindow->FocusPanel(p1); + else + { + if (MainWindow->EditWindow != NULL && MainWindow->EditWindow->HWindow != NULL) + SetFocus(MainWindow->EditWindow->HWindow); + } + } + } +} + +void CSalamanderGeneral::RestoreFocusInSourcePanel() +{ + ::RestoreFocusInSourcePanel(); +} + +BOOL CSalamanderGeneral::CheckAndCreateDirectory(const char* dir, HWND parent, BOOL quiet, + char* errBuf, int errBufSize, char* firstCreatedDir, + BOOL manualCrDir) +{ + return ::CheckAndCreateDirectory(dir, parent, quiet, errBuf, errBufSize, firstCreatedDir, FALSE, manualCrDir); +} + +BOOL CSalamanderGeneral::TestFreeSpace(HWND parent, const char* path, const CQuadWord& totalSize, + const char* messageTitle) +{ + return ::TestFreeSpace(parent, path, totalSize, messageTitle); +} + +void CSalamanderGeneral::GetDiskFreeSpace(CQuadWord* retValue, const char* path, CQuadWord* total) +{ + if (retValue == NULL) + { + TRACE_E("Unexpected situation in CSalamanderGeneral::GetDiskFreeSpace(): retValue is NULL!"); + return; + } + *retValue = MyGetDiskFreeSpace(path, total); +} + +BOOL CSalamanderGeneral::SalGetDiskFreeSpace(const char* path, LPDWORD lpSectorsPerCluster, + LPDWORD lpBytesPerSector, LPDWORD lpNumberOfFreeClusters, + LPDWORD lpTotalNumberOfClusters) +{ + return MyGetDiskFreeSpace(path, lpSectorsPerCluster, lpBytesPerSector, + lpNumberOfFreeClusters, lpTotalNumberOfClusters); +} + +BOOL CSalamanderGeneral::SalGetVolumeInformation(const char* path, char* rootOrCurReparsePoint, LPTSTR lpVolumeNameBuffer, + DWORD nVolumeNameSize, LPDWORD lpVolumeSerialNumber, + LPDWORD lpMaximumComponentLength, LPDWORD lpFileSystemFlags, + LPTSTR lpFileSystemNameBuffer, DWORD nFileSystemNameSize) +{ + return MyGetVolumeInformation(path, rootOrCurReparsePoint, NULL, NULL, lpVolumeNameBuffer, nVolumeNameSize, + lpVolumeSerialNumber, lpMaximumComponentLength, lpFileSystemFlags, + lpFileSystemNameBuffer, nFileSystemNameSize); +} + +UINT CSalamanderGeneral::SalGetDriveType(const char* path) +{ + return MyGetDriveType(path); +} + +void CSalamanderGeneral::RemoveTemporaryDir(const char* dir) +{ + ::RemoveTemporaryDir(dir); +} + +void CSalamanderGeneral::PrepareMask(char* mask, const char* src) +{ + ::PrepareMask(mask, src); +} + +BOOL CSalamanderGeneral::AgreeMask(const char* filename, const char* mask, BOOL hasExtension) +{ + return ::AgreeMask(filename, mask, hasExtension, FALSE); +} + +char* CSalamanderGeneral::MaskName(char* buffer, int bufSize, const char* name, const char* mask) +{ + return ::MaskName(buffer, bufSize, name, mask); +} + +void CSalamanderGeneral::PrepareExtMask(char* mask, const char* src) +{ + ::PrepareMask(mask, src); +} + +BOOL CSalamanderGeneral::AgreeExtMask(const char* filename, const char* mask, BOOL hasExtension) +{ + return ::AgreeMask(filename, mask, hasExtension, TRUE); +} + +void* CSalamanderGeneral::Alloc(int size) +{ + return malloc(size); +} + +void* CSalamanderGeneral::Realloc(void* ptr, int size) +{ + return realloc(ptr, size); +} + +void CSalamanderGeneral::Free(void* ptr) +{ + free(ptr); +} + +char* CSalamanderGeneral::DupStr(const char* str) +{ + return ::DupStr(str); +} + +void CSalamanderGeneral::GetLowerAndUpperCase(unsigned char** lowerCase, unsigned char** upperCase) +{ + CALL_STACK_MESSAGE1("CSalamanderGeneral::GetLowerAndUpperCase(,)"); + if (lowerCase != NULL) + *lowerCase = LowerCase; + if (upperCase != NULL) + *upperCase = UpperCase; +} + +void CSalamanderGeneral::ToLowerCase(char* str) +{ + CALL_STACK_MESSAGE1("CSalamanderGeneral::ToLowerCase()"); + char* toLow = str; + while (*toLow != 0) + { + *toLow = LowerCase[*toLow]; + toLow++; + } +} + +void CSalamanderGeneral::ToUpperCase(char* str) +{ + CALL_STACK_MESSAGE1("CSalamanderGeneral::ToUpperCase()"); + char* toUpp = str; + while (*toUpp != 0) + { + *toUpp = UpperCase[*toUpp]; + toUpp++; + } +} + +int CSalamanderGeneral::StrCmpEx(const char* s1, int l1, const char* s2, int l2) +{ + return ::StrCmpEx(s1, l1, s2, l2); +} + +int CSalamanderGeneral::StrICpy(char* dest, const char* src) +{ + return ::StrICpy(dest, src); +} + +int CSalamanderGeneral::StrICmp(const char* s1, const char* s2) +{ + return ::StrICmp(s1, s2); +} + +int CSalamanderGeneral::StrICmpEx(const char* s1, int l1, const char* s2, int l2) +{ + return ::StrICmpEx(s1, l1, s2, l2); +} + +int CSalamanderGeneral::StrNICmp(const char* s1, const char* s2, int n) +{ + return ::StrNICmp(s1, s2, n); +} + +int CSalamanderGeneral::MemICmp(const void* buf1, const void* buf2, int n) +{ + return ::MemICmp(buf1, buf2, n); +} + +int CSalamanderGeneral::RegSetStrICmp(const char* s1, const char* s2) +{ + return ::RegSetStrICmp(s1, s2); +} + +int CSalamanderGeneral::RegSetStrICmpEx(const char* s1, int l1, const char* s2, int l2, BOOL* numericalyEqual) +{ + return ::RegSetStrICmpEx(s1, l1, s2, l2, numericalyEqual); +} + +int CSalamanderGeneral::RegSetStrCmp(const char* s1, const char* s2) +{ + return ::RegSetStrCmp(s1, s2); +} + +int CSalamanderGeneral::RegSetStrCmpEx(const char* s1, int l1, const char* s2, int l2, BOOL* numericalyEqual) +{ + return ::RegSetStrCmpEx(s1, l1, s2, l2, numericalyEqual); +} + +CFilesWindow* +CSalamanderGeneral::GetPanel(int panel) +{ + return MainWindow->GetPanel(panel); +} + +BOOL CSalamanderGeneral::GetPanelPath(int panel, char* buffer, int bufferSize, int* type, + char** archiveOrFS, BOOL convertFSPathToExternal) +{ + CALL_STACK_MESSAGE3("CSalamanderGeneral::GetPanelPath(%d, , %d, ,)", panel, bufferSize); + if (type != NULL) + *type = 0; // unknown + if (archiveOrFS != NULL) + *archiveOrFS = NULL; + if (bufferSize > 0) + buffer[0] = 0; + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::GetPanelPath() only from main thread!"); + if (type != NULL) + *type = 0; + return FALSE; + } + CFilesWindow* p = GetPanel(panel); + if (p != NULL) + { + char buf[2 * MAX_PATH]; + int offset = -1; // offset into the buffer for computing archiveOrFS (-1 means NULL) + if (p->Is(ptZIPArchive)) + { + if (type != NULL) + *type = PATH_TYPE_ARCHIVE; + offset = (int)strlen(p->GetZIPArchive()); + memcpy(buf, p->GetZIPArchive(), offset + 1); + if (p->GetZIPPath()[0] != 0) + { + if (p->GetZIPPath()[0] != '\\') + strcpy(buf + offset, "\\"); + strcat(buf + offset, p->GetZIPPath()); + } + } + else + { + if (p->Is(ptPluginFS)) + { + if (type != NULL) + *type = PATH_TYPE_FS; + offset = (int)strlen(p->GetPluginFS()->GetPluginFSName()); + memcpy(buf, p->GetPluginFS()->GetPluginFSName(), offset); + buf[offset] = ':'; + if (!p->GetPluginFS()->NotEmpty() || !p->GetPluginFS()->GetCurrentPath(buf + offset + 1)) + { + return FALSE; // error + } + if (convertFSPathToExternal) + { + p->GetPluginFS()->GetPluginInterfaceForFS()->ConvertPathToExternal(p->GetPluginFS()->GetPluginFSName(), + p->GetPluginFS()->GetPluginFSNameIndex(), + buf + offset + 1); + } + } + else + { + if (p->Is(ptDisk)) + { + if (type != NULL) + *type = PATH_TYPE_WINDOWS; + strcpy(buf, p->GetPath()); + } + else + { + TRACE_E("Unexpected situation in CSalamanderGeneral::GetPanelPath()"); + return FALSE; + } + } + } + + int l = (int)strlen(buf) + 1; + if (l > bufferSize) + return bufferSize == 0; // if the user does not want the path back, we do not treat it as an error + memcpy(buffer, buf, l); + + if (archiveOrFS != NULL && offset != -1) + *archiveOrFS = buffer + offset; + + return TRUE; + } + return FALSE; +} + +BOOL CSalamanderGeneral::GetLastWindowsPanelPath(int panel, char* buffer, int bufferSize) +{ + CALL_STACK_MESSAGE3("CSalamanderGeneral::GetLastWindowsPanelPath(%d, , %d)", panel, bufferSize); + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::GetLastWindowsPanelPath() only from main thread!"); + return FALSE; + } + CFilesWindow* p = GetPanel(panel); + if (p != NULL && buffer != NULL) + { + int l = (int)strlen(p->GetPath()) + 1; + if (l > bufferSize) + return FALSE; + memcpy(buffer, p->GetPath(), l); + return TRUE; + } + return FALSE; +} + +CPluginDataInterfaceAbstract* +CSalamanderGeneral::GetPanelPluginData(int panel) +{ + CALL_STACK_MESSAGE2("CSalamanderGeneral::GetPanelPluginData(%d)", panel); + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::GetPanelPluginData() only from main thread!"); + return NULL; + } + CFilesWindow* p = GetPanel(panel); + if (p != NULL) + { + CPluginDataInterfaceAbstract* iface = p->PluginData.GetInterface(); + if (iface != NULL && p->PluginData.GetPluginInterface() != Plugin) + iface = NULL; // the object is not from this plugin -> it gets nothing + return iface; + } + return NULL; +} + +CPluginFSInterfaceAbstract* +CSalamanderGeneral::GetPanelPluginFS(int panel) +{ + CALL_STACK_MESSAGE2("CSalamanderGeneral::GetPanelPluginFS(%d)", panel); + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::GetPanelPluginFS() only from main thread!"); + return NULL; + } + CFilesWindow* p = GetPanel(panel); + if (p != NULL && p->Is(ptPluginFS)) + { + CPluginFSInterfaceAbstract* iface = p->GetPluginFS()->GetInterface(); + if (iface != NULL && p->GetPluginFS()->GetPluginInterface() != Plugin) + iface = NULL; // the object is not from this plugin -> it gets nothing + return iface; + } + return NULL; +} + +const CFileData* +CSalamanderGeneral::GetPanelFocusedItem(int panel, BOOL* isDir) +{ + CALL_STACK_MESSAGE2("CSalamanderGeneral::GetPanelFocusedItem(%d,)", panel); + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::GetPanelFocusedItem() only from main thread!"); + return NULL; + } + CFilesWindow* p = GetPanel(panel); + if (p != NULL) + { + int caret = p->GetCaretIndex(); + if (caret >= 0 && caret < p->Files->Count + p->Dirs->Count) + { + if (isDir != NULL) + *isDir = caret < p->Dirs->Count; + return (caret < p->Dirs->Count) ? &p->Dirs->At(caret) : &p->Files->At(caret - p->Dirs->Count); + } + } + return NULL; +} + +const CFileData* +CSalamanderGeneral::GetPanelItem(int panel, int* index, BOOL* isDir) +{ + CALL_STACK_MESSAGE2("CSalamanderGeneral::GetPanelItem(%d,)", panel); + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::GetPanelItem() only from main thread!"); + return NULL; + } + CFilesWindow* p = GetPanel(panel); + if (p != NULL && index != NULL) + { + int i = *index; + if (i < 0) + return NULL; // enumeration already finished + if (i < p->Files->Count + p->Dirs->Count) // enumerate more items + { + *index = i + 1; // next time move to the following item + if (isDir != NULL) + *isDir = i < p->Dirs->Count; + return (i < p->Dirs->Count) ? &p->Dirs->At(i) : &p->Files->At(i - p->Dirs->Count); + } + else + { + *index = -1; // end of enumeration + return NULL; + } + } + return NULL; +} + +BOOL CSalamanderGeneral::GetPanelSelection(int panel, int* selectedFiles, int* selectedDirs) +{ + CALL_STACK_MESSAGE2("CSalamanderGeneral::GetPanelSelection(%d, ,)", panel); + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::GetPanelSelection() only from main thread!"); + return FALSE; + } + CFilesWindow* p = GetPanel(panel); + if (p != NULL) + { + int count = p->GetSelCount(); + int selDirs = 0; + if (count > 0) + { + CFilesArray* dirs = p->Dirs; + // count how many directories are selected (the rest of the selected items are files) + int i; + for (i = 0; i < dirs->Count; i++) // ".." cannot be selected; the test would be pointless + { + if (dirs->At(i).Selected) + selDirs++; + } + } + else + count = 0; + + if (selectedDirs != NULL) + *selectedDirs = selDirs; + if (selectedFiles != NULL) + *selectedFiles = count - selDirs; + + int i = p->GetCaretIndex(); + return p->Dirs->Count + p->Files->Count > 0 && // the panel is not empty + (i != 0 || count > 0 || p->Dirs->Count == 0 || + strcmp(p->Dirs->At(0).Name, "..") != 0); // the focus is not on the up-dir, or at least one item is selected + } + return FALSE; +} + +const CFileData* +CSalamanderGeneral::GetPanelSelectedItem(int panel, int* index, BOOL* isDir) +{ + SLOW_CALL_STACK_MESSAGE2("CSalamanderGeneral::GetPanelSelectedItem(%d, ,)", panel); + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::GetPanelSelectedItem() only from main thread!"); + return NULL; + } + CFilesWindow* p = GetPanel(panel); + if (p != NULL && index != NULL) + { + int i = *index; + if (i < 0) + return NULL; // enumeration already finished + while (i < p->Files->Count + p->Dirs->Count) // searching for the next selected item + { + CFileData* data = (i < p->Dirs->Count) ? &p->Dirs->At(i) : &p->Files->At(i - p->Dirs->Count); + if (data->Selected) // selected item? + { + *index = i + 1; // next time start searching from the following item + if (isDir != NULL) + *isDir = i < p->Dirs->Count; + return data; // return the found selected item + } + i++; + } + *index = -1; // end of enumeration; no selected items remain + } + return NULL; +} + +void CSalamanderGeneral::SelectPanelItem(int panel, const CFileData* file, BOOL select) +{ + CALL_STACK_MESSAGE3("CSalamanderGeneral::SelectPanelItem(%d, , %d)", panel, select); + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::SelectPanelItem() only from main thread!"); + return; + } + CFilesWindow* p = GetPanel(panel); + if (p != NULL) + { + int index = -1; // index of 'file' in the panel + if (p->Dirs->Count > 0) + { + CFileData* first = &p->Dirs->At(0); + CFileData* last = &p->Dirs->At(p->Dirs->Count - 1); + if (first <= file && file <= last) + index = (int)(file - first); // it is a directory + } + if (index == -1 && p->Files->Count > 0) + { + CFileData* first = &p->Files->At(0); + CFileData* last = &p->Files->At(p->Files->Count - 1); + if (first <= file && file <= last) + index = p->Dirs->Count + (int)(file - first); // it is a directory + } + if (index != -1) + p->SetSel(select, index, FALSE); // change selection + else + TRACE_E("Invalid parameter 'file' in CSalamanderGeneral::SelectPanelItem()."); + } +} + +void CSalamanderGeneral::RepaintChangedItems(int panel) +{ + CALL_STACK_MESSAGE2("CSalamanderGeneral::RepaintChangedItems(%d)", panel); + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::RepaintChangedItems() only from main thread!"); + return; + } + CFilesWindow* p = GetPanel(panel); + if (p != NULL) + { + p->RepaintListBox(DRAWFLAG_DIRTY_ONLY | DRAWFLAG_SKIP_VISTEST); + PostMessage(p->HWindow, WM_USER_SELCHANGED, 0, 0); // sel-change notify + } +} + +void CSalamanderGeneral::SelectAllPanelItems(int panel, BOOL select, BOOL repaint) +{ + CALL_STACK_MESSAGE4("CSalamanderGeneral::SelectAllPanelItems(%d, %d, %d)", panel, select, repaint); + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::SelectAllPanelItems() only from main thread!"); + return; + } + CFilesWindow* p = GetPanel(panel); + if (p != NULL) + { + p->SetSel(select, -1, repaint); // change selection + if (repaint) + PostMessage(p->HWindow, WM_USER_SELCHANGED, 0, 0); // sel-change notify + } +} + +void CSalamanderGeneral::SetPanelFocusedItem(int panel, const CFileData* file, BOOL partVis) +{ + CALL_STACK_MESSAGE3("CSalamanderGeneral::SetPanelFocusedItem(%d, , %d)", panel, partVis); + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::SetPanelFocusedItem() only from main thread!"); + return; + } + CFilesWindow* p = GetPanel(panel); + if (p != NULL) + { + int index = -1; // index of 'file' in the panel + if (p->Dirs->Count > 0) + { + CFileData* first = &p->Dirs->At(0); + CFileData* last = &p->Dirs->At(p->Dirs->Count - 1); + if (first <= file && file <= last) + index = (int)(file - first); // it is a directory + } + if (index == -1 && p->Files->Count > 0) + { + CFileData* first = &p->Files->At(0); + CFileData* last = &p->Files->At(p->Files->Count - 1); + if (first <= file && file <= last) + index = p->Dirs->Count + (int)(file - first); // it is a directory + } + if (index != -1) + p->SetCaretIndex(index, partVis); // change focus + else + TRACE_E("Invalid parameter 'file' in CSalamanderGeneral::SetPanelFocusedItem()."); + } +} + +BOOL CSalamanderGeneral::GetFilterFromPanel(int panel, char* masks, int masksBufSize) +{ + CALL_STACK_MESSAGE3("CSalamanderGeneral::GetFilterFromPanel(%d, , %d)", panel, masksBufSize); + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::GetFilterFromPanel() only from main thread!"); + return FALSE; + } + CFilesWindow* p = GetPanel(panel); + BOOL ret = FALSE; + if (p != NULL && p->FilterEnabled) + { + int len = (int)strlen(p->Filter.GetMasksString()); + if (len < masksBufSize) + { + memcpy(masks, p->Filter.GetMasksString(), len + 1); + ret = TRUE; + } + } + return ret; +} + +// returns the position of the source panel (is it on the left or on the right?), returns PANEL_LEFT or PANEL_RIGHT +int CSalamanderGeneral::GetSourcePanel() +{ + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::GetSourcePanel() only from main thread!"); + return PANEL_LEFT; + } + if (MainWindow->GetActivePanel() == MainWindow->LeftPanel) + return PANEL_LEFT; + else + return PANEL_RIGHT; +} + +// activates the other panel (like the TAB key); panels marked through PANEL_SOURCE and PANEL_TARGET +// swap naturally as a result +void CSalamanderGeneral::ChangePanel() +{ + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::ChangePanel() only from main thread!"); + return; + } + MainWindow->ChangePanel(); +} + +void CSalamanderGeneral::SkipOneActivateRefresh() +{ + ::SkipOneActivateRefresh = TRUE; + PostMessage(MainWindow->HWindow, WM_USER_SKIPONEREFRESH, 0, 0); +} + +BOOL CSalamanderGeneral::SalGetTempFileName(const char* path, const char* prefix, char* tmpName, BOOL file, DWORD* err) +{ + CALL_STACK_MESSAGE1("CSalamanderGeneral::SalGetTempFileName()"); + BOOL ret = ::SalGetTempFileName(path, prefix, tmpName, file); + if (err != NULL) + *err = GetLastError(); + return ret; +} + +char* CSalamanderGeneral::NumberToStr(char* buffer, const CQuadWord& number) +{ + return ::NumberToStr(buffer, number); +} + +char* CSalamanderGeneral::PrintDiskSize(char* buf, const CQuadWord& size, int mode) +{ + return ::PrintDiskSize(buf, size, mode); +} + +char* CSalamanderGeneral::PrintTimeLeft(char* buf, const CQuadWord& secs) +{ + return ::PrintTimeLeft(buf, secs); +} + +BOOL CSalamanderGeneral::HasTheSameRootPath(const char* path1, const char* path2) +{ + return ::HasTheSameRootPath(path1, path2); +} + +int CSalamanderGeneral::CommonPrefixLength(const char* path1, const char* path2) +{ + return ::CommonPrefixLength(path1, path2); +} + +BOOL CSalamanderGeneral::PathIsPrefix(const char* prefix, const char* path) +{ + return ::SalPathIsPrefix(prefix, path); +} + +BOOL CSalamanderGeneral::IsTheSamePath(const char* path1, const char* path2) +{ + return ::IsTheSamePath(path1, path2); +} + +int CSalamanderGeneral::GetRootPath(char* root, const char* path) +{ + return ::GetRootPath(root, path); +} + +BOOL CSalamanderGeneral::CutDirectory(char* path, char** cutDir) +{ + return ::CutDirectory(path, cutDir); +} + +BOOL CSalamanderGeneral::SalPathAppend(char* path, const char* name, int pathSize) +{ + return ::SalPathAppend(path, name, pathSize); +} + +BOOL CSalamanderGeneral::SalPathAddBackslash(char* path, int pathSize) +{ + return ::SalPathAddBackslash(path, pathSize); +} + +void CSalamanderGeneral::SalPathRemoveBackslash(char* path) +{ + ::SalPathRemoveBackslash(path); +} + +void CSalamanderGeneral::SalPathStripPath(char* path) +{ + ::SalPathStripPath(path); +} + +void CSalamanderGeneral::SalPathRemoveExtension(char* path) +{ + ::SalPathRemoveExtension(path); +} + +BOOL CSalamanderGeneral::SalPathAddExtension(char* path, const char* extension, int pathSize) +{ + return ::SalPathAddExtension(path, extension, pathSize); +} + +BOOL CSalamanderGeneral::SalPathRenameExtension(char* path, const char* extension, int pathSize) +{ + return ::SalPathRenameExtension(path, extension, pathSize); +} + +const char* +CSalamanderGeneral::SalPathFindFileName(const char* path) +{ + return ::SalPathFindFileName(path); +} + +BOOL CSalamanderGeneral::SalGetFullName(char* name, int* errTextID, const char* curDir, + char* nextFocus, int nameBufSize) +{ + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::SalGetFullName() only from main thread!"); + if (errTextID != NULL) + *errTextID = GFN_PATHISINVALID; + return FALSE; + } + BOOL ret = ::SalGetFullName(name, errTextID, curDir, nextFocus, NULL, nameBufSize); + if (errTextID != NULL) + { + switch (*errTextID) + { + case IDS_SERVERNAMEMISSING: + *errTextID = GFN_SERVERNAMEMISSING; + break; + case IDS_SHARENAMEMISSING: + *errTextID = GFN_SHARENAMEMISSING; + break; + case IDS_TOOLONGPATH: + *errTextID = GFN_TOOLONGPATH; + break; + case IDS_INVALIDDRIVE: + *errTextID = GFN_INVALIDDRIVE; + break; + case IDS_INCOMLETEFILENAME: + *errTextID = GFN_INCOMLETEFILENAME; + break; + case IDS_EMPTYNAMENOTALLOWED: + *errTextID = GFN_EMPTYNAMENOTALLOWED; + break; + case IDS_PATHISINVALID: + *errTextID = GFN_PATHISINVALID; + break; + } + } + + return ret; +} + +void CSalamanderGeneral::SalUpdateDefaultDir(BOOL activePrefered) +{ + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::SalUpdateDefaultDir() only from main thread!"); + return; + } + if (MainWindow != NULL) + MainWindow->UpdateDefaultDir(activePrefered); +} + +char* CSalamanderGeneral::GetGFNErrorText(int GFN, char* buf, int bufSize) +{ + char* s = NULL; + switch (GFN) + { + case GFN_SERVERNAMEMISSING: + s = ::LoadStr(IDS_SERVERNAMEMISSING); + break; + case GFN_SHARENAMEMISSING: + s = ::LoadStr(IDS_SHARENAMEMISSING); + break; + case GFN_TOOLONGPATH: + s = ::LoadStr(IDS_TOOLONGPATH); + break; + case GFN_INVALIDDRIVE: + s = ::LoadStr(IDS_INVALIDDRIVE); + break; + case GFN_INCOMLETEFILENAME: + s = ::LoadStr(IDS_INCOMLETEFILENAME); + break; + case GFN_EMPTYNAMENOTALLOWED: + s = ::LoadStr(IDS_EMPTYNAMENOTALLOWED); + break; + case GFN_PATHISINVALID: + s = ::LoadStr(IDS_PATHISINVALID); + break; + } + if (s != NULL) + lstrcpyn(buf, s, bufSize); + else + buf[0] = 0; + return buf; +} + +char* CSalamanderGeneral::GetErrorText(int err, char* buf, int bufSize) +{ + if (buf == NULL || bufSize == 0) + return ::GetErrorText(err); + + int l = 0; + if (bufSize > 20) + l = sprintf(buf, "(%d) ", err); + if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, + NULL, + err, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + buf + l, + bufSize - l, + NULL) == 0 || + bufSize > l && *(buf + l) == 0) + { + char txt[100]; + sprintf(txt, "System error %d, text description is not available.", err); + lstrcpyn(buf, txt, bufSize); + } + return buf; +} + +char* CSalamanderGeneral::LoadStr(HINSTANCE module, int resID) +{ + if (module == NULL) + { + TRACE_E("CSalamanderGeneral::LoadStr(): module == NULL"); + static char buffEmpty[] = "ERROR LOADING STRING"; + return buffEmpty; + } + return ::LoadStr(resID, module); +} + +WCHAR* +CSalamanderGeneral::LoadStrW(HINSTANCE module, int resID) +{ + if (module == NULL) + { + TRACE_E("CSalamanderGeneral::LoadStrW(): module == NULL"); + static wchar_t buffEmpty[] = L"ERROR LOADING WIDE STRING"; + return buffEmpty; + } + return ::LoadStrW(resID, module); +} + +COLORREF +CSalamanderGeneral::GetCurrentColor(int color) +{ + int index; + SALCOLOR* arr = CurrentColors; + switch (color) + { + // CurrentColors + case SALCOL_FOCUS_ACTIVE_NORMAL: + index = FOCUS_ACTIVE_NORMAL; + break; + case SALCOL_FOCUS_ACTIVE_SELECTED: + index = FOCUS_ACTIVE_SELECTED; + break; + case SALCOL_FOCUS_FG_INACTIVE_NORMAL: + index = FOCUS_FG_INACTIVE_NORMAL; + break; + case SALCOL_FOCUS_FG_INACTIVE_SELECTED: + index = FOCUS_FG_INACTIVE_SELECTED; + break; + case SALCOL_FOCUS_BK_INACTIVE_NORMAL: + index = FOCUS_BK_INACTIVE_NORMAL; + break; + case SALCOL_FOCUS_BK_INACTIVE_SELECTED: + index = FOCUS_BK_INACTIVE_SELECTED; + break; + case SALCOL_ITEM_FG_NORMAL: + index = ITEM_FG_NORMAL; + break; + case SALCOL_ITEM_FG_SELECTED: + index = ITEM_FG_SELECTED; + break; + case SALCOL_ITEM_FG_FOCUSED: + index = ITEM_FG_FOCUSED; + break; + case SALCOL_ITEM_FG_FOCSEL: + index = ITEM_FG_FOCSEL; + break; + case SALCOL_ITEM_FG_HIGHLIGHT: + index = ITEM_FG_HIGHLIGHT; + break; + case SALCOL_ITEM_BK_NORMAL: + index = ITEM_BK_NORMAL; + break; + case SALCOL_ITEM_BK_SELECTED: + index = ITEM_BK_SELECTED; + break; + case SALCOL_ITEM_BK_FOCUSED: + index = ITEM_BK_FOCUSED; + break; + case SALCOL_ITEM_BK_FOCSEL: + index = ITEM_BK_FOCSEL; + break; + case SALCOL_ITEM_BK_HIGHLIGHT: + index = ITEM_BK_HIGHLIGHT; + break; + case SALCOL_ICON_BLEND_SELECTED: + index = ICON_BLEND_SELECTED; + break; + case SALCOL_ICON_BLEND_FOCUSED: + index = ICON_BLEND_FOCUSED; + break; + case SALCOL_ICON_BLEND_FOCSEL: + index = ICON_BLEND_FOCSEL; + break; + case SALCOL_PROGRESS_FG_NORMAL: + index = PROGRESS_FG_NORMAL; + break; + case SALCOL_PROGRESS_FG_SELECTED: + index = PROGRESS_FG_SELECTED; + break; + case SALCOL_PROGRESS_BK_NORMAL: + index = PROGRESS_BK_NORMAL; + break; + case SALCOL_PROGRESS_BK_SELECTED: + index = PROGRESS_BK_SELECTED; + break; + case SALCOL_HOT_PANEL: + index = HOT_PANEL; + break; + case SALCOL_HOT_ACTIVE: + index = HOT_ACTIVE; + break; + case SALCOL_HOT_INACTIVE: + index = HOT_INACTIVE; + break; + case SALCOL_ACTIVE_CAPTION_FG: + index = ACTIVE_CAPTION_FG; + break; + case SALCOL_ACTIVE_CAPTION_BK: + index = ACTIVE_CAPTION_BK; + break; + case SALCOL_INACTIVE_CAPTION_FG: + index = INACTIVE_CAPTION_FG; + break; + case SALCOL_INACTIVE_CAPTION_BK: + index = INACTIVE_CAPTION_BK; + break; + case SALCOL_THUMBNAIL_NORMAL: + index = THUMBNAIL_FRAME_NORMAL; + break; + case SALCOL_THUMBNAIL_SELECTED: + index = THUMBNAIL_FRAME_FOCUSED; + break; + case SALCOL_THUMBNAIL_FOCUSED: + index = THUMBNAIL_FRAME_SELECTED; + break; + case SALCOL_THUMBNAIL_FOCSEL: + index = THUMBNAIL_FRAME_FOCSEL; + break; + // ViewerColors + case SALCOL_VIEWER_FG_NORMAL: + index = VIEWER_FG_NORMAL; + arr = ViewerColors; + break; + case SALCOL_VIEWER_BK_NORMAL: + index = VIEWER_BK_NORMAL; + arr = ViewerColors; + break; + case SALCOL_VIEWER_FG_SELECTED: + index = VIEWER_FG_SELECTED; + arr = ViewerColors; + break; + case SALCOL_VIEWER_BK_SELECTED: + index = VIEWER_BK_SELECTED; + arr = ViewerColors; + break; + + default: + { + TRACE_E("Invalid color constant!"); + return COLORREF(0); + } + } + return GetCOLORREF(arr[index]); +} + +void CSalamanderGeneral::GetPluginFSName(char* buf, int fsNameIndex) +{ + CALL_STACK_MESSAGE2("CSalamanderGeneral::GetPluginFSName(, %d)", fsNameIndex); + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::GetPluginFSName() only from main thread!"); + buf[0] = 0; + return; + } + CPluginData* data = Plugins.GetPluginData(Plugin); + if (data != NULL && data->SupportFS && fsNameIndex >= 0 && fsNameIndex < data->FSNames.Count) + lstrcpyn(buf, data->FSNames[fsNameIndex], MAX_PATH); + else + { + TRACE_E("CSalamanderGeneral::GetPluginFSName(): incorrect call (not supporting FS or 'fsNameIndex' is out of range)!"); + buf[0] = 0; + } +} + +BOOL CSalamanderGeneral::SetFlagLoadOnSalamanderStart(BOOL start) +{ + CALL_STACK_MESSAGE2("CSalamanderGeneral::SetFlagLoadOnSalamanderStart(%d)", start); + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::SetFlagLoadOnSalamanderStart() only from main thread!"); + return FALSE; + } + CPluginData* data = Plugins.GetPluginData(Plugin); + if (data != NULL) + { + BOOL prev = data->LoadOnStart != 0; + data->LoadOnStart = start != 0; + return prev; + } + else + { + TRACE_E("Unexpected situation in CSalamanderGeneral::SetFlagLoadOnSalamanderStart()."); + return FALSE; + } +} + +void CSalamanderGeneral::PostUnloadThisPlugin() +{ + CALL_STACK_MESSAGE1("CSalamanderGeneral::PostUnloadThisPlugin()"); + if (MainThreadID == GetCurrentThreadId()) + { // because of calls from the entry point where Plugin is set to -1 (just to look up plugin data) + // before WM_USER_POSTCMDORUNLOADPLUGIN would arrive, Plugin would be reset (according to the entry point's return value) + CPluginData* data = Plugins.GetPluginData(Plugin); + if (data != NULL) + { + data->ShouldUnload = TRUE; + ExecCmdsOrUnloadMarkedPlugins = TRUE; + } + else + { + TRACE_E("Unexpected situation in CSalamanderGeneral::PostUnloadThisPlugin()."); + } + } + else // outside the entry point the Plugin is certainly set... + { + if (MainWindow != NULL && MainWindow->HWindow != NULL) + { + // check for a call while the entry point is starting (Plugin is set to -1) + if ((INT_PTR)Plugin == -1) + { + TRACE_E("You can call CSalamanderGeneral::PostUnloadThisPlugin only from main " + "thread when plugin entry-point is not finished yet!"); + } + else + { + PostMessage(MainWindow->HWindow, WM_USER_POSTCMDORUNLOADPLUGIN, (WPARAM)Plugin, 0); + } + } + else + { + TRACE_E("Unexpected situation (2) in CSalamanderGeneral::PostUnloadThisPlugin()."); + } + } +} + +void CSalamanderGeneral::PostPluginMenuChanged() +{ + CALL_STACK_MESSAGE1("CSalamanderGeneral::PostPluginMenuChanged()"); + if (MainThreadID == GetCurrentThreadId()) + { // because of calls from the entry point where Plugin is set to -1 (just to look up plugin data) + // before WM_USER_POSTCMDORUNLOADPLUGIN would arrive, Plugin would be reset (according to the entry point's return value) + CPluginData* data = Plugins.GetPluginData(Plugin); + if (data != NULL) + { + data->ShouldRebuildMenu = TRUE; + ExecCmdsOrUnloadMarkedPlugins = TRUE; + } + else + { + TRACE_E("Unexpected situation in CSalamanderGeneral::PostPluginMenuChanged()."); + } + } + else // outside the entry point the Plugin is certainly set... + { + if (MainWindow != NULL && MainWindow->HWindow != NULL) + { + // check for a call while the entry point is starting (Plugin is set to -1) + if ((INT_PTR)Plugin == -1) + { + TRACE_E("You can call CSalamanderGeneral::PostPluginMenuChanged only from main " + "thread when plugin entry-point is not finished yet!"); + } + else + { + PostMessage(MainWindow->HWindow, WM_USER_POSTCMDORUNLOADPLUGIN, (WPARAM)Plugin, 1); + } + } + else + { + TRACE_E("Unexpected situation (2) in CSalamanderGeneral::PostPluginMenuChanged()."); + } + } +} + +void CSalamanderGeneral::PostMenuExtCommand(int id, BOOL waitForSalIdle) +{ + CALL_STACK_MESSAGE3("CSalamanderGeneral::PostMenuExtCommand(%d, %d)", id, waitForSalIdle); + if (waitForSalIdle) + { + if (id < 0 || id >= 1000000) + { + TRACE_E("CSalamanderGeneral::PostMenuExtCommand: id is invalid (" << id << " is not in range 0-999999)."); + return; + } + + if (MainThreadID == GetCurrentThreadId()) + { // because of calls from the entry point where Plugin is set to -1 (just to look up plugin data) + // before WM_USER_POSTCMDORUNLOADPLUGIN would arrive, Plugin would be reset (according to the entry point's return value) + CPluginData* data = Plugins.GetPluginData(Plugin); + if (data != NULL) + { + data->Commands.Add(500 + id); // salCmd values are in the <0, 499> range; 500 is the first free number + ExecCmdsOrUnloadMarkedPlugins = TRUE; + } + else + { + TRACE_E("Unexpected situation in CSalamanderGeneral::PostMenuExtCommand()."); + } + } + else // outside the entry point the Plugin is certainly set... + { + if (MainWindow != NULL && MainWindow->HWindow != NULL) + { + // check for a call while the entry point is starting (Plugin is set to -1) + if ((INT_PTR)Plugin == -1) + { + TRACE_E("You can call CSalamanderGeneral::PostMenuExtCommand only from main " + "thread when plugin entry-point is not finished yet!"); + } + else + { // 0 - unload, 1 - rebuild menu, 2-501 salCmd, 502-1000501 menuCmd + PostMessage(MainWindow->HWindow, WM_USER_POSTCMDORUNLOADPLUGIN, (WPARAM)Plugin, 502 + id); + } + } + else + { + TRACE_E("Unexpected situation (2) in CSalamanderGeneral::PostMenuExtCommand()."); + } + } + } + else + { + if (MainThreadID == GetCurrentThreadId()) + { // check for a call from the entry point (Plugin is set to -1) + if ((INT_PTR)Plugin == -1) + { + TRACE_E("You may not call CSalamanderGeneral::PostMenuExtCommand from entry-point!"); + return; + } + } + else + { + // check for a call while the entry point is starting (Plugin is set to -1) + if ((INT_PTR)Plugin == -1) + { + TRACE_E("You may not call CSalamanderGeneral::PostMenuExtCommand when " + "entry-point is not finished yet!"); + return; + } + } + if (MainWindow != NULL && MainWindow->HWindow != NULL) + { + PostMessage(MainWindow->HWindow, WM_USER_POSTMENUEXTCMD, (WPARAM)Plugin, (LPARAM)id); + } + else + { + TRACE_E("Unexpected situation in CSalamanderGeneral::PostMenuExtCommand()."); + } + } +} + +BOOL CSalamanderGeneral::SalamanderIsNotBusy(DWORD* lastIdleTime) +{ + CALL_STACK_MESSAGE1("CSalamanderGeneral::SalamanderIsNotBusy()"); + return ::SalamanderIsNotBusy(lastIdleTime); +} + +void CSalamanderGeneral::CallLoadOrSaveConfiguration(BOOL load, + FSalLoadOrSaveConfiguration loadOrSaveFunc, + void* param) +{ + CALL_STACK_MESSAGE2("CSalamanderGeneral::CallLoadOrSaveConfiguration(%d, ,)", load); + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::CallLoadOrSaveConfiguration() only from main thread!"); + return; + } + CPluginData* data = Plugins.GetPluginData(Plugin); + if (data != NULL) + { + data->CallLoadOrSaveConfiguration(load, loadOrSaveFunc, param); + } + else + { + TRACE_E("Unexpected situation in CSalamanderGeneral::CallLoadOrSaveConfiguration()."); + } +} + +void CSalamanderGeneral::SetPluginBugReportInfo(const char* message, const char* email) +{ + CALL_STACK_MESSAGE2("CSalamanderGeneral::SetPluginBugReportInfo(%s)", message); + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::SetPluginBugReportInfo() only from main thread!"); + return; + } + CPluginData* data = Plugins.GetPluginData(Plugin); + if (data != NULL) + { + if (data->BugReportMessage != NULL) + free(data->BugReportMessage); + if (message != NULL) + data->BugReportMessage = ::DupStr(message); + else + data->BugReportMessage = NULL; + if (data->BugReportEMail != NULL) + free(data->BugReportEMail); + if (email != NULL) + { + data->BugReportEMail = ::DupStr(email); + if (data->BugReportEMail != NULL && strlen(data->BugReportEMail) > 100) + { + data->BugReportEMail[100] = 0; + } + } + else + data->BugReportEMail = NULL; + } + else + { + TRACE_E("Unexpected situation in CSalamanderGeneral::SetPluginBugReportInfo()."); + } +} + +void CSalamanderGeneral::FocusNameInPanel(int panel, const char* path, const char* name) +{ + CALL_STACK_MESSAGE4("CSalamanderGeneral::FocusNameInPanel(%d, %s, %s)", panel, path, name); + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::FocusNameInPanel() only from main thread!"); + return; + } + if (name == NULL || path == NULL) + { + TRACE_E("CSalamanderGeneral::FocusNameInPanel(): incorrect parameters (name == NULL || path == NULL)!"); + return; + } + CFilesWindow* p = GetPanel(panel); + char pathBackup[MAX_PATH + 200]; + char nameBackup[MAX_PATH + 200]; + lstrcpyn(pathBackup, path, MAX_PATH + 200); + lstrcpyn(nameBackup, name, MAX_PATH + 200); + if (p != NULL) + SendMessage(p->HWindow, WM_USER_FOCUSFILE, (WPARAM)nameBackup, (LPARAM)pathBackup); +} + +BOOL CSalamanderGeneral::ChangePanelPath(int panel, const char* path, int* failReason, + int suggestedTopIndex, const char* suggestedFocusName, + BOOL convertFSPathToInternal) +{ + CALL_STACK_MESSAGE6("CSalamanderGeneral::ChangePanelPath(%d, %s, , %d, %s, %d)", + panel, path, suggestedTopIndex, suggestedFocusName, convertFSPathToInternal); + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::ChangePanelPath() only from main thread!"); + if (failReason != NULL) + *failReason = CHPPFR_INVALIDPATH; + return FALSE; + } + CFilesWindow* p = GetPanel(panel); + if (p != NULL) + { + return p->ChangeDir(path, suggestedTopIndex, suggestedFocusName, 3 /*change-dir*/, + failReason, convertFSPathToInternal); + } + if (failReason != NULL) + *failReason = CHPPFR_INVALIDPATH; + return FALSE; +} + +BOOL CSalamanderGeneral::ChangePanelPathToDisk(int panel, const char* path, int* failReason, + int suggestedTopIndex, const char* suggestedFocusName) +{ + CALL_STACK_MESSAGE5("CSalamanderGeneral::ChangePanelPathToDisk(%d, %s, , %d, %s)", + panel, path, suggestedTopIndex, suggestedFocusName); + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::ChangePanelPathToDisk() only from main thread!"); + if (failReason != NULL) + *failReason = CHPPFR_INVALIDPATH; + return FALSE; + } + CFilesWindow* p = GetPanel(panel); + if (p != NULL) + { + return p->ChangePathToDisk(GetMsgBoxParent(), path, suggestedTopIndex, suggestedFocusName, + NULL, TRUE, FALSE, FALSE, failReason); + } + if (failReason != NULL) + *failReason = CHPPFR_INVALIDPATH; + return FALSE; +} + +BOOL CSalamanderGeneral::ChangePanelPathToArchive(int panel, const char* archive, const char* archivePath, + int* failReason, int suggestedTopIndex, + const char* suggestedFocusName, BOOL forceUpdate) +{ + CALL_STACK_MESSAGE7("CSalamanderGeneral::ChangePanelPathToArchive(%d, %s, %s, , %d, %s, %d)", + panel, archive, archivePath, suggestedTopIndex, suggestedFocusName, forceUpdate); + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::ChangePanelPathToArchive() only from main thread!"); + if (failReason != NULL) + *failReason = CHPPFR_INVALIDPATH; + return FALSE; + } + CFilesWindow* p = GetPanel(panel); + if (p != NULL) + { + return p->ChangePathToArchive(archive, archivePath, suggestedTopIndex, suggestedFocusName, + forceUpdate, NULL, TRUE, failReason); + } + if (failReason != NULL) + *failReason = CHPPFR_INVALIDPATH; + return FALSE; +} + +BOOL CSalamanderGeneral::ChangePanelPathToPluginFS(int panel, const char* fsName, const char* fsUserPart, + int* failReason, int suggestedTopIndex, + const char* suggestedFocusName, BOOL forceUpdate, + BOOL convertPathToInternal) +{ + CALL_STACK_MESSAGE8("CSalamanderGeneral::ChangePanelPathToPluginFS(%d, %s, %s, , %d, %s, %d, %d)", + panel, fsName, fsUserPart, suggestedTopIndex, suggestedFocusName, forceUpdate, + convertPathToInternal); + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::ChangePanelPathToPluginFS() only from main thread!"); + if (failReason != NULL) + *failReason = CHPPFR_INVALIDPATH; + return FALSE; + } + CFilesWindow* p = GetPanel(panel); + if (p != NULL) + { + return p->ChangePathToPluginFS(fsName, fsUserPart, suggestedTopIndex, suggestedFocusName, + forceUpdate, 2 /*report all errors*/, NULL, TRUE, failReason, + FALSE, FALSE, convertPathToInternal); + } + if (failReason != NULL) + *failReason = CHPPFR_INVALIDPATH; + return FALSE; +} + +BOOL CSalamanderGeneral::ChangePanelPathToDetachedFS(int panel, CPluginFSInterfaceAbstract* detachedFS, + int* failReason, int suggestedTopIndex, + const char* suggestedFocusName) +{ + CALL_STACK_MESSAGE4("CSalamanderGeneral::ChangePanelPathToDetachedFS(%d, , , %d, %s)", + panel, suggestedTopIndex, suggestedFocusName); + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::ChangePanelPathToDetachedFS() only from main thread!"); + if (failReason != NULL) + *failReason = CHPPFR_INVALIDPATH; + return FALSE; + } + CFilesWindow* p = GetPanel(panel); + if (p != NULL) + { + int fsIndex = -1; + CDetachedFSList* list = MainWindow->DetachedFSList; + int i; + for (i = 0; i < list->Count; i++) + { + if (list->At(i)->GetInterface() == detachedFS) + { + fsIndex = i; + break; + } + } + if (fsIndex != -1) + { + return p->ChangePathToDetachedFS(fsIndex, suggestedTopIndex, suggestedFocusName, TRUE, failReason); + } + else + { + TRACE_E("Parameter 'detachedFS' is not detached FS in " + "CSalamanderGeneral::ChangePanelPathToDetachedFS()."); + } + } + if (failReason != NULL) + *failReason = CHPPFR_INVALIDPATH; + return FALSE; +} + +BOOL CSalamanderGeneral::ChangePanelPathToFixedDrive(int panel, int* failReason) +{ + CALL_STACK_MESSAGE2("CSalamanderGeneral::ChangePanelPathToFixedDrive(%d,)", panel); + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::ChangePanelPathToFixedDrive() only from main thread!"); + if (failReason != NULL) + *failReason = CHPPFR_INVALIDPATH; + return FALSE; + } + CFilesWindow* p = GetPanel(panel); + if (p != NULL) + { + return p->ChangeToFixedDrive(GetMsgBoxParent(), NULL, TRUE, FALSE, failReason); + } + if (failReason != NULL) + *failReason = CHPPFR_INVALIDPATH; + return FALSE; +} + +BOOL CSalamanderGeneral::ChangePanelPathToRescuePathOrFixedDrive(int panel, int* failReason) +{ + CALL_STACK_MESSAGE2("CSalamanderGeneral::ChangePanelPathToRescuePathOrFixedDrive(%d,)", panel); + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::ChangePanelPathToRescuePathOrFixedDrive() only from main thread!"); + if (failReason != NULL) + *failReason = CHPPFR_INVALIDPATH; + return FALSE; + } + CFilesWindow* p = GetPanel(panel); + if (p != NULL) + { + return p->ChangeToRescuePathOrFixedDrive(GetMsgBoxParent(), NULL, TRUE, FALSE, FSTRYCLOSE_CHANGEPATH, failReason); + } + if (failReason != NULL) + *failReason = CHPPFR_INVALIDPATH; + return FALSE; +} + +void CSalamanderGeneral::RefreshPanelPath(int panel, BOOL forceRefresh, BOOL focusFirstNewItem) +{ + CALL_STACK_MESSAGE4("CSalamanderGeneral::RefreshPanelPath(%d, %d, %d)", + panel, forceRefresh, focusFirstNewItem); + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::RefreshPanelPath() only from main thread!"); + return; + } + CFilesWindow* p = GetPanel(panel); + if (p != NULL) + { + if (forceRefresh && p->Is(ptZIPArchive)) + { // for archives ensure a hard refresh by invalidating the archive stamp + p->SetZIPArchiveSize(CQuadWord(-1, -1)); + } + p->FocusFirstNewItem = focusFirstNewItem; + p->RefreshDirectory(FALSE, forceRefresh); + } +} + +void CSalamanderGeneral::PostRefreshPanelPath(int panel, BOOL focusFirstNewItem) +{ + CALL_STACK_MESSAGE3("CSalamanderGeneral::PostRefreshPanelPath(%d, %d)", panel, focusFirstNewItem); + CFilesWindow* p = GetPanel(panel); + if (p != NULL) + { + // post a hard refresh + HANDLES(EnterCriticalSection(&TimeCounterSection)); + int t1 = MyTimeCounter++; + HANDLES(LeaveCriticalSection(&TimeCounterSection)); + p->FocusFirstNewItem = focusFirstNewItem; // not synchronized (may be called outside the main thread) but should not matter + PostMessage(p->HWindow, WM_USER_REFRESH_DIR, 0, t1); + } +} + +void CSalamanderGeneral::PostRefreshPanelFS(CPluginFSInterfaceAbstract* modifiedFS, BOOL focusFirstNewItem) +{ + CALL_STACK_MESSAGE2("CSalamanderGeneral::PostRefreshPanelFS(, %d)", focusFirstNewItem); + PostRefreshPanelFS2(modifiedFS, focusFirstNewItem); +} + +BOOL CSalamanderGeneral::PostRefreshPanelFS2(CPluginFSInterfaceAbstract* modifiedFS, BOOL focusFirstNewItem) +{ + CALL_STACK_MESSAGE2("CSalamanderGeneral::PostRefreshPanelFS2(, %d)", focusFirstNewItem); + CFilesWindow* p = NULL; + if (MainWindow != NULL) + { + // no synchronization issue, because PluginFS is cleared only after CloseFS, which + // should terminate the thread monitoring FS changes (after CloseFS there should be no call to + // PostRefreshPanelFS2) + if (MainWindow->LeftPanel != NULL && MainWindow->LeftPanel->Is(ptPluginFS) && + MainWindow->LeftPanel->GetPluginFS()->Contains(modifiedFS)) + { + p = MainWindow->LeftPanel; + } + if (MainWindow->RightPanel != NULL && MainWindow->RightPanel->Is(ptPluginFS) && + MainWindow->RightPanel->GetPluginFS()->Contains(modifiedFS)) + { + p = MainWindow->RightPanel; + } + } + if (p != NULL) + { + // post a hard refresh + HANDLES(EnterCriticalSection(&TimeCounterSection)); + int t1 = MyTimeCounter++; + HANDLES(LeaveCriticalSection(&TimeCounterSection)); + p->FocusFirstNewItem = focusFirstNewItem; // not synchronized (may be called outside the main thread) but should not matter + PostMessage(p->HWindow, WM_USER_REFRESH_DIR, 0, t1); + return TRUE; + } + else + return FALSE; +} + +BOOL CSalamanderGeneral::CloseDetachedFS(HWND parent, CPluginFSInterfaceAbstract* detachedFS) +{ + CALL_STACK_MESSAGE1("CSalamanderGeneral::CloseDetachedFS(,)"); + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::CloseDetachedFS() only from main thread!"); + return FALSE; + } + if (MainWindow->DetachedFSList->IsGood()) // to guarantee Delete succeeds + { + CDetachedFSList* list = MainWindow->DetachedFSList; + int i; + for (i = 0; i < list->Count; i++) + { + if (list->At(i)->GetInterface() == detachedFS) + { + CPluginFSInterfaceEncapsulation* fs = list->At(i); + BOOL dummy; + if (fs->TryCloseOrDetach(FALSE, FALSE, dummy, FSTRYCLOSE_PLUGINCLOSEDETACHEDFS)) // the FS has no objection to closing + { + CPluginInterfaceForFSEncapsulation plugin(fs->GetPluginInterfaceForFS()->GetInterface(), + fs->GetPluginInterfaceForFS()->GetBuiltForVersion()); + if (plugin.NotEmpty()) + { + fs->ReleaseObject(parent); + plugin.CloseFS(fs->GetInterface()); + list->Delete(i); + if (!list->IsGood()) + list->ResetState(); + return TRUE; + } + else + TRACE_E("Unexpected situation in CSalamanderGeneral::CloseDetachedFS()"); + } + break; + } + } + } + return FALSE; +} + +BOOL CSalamanderGeneral::DuplicateAmpersands(char* buffer, int bufferSize) +{ + CALL_STACK_MESSAGE3("CSalamanderGeneral::DuplicateAmpersands(%s, %d)", buffer, bufferSize); + return ::DuplicateAmpersands(buffer, bufferSize); +} + +void CSalamanderGeneral::RemoveAmpersands(char* text) +{ + CALL_STACK_MESSAGE2("CSalamanderGeneral::RemoveAmpersands(%s)", text); + ::RemoveAmpersands(text); +} + +BOOL CSalamanderGeneral::ValidateVarString(HWND msgParent, const char* varText, int& errorPos1, int& errorPos2, + const CSalamanderVarStrEntry* variables) +{ + CALL_STACK_MESSAGE2("CSalamanderGeneral::ValidateVarString(, %s, , ,)", varText); + if (varText == NULL || variables == NULL) + { + TRACE_E("CSalamanderGeneral::ValidateVarString(): invalid parameters!"); + return FALSE; + } + return ::ValidateVarString(msgParent, varText, errorPos1, errorPos2, variables); +} + +BOOL CSalamanderGeneral::ExpandVarString(HWND msgParent, const char* varText, char* buffer, int bufferLen, + const CSalamanderVarStrEntry* variables, void* param, + BOOL ignoreEnvVarNotFoundOrTooLong, + DWORD* varPlacements, int* varPlacementsCount, + BOOL detectMaxVarWidths, int* maxVarWidths, + int maxVarWidthsCount) +{ + CALL_STACK_MESSAGE6("CSalamanderGeneral::ExpandVarString(, %s, , %d, , , %d, , , %d, , %d)", + varText, bufferLen, ignoreEnvVarNotFoundOrTooLong, detectMaxVarWidths, + maxVarWidthsCount); + if (bufferLen <= 0 || buffer == NULL || varText == NULL || variables == NULL) + { + TRACE_E("CSalamanderGeneral::ExpandVarString(): invalid parameters!"); + return FALSE; + } + return ::ExpandVarString(msgParent, varText, buffer, bufferLen, variables, param, + ignoreEnvVarNotFoundOrTooLong, varPlacements, varPlacementsCount, + detectMaxVarWidths, maxVarWidths, maxVarWidthsCount); +} + +BOOL CSalamanderGeneral::EnumInstalledModules(int* index, char* module, char* version) +{ + CALL_STACK_MESSAGE1("CSalamanderGeneral::EnumInstalledModules(, ,)"); + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::EnumInstalledModules() only from main thread!"); + return FALSE; + } + return Plugins.EnumInstalledModules(index, module, version); +} + +BOOL CSalamanderGeneral::CopyTextToClipboard(const char* text, int textLen, BOOL showEcho, HWND echoParent) +{ + CALL_STACK_MESSAGE3("CSalamanderGeneral::CopyTextToClipboard(, %d, %d,)", textLen, showEcho); + // j.r. threw the text parameter, which did not have to be null-terminated + if (text == NULL) + { + TRACE_E("Unexpected parameter (NULL) in CSalamanderGeneral::CopyTextToClipboard()."); + return FALSE; + } + return ::CopyTextToClipboard(text, textLen, showEcho, echoParent); +} + +BOOL CSalamanderGeneral::CopyTextToClipboardW(const wchar_t* text, int textLen, BOOL showEcho, HWND echoParent) +{ + CALL_STACK_MESSAGE3("CSalamanderGeneral::CopyTextToClipboardW(, %d, %d,)", textLen, showEcho); + // j.r. threw the text parameter, which did not have to be null-terminated + if (text == NULL) + { + TRACE_E("Unexpected parameter (NULL) in CSalamanderGeneral::CopyTextToClipboardW()."); + return FALSE; + } + return ::CopyTextToClipboardW(text, textLen, showEcho, echoParent); +} + +BOOL CSalamanderGeneral::IsPluginInstalled(const char* pluginSPL) +{ + CALL_STACK_MESSAGE2("CSalamanderGeneral::IsPluginInstalled(%s)", pluginSPL); + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::IsPluginInstalled() only from main thread!"); + return FALSE; + } + if (pluginSPL != NULL) + { + CPluginData* data = Plugins.GetPluginDataFromSuffix(pluginSPL); + return data != NULL; + } + else + { + TRACE_E("Unexpected parameter 'pluginSPL' (NULL) in CSalamanderGeneral::IsPluginInstalled()."); + return FALSE; + } +} + +BOOL ViewFileInPluginViewer(const char* pluginSPL, + CSalamanderPluginViewerData* pluginData, + BOOL useCache, const char* rootTmpPath, + const char* fileNameInCache, int& error) +{ + error = -1; // unknown + if (pluginData == NULL || pluginData->Size < sizeof(CSalamanderPluginViewerData) || + pluginData->FileName == NULL || pluginData->FileName[0] == 0) + { + TRACE_E("Unexpected value of 'pluginData' in CSalamanderGeneral::ViewFileInPluginViewer!"); + return FALSE; + } + + CALL_STACK_MESSAGE7("CSalamanderGeneral::ViewFileInPluginViewer(%s, %d, %s, %d, %s, %s,)", + pluginSPL, pluginData->Size, pluginData->FileName, useCache, + (useCache ? rootTmpPath : "(ignored)"), + (useCache ? fileNameInCache : "(ignored)")); + + char viewUniqueName[50]; // we need a unique name for the viewed file in the cache + viewUniqueName[0] = 0; + const char* fileName; // name of the file we will pass to the viewer + if (useCache) + { + // verify that 'fileNameInCache' is valid (a name without path) + const char* s = NULL; + if (fileNameInCache != NULL) + { + s = fileNameInCache; + while (*s != 0 && *s != '\\' && *s != '/' && *s != ':' && + *s >= 32 && *s != '<' && *s != '>' && *s != '|' && *s != '"') + s++; + } + if (fileNameInCache == NULL || fileNameInCache[0] == 0 || *s != 0) + { + TRACE_E("Unexpected value of 'fileNameInCache' in CSalamanderGeneral::ViewFileInPluginViewer!"); + error = 3; + ::DeleteFileUtf8(pluginData->FileName); + return FALSE; + } + + // insert the file 'pluginData->FileName' into the disk cache under the name 'fileNameInCache' + while (1) + { + sprintf(viewUniqueName, "ViewFile %X", GetTickCount()); + BOOL exists; + fileName = DiskCache.GetName(viewUniqueName, fileNameInCache, &exists, TRUE, rootTmpPath, FALSE, NULL, NULL); + if (fileName == NULL) // error (if 'exists' is TRUE -> fatal, otherwise "file already exists") + { + if (!exists) + Sleep(100); // the file exists -> almost impossible, still handle it + else // fatal error + { + error = 3; + ::DeleteFileUtf8(pluginData->FileName); + return FALSE; // fatal error + } + } + else + break; // we have the name in the disk cache, all OK + } + if (!::SalMoveFile(pluginData->FileName, fileName)) + { + DWORD err = GetLastError(); + TRACE_E("Unable to move file to disk cache! (error " << ::GetErrorText(err) << ")"); + ::DeleteFileUtf8(pluginData->FileName); + DiskCache.ReleaseName(viewUniqueName, FALSE); + error = 3; + return FALSE; + } + else // successfully obtained a temp file; we must call NamePrepared() + { + CQuadWord size(0, 0); + HANDLE file = HANDLES_Q(CreateFileUtf8(fileName, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, 0, NULL)); + if (file != INVALID_HANDLE_VALUE) + { // ignore the error; the file size is not that important + DWORD err; + ::SalGetFileSize(file, size, err); + HANDLES(CloseHandle(file)); + } + + DiskCache.NamePrepared(viewUniqueName, size); + } + } + else + fileName = pluginData->FileName; + + // position for viewers + WINDOWPLACEMENT place; + place.length = sizeof(WINDOWPLACEMENT); + GetWindowPlacement(MainWindow->HWindow, &place); + // GetWindowPlacement accounts for the taskbar, so if the taskbar is at the top or left, + // the values are shifted by its dimensions. Apply a correction. + RECT monitorRect; + RECT workRect; + MultiMonGetClipRectByRect(&place.rcNormalPosition, &workRect, &monitorRect); + OffsetRect(&place.rcNormalPosition, workRect.left - monitorRect.left, + workRect.top - monitorRect.top); + //we do not want a minimized viewer, even if the main window is minimized + if (place.showCmd == SW_MINIMIZE || place.showCmd == SW_SHOWMINIMIZED || + place.showCmd == SW_SHOWMINNOACTIVE) + place.showCmd = SW_SHOWNORMAL; + + // finally open the viewer itself + BOOL diskCacheNameClosed = FALSE; + error = 0; + if (pluginSPL != NULL) // viewer from a plug-in + { + CPluginData* data = Plugins.GetPluginDataFromSuffix(pluginSPL); + if (data != NULL && data->SupportViewer) + { + if (data->InitDLL(MainWindow->HWindow) + /*&& PluginIfaceForViewer.NotEmpty()*/) // redundant, because downgrade is impossible and InitDLL checks the interfaces + { + HANDLE lock = NULL; + BOOL lockOwner = FALSE; + BOOL ret = data->GetPluginInterfaceForViewer()->ViewFile(fileName, place.rcNormalPosition.left, + place.rcNormalPosition.top, + place.rcNormalPosition.right - place.rcNormalPosition.left, + place.rcNormalPosition.bottom - place.rcNormalPosition.top, + place.showCmd, Configuration.AlwaysOnTop, + useCache, &lock, &lockOwner, pluginData, -1, -1); + if (!ret) + { + TRACE_E("PluginIfaceForViewer.ViewFile() returns error."); + error = 2; + } + else + { + if (useCache && lock != NULL) + { + if (lockOwner) // add the handle for 'lock' to HANDLES (the disk cache will want to close it and will look for it) + HANDLES_ADD(__htEvent, __hoCreateEvent, lock); + DiskCache.AssignName(viewUniqueName, lock, lockOwner, crtDirect); + diskCacheNameClosed = TRUE; + } + } + } + else + error = 1; + } + else + error = 1; + if (error == 1) + TRACE_E("Unable to load plugin."); + } + else // internal viewer + { + if (Configuration.SavePosition && + Configuration.WindowPlacement.length != 0) + { + place = Configuration.WindowPlacement; + // GetWindowPlacement accounts for the taskbar, so if the taskbar is at the top or left, + // the values are shifted by its dimensions. Apply a correction. + RECT monitorRect2; + RECT workRect2; + MultiMonGetClipRectByRect(&place.rcNormalPosition, &workRect2, &monitorRect2); + OffsetRect(&place.rcNormalPosition, workRect2.left - monitorRect2.left, + workRect2.top - monitorRect2.top); + MultiMonEnsureRectVisible(&place.rcNormalPosition, TRUE); + } + + HANDLE lock = NULL; + BOOL lockOwner = FALSE; + if (OpenViewer(fileName, vtText, + place.rcNormalPosition.left, + place.rcNormalPosition.top, + place.rcNormalPosition.right - place.rcNormalPosition.left, + place.rcNormalPosition.bottom - place.rcNormalPosition.top, + place.showCmd, useCache, &lock, &lockOwner, pluginData, -1, -1)) + { + if (useCache && lock != NULL) + { + DiskCache.AssignName(viewUniqueName, lock, lockOwner, crtDirect); + diskCacheNameClosed = TRUE; + } + } + else + { + TRACE_E("OpenViewer() returns error."); + error = 2; + } + } + + // if we did not assign a name in the disk cache, release the record... + if (useCache && !diskCacheNameClosed) + { + DiskCache.ReleaseName(viewUniqueName, FALSE); + // ::DeleteFileUtf8(fileName); // the cache already removed the file and deallocated fileName + } + return error == 0; // returning success? +} + +BOOL CSalamanderGeneral::ViewFileInPluginViewer(const char* pluginSPL, + CSalamanderPluginViewerData* pluginData, + BOOL useCache, const char* rootTmpPath, + const char* fileNameInCache, int& error) +{ + error = -1; // unknown + + // guard against calls from outside the main thread and from the entry point + if (MainThreadID != GetCurrentThreadId() || (INT_PTR)Plugin == -1) + { + if (MainThreadID == GetCurrentThreadId()) // if both errors occur (different thread + unfinished entry point), the entry point takes priority + TRACE_E("You may not call CSalamanderGeneral::ViewFileInPluginViewer from entry-point!"); + else + TRACE_E("You can call CSalamanderGeneral::ViewFileInPluginViewer only from main thread!"); + return FALSE; + } + + return ::ViewFileInPluginViewer(pluginSPL, pluginData, useCache, + rootTmpPath, fileNameInCache, error); +} + +void CSalamanderGeneral::ExecuteAssociation(HWND parent, const char* path, const char* name) +{ + CALL_STACK_MESSAGE4("CSalamanderGeneral::ExecuteAssociation(0x%p, %s, %s)", parent, path, name); + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::ExecuteAssociation() only from main thread!"); + return; + } + MainWindow->SetDefaultDirectories(); // so the starting process inherits the correct current directories + ::ExecuteAssociation(parent, path, name); +} + +int CSalamanderGeneral::GetPanelTopIndex(int panel) +{ + CALL_STACK_MESSAGE2("CSalamanderGeneral::GetPanelTopIndex(%d)", panel); + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::GetPanelTopIndex() only from main thread!"); + return 0; + } + CFilesWindow* p = GetPanel(panel); + if (p != NULL) + return p->ListBox->GetTopIndex(); + return 0; // error; should not happen... +} + +void CSalamanderGeneral::GetPanelEnumFilesParams(int panel, int* enumFilesSourceUID, int* enumFilesCurrentIndex) +{ + CALL_STACK_MESSAGE2("CSalamanderGeneral::GetPanelEnumFilesParams(%d, ,)", panel); + if (enumFilesCurrentIndex != NULL) + *enumFilesCurrentIndex = -1; + if (enumFilesSourceUID != NULL) + *enumFilesSourceUID = -1; + else + { + TRACE_E("CSalamanderGeneral::GetPanelEnumFilesParams(): 'enumFilesSourceUID' cannot be NULL!"); + return; + } + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::GetPanelEnumFilesParams() only from main thread!"); + return; + } + + CFilesWindow* p = GetPanel(panel); + if (p != NULL && p->Is(ptDisk)) + { + *enumFilesSourceUID = p->EnumFileNamesSourceUID; + if (enumFilesCurrentIndex != NULL) + { + int i = p->GetCaretIndex(); + if (i >= p->Dirs->Count && i < p->Dirs->Count + p->Files->Count) + *enumFilesCurrentIndex = i - p->Dirs->Count; + } + } +} + +BOOL CSalamanderGeneral::GetPanelWithPluginFS(CPluginFSInterfaceAbstract* pluginFS, int& panel) +{ + CALL_STACK_MESSAGE1("CSalamanderGeneral::GetPanelWithPluginFS(, )"); + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::GetPanelWithPluginFS() only from main thread!"); + return FALSE; + } + if (pluginFS == NULL) + return FALSE; + if (MainWindow->LeftPanel->Is(ptPluginFS) && + MainWindow->LeftPanel->GetPluginFS()->GetInterface() == pluginFS) + { + panel = PANEL_LEFT; + return TRUE; + } + if (MainWindow->RightPanel->Is(ptPluginFS) && + MainWindow->RightPanel->GetPluginFS()->GetInterface() == pluginFS) + { + panel = PANEL_RIGHT; + return TRUE; + } + return FALSE; +} + +void CSalamanderGeneral::PostChangeOnPathNotification(const char* path, BOOL includingSubdirs) +{ + CALL_STACK_MESSAGE3("CSalamanderGeneral::PostChangeOnPathNotification(%s, %d)", path, includingSubdirs); + MainWindow->PostChangeOnPathNotification(path, includingSubdirs); +} + +DWORD +CSalamanderGeneral::SalCheckPath(BOOL echo, const char* path, DWORD err, HWND parent) +{ + CALL_STACK_MESSAGE4("CSalamanderGeneral::SalCheckPath(%d, %s, %u,)", echo, path, err); + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::SalCheckPath() only from main thread!"); + return ERROR_SUCCESS; + } + return ::SalCheckPath(echo, path, err, TRUE, parent); // the value of 'postRefresh' does not matter (StopRefresh is surely > 0) +} + +BOOL CSalamanderGeneral::SalCheckAndRestorePath(HWND parent, const char* path, BOOL tryNet) +{ + CALL_STACK_MESSAGE3("CSalamanderGeneral::SalCheckAndRestorePath(, %s, %d)", path, tryNet); + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::SalCheckAndRestorePath() only from main thread!"); + return FALSE; + } + return ::SalCheckAndRestorePath(parent, path, tryNet); +} + +BOOL CSalamanderGeneral::SalCheckAndRestorePathWithCut(HWND parent, char* path, BOOL& tryNet, DWORD& err, + DWORD& lastErr, BOOL& pathInvalid, BOOL& cut, + BOOL donotReconnect) +{ + CALL_STACK_MESSAGE4("CSalamanderGeneral::SalCheckAndRestorePathWithCut(, %s, %d, , , , , %d)", + path, tryNet, donotReconnect); + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::SalCheckAndRestorePathWithCut() only from main thread!"); + lastErr = err = ERROR_SUCCESS; + pathInvalid = TRUE; + cut = FALSE; + return FALSE; + } + return ::SalCheckAndRestorePathWithCut(parent, path, tryNet, err, lastErr, pathInvalid, cut, + donotReconnect); +} + +BOOL CSalamanderGeneral::SalParsePath(HWND parent, char* path, int& type, BOOL& isDir, char*& secondPart, + const char* errorTitle, char* nextFocus, BOOL curPathIsDiskOrArchive, + const char* curPath, const char* curArchivePath, int* error, + int pathBufSize) +{ + CALL_STACK_MESSAGE7("CSalamanderGeneral::SalParsePath(, %s, , , , %s, , %d, %s, %s, , %d)", + path, errorTitle, curPathIsDiskOrArchive, curPath, curArchivePath, + pathBufSize); + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::SalParsePath() only from main thread!"); + if (error != NULL) + *error = SPP_WINDOWSPATHERROR; + return FALSE; + } + return ::SalParsePath(parent, path, type, isDir, secondPart, errorTitle, nextFocus, + curPathIsDiskOrArchive, curPath, curArchivePath, error, pathBufSize); +} + +BOOL CSalamanderGeneral::SalSplitWindowsPath(HWND parent, const char* title, const char* errorTitle, + int selCount, char* path, char* secondPart, BOOL pathIsDir, + BOOL backslashAtEnd, const char* dirName, + const char* curDiskPath, char*& mask) +{ + CALL_STACK_MESSAGE10("CSalamanderGeneral::SalSplitWindowsPath(, %s, %s, %d, %s, %s, %d, %d, %s, %s,)", + title, errorTitle, selCount, path, secondPart, pathIsDir, backslashAtEnd, + dirName, curDiskPath); + return ::SalSplitWindowsPath(parent, title, errorTitle, selCount, path, secondPart, + pathIsDir, backslashAtEnd, dirName, curDiskPath, mask); +} + +BOOL CSalamanderGeneral::SalSplitGeneralPath(HWND parent, const char* title, const char* errorTitle, + int selCount, char* path, char* afterRoot, char* secondPart, + BOOL pathIsDir, BOOL backslashAtEnd, const char* dirName, + const char* curPath, char*& mask, char* newDirs, + SGP_IsTheSamePathF isTheSamePathF) +{ + CALL_STACK_MESSAGE11("CSalamanderGeneral::SalSplitGeneralPath(, %s, %s, %d, %s, %s, %s, %d, %d, %s, %s, , ,)", + title, errorTitle, selCount, path, afterRoot, secondPart, pathIsDir, backslashAtEnd, + dirName, curPath); + return ::SalSplitGeneralPath(parent, title, errorTitle, selCount, path, afterRoot, secondPart, + pathIsDir, backslashAtEnd, dirName, curPath, mask, newDirs, + isTheSamePathF); +} + +BOOL CSalamanderGeneral::SalRemovePointsFromPath(char* afterRoot) +{ + CALL_STACK_MESSAGE2("CSalamanderGeneral::SalRemovePointsFromPath(%s)", afterRoot); + return ::SalRemovePointsFromPath(afterRoot); +} + +BOOL CSalamanderGeneral::GetConfigParameter(int paramID, void* buffer, int bufferSize, int* type) +{ + SLOW_CALL_STACK_MESSAGE3("CSalamanderGeneral::GetConfigParameter(%d, , %d,)", paramID, bufferSize); + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::GetConfigParameter() only from main thread!"); + if (type != NULL) + *type = SALCFGTYPE_NOTFOUND; + return FALSE; + } + char auxBuf[500]; + int auxType = SALCFGTYPE_BOOL; + int auxDataSize = 4; + BOOL ret = TRUE; + switch (paramID) + { + case SALCFG_SELOPINCLUDEDIRS: + *((DWORD*)auxBuf) = (DWORD)Configuration.IncludeDirs; + break; + case SALCFG_SAVEONEXIT: + *((DWORD*)auxBuf) = (DWORD)Configuration.AutoSave; + break; + case SALCFG_MINBEEPWHENDONE: + *((DWORD*)auxBuf) = (DWORD)Configuration.MinBeepWhenDone; + break; + case SALCFG_HIDEHIDDENORSYSTEMFILES: + *((DWORD*)auxBuf) = (DWORD)Configuration.NotHiddenSystemFiles; + break; + case SALCFG_ALWAYSONTOP: + *((DWORD*)auxBuf) = (DWORD)Configuration.AlwaysOnTop; + break; + // case SALCFG_FASTDIRMOVE: *((DWORD *)auxBuf) = (DWORD)Configuration.FastDirectoryMove; break; + case SALCFG_SORTUSESLOCALE: + *((DWORD*)auxBuf) = (DWORD)Configuration.SortUsesLocale; + break; + case SALCFG_SORTDETECTNUMBERS: + *((DWORD*)auxBuf) = (DWORD)Configuration.SortDetectNumbers; + break; + case SALCFG_SORTBYEXTDIRSASFILES: + *((DWORD*)auxBuf) = (DWORD)Configuration.SortDirsByExt; + break; + case SALCFG_SINGLECLICK: + *((DWORD*)auxBuf) = (DWORD)Configuration.SingleClick; + break; + case SALCFG_TOPTOOLBARVISIBLE: + *((DWORD*)auxBuf) = (DWORD)Configuration.TopToolBarVisible; + break; + case SALCFG_MIDDLETOOLBARVISIBLE: + *((DWORD*)auxBuf) = (DWORD)Configuration.MiddleToolBarVisible; + break; + case SALCFG_BOTTOMTOOLBARVISIBLE: + *((DWORD*)auxBuf) = (DWORD)Configuration.BottomToolBarVisible; + break; + case SALCFG_USERMENUTOOLBARVISIBLE: + *((DWORD*)auxBuf) = (DWORD)Configuration.UserMenuToolBarVisible; + break; + case SALCFG_SAVEHISTORY: + *((DWORD*)auxBuf) = (DWORD)Configuration.SaveHistory; + break; + case SALCFG_ENABLECMDLINEHISTORY: + *((DWORD*)auxBuf) = (DWORD)Configuration.EnableCmdLineHistory; + break; + case SALCFG_SAVECMDLINEHISTORY: + *((DWORD*)auxBuf) = (DWORD)Configuration.SaveCmdLineHistory; + break; + case SALCFG_SIZEFORMAT: + *((DWORD*)auxBuf) = (DWORD)Configuration.SizeFormat; + break; + case SALCFG_SELECTWHOLENAME: + *((DWORD*)auxBuf) = (DWORD)Configuration.QuickRenameSelectAll; + break; + + case SALCFG_FILENAMEFORMAT: + { + auxType = SALCFGTYPE_INT; + auxDataSize = 4; + *((DWORD*)auxBuf) = (DWORD)Configuration.FileNameFormat; + break; + } + + case SALCFG_INFOLINECONTENT: + { + auxType = SALCFGTYPE_STRING; + auxDataSize = (int)strlen(Configuration.InfoLineContent) + 1; + if (auxDataSize > 200) + auxDataSize = 200; // we limited the required buffer to 200 characters + memcpy(auxBuf, Configuration.InfoLineContent, auxDataSize); + auxBuf[auxDataSize - 1] = 0; + break; + } + + case SALCFG_USERECYCLEBIN: + { + auxType = SALCFGTYPE_INT; + auxDataSize = 4; + *((DWORD*)auxBuf) = (DWORD)Configuration.UseRecycleBin; + break; + } + + case SALCFG_RECYCLEBINMASKS: + { + auxType = SALCFGTYPE_STRING; + auxDataSize = (int)strlen(Configuration.RecycleMasks.GetMasksString()) + 1; + if (auxDataSize > MAX_PATH) + auxDataSize = MAX_PATH; // we limited the required buffer to MAX_PATH characters + memcpy(auxBuf, Configuration.RecycleMasks.GetMasksString(), auxDataSize); + auxBuf[auxDataSize - 1] = 0; + break; + } + + case SALCFG_COMPDIRSUSETIMERES: + *((DWORD*)auxBuf) = (DWORD)Configuration.UseTimeResolution; + break; + + case SALCFG_COMPDIRTIMERES: + { + auxType = SALCFGTYPE_INT; + auxDataSize = 4; + *((DWORD*)auxBuf) = (DWORD)Configuration.TimeResolution; + break; + } + + case SALCFG_CNFRMFILEDIRDEL: + *((DWORD*)auxBuf) = (DWORD)Configuration.CnfrmFileDirDel; + break; + case SALCFG_CNFRMNEDIRDEL: + *((DWORD*)auxBuf) = (DWORD)Configuration.CnfrmNEDirDel; + break; + case SALCFG_CNFRMFILEOVER: + *((DWORD*)auxBuf) = (DWORD)Configuration.CnfrmFileOver; + break; + case SALCFG_CNFRMDIROVER: + *((DWORD*)auxBuf) = (DWORD)Configuration.CnfrmDirOver; + break; + case SALCFG_CNFRMSHFILEDEL: + *((DWORD*)auxBuf) = (DWORD)Configuration.CnfrmSHFileDel; + break; + case SALCFG_CNFRMSHDIRDEL: + *((DWORD*)auxBuf) = (DWORD)Configuration.CnfrmSHDirDel; + break; + case SALCFG_CNFRMSHFILEOVER: + *((DWORD*)auxBuf) = (DWORD)Configuration.CnfrmSHFileOver; + break; + case SALCFG_CNFRMCREATEPATH: + *((DWORD*)auxBuf) = (DWORD)Configuration.CnfrmCreatePath; + break; + case SALCFG_DRVSPECFLOPPYMON: + *((DWORD*)auxBuf) = (DWORD)Configuration.DrvSpecFloppyMon; + break; + case SALCFG_DRVSPECFLOPPYSIM: + *((DWORD*)auxBuf) = (DWORD)Configuration.DrvSpecFloppySimple; + break; + case SALCFG_DRVSPECREMOVABLEMON: + *((DWORD*)auxBuf) = (DWORD)Configuration.DrvSpecRemovableMon; + break; + case SALCFG_DRVSPECREMOVABLESIM: + *((DWORD*)auxBuf) = (DWORD)Configuration.DrvSpecRemovableSimple; + break; + case SALCFG_DRVSPECFIXEDMON: + *((DWORD*)auxBuf) = (DWORD)Configuration.DrvSpecFixedMon; + break; + case SALCFG_DRVSPECFIXEDSIMPLE: + *((DWORD*)auxBuf) = (DWORD)Configuration.DrvSpecFixedSimple; + break; + case SALCFG_DRVSPECREMOTEMON: + *((DWORD*)auxBuf) = (DWORD)Configuration.DrvSpecRemoteMon; + break; + case SALCFG_DRVSPECREMOTESIMPLE: + *((DWORD*)auxBuf) = (DWORD)Configuration.DrvSpecRemoteSimple; + break; + case SALCFG_DRVSPECREMOTEDONOTREF: + *((DWORD*)auxBuf) = (DWORD)Configuration.DrvSpecRemoteDoNotRefreshOnAct; + break; + case SALCFG_DRVSPECCDROMMON: + *((DWORD*)auxBuf) = (DWORD)Configuration.DrvSpecCDROMMon; + break; + case SALCFG_DRVSPECCDROMSIMPLE: + *((DWORD*)auxBuf) = (DWORD)Configuration.DrvSpecCDROMSimple; + break; + + case SALCFG_IFPATHISINACCESSIBLEGOTO: + { + auxType = SALCFGTYPE_STRING; + char ifPathIsInaccessibleGoTo[MAX_PATH]; + GetIfPathIsInaccessibleGoTo(ifPathIsInaccessibleGoTo, _countof(ifPathIsInaccessibleGoTo), FALSE); + auxDataSize = (int)strlen(ifPathIsInaccessibleGoTo) + 1; + if (auxDataSize > MAX_PATH) + auxDataSize = MAX_PATH; // we limited the required buffer to MAX_PATH characters + memcpy(auxBuf, ifPathIsInaccessibleGoTo, auxDataSize); + auxBuf[auxDataSize - 1] = 0; + break; + } + + case SALCFG_VIEWEREOLCRLF: + *((DWORD*)auxBuf) = (DWORD)Configuration.EOL_CRLF; + break; + case SALCFG_VIEWEREOLCR: + *((DWORD*)auxBuf) = (DWORD)Configuration.EOL_CR; + break; + case SALCFG_VIEWEREOLLF: + *((DWORD*)auxBuf) = (DWORD)Configuration.EOL_LF; + break; + case SALCFG_VIEWEREOLNULL: + *((DWORD*)auxBuf) = (DWORD)Configuration.EOL_NULL; + break; + case SALCFG_VIEWERSAVEPOSITION: + *((DWORD*)auxBuf) = (DWORD)Configuration.SavePosition; + break; + case SALCFG_VIEWERWRAPTEXT: + *((DWORD*)auxBuf) = (DWORD)Configuration.WrapText; + break; + case SALCFG_AUTOCOPYSELTOCLIPBOARD: + *((DWORD*)auxBuf) = (DWORD)Configuration.AutoCopySelection; + break; + + case SALCFG_VIEWERTABSIZE: + { + auxType = SALCFGTYPE_INT; + auxDataSize = 4; + *((DWORD*)auxBuf) = (DWORD)Configuration.TabSize; + break; + } + + case SALCFG_VIEWERFONT: + { + auxType = SALCFGTYPE_LOGFONT; + auxDataSize = sizeof(LOGFONT); + if (UseCustomViewerFont) + *((LOGFONT*)auxBuf) = ViewerLogFont; + else + GetDefaultViewerLogFont((LOGFONT*)auxBuf); + break; + } + + case SALCFG_ARCOTHERPANELFORPACK: + *((DWORD*)auxBuf) = (DWORD)Configuration.UseAnotherPanelForPack; + break; + case SALCFG_ARCOTHERPANELFORUNPACK: + *((DWORD*)auxBuf) = (DWORD)Configuration.UseAnotherPanelForUnpack; + break; + case SALCFG_ARCSUBDIRBYARCFORUNPACK: + *((DWORD*)auxBuf) = (DWORD)Configuration.UseSubdirNameByArchiveForUnpack; + break; + case SALCFG_ARCUSESIMPLEICONS: + *((DWORD*)auxBuf) = (DWORD)Configuration.UseSimpleIconsInArchives; + break; + + default: + { + auxType = SALCFGTYPE_NOTFOUND; + auxDataSize = 0; + TRACE_E("Unknown parameter ID (" << paramID << ") in CSalamanderGeneral::GetConfigParameter()."); + ret = FALSE; + } + } + if (type != NULL) + *type = auxType; + if (auxDataSize > 0 && auxDataSize <= bufferSize) + memcpy(buffer, auxBuf, auxDataSize); + else + { + if (bufferSize > 0 && auxDataSize > 0) // copy at least what fits + { + memcpy(buffer, auxBuf, bufferSize); + if (auxType == SALCFGTYPE_STRING) + ((char*)buffer)[bufferSize - 1] = 0; // trim the string with a zero + } + ret = FALSE; + } + return ret; +} + +void CSalamanderGeneral::AlterFileName(char* tgtName, char* srcName, int format, int changedParts, + BOOL isDir) +{ + CALL_STACK_MESSAGE5("CSalamanderGeneral::AlterFileName(, %s, %d, %d, %d)", + srcName, format, changedParts, isDir); + ::AlterFileName(tgtName, srcName, -1, format, changedParts, isDir); +} + +void CSalamanderGeneral::CreateSafeWaitWindow(const char* message, const char* caption, + int delay, BOOL showCloseButton, HWND hForegroundWnd) +{ + CALL_STACK_MESSAGE5("CSalamanderGeneral::CreateSafeWaitWindow(%s, , %d, %d, 0x%p)", message, delay, showCloseButton, hForegroundWnd); + ::CreateSafeWaitWindow(message, caption, delay, showCloseButton, hForegroundWnd); +} + +void CSalamanderGeneral::DestroySafeWaitWindow() +{ + CALL_STACK_MESSAGE1("CSalamanderGeneral::DestroySafeWaitWindow()"); + ::DestroySafeWaitWindow(); +} + +void CSalamanderGeneral::ShowSafeWaitWindow(BOOL show) +{ + CALL_STACK_MESSAGE2("CSalamanderGeneral::ShowSafeWaitWindow(%d)", show); + ::ShowSafeWaitWindow(show); +} + +BOOL CSalamanderGeneral::GetSafeWaitWindowClosePressed() +{ + CALL_STACK_MESSAGE1("CSalamanderGeneral::GetSafeWaitWindowClosePressed()"); + return ::GetSafeWaitWindowClosePressed(); +} + +void CSalamanderGeneral::SetSafeWaitWindowText(const char* message) +{ + CALL_STACK_MESSAGE2("CSalamanderGeneral::SetSafeWaitWindowText(%s)", message); + ::SetSafeWaitWindowText(message); +} + +BOOL CSalamanderGeneral::GetFileFromCache(const char* uniqueFileName, const char*& tmpName, + HANDLE fileLock) +{ + CALL_STACK_MESSAGE2("CSalamanderGeneral::GetFileFromCache(%s, ,)", uniqueFileName); + tmpName = NULL; + if (uniqueFileName == NULL || fileLock == NULL) + { + TRACE_E("Invalid parameter in CSalamanderGeneral::GetFileFromCache!"); + return FALSE; + } + + BOOL fileExists; + const char* name = DiskCache.GetName(uniqueFileName, NULL, &fileExists, FALSE, NULL, FALSE, NULL, NULL); + if (name != NULL) // file found + { + if (!fileExists) // some helpful soul deleted it straight from the disk + { + // cannot prepare the file; tell the disk cache we give up + DiskCache.ReleaseName(uniqueFileName, FALSE); + } + else + { + DiskCache.AssignName(uniqueFileName, fileLock, FALSE, crtCache); + tmpName = name; + return TRUE; + } + } + return FALSE; +} + +void CSalamanderGeneral::UnlockFileInCache(HANDLE fileLock) +{ + CALL_STACK_MESSAGE2("CSalamanderGeneral::UnlockFileInCache(0x%p)", fileLock); + + SetEvent(fileLock); // start cleaning up the file + DiskCache.WaitForIdle(); + ResetEvent(fileLock); // finish cleaning up the file +} + +BOOL CSalamanderGeneral::MoveFileToCache(const char* uniqueFileName, const char* nameInCache, + const char* rootTmpPath, const char* newFileName, + const CQuadWord& newFileSize, BOOL* alreadyExists) +{ + CALL_STACK_MESSAGE6("CSalamanderGeneral::MoveFileToCache(%s, %s, %s, %s, %g, )", + uniqueFileName, nameInCache, rootTmpPath, newFileName, newFileSize.GetDouble()); + if (alreadyExists != NULL) + *alreadyExists = FALSE; + if (uniqueFileName == NULL || newFileName == NULL || nameInCache == NULL) + { + TRACE_E("Invalid parameter in CSalamanderGeneral::GetFileFromCache!"); + return FALSE; + } + + // verify that 'nameInCache' is valid (a name without a path) + const char* s = nameInCache; + while (*s != 0 && *s != '\\' && *s != '/' && *s != ':' && + *s >= 32 && *s != '<' && *s != '>' && *s != '|' && *s != '"') + s++; + if (nameInCache[0] == 0 || *s != 0) + { + TRACE_E("Unexpected value of 'nameInCache' in CSalamanderGeneral::MoveFileToCache!"); + return FALSE; + } + + // add the file 'newFileName' to the disk cache under the name 'uniqueFileName' + BOOL exists; + const char* fileName = DiskCache.GetName(uniqueFileName, nameInCache, &exists, TRUE, rootTmpPath, FALSE, NULL, NULL); + if (fileName == NULL) // error (if 'exists' is TRUE -> fatal, otherwise "file already exists") + { + if (alreadyExists != NULL) + *alreadyExists = !exists; + return FALSE; + } + + if (!::SalMoveFile(newFileName, fileName)) + { + DWORD err = GetLastError(); + TRACE_E("Unable to move file to disk cache! (error " << ::GetErrorText(err) << ")"); + DiskCache.ReleaseName(uniqueFileName, FALSE); // nothing to keep in the cache + return FALSE; + } + else // successfully obtained a temp file; we must call NamePrepared() + { + DiskCache.NamePrepared(uniqueFileName, newFileSize); + DiskCache.ReleaseName(uniqueFileName, TRUE); // leave the prepared file in the cache (even if it is not locked) + return TRUE; + } +} + +void CSalamanderGeneral::RemoveOneFileFromCache(const char* uniqueFileName) +{ + CALL_STACK_MESSAGE2("CSalamanderGeneral::RemoveOneFileFromCache(%s)", uniqueFileName); + if (uniqueFileName == NULL) + { + TRACE_E("Invalid parametr (NULL) in CSalamanderGeneral::RemoveOneFileFromCache!"); + return; + } + DiskCache.FlushOneFile(uniqueFileName); +} + +void CSalamanderGeneral::RemoveFilesFromCache(const char* fileNamesRoot) +{ + CALL_STACK_MESSAGE2("CSalamanderGeneral::RemoveFilesFromCache(%s)", fileNamesRoot); + if (fileNamesRoot == NULL) + { + TRACE_E("Invalid parametr (NULL) in CSalamanderGeneral::RemoveFilesFromCache!"); + return; + } + DiskCache.FlushCache(fileNamesRoot); +} + +BOOL CSalamanderGeneral::EnumConversionTables(HWND parent, int* index, const char** name, const char** table) +{ + if (index == NULL) + { + TRACE_E("Unexpected value of 'index' (NULL) in CSalamanderGeneral::EnumConversionTables()."); + return FALSE; + } + CALL_STACK_MESSAGE2("CSalamanderGeneral::EnumConversionTables(, %d, ,)", *index); + parent = (parent == NULL ? MainWindow->HWindow : parent); + return CodeTables.EnumCodeTables(parent, index, name, table); +} + +BOOL CSalamanderGeneral::GetConversionTable(HWND parent, char* table, const char* conversion) +{ + CALL_STACK_MESSAGE2("CSalamanderGeneral::GetConversionTable(, , %s)", conversion); + if (table == NULL) + { + TRACE_E("Invalid parametr (table==NULL) in CSalamanderGeneral::GetConversionTable!"); + return FALSE; + } + parent = (parent == NULL ? MainWindow->HWindow : parent); + BOOL ret = CodeTables.Init(parent); + int codeType; + if (ret) + ret &= CodeTables.GetCodeType(conversion, codeType); + if (ret) + ret &= CodeTables.GetCode(table, codeType); + return ret; +} + +void CSalamanderGeneral::GetWindowsCodePage(HWND parent, char* codePage) +{ + CALL_STACK_MESSAGE1("CSalamanderGeneral::GetWindowsCodePage(,)"); + parent = (parent == NULL ? MainWindow->HWindow : parent); + CodeTables.Init(parent); + CodeTables.GetWinCodePage(codePage); +} + +void CSalamanderGeneral::RecognizeFileType(HWND parent, const char* pattern, int patternLen, BOOL forceText, + BOOL* isText, char* codePage) +{ + CALL_STACK_MESSAGE3("CSalamanderGeneral::RecognizeFileType(, , %d, %d, ,)", patternLen, forceText); + parent = (parent == NULL ? MainWindow->HWindow : parent); + ::RecognizeFileType(parent, pattern, patternLen, forceText, isText, codePage); +} + +BOOL CSalamanderGeneral::IsANSIText(const char* text, int textLen) +{ + CALL_STACK_MESSAGE2("CSalamanderGeneral::IsANSIText(, %d)", textLen); + + const unsigned char* s = (const unsigned char*)text; + const unsigned char* end = s + textLen; + while (s < end) + { + if (*s < ' ' && *s != '\a' && *s != '\b' && *s != '\r' && // *s != 0 must not be here because of Unicode files ("0A 00" cannot be expanded to "0D 0A 00") + *s != '\f' && *s != '\n' && *s != '\t' && *s != '\v' && + *s != '\x1a' && *s != '\x04' && *s != '\x06') + { // disallowed character + break; + } + s++; + } + return s == end; +} + +BOOL CSalamanderGeneral::SalMoveFile(const char* srcName, const char* destName, DWORD* err) +{ + CALL_STACK_MESSAGE3("CSalamanderGeneral::SalMoveFile(%s, %s,)", srcName, destName); + BOOL ret = ::SalMoveFile(srcName, destName); + if (err != NULL) + *err = GetLastError(); + return ret; +} + +BOOL CSalamanderGeneral::SalGetFileSize(HANDLE file, CQuadWord& size, DWORD& err) +{ + CALL_STACK_MESSAGE1("CSalamanderGeneral::SalGetFileSize(, ,)"); + return ::SalGetFileSize(file, size, err); +} + +BOOL CSalamanderGeneral::GetTargetDirectory(HWND parent, HWND hCenterWindow, const char* title, + const char* comment, char* path, BOOL onlyNet, + const char* initDir) +{ + CALL_STACK_MESSAGE5("CSalamanderGeneral::GetTargetDirectory(, , %s, %s, , %d, %s)", + title, comment, onlyNet, initDir); + return ::GetTargetDirectory(parent, hCenterWindow, title, comment, path, onlyNet, initDir); +} + +void CSalamanderGeneral::CallPluginOperationFromDisk(int panel, SalPluginOperationFromDisk callback, + void* param) +{ + CALL_STACK_MESSAGE2("CSalamanderGeneral::CallPluginOperationFromDisk(%d, ,)", panel); + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::CallPluginOperationFromDisk() only from main thread!"); + return; + } + if (callback == NULL) + { + TRACE_E("Unexpected value of parameter 'callback' (NULL) in CSalamanderGeneral::CallPluginOperationFromDisk()."); + return; + } + CFilesWindow* p = GetPanel(panel); + if (p != NULL) + { + if (!p->Is(ptDisk)) + { + TRACE_E("CSalamanderGeneral::CallPluginOperationFromDisk(): there must be windows (disk) path in panel!"); + return; + } + if (p->Files->Count + p->Dirs->Count <= 0) + { + TRACE_I("CSalamanderGeneral::CallPluginOperationFromDisk(): no items in panel!"); + return; + } + // prepare data for enumerating files and directories from the panel + CPanelTmpEnumData data; + int oneIndex = -1; + int count = p->GetSelCount(); + if (count > 0) // some files are selected + { + data.IndexesCount = count; + data.Indexes = new int[count]; + if (data.Indexes == NULL) + { + TRACE_E(LOW_MEMORY); + return; + } + else + p->GetSelItems(count, data.Indexes); + } + else // take the focus + { + oneIndex = p->GetCaretIndex(); + + BOOL subDir; + if (p->Dirs->Count > 0) + subDir = (strcmp(p->Dirs->At(0).Name, "..") == 0); + else + subDir = FALSE; + if (oneIndex == 0 && subDir) + { + TRACE_E("Unexpected situation in CSalamanderGeneral::CallPluginOperationFromDisk(): no files nor directories selected and focus is on up-dir symbol."); + return; + } + + data.IndexesCount = 1; + data.Indexes = &oneIndex; // not deallocated + } + data.CurrentIndex = 0; + data.ZIPPath = p->GetZIPPath(); + data.Dirs = p->Dirs; + data.Files = p->Files; + data.ArchiveDir = p->GetArchiveDir(); + lstrcpyn(data.WorkPath, p->GetPath(), MAX_PATH); + data.EnumLastDir = NULL; + data.EnumLastIndex = -1; + + callback(p->GetPath(), PanelEnumDiskSelection, &data, param); + + if (count > 0) + delete[] (data.Indexes); + } +} + +BYTE CSalamanderGeneral::GetUserDefaultCharset() +{ + return (BYTE)UserCharset; +} + diff --git a/src/zip_progress.cpp b/src/zip_progress.cpp new file mode 100644 index 00000000..94f67095 --- /dev/null +++ b/src/zip_progress.cpp @@ -0,0 +1,439 @@ +// SPDX-FileCopyrightText: 2023 Taskscape Ltd +// SPDX-License-Identifier: GPL-2.0-or-later +// CommentsTranslationProject: TRANSLATED + +#include "precomp.h" + +#include "menu.h" +#include "cfgdlg.h" +#include "dialogs.h" +#include "mainwnd.h" +#include "plugins.h" +#include "filesbox.h" +#include "fileswnd.h" +#include "stswnd.h" +#include "editwnd.h" +#include "zip.h" +#include "cache.h" +#include "viewer.h" +#include "codetbl.h" +#include "shellib.h" +#include "gui.h" +#include "tasklist.h" +#include "olespy.h" +#include "md5.h" +#include "geticon.h" +#include "pack.h" +extern "C" +{ +#include "shexreg.h" +} +#include "salshlib.h" +#include "crypt\fileenc.h" +#include "crypt\sha1.h" +#include "pwdmngr.h" + +CPackerConfig PackerConfig; +CUnpackerConfig UnpackerConfig; + +const char* STR_NONE = "(none)"; + +CSalamanderDirectory GlobalEmptySalDir(FALSE); // returned as an empty sal-dir (instead of NULL) - only for archives + +HWND ProgressDialogActivateDrop = NULL; + +// +// **************************************************************************** +// CZIPUnpackProgress +// + +CZIPUnpackProgress::CZIPUnpackProgress() : CCommonDialog(HLanguage, IDD_ZIPUNPACKPROG, NULL, ooStatic) +{ + Init(); +} + +void CZIPUnpackProgress::Init() +{ + SetTotal(CQuadWord(0, 0), CQuadWord(0, 0)); + ActualSize = CQuadWord(0, 0); + ActualSize2 = CQuadWord(0, 0); + Title = NULL; + Cancel = FALSE; + SetRemapNames(NULL, NULL); + int i; + for (i = 0; i < ZIP_UNPACK_NUMLINES; i++) + LinesCache[i][0] = 0; + CacheIndex = 0; + CacheIsDirty = FALSE; + SizeIsDirty = FALSE; + Size2IsDirty = FALSE; + LastTickCount = 0; + FileProgress = FALSE; + TaskBarList3 = NULL; +} + +CZIPUnpackProgress::CZIPUnpackProgress(const char* title, HWND parent, const CQuadWord& totalSize, CITaskBarList3* taskBarList3) + : CCommonDialog(HLanguage, IDD_ZIPUNPACKPROG, parent, ooStatic) +{ + SetTotal(totalSize, CQuadWord(0, 0)); + ActualSize = CQuadWord(0, 0); + ActualSize2 = CQuadWord(0, 0); + Title = title; + Cancel = FALSE; + SetRemapNames(NULL, NULL); + int i; + for (i = 0; i < ZIP_UNPACK_NUMLINES; i++) + LinesCache[i][0] = 0; + CacheIndex = 0; + CacheIsDirty = FALSE; + SizeIsDirty = FALSE; + Size2IsDirty = FALSE; + LastTickCount = 0; + FileProgress = FALSE; + TaskBarList3 = taskBarList3; +} + +void CZIPUnpackProgress::Set(const char* title, HWND parent, const CQuadWord& totalSize, BOOL fileProgress) +{ + ResID = IDD_ZIPUNPACKPROG; + SetTotal(totalSize, CQuadWord(0, 0)); + SetParent(parent); + Title = title; + ActualSize = CQuadWord(0, 0); + ActualSize2 = CQuadWord(0, 0); + FileProgress = fileProgress; +} + +void CZIPUnpackProgress::Set(const char* title, HWND parent, const CQuadWord& totalSize1, + const CQuadWord& totalSize2) +{ + ResID = IDD_ZIPUNPACKPROG2; + SetTotal(totalSize1, totalSize2); + SetParent(parent); + Title = title; + ActualSize = CQuadWord(0, 0); + ActualSize2 = CQuadWord(0, 0); + FileProgress = FALSE; +} + +void CZIPUnpackProgress::SetTotal(const CQuadWord& total1, const CQuadWord& total2) +{ + if (total1 != CQuadWord(-1, -1)) + { + TotalSize = max(CQuadWord(1, 0), total1); + SizeIsDirty = FALSE; + } + if (total2 != CQuadWord(-1, -1)) + { + TotalSize2 = max(CQuadWord(1, 0), total2); + Size2IsDirty = FALSE; + } +} + +void CZIPUnpackProgress::SetRemapNames(const char* nameFrom, const char* nameTo) +{ + RemapNameFrom = nameFrom; + RemapNameTo = nameTo; +} + +void CZIPUnpackProgress::DoRemapNames(char* txt, int bufLen) +{ + if (RemapNameFrom != NULL && RemapNameTo != NULL) + { + char* s = strstr(txt, RemapNameFrom); + if (s != NULL) + { + int len = (int)strlen(txt); + int lenFrom = (int)strlen(RemapNameFrom); + int lenTo = (int)strlen(RemapNameTo); + if (len - lenFrom + lenTo < bufLen) + { + memmove(s + lenTo, s + lenFrom, len - ((s + lenFrom) - txt) + 1); + memcpy(s, RemapNameTo, lenTo); + } + else + TRACE_E("Remap: too long name."); + } + } +} + +void CZIPUnpackProgress::SetTaskBarList3(CITaskBarList3* taskBarList3) +{ + TaskBarList3 = taskBarList3; +} + +void CZIPUnpackProgress::DispatchMessages() +{ + // pump the message queue + MSG msg; + while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) + { + if (!IsWindow(HWindow) || !IsDialogMessage(HWindow, &msg)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } +} + +BOOL CZIPUnpackProgress::HasTwoProgress() +{ + return ResID == IDD_ZIPUNPACKPROG2; +} + +int CZIPUnpackProgress::SetSize(const CQuadWord& size1, const CQuadWord& size2, BOOL delayedPaint) +{ + // does the user want to set size1? + if (size1 != CQuadWord(-1, -1)) + { + CQuadWord newSize = max(CQuadWord(0, 0), size1); + if (newSize != ActualSize) + { + ActualSize = newSize; + SizeIsDirty = TRUE; + } + } + // does the user want to set size2? + if (size2 != CQuadWord(-1, -1)) + { + CQuadWord newSize = max(CQuadWord(0, 0), size2); + if (newSize != ActualSize2) + { + ActualSize2 = newSize; + Size2IsDirty = TRUE; + } + } + return AddSize(0, delayedPaint); +} + +int CZIPUnpackProgress::AddSize(int size, BOOL delayedPaint) +{ + // time-critical function + // CALL_STACK_MESSAGE2("CZIPUnpackProgress::AddSize(%d)", size); + + ActualSize += CQuadWord(size, 0); + if (size != 0) + SizeIsDirty = TRUE; + + if (HasTwoProgress()) + { + ActualSize2 += CQuadWord(size, 0); + if (size != 0) + Size2IsDirty = TRUE; + } + + if (!delayedPaint) + { + // should we draw the text immediately + FlushDataToControls(); + } + + // every 100 ms redraw the changed data (text + progress bars) + DWORD ticks = GetTickCount(); + if (ticks - LastTickCount > 100) + { + LastTickCount = ticks; + // if we have not repainted a moment ago, do it now + if (delayedPaint) + FlushDataToControls(); + } + + DispatchMessages(); // give the user a moment ... + + return !Cancel; +} + +void CZIPUnpackProgress::NewLine(const char* txt, BOOL delayedPaint) +{ + // time-critical function + // CALL_STACK_MESSAGE2("CZIPUnpackProgress::NewLine(%s)", txt); + if (txt == NULL) + return; + + while (1) // output even multiple lines into the dialog + { + while (*txt != 0 && (*txt == '\r' || *txt == '\n' || *txt == ' ' || *txt == '\t')) + txt++; + if (*txt == 0) + break; + + // store it in the cache that we display on WM_TIMER + + // the cache index cycles through the items + CacheIndex++; + if (CacheIndex >= ZIP_UNPACK_NUMLINES) + CacheIndex = 0; + + char* s = LinesCache[CacheIndex]; + char* sEnd = s + 300 - 1; + while (*txt != 0 && *txt != '\r' && *txt != '\n') // read one line + convert '/' -> '\\' + { + if (*txt == '/') + { + if (s < sEnd) + *s++ = '\\'; + txt++; + } + else + { + if (s < sEnd) + *s++ = *txt++; + else + txt++; // simply ignore the rest of the text (it would not fit in the dialog anyway) + } + } + *s = 0; + DoRemapNames(LinesCache[CacheIndex], 300); + + // we dirtied the cache + CacheIsDirty = TRUE; + } + + if (!delayedPaint) + { + // should we draw the text immediately + FlushDataToControls(); + } + + // every 100 ms redraw the changed data (text + progress bars) + DWORD ticks = GetTickCount(); + if (ticks - LastTickCount > 100) + { + LastTickCount = ticks; + // if we have not repainted a moment ago, do it now + if (delayedPaint) + FlushDataToControls(); + } + + // be careful, do not call here + DispatchMessages(); // give the user a moment ... +} + +void CZIPUnpackProgress::EnableCancel(BOOL enable) +{ + if (HWindow != NULL) + { + HWND cancel = GetDlgItem(HWindow, IDCANCEL); + if (IsWindowEnabled(cancel) != enable) + { + EnableWindow(cancel, enable); + if (enable) + SetFocus(cancel); + PostMessage(cancel, BM_SETSTYLE, enable ? BS_DEFPUSHBUTTON : BS_PUSHBUTTON, TRUE); + + DispatchMessages(); // give the user a moment ... + } + } +} + +void CZIPUnpackProgress::FlushDataToControls() +{ + // texts + if (CacheIsDirty) + { + int index = CacheIndex; + int i; + for (i = ZIP_UNPACK_NUMLINES - 1; i >= 0; i--) + { + if (Lines[i] != NULL) + Lines[i]->SetText(LinesCache[index]); + index--; + if (index < 0) + index = ZIP_UNPACK_NUMLINES - 1; + } + CacheIsDirty = FALSE; + } + + if (TaskBarList3 != NULL) + { + if (HasTwoProgress()) + { + if (Size2IsDirty) + TaskBarList3->SetProgress2(ActualSize2, TotalSize2); + } + else + { + if (SizeIsDirty) + TaskBarList3->SetProgress2(ActualSize, TotalSize); + } + } + + // size + if (SizeIsDirty) + { + if (Summary != NULL) + { + Summary->SetProgress2(ActualSize, TotalSize); + } + SizeIsDirty = FALSE; + } + + // size2 + if (Size2IsDirty) + { + if (Summary2 != NULL) + { + Summary2->SetProgress2(ActualSize2, TotalSize2); + } + Size2IsDirty = FALSE; + } +} + +INT_PTR +CZIPUnpackProgress::DialogProc(UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + if (ResID == IDD_ZIPUNPACKPROG && FileProgress) // it is necessary to replace the text "Total:" with "File:" + SetDlgItemText(HWindow, IDT_PROGTITLE, LoadStr(IDS_UNPACKFILEPROGRESS)); + + SetWindowText(HWindow, Title); + // the entire object assumes that the allocations may have failed + int i; + for (i = 0; i < ZIP_UNPACK_NUMLINES; i++) + { + if ((Lines[i] = new CStaticText(HWindow, IDS_ZIPLINE1 + i, STF_PATH_ELLIPSIS | STF_CACHED_PAINT)) == NULL) + TRACE_E(LOW_MEMORY); + } + if ((Summary = new CProgressBar(HWindow, IDC_ZIPSUMMARY)) == NULL) + TRACE_E(LOW_MEMORY); + if (HasTwoProgress()) + { + if ((Summary2 = new CProgressBar(HWindow, IDC_ZIPSUMMARY2)) == NULL) + TRACE_E(LOW_MEMORY); + } + break; + } + + case WM_DESTROY: + { + if (TaskBarList3 != NULL) + TaskBarList3->SetProgressState(TBPF_NOPROGRESS); + break; + } + + case WM_COMMAND: + { + // if the user clicked the Cancel button and has not confirmed it earlier, ask again + if (LOWORD(wParam) == IDCANCEL && !Cancel) + { + // the Cancel button must be enabled + if (IsWindowEnabled(GetDlgItem(HWindow, IDCANCEL))) + { + // to avoid repainting under the message box, repaint explicitly now + FlushDataToControls(); + + // ask the user whether they want to abort the operation + Cancel = (SalMessageBox(HWindow, LoadStr(IDS_CANCELOPERATION), LoadStr(IDS_QUESTION), + MB_YESNO | MB_ICONQUESTION) == IDYES); + } + } + // do not let the command fall through, otherwise the dialog would close + return TRUE; + } + } + return CCommonDialog::DialogProc(uMsg, wParam, lParam); +} + diff --git a/src/zip_utilities.cpp b/src/zip_utilities.cpp new file mode 100644 index 00000000..377592ae --- /dev/null +++ b/src/zip_utilities.cpp @@ -0,0 +1,1850 @@ +// SPDX-FileCopyrightText: 2023 Taskscape Ltd +// SPDX-License-Identifier: GPL-2.0-or-later +// CommentsTranslationProject: TRANSLATED + +#include "precomp.h" + +#include "menu.h" +#include "cfgdlg.h" +#include "dialogs.h" +#include "mainwnd.h" +#include "plugins.h" +#include "filesbox.h" +#include "fileswnd.h" +#include "stswnd.h" +#include "editwnd.h" +#include "zip.h" +#include "cache.h" +#include "viewer.h" +#include "codetbl.h" +#include "shellib.h" +#include "gui.h" +#include "tasklist.h" +#include "olespy.h" +#include "md5.h" +#include "geticon.h" +#include "pack.h" +extern "C" +{ +#include "shexreg.h" +} +#include "salshlib.h" +#include "crypt\fileenc.h" +#include "crypt\sha1.h" +#include "pwdmngr.h" + +// Globals defined in zip_progress.cpp +extern const char* STR_NONE; +extern CSalamanderDirectory GlobalEmptySalDir; +extern HWND ProgressDialogActivateDrop; + +class CSalamanderBMSearchDataImp : public CSalamanderBMSearchData +{ +protected: + CSearchData Moore; + +public: + CSalamanderBMSearchDataImp() : Moore() {} + + virtual void WINAPI Set(const char* pattern, WORD flags) { Moore.Set(pattern, flags); } + virtual void WINAPI Set(const char* pattern, const int length, WORD flags) { Moore.Set(pattern, length, flags); } + virtual void WINAPI SetFlags(WORD flags) { Moore.SetFlags(flags); } + virtual int WINAPI GetLength() const { return Moore.GetLength(); } + virtual const char* WINAPI GetPattern() const { return Moore.GetPattern(); } + virtual BOOL WINAPI IsGood() const { return Moore.IsGood(); } + virtual int WINAPI SearchForward(const char* text, int length, int start) { return Moore.SearchForward(text, length, start); } + virtual int WINAPI SearchBackward(const char* text, int length) { return Moore.SearchBackward(text, length); } +}; + +CSalamanderBMSearchData* +CSalamanderGeneral::AllocSalamanderBMSearchData() +{ + CALL_STACK_MESSAGE1("CSalamanderGeneral::AllocSalamanderBMSearchData()"); + CSalamanderBMSearchData* ret = new CSalamanderBMSearchDataImp; + if (ret == NULL) + TRACE_E(LOW_MEMORY); + return ret; +} + +void CSalamanderGeneral::FreeSalamanderBMSearchData(CSalamanderBMSearchData* data) +{ + CALL_STACK_MESSAGE1("CSalamanderGeneral::FreeSalamanderBMSearchData()"); + if (data != NULL) + delete ((CSalamanderBMSearchDataImp*)data); +} + +class CSalamanderREGEXPSearchDataImp : public CSalamanderREGEXPSearchData +{ +protected: + CRegularExpression REGEXP; + +public: + CSalamanderREGEXPSearchDataImp() : REGEXP() {} + + virtual BOOL WINAPI Set(const char* pattern, WORD flags) { return REGEXP.Set(pattern, flags); } + virtual BOOL WINAPI SetFlags(WORD flags) { return REGEXP.SetFlags(flags); } + virtual const char* WINAPI GetLastErrorText() const { return REGEXP.GetLastErrorText(); } + virtual const char* WINAPI GetPattern() const { return REGEXP.GetPattern(); } + virtual BOOL WINAPI SetLine(const char* start, const char* end) + { + REGEXP.SetLine(start, end); + return TRUE; + } + virtual int WINAPI SearchForward(int start, int& foundLen) { return REGEXP.SearchForward(start, foundLen); } + virtual int WINAPI SearchBackward(int length, int& foundLen) { return REGEXP.SearchBackward(length, foundLen); } +}; + +CSalamanderREGEXPSearchData* +CSalamanderGeneral::AllocSalamanderREGEXPSearchData() +{ + CALL_STACK_MESSAGE1("CSalamanderGeneral::AllocSalamanderREGEXPSearchData()"); + CSalamanderREGEXPSearchData* ret = new CSalamanderREGEXPSearchDataImp; + return ret; +} + +void CSalamanderGeneral::FreeSalamanderREGEXPSearchData(CSalamanderREGEXPSearchData* data) +{ + CALL_STACK_MESSAGE1("CSalamanderGeneral::FreeSalamanderREGEXPSearchData()"); + if (data != NULL) + delete ((CSalamanderREGEXPSearchDataImp*)data); +} + +struct CSalCommandsAux +{ + int SalCmd; + int Cmd; + int TextID; + DWORD* Enabled; // NULL == "always TRUE" + int Type; +}; + +CSalCommandsAux SalCommandsArray[] = // ends with an item whose 'SalCmd' == -1 + { + {SALCMD_VIEW, CM_VIEW, IDS_MENU_FILES_VIEW, &EnablerViewFile, sctyForFocusedFile}, + {SALCMD_ALTVIEW, CM_ALTVIEW, IDS_MENU_FILES_ALTVIEW, &EnablerViewFile, sctyForFocusedFile}, + {SALCMD_VIEWWITH, CM_VIEW_WITH, IDS_SALCMD_VIEWWITH, &EnablerViewFile, sctyForFocusedFile}, + {SALCMD_EDIT, CM_EDIT, IDS_MENU_FILES_EDIT, &EnablerFileOnDiskOrArchive, sctyForFocusedFile}, + {SALCMD_EDITWITH, CM_EDIT_WITH, IDS_SALCMD_EDITWITH, &EnablerFileOnDiskOrArchive, sctyForFocusedFile}, + + {SALCMD_OPEN, CM_OPEN, IDS_SALCMD_OPEN, NULL, sctyForFocusedFileOrDirectory}, + {SALCMD_QUICKRENAME, CM_RENAMEFILE, IDS_MENU_FILES_RENAME, &EnablerQuickRename, sctyForFocusedFileOrDirectory}, + + {SALCMD_COPY, CM_COPYFILES, IDS_MENU_FILES_COPY, &EnablerFilesCopy, sctyForSelectedFilesAndDirectories}, + {SALCMD_MOVE, CM_MOVEFILES, IDS_MENU_FILES_MOVE, &EnablerFilesMove, sctyForSelectedFilesAndDirectories}, + {SALCMD_EMAIL, CM_EMAILFILES, IDS_MENU_FILES_EMAIL, &EnablerFilesOnDisk, sctyForSelectedFilesAndDirectories}, + {SALCMD_DELETE, CM_DELETEFILES, IDS_MENU_FILES_DELETE, &EnablerFilesDelete, sctyForSelectedFilesAndDirectories}, + {SALCMD_PROPERTIES, CM_PROPERTIES, IDS_MENU_FILES_PROPERTIES, &EnablerShowProperties, sctyForSelectedFilesAndDirectories}, + {SALCMD_CHANGECASE, CM_CHANGECASE, IDS_MENU_FILES_CHANGECASE, &EnablerFilesOnDisk, sctyForSelectedFilesAndDirectories}, + {SALCMD_CHANGEATTRS, CM_CHANGEATTR, IDS_MENU_FILES_CHANGEATTR, &EnablerChangeAttrs, sctyForSelectedFilesAndDirectories}, + {SALCMD_OCCUPIEDSPACE, CM_OCCUPIEDSPACE, IDS_MENU_CMD_OCCUPIED, &EnablerOccupiedSpace, sctyForSelectedFilesAndDirectories}, + + {SALCMD_EDITNEWFILE, CM_EDITNEW, IDS_MENU_FILES_EDITNEW, &EnablerOnDisk, sctyForCurrentPath}, + {SALCMD_REFRESH, CM_ACTIVEREFRESH, IDS_MENU_LEFT_REFRESH, NULL, sctyForCurrentPath}, + {SALCMD_CREATEDIRECTORY, CM_CREATEDIR, IDS_MENU_CMD_CREATEDIR, &EnablerCreateDir, sctyForCurrentPath}, + {SALCMD_DRIVEINFO, CM_DRIVEINFO, IDS_MENU_CMD_DRIVEINFO, &EnablerDriveInfo, sctyForCurrentPath}, + {SALCMD_CALCDIRSIZES, CM_CALCDIRSIZES, IDS_MENU_CMD_CALCDIRSIZES, &EnablerCalcDirSizes, sctyForCurrentPath}, + + {SALCMD_DISCONNECT, CM_DISCONNECTNET, IDS_MENU_CMD_DISCONNECTNET, NULL, sctyForConnectedDrivesAndFS}, + + {-1, -1, -1, NULL, sctyUnknown} // terminator +}; + +int GetWMCommandFromSalCmd(int salCmd) +{ + int index = 0; + while (SalCommandsArray[index].SalCmd != -1) + { + if (SalCommandsArray[index].SalCmd == salCmd) + return SalCommandsArray[index].Cmd; + index++; + } + TRACE_E("You have used CSalamanderGeneral::PostSalamanderCommand for invalid command (" << salCmd << ")!"); + return -1; +} + +BOOL CSalamanderGeneral::GetSalamanderCommand(int salCmd, char* nameBuf, int nameBufSize, BOOL* enabled, + int* type) +{ + CALL_STACK_MESSAGE3("CSalamanderGeneral::GetSalamanderCommand(%d, , %d, ,)", salCmd, nameBufSize); + int index = 0; + while (SalCommandsArray[index].SalCmd != -1) + { + if (SalCommandsArray[index].SalCmd == salCmd) // found it + { + // need to compute the command states; SalCommandsArray uses them + MainWindow->OnEnterIdle(); + + if (nameBuf != NULL && nameBufSize > 0) + { + lstrcpyn(nameBuf, ::LoadStr(SalCommandsArray[index].TextID), nameBufSize); + } + if (SalCommandsArray[index].Enabled != NULL) + { + if (enabled != NULL) + *enabled = *(SalCommandsArray[index].Enabled); + } + if (type != NULL) + *type = SalCommandsArray[index].Type; + + return TRUE; + } + index++; + } + return FALSE; +} + +BOOL CSalamanderGeneral::EnumSalamanderCommands(int* index, int* salCmd, char* nameBuf, int nameBufSize, + BOOL* enabled, int* type) +{ + CALL_STACK_MESSAGE2("CSalamanderGeneral::EnumSalamanderCommands(, , , %d, ,)", nameBufSize); + if (salCmd != NULL) + *salCmd = -1; + if (nameBuf != NULL && nameBufSize > 0) + nameBuf[0] = 0; + if (enabled != NULL) + *enabled = TRUE; + if (type != NULL) + *type = sctyUnknown; + + if (index != NULL && *index >= 0 && SalCommandsArray[*index].SalCmd != -1) + { + if (*index == 0) + { + // need to compute the command states; SalCommandsArray uses them + MainWindow->OnEnterIdle(); + } + + if (salCmd != NULL) + *salCmd = SalCommandsArray[*index].SalCmd; + if (nameBuf != NULL && nameBufSize > 0) + { + lstrcpyn(nameBuf, ::LoadStr(SalCommandsArray[*index].TextID), nameBufSize); + } + if (SalCommandsArray[*index].Enabled != NULL) + { + if (enabled != NULL) + *enabled = *(SalCommandsArray[*index].Enabled); + } + if (type != NULL) + *type = SalCommandsArray[*index].Type; + + (*index)++; + return TRUE; + } + return FALSE; +} + +void CSalamanderGeneral::PostSalamanderCommand(int salCmd) +{ + CALL_STACK_MESSAGE2("CSalamanderGeneral::PostSalamanderCommand(%d)", salCmd); + if (salCmd < 0 || salCmd >= 500) + { + TRACE_E("CSalamanderGeneral::PostSalamanderCommand: salCmd is invalid (" << salCmd << " is not in range 0-499)."); + return; + } + + if (MainThreadID == GetCurrentThreadId()) + { // because of calls from the entry point where Plugin is set to -1 (just to look up plugin data) + // before WM_USER_POSTCMDORUNLOADPLUGIN would arrive, Plugin would be reset (according to the entry point's return value) + CPluginData* data = Plugins.GetPluginData(Plugin); + if (data != NULL) + { + data->Commands.Add(salCmd); + ExecCmdsOrUnloadMarkedPlugins = TRUE; + } + else + { + TRACE_E("Unexpected situation in CSalamanderGeneral::PostSalamanderCommand()."); + } + } + else // outside the entry point the Plugin is certainly set... + { + if (MainWindow != NULL && MainWindow->HWindow != NULL) + { + // check for a call while the entry point is starting (Plugin is set to -1) + if ((INT_PTR)Plugin == -1) + { + TRACE_E("You can call CSalamanderGeneral::PostSalamanderCommand only from main " + "thread when plugin entry-point is not finished yet!"); + } + else + { // 0 - unload, 1 - rebuild menu, 2-501 salCmd, 502-1000501 menuCmd + PostMessage(MainWindow->HWindow, WM_USER_POSTCMDORUNLOADPLUGIN, (WPARAM)Plugin, 2 + salCmd); + } + } + else + { + TRACE_E("Unexpected situation (2) in CSalamanderGeneral::PostSalamanderCommand()."); + } + } +} + +void CSalamanderGeneral::SetUserWorkedOnPanelPath(int panel) +{ + CALL_STACK_MESSAGE2("CSalamanderGeneral::SetUserWorkedOnPanelPath(%d)", panel); + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::SetUserWorkedOnPanelPath() only from main thread!"); + return; + } + CFilesWindow* p = GetPanel(panel); + if (p != NULL) + p->UserWorkedOnThisPath = TRUE; +} + +void CSalamanderGeneral::StoreSelectionOnPanelPath(int panel) +{ + CALL_STACK_MESSAGE2("CSalamanderGeneral::StoreSelectionOnPanelPath(%d)", panel); + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::StoreSelectionOnPanelPath() only from main thread!"); + return; + } + CFilesWindow* p = GetPanel(panel); + if (p != NULL) + p->StoreSelection(); +} + +class CSalamanderMaskGroupImp : public CSalamanderMaskGroup +{ +protected: + CMaskGroup maskGroup; + +public: + CSalamanderMaskGroupImp() : maskGroup() {} + + virtual void WINAPI SetMasksString(const char* masks, BOOL extendedMode) { maskGroup.SetMasksString(masks, extendedMode); } + virtual void WINAPI GetMasksString(char* buffer) { lstrcpyn(buffer, maskGroup.GetMasksString(), MAX_GROUPMASK); } + virtual BOOL WINAPI GetExtendedMode() { return maskGroup.GetExtendedMode(); } + virtual BOOL WINAPI PrepareMasks(int& errorPos) { return maskGroup.PrepareMasks(errorPos); } + virtual BOOL WINAPI AgreeMasks(const char* fileName, const char* fileExt) { return maskGroup.AgreeMasks(fileName, fileExt); } +}; + +CSalamanderMaskGroup* +CSalamanderGeneral::AllocSalamanderMaskGroup() +{ + CALL_STACK_MESSAGE1("CSalamanderGeneral::AllocSalamanderMaskGroup()"); + CSalamanderMaskGroup* ret = new CSalamanderMaskGroupImp; + if (ret == NULL) + TRACE_E(LOW_MEMORY); + return ret; +} + +void CSalamanderGeneral::FreeSalamanderMaskGroup(CSalamanderMaskGroup* maskGroup) +{ + CALL_STACK_MESSAGE1("CSalamanderGeneral::FreeSalamanderMaskGroup()"); + if (maskGroup != NULL) + delete ((CSalamanderMaskGroupImp*)maskGroup); +} + +DWORD +CSalamanderGeneral::UpdateCrc32(const void* buffer, DWORD count, DWORD crcVal) +{ + CALL_STACK_MESSAGE_NONE + return ::UpdateCrc32(buffer, count, crcVal); +} + +class CSalamanderMD5Imp : public CSalamanderMD5 +{ +protected: + MD5 md5; + +public: + CSalamanderMD5Imp() : md5() {} + + virtual void WINAPI Init() { md5.init(); } + virtual void WINAPI Update(const void* input, DWORD input_length) { md5.update((unsigned char*)input, input_length); } + virtual void WINAPI Finalize() { md5.finalize(); } + virtual void WINAPI GetDigest(void* dest) { memcpy(dest, md5.digest, 16); } +}; + +CSalamanderMD5* +CSalamanderGeneral::AllocSalamanderMD5() +{ + CALL_STACK_MESSAGE1("CSalamanderGeneral::AllocSalamanderMD5()"); + CSalamanderMD5Imp* ret = new CSalamanderMD5Imp; + if (ret == NULL) + TRACE_E(LOW_MEMORY); + return ret; +} + +void CSalamanderGeneral::FreeSalamanderMD5(CSalamanderMD5* md5) +{ + CALL_STACK_MESSAGE1("CSalamanderGeneral::FreeSalamanderMD5()"); + if (md5 != NULL) + delete ((CSalamanderMD5Imp*)md5); +} + +BOOL CSalamanderGeneral::LookForSubTexts(char* text, DWORD* varPlacements, int* varPlacementsCount) +{ + CALL_STACK_MESSAGE_NONE + return ::LookForSubTexts(text, varPlacements, varPlacementsCount); +} + +void CSalamanderGeneral::WaitForESCRelease() +{ + CALL_STACK_MESSAGE_NONE + ::WaitForESCRelease(); +} + +DWORD +CSalamanderGeneral::GetMouseWheelScrollLines() +{ + CALL_STACK_MESSAGE_NONE + return ::GetMouseWheelScrollLines(); +} + +DWORD +CSalamanderGeneral::GetMouseWheelScrollChars() +{ + CALL_STACK_MESSAGE_NONE + return ::GetMouseWheelScrollChars(); +} + +HWND CSalamanderGeneral::GetTopVisibleParent(HWND hParent) +{ + CALL_STACK_MESSAGE_NONE + return ::GetTopVisibleParent(hParent); +} + +BOOL CSalamanderGeneral::MultiMonGetDefaultWindowPos(HWND hByWnd, POINT* p) +{ + CALL_STACK_MESSAGE_NONE + return ::MultiMonGetDefaultWindowPos(hByWnd, p); +} + +void CSalamanderGeneral::MultiMonGetClipRectByRect(const RECT* rect, RECT* workClipRect, RECT* monitorClipRect) +{ + CALL_STACK_MESSAGE_NONE + ::MultiMonGetClipRectByRect(rect, workClipRect, monitorClipRect); +} + +void CSalamanderGeneral::MultiMonGetClipRectByWindow(HWND hByWnd, RECT* workClipRect, RECT* monitorClipRect) +{ + CALL_STACK_MESSAGE_NONE + ::MultiMonGetClipRectByWindow(hByWnd, workClipRect, monitorClipRect); +} + +void CSalamanderGeneral::MultiMonCenterWindow(HWND hWindow, HWND hByWnd, BOOL findTopWindow) +{ + CALL_STACK_MESSAGE_NONE + ::MultiMonCenterWindow(hWindow, hByWnd, findTopWindow); +} + +BOOL CSalamanderGeneral::MultiMonEnsureRectVisible(RECT* rect, BOOL partialOK) +{ + CALL_STACK_MESSAGE_NONE + return ::MultiMonEnsureRectVisible(rect, partialOK); +} + +BOOL CSalamanderGeneral::InstallWordBreakProc(HWND hWindow) +{ + CALL_STACK_MESSAGE_NONE + return ::InstallWordBreakProc(hWindow); +} + +BOOL CSalamanderGeneral::IsFirstInstance3OrLater() +{ + CALL_STACK_MESSAGE_NONE + return FirstInstance_3_or_later; +} + +int CSalamanderGeneral::ExpandPluralString(char* buffer, int bufferSize, const char* format, + int parametersCount, const CQuadWord* parametersArray) +{ + CALL_STACK_MESSAGE4("CSalamanderGeneral::ExpandPluralString(, %d, %s, %d, )", + bufferSize, format, parametersCount); + return ::ExpandPluralString(buffer, bufferSize, format, parametersCount, parametersArray); +} + +int CSalamanderGeneral::ExpandPluralFilesDirs(char* buffer, int bufferSize, int files, int dirs, + int mode, BOOL forDlgCaption) +{ + CALL_STACK_MESSAGE6("CSalamanderGeneral::ExpandPluralFilesDirs(, %d, %d, %d, %d, %d)", + bufferSize, files, dirs, (int)mode, forDlgCaption); + return ::ExpandPluralFilesDirs(buffer, bufferSize, files, dirs, mode, forDlgCaption); +} + +int CSalamanderGeneral::ExpandPluralBytesFilesDirs(char* buffer, int bufferSize, + const CQuadWord& selectedBytes, int files, int dirs, + BOOL useSubTexts) +{ + CALL_STACK_MESSAGE6("CSalamanderGeneral::FreeSalamanderMaskGroup(, %d, %g, %d, %d, %d)", + bufferSize, selectedBytes.GetDouble(), files, dirs, useSubTexts); + return ::ExpandPluralBytesFilesDirs(buffer, bufferSize, selectedBytes, files, dirs, useSubTexts); +} + +void CSalamanderGeneral::GetCommonFSOperSourceDescr(char* sourceDescr, int sourceDescrSize, + int panel, int selectedFiles, int selectedDirs, + const char* fileOrDirName, BOOL isDir, + BOOL forDlgCaption) +{ + CALL_STACK_MESSAGE8("CSalamanderGeneral::GetCommonFSOperSourceDescr(, %d, %d, %d, %d, %s, %d, %d)", + sourceDescrSize, panel, selectedFiles, selectedDirs, fileOrDirName, + isDir, forDlgCaption); + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::GetCommonFSOperSourceDescr() only from main thread!"); + return; + } + if (sourceDescrSize <= 0) + return; + if (sourceDescr == NULL) + { + TRACE_E("CSalamanderGeneral::GetCommonFSOperSourceDescr(): 'sourceDescr' may not be NULL!"); + return; + } + if (selectedFiles + selectedDirs <= 1 && panel == -1 && fileOrDirName == NULL) + { + TRACE_E("CSalamanderGeneral::GetCommonFSOperSourceDescr(): 'fileOrDirName' may not be NULL!"); + sourceDescr[0] = 0; + return; + } + if (selectedFiles + selectedDirs <= 1) // one selected item or the focus + { + BOOL nameIsDir; + char* name; + char nameBuf[MAX_PATH]; + if (panel != -1) + { + const CFileData* f; + if (selectedFiles == 0 && selectedDirs == 0) + f = GetPanelFocusedItem(panel, &nameIsDir); + else + { + int index = 0; + f = GetPanelSelectedItem(panel, &index, &nameIsDir); + } + if (f != NULL && f->Name != NULL) + name = f->Name; + else + { + TRACE_E("Unexpected situation in CSalamanderGeneral::GetCommonFSOperSourceDescr()!"); + sourceDescr[0] = 0; + return; + } + } + else + { + lstrcpyn(nameBuf, fileOrDirName, MAX_PATH); + name = nameBuf; + nameIsDir = isDir; + } + int fileNameFormat; + GetConfigParameter(SALCFG_FILENAMEFORMAT, &fileNameFormat, + sizeof(fileNameFormat), NULL); + char formatedFileName[MAX_PATH]; // CFileData::Name is at most MAX_PATH-5 characters long - Salamander's limit + ::AlterFileName(formatedFileName, name, -1, fileNameFormat, 0, nameIsDir); + _snprintf_s(sourceDescr, sourceDescrSize, _TRUNCATE, + ::LoadStr(nameIsDir ? (forDlgCaption ? IDS_DLG_QUESTION_DIRECTORY : IDS_QUESTION_DIRECTORY) : (forDlgCaption ? IDS_DLG_QUESTION_FILE : IDS_QUESTION_FILE)), + formatedFileName); + } + else // multiple directories and files + { + ExpandPluralFilesDirs(sourceDescr, sourceDescrSize, selectedFiles, selectedDirs, + epfdmNormal, forDlgCaption); + } +} + +void CSalamanderGeneral::AddStrToStr(char* dstStr, int dstBufSize, const char* srcStr) +{ + CALL_STACK_MESSAGE1("CSalamanderGeneral::AddStrToStr(, ,)"); + if (dstBufSize < 2) + { + TRACE_E("CSalamanderGeneral::AddStrToStr(): dstBufSize must be greater or equal to 2"); + return; + } + ::AddStrToStr(dstStr, dstBufSize, srcStr); +} + +BOOL CSalamanderGeneral::SalIsValidFileNameComponent(const char* fileNameComponent) +{ + CALL_STACK_MESSAGE1("CSalamanderGeneral::SalIsValidFileNameComponent()"); + return ::SalIsValidFileNameComponent(fileNameComponent); +} + +void CSalamanderGeneral::SalMakeValidFileNameComponent(char* fileNameComponent) +{ + CALL_STACK_MESSAGE1("CSalamanderGeneral::SalMakeValidFileNameComponent()"); + ::SalMakeValidFileNameComponent(fileNameComponent); +} + +BOOL CSalamanderGeneral::IsFileEnumSourcePanel(int srcUID, int* panel) +{ + CALL_STACK_MESSAGE2("CSalamanderGeneral::IsFileEnumSourcePanel(%d,)", srcUID); + return ::IsFileEnumSourcePanel(srcUID, panel); +} + +BOOL CSalamanderGeneral::GetNextFileNameForViewer(int srcUID, int* lastFileIndex, const char* lastFileName, + BOOL preferSelected, BOOL onlyAssociatedExtensions, + char* fileName, BOOL* noMoreFiles, BOOL* srcBusy) +{ + CALL_STACK_MESSAGE5("CSalamanderGeneral::GetNextFileNameForViewer(%d, , , %d, %d, %s, ,)", + srcUID, preferSelected, onlyAssociatedExtensions, fileName); + if (fileName == NULL || lastFileIndex == NULL) + { + if (noMoreFiles != NULL) + *noMoreFiles = FALSE; + if (srcBusy != NULL) + *srcBusy = FALSE; + TRACE_E("CSalamanderGeneral::GetNextFileNameForViewer(): invalid parameters (fileName == NULL || lastFileIndex == NULL)!"); + return FALSE; + } + if (Plugin == NULL || (INT_PTR)Plugin == -1) + { + if (noMoreFiles != NULL) + *noMoreFiles = FALSE; + if (srcBusy != NULL) + *srcBusy = FALSE; + TRACE_E("CSalamanderGeneral::GetNextFileNameForViewer(): unexpected call, plugin is not initialized yet!"); + return FALSE; + } + return ::GetNextFileNameForViewer(srcUID, lastFileIndex, lastFileName, preferSelected, + onlyAssociatedExtensions, fileName, + noMoreFiles, srcBusy, Plugin); +} + +BOOL CSalamanderGeneral::GetPreviousFileNameForViewer(int srcUID, int* lastFileIndex, const char* lastFileName, + BOOL preferSelected, BOOL onlyAssociatedExtensions, + char* fileName, BOOL* noMoreFiles, BOOL* srcBusy) +{ + CALL_STACK_MESSAGE5("CSalamanderGeneral::GetPreviousFileNameForViewer(%d, , , %d, %d, %s, ,)", + srcUID, preferSelected, onlyAssociatedExtensions, fileName); + if (fileName == NULL || lastFileIndex == NULL) + { + if (noMoreFiles != NULL) + *noMoreFiles = FALSE; + if (srcBusy != NULL) + *srcBusy = FALSE; + TRACE_E("CSalamanderGeneral::GetPreviousFileNameForViewer(): invalid parameters (fileName == NULL || lastFileIndex == NULL)!"); + return FALSE; + } + if (Plugin == NULL || (INT_PTR)Plugin == -1) + { + if (noMoreFiles != NULL) + *noMoreFiles = FALSE; + if (srcBusy != NULL) + *srcBusy = FALSE; + TRACE_E("CSalamanderGeneral::GetPreviousFileNameForViewer(): unexpected call, plugin is not initialized yet!"); + return FALSE; + } + return ::GetPreviousFileNameForViewer(srcUID, lastFileIndex, lastFileName, preferSelected, + onlyAssociatedExtensions, fileName, + noMoreFiles, srcBusy, Plugin); +} + +BOOL CSalamanderGeneral::IsFileNameForViewerSelected(int srcUID, int lastFileIndex, + const char* lastFileName, + BOOL* isFileSelected, BOOL* srcBusy) +{ + CALL_STACK_MESSAGE3("CSalamanderGeneral::IsFileNameForViewerSelected(%d, %d, , , ,)", + srcUID, lastFileIndex); + if (isFileSelected == NULL) + { + if (srcBusy != NULL) + *srcBusy = FALSE; + TRACE_E("CSalamanderGeneral::IsFileNameForViewerSelected(): invalid parameters (isFileSelected == NULL)!"); + return FALSE; + } + return ::IsFileNameForViewerSelected(srcUID, lastFileIndex, lastFileName, + isFileSelected, srcBusy); +} + +BOOL CSalamanderGeneral::SetSelectionOnFileNameForViewer(int srcUID, int lastFileIndex, + const char* lastFileName, BOOL select, + BOOL* srcBusy) +{ + CALL_STACK_MESSAGE4("CSalamanderGeneral::SetSelectionOnFileNameForViewer(%d, %d, , %d,)", + srcUID, lastFileIndex, select); + return ::SetSelectionOnFileNameForViewer(srcUID, lastFileIndex, lastFileName, select, srcBusy); +} + +BOOL CSalamanderGeneral::GetStdHistoryValues(int historyID, char*** historyArr, int* historyItemsCount) +{ + CALL_STACK_MESSAGE2("CSalamanderGeneral::GetStdHistoryValues(%d, ,)", historyID); + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::GetStdHistoryValues() only from main thread!"); + if (historyArr != NULL) + *historyArr = NULL; + if (historyItemsCount != NULL) + *historyItemsCount = 0; + return FALSE; + } + if (historyArr == NULL || historyItemsCount == NULL) + { + TRACE_E("CSalamanderGeneral::GetStdHistoryValues(): invalid parameters!"); + return FALSE; + } + switch (historyID) + { + case SALHIST_COPYMOVETGT: + { + *historyArr = Configuration.CopyHistory; + *historyItemsCount = COPY_HISTORY_SIZE; + return TRUE; + } + + case SALHIST_CREATEDIR: + { + *historyArr = Configuration.CreateDirHistory; + *historyItemsCount = CREATEDIR_HISTORY_SIZE; + return TRUE; + } + + case SALHIST_CHANGEDIR: + { + *historyArr = Configuration.ChangeDirHistory; + *historyItemsCount = CHANGEDIR_HISTORY_SIZE; + return TRUE; + } + + case SALHIST_QUICKRENAME: + { + *historyArr = Configuration.QuickRenameHistory; + *historyItemsCount = QUICKRENAME_HISTORY_SIZE; + return TRUE; + } + + case SALHIST_EDITNEW: + { + *historyArr = Configuration.EditNewHistory; + *historyItemsCount = EDITNEW_HISTORY_SIZE; + return TRUE; + } + + case SALHIST_CONVERT: + { + *historyArr = Configuration.ConvertHistory; + *historyItemsCount = CONVERT_HISTORY_SIZE; + return TRUE; + } + + default: + { + *historyArr = NULL; + *historyItemsCount = 0; + return FALSE; + } + } +} + +void CSalamanderGeneral::AddValueToStdHistoryValues(char** historyArr, int historyItemsCount, + const char* value, BOOL caseSensitiveValue) +{ + CALL_STACK_MESSAGE4("CSalamanderGeneral::AddValueToStdHistoryValues(, %d, %s, %d)", + historyItemsCount, value, caseSensitiveValue); + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::AddValueToStdHistoryValues() only from main thread!"); + return; + } + if (historyArr == NULL || value == NULL) + { + TRACE_E("CSalamanderGeneral::AddValueToStdHistoryValues(): 'historyArr' and 'value' may not be NULL!"); + return; + } + ::AddValueToStdHistoryValues(historyArr, historyItemsCount, value, caseSensitiveValue); +} + +void CSalamanderGeneral::LoadComboFromStdHistoryValues(HWND combo, char** historyArr, int historyItemsCount) +{ + CALL_STACK_MESSAGE2("CSalamanderGeneral::LoadComboFromStdHistoryValues(, , %d)", + historyItemsCount); + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::LoadComboFromStdHistoryValues() only from main thread!"); + return; + } + if (historyArr == NULL) + { + TRACE_E("CSalamanderGeneral::LoadComboFromStdHistoryValues(): 'historyArr' may not be NULL!"); + return; + } + ::LoadComboFromStdHistoryValues(combo, historyArr, historyItemsCount); +} + +BOOL CSalamanderGeneral::CanUse256ColorsBitmap() +{ + CALL_STACK_MESSAGE1("CSalamanderGeneral::CanUse256ColorsBitmap()"); + return ::Use256ColorsBitmap(); +} + +HWND CSalamanderGeneral::GetWndToFlash(HWND parent) +{ + CALL_STACK_MESSAGE1("CSalamanderGeneral::GetWndToFlash()"); + return ::GetWndToFlash(parent); +} + +void ActivateDropTarget(HWND dropTarget, HWND progressWnd) +{ + if (dropTarget != NULL) + { // this dirty hack removes the activated state without an active application (visually inactive, but WM_ACTIVATEAPP with "activate" already arrived) + HWND tgtWnd = dropTarget; + HWND tmp; + while ((tmp = ::GetParent(tgtWnd)) != NULL && IsWindowEnabled(tmp)) + tgtWnd = tmp; + if (MainWindow != NULL && tgtWnd != MainWindow->HWindow) + { // perform it only if it is not an operation inside our Salamander + SetForegroundWindow(progressWnd); + SetForegroundWindow(tgtWnd); + // TRACE_I("SetForegroundWindow: " << hex << tgtWnd); + } + } +} + +void CSalamanderGeneral::ActivateDropTarget(HWND dropTarget, HWND progressWnd) +{ + CALL_STACK_MESSAGE1("CSalamanderGeneral::ActivateDropTarget(,)"); + ::ActivateDropTarget(dropTarget, progressWnd); +} + +void CSalamanderGeneral::PostOpenPackDlgForThisPlugin(int delFilesAfterPacking) +{ + CALL_STACK_MESSAGE2("CSalamanderGeneral::PostOpenPackDlgForThisPlugin(%d)", delFilesAfterPacking); + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::PostOpenPackDlgForThisPlugin() only from main thread!"); + return; + } + + CPluginData* data = Plugins.GetPluginData(Plugin); + if (data != NULL) + { + data->OpenPackDlg = TRUE; + data->PackDlgDelFilesAfterPacking = delFilesAfterPacking; + OpenPackOrUnpackDlgForMarkedPlugins = TRUE; + } + else + { + TRACE_E("Unexpected situation in CSalamanderGeneral::PostOpenPackDlgForThisPlugin()."); + } +} + +void CSalamanderGeneral::PostOpenUnpackDlgForThisPlugin(const char* unpackMask) +{ + CALL_STACK_MESSAGE2("CSalamanderGeneral::PostOpenUnpackDlgForThisPlugin(%s)", unpackMask); + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::PostOpenUnpackDlgForThisPlugin() only from main thread!"); + return; + } + + CPluginData* data = Plugins.GetPluginData(Plugin); + if (data != NULL) + { + data->OpenUnpackDlg = TRUE; + if (data->UnpackDlgUnpackMask != NULL) + free(data->UnpackDlgUnpackMask); + if (unpackMask != NULL) + data->UnpackDlgUnpackMask = DupStr(unpackMask); + else + data->UnpackDlgUnpackMask = NULL; + OpenPackOrUnpackDlgForMarkedPlugins = TRUE; + } + else + { + TRACE_E("Unexpected situation in CSalamanderGeneral::PostOpenUnpackDlgForThisPlugin()."); + } +} + +HANDLE +CSalamanderGeneral::SalCreateFileEx(const char* fileName, DWORD desiredAccess, DWORD shareMode, + DWORD flagsAndAttributes, DWORD* err) +{ + CALL_STACK_MESSAGE1("CSalamanderGeneral::SalCreateFileEx()"); + HANDLE ret = ::SalCreateFileEx(fileName, desiredAccess, shareMode, flagsAndAttributes, NULL); + if (err != NULL) + *err = GetLastError(); + return ret; +} + +BOOL CSalamanderGeneral::SalCreateDirectoryEx(const char* name, DWORD* err) +{ + CALL_STACK_MESSAGE1("CSalamanderGeneral::SalCreateDirectoryEx()"); + return ::SalCreateDirectoryEx(name, err); +} + +void CSalamanderGeneral::PanelStopMonitoring(int panel, BOOL stopMonitoring) +{ + CALL_STACK_MESSAGE3("CSalamanderGeneral::PanelStopMonitoring(%d, %d)", panel, stopMonitoring); + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::PanelStopMonitoring() only from main thread!"); + return; + } + CFilesWindow* p = GetPanel(panel); + if (p != NULL) + p->HandsOff(stopMonitoring); +} + +CSalamanderDirectoryAbstract* +CSalamanderGeneral::AllocSalamanderDirectory(BOOL isForFS) +{ + CALL_STACK_MESSAGE2("CSalamanderGeneral::AllocSalamanderDirectory(%d)", isForFS); + CSalamanderDirectory* ret = new CSalamanderDirectory(isForFS); + if (ret == NULL) + TRACE_E(LOW_MEMORY); + else + ret->AllocAddCache(); + return ret; +} + +void CSalamanderGeneral::FreeSalamanderDirectory(CSalamanderDirectoryAbstract* salDir) +{ + CALL_STACK_MESSAGE1("CSalamanderGeneral::FreeSalamanderDirectory()"); + if (salDir != NULL) + delete ((CSalamanderDirectory*)salDir); +} + +BOOL CSalamanderGeneral::AddPluginFSTimer(int timeout, CPluginFSInterfaceAbstract* timerOwner, + DWORD timerParam) // FIXME_X64 - review the Salamander interface to ensure we do not pass parameters that should hold x64 pointers; for example here 'timerParam' +{ + CALL_STACK_MESSAGE3("CSalamanderGeneral::AddPluginFSTimer(%d, , 0x%X)", timeout, timerParam); + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::AddPluginFSTimer() only from main thread!"); + return FALSE; + } + if (timeout < 0) + { + TRACE_E("CSalamanderGeneral::AddPluginFSTimer(): invalid timeout value (" << timeout << ")!"); + return FALSE; + } + if (timerOwner == NULL) + { + TRACE_E("CSalamanderGeneral::AddPluginFSTimer(): invalid timerOwner (NULL)!"); + return FALSE; + } + return Plugins.AddPluginFSTimer(timeout, timerOwner, timerParam); +} + +int CSalamanderGeneral::KillPluginFSTimer(CPluginFSInterfaceAbstract* timerOwner, BOOL allTimers, + DWORD timerParam) +{ + CALL_STACK_MESSAGE3("CSalamanderGeneral::KillPluginFSTimer(, %d, 0x%X)", allTimers, timerParam); + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::KillPluginFSTimer() only from main thread!"); + return 0; + } + if (timerOwner == NULL) + { + TRACE_E("CSalamanderGeneral::KillPluginFSTimer(): invalid timer owner (NULL)!"); + return 0; + } + return Plugins.KillPluginFSTimer(timerOwner, allTimers, timerParam); +} + +BOOL CSalamanderGeneral::GetChangeDriveMenuItemVisibility() +{ + CALL_STACK_MESSAGE1("CSalamanderGeneral::GetChangeDriveMenuItemVisibility()"); + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::GetChangeDriveMenuItemVisibility() only from main thread!"); + return FALSE; + } + CPluginData* data = Plugins.GetPluginData(Plugin); + if (data != NULL) + return data->ChDrvMenuFSItemVisible; + else + TRACE_E("Unexpected situation in CSalamanderGeneral::GetChangeDriveMenuItemVisibility()."); + return FALSE; +} + +void CSalamanderGeneral::SetChangeDriveMenuItemVisibility(BOOL visible) +{ + CALL_STACK_MESSAGE2("CSalamanderGeneral::SetChangeDriveMenuItemVisibility(%d)", visible); + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::SetChangeDriveMenuItemVisibility() only from main thread!"); + return; + } + CPluginData* data = Plugins.GetPluginData(Plugin); + if (data != NULL) + data->ChDrvMenuFSItemVisible = (visible != FALSE); + else + TRACE_E("Unexpected situation in CSalamanderGeneral::SetChangeDriveMenuItemVisibility()."); +} + +void CSalamanderGeneral::OleSpySetBreak(int alloc) +{ + CALL_STACK_MESSAGE2("CSalamanderGeneral::OleSpySetBreak(%d)", alloc); + ::OleSpySetBreak(alloc); +} + +HICON +CSalamanderGeneral::GetSalamanderIcon(int icon, int iconSize) +{ + CALL_STACK_MESSAGE3("CSalamanderGeneral::GetSalamanderIcon(%d, %d)", icon, iconSize); + + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::GetSalamanderIcon() only from main thread!"); + return NULL; + } + + CSymbolsImageListIndexes iconIndex; + switch (icon) + { + case SALICON_EXECUTABLE: + iconIndex = symbolsExecutable; + break; + case SALICON_DIRECTORY: + iconIndex = symbolsDirectory; + break; + case SALICON_NONASSOCIATED: + iconIndex = symbolsNonAssociated; + break; + case SALICON_ASSOCIATED: + iconIndex = symbolsAssociated; + break; + case SALICON_UPDIR: + iconIndex = symbolsUpDir; + break; + case SALICON_ARCHIVE: + iconIndex = symbolsArchive; + break; + default: + { + TRACE_E("CSalamanderGeneral::GetSalamanderIcon: invalid icon=" << icon << " forcing SALICON_NONASSOCIATED"); + iconIndex = symbolsNonAssociated; + } + } + + CIconSizeEnum salIconSize; + switch (iconSize) + { + case SALICONSIZE_16: + salIconSize = ICONSIZE_16; + break; + case SALICONSIZE_32: + salIconSize = ICONSIZE_32; + break; + case SALICONSIZE_48: + salIconSize = ICONSIZE_48; + break; + default: + { + TRACE_E("CSalamanderGeneral::GetSalamanderIcon: invalid iconSize=" << iconSize << " forcing SALICONSIZE_16"); + salIconSize = ICONSIZE_16; + } + } + + CIconList* list = SimpleIconLists[salIconSize]; + if (list == NULL) + { + TRACE_E("CSalamanderGeneral::GetSalamanderIcon: list == NULL"); + return NULL; + } + + return list->GetIcon(iconIndex, FALSE); +} + +BOOL CSalamanderGeneral::GetFileIcon(const char* path, BOOL pathIsPIDL, HICON* hIcon, int iconSize, + BOOL fallbackToDefIcon, BOOL defIconIsDir) +{ + CALL_STACK_MESSAGE5("CSalamanderGeneral::GetFileIcon(, %d, , %d, %d, %d)", + pathIsPIDL, iconSize, fallbackToDefIcon, defIconIsDir); + CIconSizeEnum salIconSize; + switch (iconSize) + { + case SALICONSIZE_16: + salIconSize = ICONSIZE_16; + break; + case SALICONSIZE_32: + salIconSize = ICONSIZE_32; + break; + case SALICONSIZE_48: + salIconSize = ICONSIZE_48; + break; + default: + { + TRACE_E("CSalamanderGeneral::GetFileIcon: invalid iconSize=" << iconSize << " forcing SALICONSIZE_16"); + salIconSize = ICONSIZE_16; + } + } + return ::GetFileIcon(path, pathIsPIDL, hIcon, salIconSize, fallbackToDefIcon, defIconIsDir); +} + +class CSalamanderPNG : public CSalamanderPNGAbstract +{ +public: + virtual HBITMAP WINAPI LoadPNGBitmap(HINSTANCE hInstance, LPCTSTR lpBitmapName, DWORD flags, COLORREF unused) + { + HBITMAP hBitmap = ::LoadPNGBitmap(hInstance, lpBitmapName, flags); + if (hBitmap != NULL) // the handle is handed over to the plug-in; the plug-in is responsible for destroying it, remove it from Salamander HANDLES + HANDLES_REMOVE(hBitmap, __htHandle_comp_with_DeleteObject, "DeleteObject"); + return hBitmap; + } + + virtual HBITMAP WINAPI LoadRawPNGBitmap(const void* rawPNG, DWORD rawPNGSize, DWORD flags, COLORREF unused) + { + HBITMAP hBitmap = ::LoadRawPNGBitmap(rawPNG, rawPNGSize, flags); + if (hBitmap != NULL) // the handle is handed over to the plug-in; the plug-in is responsible for destroying it, remove it from Salamander HANDLES + HANDLES_REMOVE(hBitmap, __htHandle_comp_with_DeleteObject, "DeleteObject"); + return hBitmap; + } +}; + +CSalamanderPNG SalamanderPNG; + +CSalamanderPNGAbstract* +CSalamanderGeneral::GetSalamanderPNG() +{ + CALL_STACK_MESSAGE_NONE + return &SalamanderPNG; +} + +CSalamanderPasswordManagerAbstract* +CSalamanderGeneral::GetSalamanderPasswordManager() +{ + CALL_STACK_MESSAGE1("CSalamanderGeneral::GetSalamanderPasswordManager()"); + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::GetSalamanderPasswordManager() only from main thread!"); + return NULL; + } + CPluginData* data = Plugins.GetPluginData(Plugin); + if (data != NULL) + return &data->SalamanderPasswordManager; + else + TRACE_E("Unexpected situation in CSalamanderGeneral::GetSalamanderPasswordManager()."); + return NULL; +} + +class CSalamanderCrypt : public CSalamanderCryptAbstract +{ +public: + /* AES functions */ + virtual int WINAPI AESInit(CSalAES* aes, + int mode, /* Mode (key size) to be used (input) */ + LPCSTR pwd, /* User specified password (input) */ + size_t pwd_len, /* Password length (input) */ + LPBYTE salt, /* Salt (input) */ + LPWORD pwd_ver) /* 2 byte password verifier (output) */ + { + _ASSERT(sizeof(aes->nonce) == sizeof(((fcrypt_ctx*)aes)->nonce)); + _ASSERT(sizeof(aes->encr_bfr) == sizeof(((fcrypt_ctx*)aes)->encr_bfr)); + _ASSERT(sizeof(aes->encr_ctx) == sizeof(((fcrypt_ctx*)aes)->encr_ctx)); + _ASSERT(sizeof(aes->auth_ctx) == sizeof(((fcrypt_ctx*)aes)->auth_ctx)); + _ASSERT(sizeof(aes->nonce) == sizeof(((fcrypt_ctx*)aes)->nonce)); + _ASSERT(sizeof(CSalAES) == sizeof(fcrypt_ctx)); + return fcrypt_init(mode, (unsigned char*)pwd, (unsigned int)pwd_len, salt, (unsigned char*)pwd_ver, (fcrypt_ctx*)aes); + } + + virtual void WINAPI AESEncrypt(CSalAES* aes, LPVOID data, size_t dataLen) + { + fcrypt_encrypt((unsigned char*)data, (unsigned int)dataLen, (fcrypt_ctx*)aes); + } + + virtual void WINAPI AESDecrypt(CSalAES* aes, LPVOID data, size_t dataLen) + { + fcrypt_decrypt((unsigned char*)data, (unsigned int)dataLen, (fcrypt_ctx*)aes); + } + + virtual void WINAPI AESEnd(CSalAES* aes, LPBYTE mac, LPDWORD pMacLen) + { + if (pMacLen) + *pMacLen = SAL_AES_MAC_LENGTH(aes->mode); + fcrypt_end(mac, (fcrypt_ctx*)aes); + } + + /* SHA1 functions */ + virtual void WINAPI SHA1Init(CSalSHA1* sha1) + { + _ASSERT(sizeof(CSalSHA1) == sizeof(SHA1_Context)); + ::SHA1Init((SHA1_CTX*)sha1); + } + + virtual void WINAPI SHA1Update(CSalSHA1* sha1, const LPBYTE data, size_t dataLen) + { + ::SHA1Update((SHA1_CTX*)sha1, data, (unsigned int)dataLen); + } + + virtual void WINAPI SHA1Final(CSalSHA1* sha1, BYTE digest[20]) + { + ::SHA1Final(digest, (SHA1_CTX*)sha1); + } +}; + +CSalamanderCrypt SalamanderCrypt; + +CSalamanderCryptAbstract* GetSalamanderCrypt() // for Salamander's internal use +{ + CALL_STACK_MESSAGE_NONE + return &SalamanderCrypt; +} + +CSalamanderCryptAbstract* +CSalamanderGeneral::GetSalamanderCrypt() +{ + CALL_STACK_MESSAGE_NONE + return &SalamanderCrypt; +} + +BOOL CSalamanderGeneral::FileExists(const char* fileName) +{ + CALL_STACK_MESSAGE2("CSalamanderGeneral::FileExists(%s)", fileName); + return ::FileExists(fileName); +} + +void CSalamanderGeneral::DisconnectFSFromPanel(HWND parent, int panel) +{ + CALL_STACK_MESSAGE2("CSalamanderGeneral::DisconnectFSFromPanel(, %d)", panel); + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::DisconnectFSFromPanel() only from main thread!"); + return; + } + char buf[MAX_PATH]; + BOOL rescueOrFixed = TRUE; + if (GetLastWindowsPanelPath(panel, buf, MAX_PATH)) + { // change the path to the last visited Windows path + BOOL tryNet = FALSE; + DWORD err; + DWORD lastErr; + BOOL pathInvalid; + BOOL cut; + if (::SalCheckAndRestorePathWithCut(parent, buf, tryNet, err, lastErr, + pathInvalid, cut, TRUE)) + { + int failReason; + if (ChangePanelPathToDisk(panel, buf, &failReason) || + failReason != CHPPFR_INVALIDPATH) // except for the "bad path" error (closing the FS was refused, etc.) + { + rescueOrFixed = FALSE; + } + } + } + if (rescueOrFixed) + ChangePanelPathToRescuePathOrFixedDrive(panel); // "always false" +} + +BOOL CSalamanderGeneral::IsArchiveHandledByThisPlugin(const char* name) +{ + CALL_STACK_MESSAGE2("CSalamanderGeneral::IsArchiveHandledByThisPlugin(%s)", name); + if (Plugin == NULL || (INT_PTR)Plugin == -1) + { + TRACE_E("CSalamanderGeneral::IsArchiveHandledByThisPlugin() unexpected call, plugin is not initialized yet!"); + return FALSE; + } + CPluginData* data = Plugins.GetPluginData(Plugin); + if (data == NULL) + { + TRACE_E("Unexpected situation in CSalamanderGeneral::IsArchiveHandledByThisPlugin()"); + return FALSE; + } + if (!data->SupportPanelView) + return FALSE; + + int format = PackerFormatConfig.PackIsArchive(name); + if (format != 0) // found a supported archive + { + format--; + int index = PackerFormatConfig.GetUnpackerIndex(format); + if (index < 0) // view: is it internal processing (plug-in)? + { + CPluginData* foundData = Plugins.Get(-index - 1); + if (foundData == data) // is it us? + return TRUE; + } + } + return FALSE; +} + +DWORD +CSalamanderGeneral::GetIconLRFlags() +{ + return IconLRFlags; +} + +int CSalamanderGeneral::IsFileLink(const char* fileExtension) +{ + CALL_STACK_MESSAGE_NONE + // CALL_STACK_MESSAGE1("CSalamanderGeneral::IsFileLink()"); + if (fileExtension == NULL) + return 0; + return ::IsFileLink(fileExtension); +} + +DWORD +CSalamanderGeneral::GetImageListColorFlags() +{ + CALL_STACK_MESSAGE_NONE + return ::GetImageListColorFlags(); +} + +void CSalamanderGeneral::SetHelpFileName(const char* chmName) +{ + CALL_STACK_MESSAGE_NONE + if (chmName == NULL || *chmName == 0) + TRACE_E("CSalamanderGeneral::SetHelpFileName(): invalid parameter 'chmName'."); + else + lstrcpyn(HelpFileName, chmName, MAX_PATH); +} + +BOOL CSalamanderGeneral::OpenHtmlHelp(HWND parent, CHtmlHelpCommand command, DWORD_PTR dwData, BOOL quiet) +{ + CALL_STACK_MESSAGE4("CSalamanderGeneral::OpenHtmlHelp(, %d, %Iu, %d)", command, dwData, quiet); + if (HelpFileName[0] == 0) + { + TRACE_E("CSalamanderGeneral::OpenHtmlHelp(): plugin must call CSalamanderGeneral::SetHelpFileName() first!"); + return FALSE; + } + else + { + return ::OpenHtmlHelp(HelpFileName, parent, command, dwData, quiet); + } +} + +BOOL CSalamanderGeneral::OpenHtmlHelpForSalamander(HWND parent, CHtmlHelpCommand command, DWORD_PTR dwData, BOOL quiet) +{ + CALL_STACK_MESSAGE4("CSalamanderGeneral::OpenHtmlHelpForSalamander(, %d, %Iu, %d)", command, dwData, quiet); + DWORD_PTR newData = dwData; + if (command == HHCDisplayContext) + { + switch (dwData) + { + case HTMLHELP_SALID_PWDMANAGER: + newData = IDH_PWDMANAGER; + break; // password manager help + default: + { + TRACE_E("CSalamanderGeneral::OpenHtmlHelpForSalamander(): invalid dwData parameter, see allowed HTMLHELP_SALID_XXX constants."); + return FALSE; + } + } + } + return ::OpenHtmlHelp(NULL, parent, command, newData, quiet); +} + +BOOL CSalamanderGeneral::PathsAreOnTheSameVolume(const char* path1, const char* path2, + BOOL* resIsOnlyEstimation) +{ + CALL_STACK_MESSAGE1("CSalamanderGeneral::PathsAreOnTheSameVolume(, ,)"); + return ::PathsAreOnTheSameVolume(path1, path2, resIsOnlyEstimation); +} + +BOOL CSalamanderGeneral::SafeGetOpenFileName(LPOPENFILENAME lpofn) +{ + CALL_STACK_MESSAGE_NONE + return ::SafeGetOpenFileName(lpofn); +} + +BOOL CSalamanderGeneral::SafeGetSaveFileName(LPOPENFILENAME lpofn) +{ + CALL_STACK_MESSAGE_NONE + return ::SafeGetSaveFileName(lpofn); +} + +void CSalamanderGeneral::SetPluginIsNethood() +{ + CALL_STACK_MESSAGE_NONE + if (MainThreadID != GetCurrentThreadId() || (INT_PTR)Plugin != -1) + { + TRACE_E("You can call CSalamanderGeneral::SetPluginIsNethood() only from entry-point!"); + return; + } + CPluginData* data = Plugins.GetPluginData(Plugin); + if (data != NULL) + data->PluginIsNethood = TRUE; + else + TRACE_E("Unexpected situation in CSalamanderGeneral::SetPluginIsNethood()."); +} + +void CSalamanderGeneral::SetPluginUsesPasswordManager() +{ + CALL_STACK_MESSAGE_NONE + if (MainThreadID != GetCurrentThreadId() || (INT_PTR)Plugin != -1) + { + TRACE_E("You can call CSalamanderGeneral::SetPluginUsesPasswordManager() only from entry-point!"); + return; + } + CPluginData* data = Plugins.GetPluginData(Plugin); + if (data != NULL) + data->PluginUsesPasswordManager = TRUE; + else + TRACE_E("Unexpected situation in CSalamanderGeneral::SetPluginUsesPasswordManager()."); +} + +void CSalamanderGeneral::OpenNetworkContextMenu(HWND parent, int panel, BOOL forItems, int menuX, + int menuY, const char* netPath, char* newlyMappedDrive) +{ + CALL_STACK_MESSAGE6("CSalamanderGeneral::OpenNetworkContextMenu(, %d, %d, %d, %d, %s,)", + panel, forItems, menuX, menuY, netPath); + + if (newlyMappedDrive != NULL) + *newlyMappedDrive = 0; + + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::OpenNetworkContextMenu() only from main thread!"); + return; + } + + if (netPath == NULL || netPath[0] != '\\' || netPath[1] != '\\' || strchr(netPath + 2, '\\') != NULL) + { + TRACE_E("CSalamanderGeneral::OpenNetworkContextMenu(): invalid netPath: " << (netPath != NULL ? netPath : "(null)")); + return; + } + + CFilesWindow* p = GetPanel(panel); + if (p != NULL) + { + BeginStopRefresh(); // no refreshes needed (formality: the call comes from a plug-in, so refreshes are already disabled by EnterPlugin) + + int* indexes = NULL; + int index = 0; + int count = 0; + if (forItems) + { + BOOL subDir; + if (p->Dirs->Count > 0) + subDir = (strcmp(p->Dirs->At(0).Name, "..") == 0); + else + subDir = FALSE; + + count = p->GetSelCount(); + if (count != 0) + { + indexes = new int[count]; + p->GetSelItems(count, indexes, TRUE); // we stepped back from this (see GetSelItems): for context menus we start from the focused item and end with the item before the focus (there is an intermediate wrap to the start of the name list) (the system does the same, e.g., Add To Windows Media Player List on MP3 files) + } + else + { + index = p->GetCaretIndex(); + if (subDir && index == 0) + { + EndStopRefresh(); + return; + } + } + } + else + index = -1; + + if (p->ContextMenu != NULL) + { + TRACE_E("CSalamanderGeneral::OpenNetworkContextMenu: p->ContextMenu must be NULL (probably forbidden recursive call)!"); + } + else + { + if (forItems) + { + CTmpEnumData data; + data.Indexes = (count == 0) ? &index : indexes; + data.Panel = p; + p->ContextMenu = CreateIContextMenu2(MainWindow->HWindow, netPath, (count == 0) ? 1 : count, + EnumFileNames, &data); + } + else + { + p->ContextMenu = CreateIContextMenu2(MainWindow->HWindow, netPath); + } + + HMENU h = CreatePopupMenu(); + if (p->ContextMenu != NULL && h != NULL) + { + UINT flags = CMF_NORMAL | CMF_EXPLORE; + // handle the pressed Shift key - extended context menu; under W2K it contains, for example, Run as... +#define CMF_EXTENDEDVERBS 0x00000100 // rarely used verbs + BOOL shiftPressed = (GetKeyState(VK_SHIFT) & 0x8000) != 0; + if (shiftPressed) + flags |= CMF_EXTENDEDVERBS; + + ShellActionAux5(flags, p, h); + RemoveUselessSeparatorsFromMenu(h); + + int cmd = 0; + if (GetMenuItemCount(h) > 0) // guard against a completely trimmed menu + { + CMenuPopup contextPopup; + contextPopup.SetTemplateMenu(h); + cmd = contextPopup.Track(MENU_TRACK_RETURNCMD | MENU_TRACK_RIGHTBUTTON, + menuX, menuY, parent, NULL); + } + if (cmd != 0) + { + CALL_STACK_MESSAGE1("CSalamanderGeneral::OpenNetworkContextMenu::exec"); + + char cmdName[2000]; // deliberately 2000 instead of 200; shell extensions sometimes write double (considering: Unicode = 2 * "number of characters"), etc. + if (AuxGetCommandString(p->ContextMenu, cmd, GCS_VERB, NULL, cmdName, 200) != NOERROR) + cmdName[0] = 0; + + // the Map Network Drive command is 40 on XP, 43 on W2K, and only under Vista has a defined cmdName + if ((stricmp(cmdName, "connectNetworkDrive") == 0 || + !WindowsVistaAndLater && cmd == 40) && + forItems && netPath[2] != 0) + { + char root[MAX_PATH]; + strcpy(root, netPath); + int focus = p->GetCaretIndex(); + if (SalPathAppend(root, (focus < p->Dirs->Count ? p->Dirs->At(focus) : p->Files->At(focus - p->Dirs->Count)).Name, MAX_PATH)) + { + char newDrive = 0; + p->ConnectNet(TRUE, root, FALSE /* called from a plug-in; must not change the panel path, otherwise + we would return to a deallocated FS object */ + , + &newDrive); + if (newlyMappedDrive != NULL) + *newlyMappedDrive = newDrive; + } + } + else + { + CShellExecuteWnd shellExecuteWnd; + CMINVOKECOMMANDINFOEX ici; + ZeroMemory(&ici, sizeof(CMINVOKECOMMANDINFOEX)); + ici.cbSize = sizeof(CMINVOKECOMMANDINFOEX); + ici.fMask = CMIC_MASK_PTINVOKE; + if (CanUseShellExecuteWndAsParent(cmdName)) + ici.hwnd = shellExecuteWnd.Create(MainWindow->HWindow, "SEW: CSalamanderGeneral::OpenNetworkContextMenu cmd=%d", cmd); + else + ici.hwnd = MainWindow->HWindow; + ici.lpVerb = MAKEINTRESOURCE(cmd); + ici.nShow = SW_SHOWNORMAL; + ici.ptInvoke.x = menuX; + ici.ptInvoke.y = menuY; + + AuxInvokeCommand(p, (CMINVOKECOMMANDINFO*)&ici); + + IdleRefreshStates = TRUE; // during the next Idle force checking the status variables + IdleCheckClipboard = TRUE; // also request checking the clipboard + } + } + } + { + CALL_STACK_MESSAGE1("CSalamanderGeneral::OpenNetworkContextMenu::release"); + ShellActionAux6(p); + if (h != NULL) + DestroyMenu(h); + } + } + + if (count != 0) + delete[] (indexes); + + EndStopRefresh(); + } +} + +BOOL CSalamanderGeneral::DuplicateBackslashes(char* buffer, int bufferSize) +{ + CALL_STACK_MESSAGE3("CSalamanderGeneral::DuplicateBackslashes(%s, %d)", buffer, bufferSize); + return ::DuplicateBackslashes(buffer, bufferSize); +} + +int CSalamanderGeneral::StartThrobber(int panel, const char* tooltip, int delay) +{ + CALL_STACK_MESSAGE4("CSalamanderGeneral::StartThrobber(%d, %s, %d)", panel, tooltip, delay); + + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::StartThrobber() only from main thread!"); + return -1; + } + + CFilesWindow* p = GetPanel(panel); + if (p != NULL && p->DirectoryLine != NULL) + { + p->DirectoryLine->SetThrobber(TRUE, delay); + p->DirectoryLine->SetThrobberTooltip(tooltip); + return p->DirectoryLine->ChangeThrobberID(); + } + return -1; +} + +BOOL CSalamanderGeneral::StopThrobber(int id) +{ + CALL_STACK_MESSAGE2("CSalamanderGeneral::StopThrobber(%d)", id); + + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::StopThrobber() only from main thread!"); + return FALSE; + } + + if (MainWindow->LeftPanel->DirectoryLine != NULL && + MainWindow->LeftPanel->DirectoryLine->IsThrobberVisible(id)) + { + MainWindow->LeftPanel->DirectoryLine->SetThrobber(FALSE); + return TRUE; + } + if (MainWindow->RightPanel->DirectoryLine != NULL && + MainWindow->RightPanel->DirectoryLine->IsThrobberVisible(id)) + { + MainWindow->RightPanel->DirectoryLine->SetThrobber(FALSE); + return TRUE; + } + return FALSE; +} + +void CSalamanderGeneral::ShowSecurityIcon(int panel, BOOL showIcon, BOOL isLocked, + const char* tooltip) +{ + CALL_STACK_MESSAGE5("CSalamanderGeneral::ShowSecurityIcon(%d, %d, %d, %s)", + panel, showIcon, isLocked, tooltip); + + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::ShowSecurityIcon() only from main thread!"); + return; + } + + CFilesWindow* p = GetPanel(panel); + if (p != NULL && p->DirectoryLine != NULL) + { + p->DirectoryLine->SetSecurity(showIcon ? (isLocked ? sisSecured : sisUnsecured) : sisNone); + p->DirectoryLine->SetSecurityTooltip(tooltip); + } +} + +void CSalamanderGeneral::RemoveCurrentPathFromHistory(int panel) +{ + CALL_STACK_MESSAGE2("CSalamanderGeneral::RemoveCurrentPathFromHistory(%d)", panel); + + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::RemoveCurrentPathFromHistory() only from main thread!"); + return; + } + + CFilesWindow* p = GetPanel(panel); + if (p != NULL) + { + p->RemoveCurrentPathFromHistory(); + if (MainWindow != NULL) + MainWindow->DirHistoryRemoveActualPath(p); + p->UserWorkedOnThisPath = FALSE; + } +} + +BOOL CSalamanderGeneral::IsUserAdmin() +{ + CALL_STACK_MESSAGE1("CSalamanderGeneral::IsUserAdmin()"); + return ::IsUserAdmin(); +} + +BOOL CSalamanderGeneral::IsRemoteSession() +{ + CALL_STACK_MESSAGE1("CSalamanderGeneral::IsRemoteSession()"); + return ::IsRemoteSession(); +} + +DWORD +CSalamanderGeneral::SalWNetAddConnection2Interactive(LPNETRESOURCE lpNetResource) +{ + CALL_STACK_MESSAGE1("CSalamanderGeneral::SalWNetAddConnection2Interactive()"); + DWORD err; + RestoreNetworkConnection(NULL, NULL, NULL, &err, lpNetResource); + return err; +} + +void CSalamanderGeneral::GetFocusedItemMenuPos(POINT* pos) +{ + CALL_STACK_MESSAGE1("CSalamanderGeneral::GetFocusedItemMenuPos()"); + + if (pos == NULL) + { + TRACE_E("CSalamanderGeneral::GetFocusedItemMenuPos(): invalid 'pos' (NULL)!"); + return; + } + + pos->x = 0; + pos->y = 0; + + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::GetFocusedItemMenuPos() only from main thread!"); + return; + } + + if (MainWindow != NULL) + { + CFilesWindow* activePanel = MainWindow->GetActivePanel(); + if (activePanel != NULL) + { + activePanel->GetContextMenuPos(pos); + return; + } + } + + pos->x = 0; + pos->y = 0; +} + +void CSalamanderGeneral::LockMainWindow(BOOL lock, HWND hToolWnd, const char* lockReason) +{ + CALL_STACK_MESSAGE4("CSalamanderGeneral::LockMainWindow(%d, 0x%p, %s)", lock, hToolWnd, lockReason); + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::LockMainWindow() only from main thread!"); + return; + } + if (MainWindow != NULL) + MainWindow->LockUI(lock, hToolWnd, lockReason); +} + +BOOL CSalamanderGeneral::GetMenuItemHotKey(int id, WORD* hotKey, char* hotKeyText, int hotKeyTextSize) +{ + CALL_STACK_MESSAGE2("CSalamanderGeneral::GetMenuItemHotKey(%d, , , )", id); + if (MainThreadID != GetCurrentThreadId()) + { + TRACE_E("You can call CSalamanderGeneral::GetMenuItemHotKey() only from main thread!"); + return FALSE; + } + BOOL ret = FALSE; + CPluginData* data = Plugins.GetPluginData(Plugin); + if (data != NULL) + ret = data->GetMenuItemHotKey(id, hotKey, hotKeyText, hotKeyTextSize); + else + TRACE_E("Unexpected situation in CSalamanderGeneral::GetMenuItemHotKey()."); + return ret; +} + +LONG CSalamanderGeneral::SalRegQueryValue(HKEY hKey, LPCSTR lpSubKey, LPSTR lpData, PLONG lpcbData) +{ + CALL_STACK_MESSAGE1("CSalamanderGeneral::SalRegQueryValue(, , ,)"); + return ::SalRegQueryValue(hKey, lpSubKey, lpData, lpcbData); +} + +LONG CSalamanderGeneral::SalRegQueryValueEx(HKEY hKey, LPCSTR lpValueName, LPDWORD lpReserved, + LPDWORD lpType, LPBYTE lpData, LPDWORD lpcbData) +{ + CALL_STACK_MESSAGE1("CSalamanderGeneral::SalRegQueryValueEx(, , , , ,)"); + return ::SalRegQueryValueEx(hKey, lpValueName, lpReserved, lpType, lpData, lpcbData); +} + +DWORD +CSalamanderGeneral::SalGetFileAttributes(const char* fileName) +{ + CALL_STACK_MESSAGE1("CSalamanderGeneral::SalGetFileAttributes()"); + return ::SalGetFileAttributes(fileName); +} + +BOOL CSalamanderGeneral::IsPathOnSSD(const char* path) +{ + CALL_STACK_MESSAGE1("CSalamanderGeneral::IsPathOnSSD()"); + return ::IsPathOnSSD(path); +} + +BOOL CSalamanderGeneral::IsUNCPath(const char* path) +{ + CALL_STACK_MESSAGE1("CSalamanderGeneral::IsUNCPath()"); + return ::IsUNCPath(path); +} + +BOOL CSalamanderGeneral::ResolveSubsts(char* resPath) +{ + CALL_STACK_MESSAGE1("CSalamanderGeneral::ResolveSubsts()"); + return ::ResolveSubsts(resPath); +} + +void CSalamanderGeneral::ResolveLocalPathWithReparsePoints(char* resPath, const char* path, BOOL* cutResPathIsPossible, + BOOL* rootOrCurReparsePointSet, char* rootOrCurReparsePoint, + char* junctionOrSymlinkTgt, int* linkType, char* netPath) +{ + CALL_STACK_MESSAGE1("CSalamanderGeneral::ResolveLocalPathWithReparsePoints()"); + ::ResolveLocalPathWithReparsePoints(resPath, path, cutResPathIsPossible, + rootOrCurReparsePointSet, rootOrCurReparsePoint, + junctionOrSymlinkTgt, linkType, netPath); +} + +BOOL CSalamanderGeneral::GetResolvedPathMountPointAndGUID(const char* path, char* mountPoint, char* guidPath) +{ + CALL_STACK_MESSAGE2("CSalamanderGeneral::GetResolvedPathMountPointAndGUID(%s, ,)", path); + return ::GetResolvedPathMountPointAndGUID(path, mountPoint, guidPath); +} + +BOOL CSalamanderGeneral::PointToLocalDecimalSeparator(char* buffer, int bufferSize) +{ + CALL_STACK_MESSAGE1("CSalamanderGeneral::PointToLocalDecimalSeparator()"); + return ::PointToLocalDecimalSeparator(buffer, bufferSize); +} + +void CSalamanderGeneral::SetPluginIconOverlays(int iconOverlaysCount, HICON* iconOverlays) +{ + CALL_STACK_MESSAGE2("CSalamanderGeneral::SetPluginIconOverlays(%d,)", iconOverlaysCount); + if (MainThreadID != GetCurrentThreadId()) + { // this is a call error; we do not release the icons, not interested... + TRACE_E("You can call CSalamanderGeneral::SetPluginIconOverlays() only from main thread!"); + return; + } + CPluginData* data = Plugins.GetPluginData(Plugin); + if (data != NULL) + { + data->ReleaseIconOverlays(); + if (iconOverlaysCount > 0) + { + if (iconOverlays != NULL) + { + data->IconOverlays = (HICON*)malloc(3 * iconOverlaysCount * sizeof(HICON)); + memcpy(data->IconOverlays, iconOverlays, 3 * iconOverlaysCount * sizeof(HICON)); + data->IconOverlaysCount = iconOverlaysCount; + + BOOL err = FALSE; + for (int i = 0; i < 3 * iconOverlaysCount; i++) + { + if (data->IconOverlays[i] == NULL) + { + if (!err) + TRACE_E("CSalamanderGeneral::SetPluginIconOverlays(): invalid 'iconOverlays' (contains at least one NULL instead of icon handle)!"); + err = TRUE; + } + else + HANDLES_ADD(__htIcon, __hoLoadImage, data->IconOverlays[i]); + } + if (err) + data->ReleaseIconOverlays(); + } + else + TRACE_E("CSalamanderGeneral::SetPluginIconOverlays(): invalid 'iconOverlays' (NULL)!"); + } + else + { + if (iconOverlaysCount < 0) + TRACE_E("CSalamanderGeneral::SetPluginIconOverlays(): invalid 'iconOverlaysCount' (negative value)!"); + } + } + else + TRACE_E("Unexpected situation in CSalamanderGeneral::SetPluginIconOverlays()."); +} + +BOOL CSalamanderGeneral::SalGetFileSize2(const char* fileName, CQuadWord& size, DWORD* err) +{ + CALL_STACK_MESSAGE1("CSalamanderGeneral::SalGetFileSize2()"); + return ::SalGetFileSize2(fileName, size, err); +} + +BOOL CSalamanderGeneral::GetLinkTgtFileSize(HWND parent, const char* fileName, CQuadWord* size, + BOOL* cancel, BOOL* ignoreAll) +{ + CALL_STACK_MESSAGE1("CSalamanderGeneral::GetLinkTgtFileSize()"); + return ::GetLinkTgtFileSize(parent, fileName, NULL, size, cancel, ignoreAll); +} + +BOOL CSalamanderGeneral::DeleteDirLink(const char* name, DWORD* err) +{ + CALL_STACK_MESSAGE1("CSalamanderGeneral::DeleteDirLink()"); + return ::DeleteDirLink(name, err); +} + +BOOL CSalamanderGeneral::ClearReadOnlyAttr(const char* name, DWORD attr) +{ + CALL_STACK_MESSAGE1("CSalamanderGeneral::ClearReadOnlyAttr()"); + return ::ClearReadOnlyAttr(name, attr); +} + +BOOL CSalamanderGeneral::IsCriticalShutdown() +{ + return CriticalShutdown; +} + +void CSalamanderGeneral::CloseAllOwnedEnabledDialogs(HWND parent, DWORD tid) +{ + CALL_STACK_MESSAGE2("CSalamanderGeneral::CloseAllOwnedEnabledDialogs(, %d)", tid); + ::CloseAllOwnedEnabledDialogs(parent, tid); +} + +//