1
0

feat: update qwfassoc

This commit is contained in:
2026-06-23 21:38:54 +08:00
parent 071347f4d4
commit 1a44240f88
24 changed files with 1169 additions and 684 deletions

View File

@@ -1,137 +1,154 @@
# qwfassoc
# qwfassoc (suite)
A Qt Widgets based GUI front-end for the [wfassoc](../../wfassoc) library.
A Qt Widgets based GUI for the [wfassoc](../../wfassoc) library, split into a
reusable shared library and a small standalone executable that exercises it.
`qwfassoc` exposes the same install / uninstall / file-association operations
as the `wfassoc-exec` command line tool, but in a small tabbed dialog aimed at
end users rather than scripts.
## Layout
The project is organized as two CMake subprojects:
```
qwfassoc/
├── CMakeLists.txt CMake build script
├── README.md This file
qwfassoc/ Parent directory (this README)
├── CMakeLists.txt Top-level CMake; add_subdirectory's both
subprojects and finds Qt, wfassoc, toml11
├── cmake/
│ ├── Findwfassoc.cmake Verbatim copy of wfassoc's Find module
│ └── README.md Provenance notes for the copy
├── i18n/
── qwfassoc_zh_CN.ts Empty placeholder translation file
└── src/
├── main.cpp Entry point, CLI parsing, translator loading
── main_window.h/.cpp Main configuration dialog
├── main_window.ui Qt Designer description of the dialog
├── manifest.h/.cpp TOML manifest loader (toml11) and schema builder
└── icon_utils.h/.cpp wfassocpp::HICON -> QPixmap conversion helper
│ ├── Findwfassoc.cmake Verbatim copy of wfassoc's Find module
│ └── README.md Provenance notes for the copy
├── qwfassoc/ Shared library subproject
── CMakeLists.txt
│ ├── i18n/
│ └── qwfassoc_zh_CN.ts Empty placeholder translation file
── src/
├── qwfassoc_global.h QWFASSOC_EXPORT macro
├── scope.h Shared TargetScope enum
├── manifest.h Manifest data struct (no TOML dependency)
│ ├── icon_utils.h/.cpp wfassocpp::HICON -> QPixmap conversion
│ ├── application_widget.h/.cpp/.ui
│ │ Install / uninstall widget
│ └── association_widget.h/.cpp/.ui
│ File associations widget
└── qwfassoc-standalone/ Executable subproject
├── CMakeLists.txt
├── i18n/
│ └── qwfassoc-standalone_zh_CN.ts
│ Empty placeholder translation file
└── src/
├── main.cpp Entry point, CLI parsing, translator loading
├── main_window.h/.cpp/.ui
│ QDialog hosting the two widgets in a tab widget
└── manifest_parser.h/.cpp
TOML -> Manifest, Manifest -> Schema
```
## Subprojects at a glance
### `qwfassoc` (shared library)
Exports two reusable widgets that wrap wfassoc:
* `qwfassoc::ApplicationWidget` — install / uninstall the program in the
configured scope.
* `qwfassoc::AssociationWidget` — stage and apply per-extension link / unlink
operations.
Both widgets follow the **two-phase initialization** pattern expected by Qt
Designer promoted widgets: the constructor only takes a `QWidget*` parent and
leaves the widget disabled. A `setConfig(Config)` method injects the
`wfassocpp::Program` pointer and `TargetScope` (and, for the association
widget, whether the OK/Cancel buttons are visible). Each widget also exposes:
* a `refresh()` slot that re-queries the live wfassoc state, intended to be
called by the host when another component has mutated the registry;
* a `changed()` signal emitted whenever the widget itself mutates the
registry (install / uninstall / apply);
* (`AssociationWidget` only) a `finished(bool accepted)` signal emitted when
the user clicks OK (after `changed()`) or Cancel, so the host can close the
dialog.
The library also exposes the plain `qwfassoc::Manifest` data struct and a
`qwfassoc::icon_utils::fromHicon()` helper, but the TOML parsing logic (which
depends on toml11) lives in the standalone executable.
### `qwfassoc-standalone` (executable)
Reproduces the original tabbed wfassoc configurator by:
1. parsing `-c/--manifest <path>` and `-f/--for <user|system>` from the
command line;
2. building a `wfassocpp::Program` via `parseManifestFile` + `buildSchema`;
3. hosting `ApplicationWidget` and `AssociationWidget` inside a `QTabWidget`
in a `MainWindow` dialog;
4. wiring the widgets' `changed()` and `finished()` signals so that any
registry mutation refreshes both pages and OK/Cancel drive the dialog's
acceptance.
## Requirements
* **CMake** 3.20 or newer.
* **CMake** 3.20 or newer (3.21+ recommended for `qt6_add_translations`).
* A C++17 compiler.
* **Qt 6** (6.3 or newer is recommended for `qt6_add_translations`). The
`Widgets` and `LinguistTools` components are required:
`find_package(Qt6 COMPONENTS Widgets LinguistTools)`.
* **toml11**. `find_package(toml11)` is used.
* **wfassoc**. `find_package(wfassoc)` is used, which requires `wfassoc_ROOT`
to point at an installed wfassoc tree (see
* **Qt 6** with the `Widgets` and `LinguistTools` components.
* **wfassoc**, with `wfassoc_ROOT` pointing at an installed tree (see
[`cmake/Findwfassoc.cmake`](cmake/Findwfassoc.cmake) for the expected
directory layout).
* **toml11** — only required when building the standalone executable.
## Building
```bat
cmake -S . -B build -DCMAKE_PREFIX_PATH=C:\Qt\6.x.x\msvc2022_64 ^
cmake -S . -B build ^
-DCMAKE_PREFIX_PATH=C:\Qt\6.x.x\msvc2022_64 ^
-Dwfassoc_ROOT=C:\path\to\wfassoc\install ^
-Dtoml11_DIR=C:\path\to\toml11\share\toml11\cmake
cmake --build build --config Release
```
The resulting executable is `build/Release/qwfassoc.exe` (or a similar path
depending on the generator).
## Running
`qwfassoc` requires two command line arguments:
| Short | Long | Meaning |
| ----- | ------------ | ------------------------------------------------------------- |
| `-c` | `--manifest` | Path to the application manifest TOML file (see [`example/manifest/ppic.toml`](../manifest/ppic.toml)). |
| `-f` | `--for` | Target scope: `user` or `system`. |
Example:
To skip the standalone executable (and the toml11 dependency):
```bat
qwfassoc -c C:\path\to\ppic.toml -f user
cmake -S . -B build -DQWFASSOC_BUILD_STANDALONE=OFF ...
```
When `-f user` is used the "All Users" column of the file-association table is
read-only (cells render greyed out and clicks are ignored). Use `-f system`
(with appropriate administrator privileges) to make changes that affect all
users.
The standalone executable is `build/qwfassoc-standalone/Release/qwfassoc-standalone.exe`
(or similar, depending on the generator); the library is
`build/qwfassoc/Release/qwfassoc.dll`.
## Running the standalone executable
| Short | Long | Meaning |
| ----- | ------------ | ------------------------------------------------------------------------ |
| `-c` | `--manifest` | Path to the application manifest TOML file (see [`example/manifest/ppic.toml`](../manifest/ppic.toml)). |
| `-f` | `--for` | Target scope: `user` or `system`. |
```bat
qwfassoc-standalone -c C:\path\to\ppic.toml -f user
```
## Internationalization
The user-facing strings shipped in the source code and the `.ui` file are
written in English. Every translatable string is wrapped in `tr()` (in code)
or marked as a regular `<string>` (in the `.ui` file, which `uic` then wraps
in `QCoreApplication::translate`).
Source strings are English and every user-facing string is wrapped in `tr()`
(in code) or is a plain `<string>` element in the `.ui` file (which `uic`
wraps in `QCoreApplication::translate`).
The CMake build wires up the standard Qt translation pipeline via
`qt6_add_translations()`:
Each subproject ships its own empty placeholder `.ts` file under its
`i18n/` directory and registers it with `qt6_add_translations()`:
* the listed `.ts` files under `i18n/` are kept in sync with the source by
`lupdate`,
* `lrelease` compiles them into `.qm` files which are embedded under the
`:/i18n/` resource prefix,
* `installTranslators()` in `src/main.cpp` loads the `.qm` file matching the
current locale at startup.
* `qwfassoc/i18n/qwfassoc_zh_CN.ts` — covers the library widgets.
* `qwfassoc-standalone/i18n/qwfassoc-standalone_zh_CN.ts` — covers the
executable-specific messages (CLI errors, tab titles, dialog window
title, etc.).
The repository ships `i18n/qwfassoc_zh_CN.ts` as an **empty placeholder**.
Translators are expected to fill it in (or add new language files and list
them in `CMakeLists.txt`). No actual translation work is performed by the
build on its own.
## UI Overview
The dialog is a fixed 480x600 `QDialog` with two tabs.
### Applications tab
Lets the user install or uninstall the program described by the manifest in
the scope selected by `--for`. The currently-active action button is enabled
based on whether the program is already registered; the other one is disabled.
### File associations tab
Lists every extension declared in the manifest. Each row shows:
* the dotted extension (`.jpg`) with a hybrid-view icon,
* the display name of the handler currently registered for the current user,
* the display name of the handler currently registered for all users.
Clicking a cell in the user or all-users column toggles the cell state between
the program-provided handler (link) and no handler (unlink). The "+ " buttons
above each column progressively select more: the first click only fills blank
cells, the next click overrides any cell pointing at another handler.
Changes are buffered in memory and only written to the registry when **OK** or
**Apply** is pressed. **OK** writes and closes the dialog; **Apply** writes
and refreshes the table; **Cancel** discards the pending changes and closes
the dialog. The **Apply** button is disabled when no changes are pending.
If the program is not installed in the active scope, the whole file
associations tab is disabled.
At runtime, `installTranslators()` in `qwfassoc-standalone/src/main.cpp`
loads both `.qm` files for the user's preferred UI language from the
`:/i18n/` resource prefix. Translators are expected to fill in the `.ts`
files; no actual translation work is performed by the build on its own.
## Notes and Limitations
* The "self" detection in the file-association table is based on comparing the
* "Self" detection in the file-association table is based on comparing the
display name returned by wfassoc with the display name this program would
use. Two programs sharing the exact same display name could therefore be
confused.
* The dialog uses `Qt::ItemIsSelectable` (without `Qt::ItemIsEnabled`) to
render disabled system-column cells in user mode; their text is still shown
but they cannot be clicked.
* The system column in `AssociationWidget` is rendered disabled (using
`Qt::ItemIsSelectable` without `Qt::ItemIsEnabled`) when `TargetScope` is
`User`; the cells stay visible but cannot be clicked.
* All errors originating from wfassoc are surfaced through `QMessageBox`
dialogs; fatal errors during startup cause the process to exit with a
non-zero status code.