feat: update qwfassoc
This commit is contained in:
@@ -1,13 +1,14 @@
|
|||||||
cmake_minimum_required(VERSION 3.20)
|
cmake_minimum_required(VERSION 3.20)
|
||||||
|
|
||||||
project(qwfassoc LANGUAGES CXX)
|
project(qwfassoc_suite LANGUAGES CXX)
|
||||||
|
|
||||||
# Qt 6 requires C++17 at minimum.
|
# Qt 6 requires C++17 at minimum.
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||||
|
|
||||||
# Let CMake auto-process Q_OBJECT, .ui files and .qrc resources.
|
# Let CMake auto-process Q_OBJECT, .ui files and .qrc resources for every
|
||||||
|
# subproject below.
|
||||||
set(CMAKE_AUTOMOC ON)
|
set(CMAKE_AUTOMOC ON)
|
||||||
set(CMAKE_AUTOUIC ON)
|
set(CMAKE_AUTOUIC ON)
|
||||||
set(CMAKE_AUTORCC ON)
|
set(CMAKE_AUTORCC ON)
|
||||||
@@ -15,64 +16,21 @@ set(CMAKE_AUTORCC ON)
|
|||||||
# Make the bundled Findwfassoc.cmake module visible to find_package().
|
# Make the bundled Findwfassoc.cmake module visible to find_package().
|
||||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
|
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
|
||||||
|
|
||||||
# Locate third-party dependencies. The user is responsible for making these
|
# Qt and LinguistTools are used by both subprojects, so they are looked up at
|
||||||
# discoverable through the usual CMake mechanisms (CMAKE_PREFIX_PATH, etc.).
|
# the top level. The same is true for wfassoc: although only the qwfassoc
|
||||||
# LinguistTools is required to wire up Qt's translation pipeline
|
# library links against it directly, PUBLIC propagation from the library
|
||||||
# (lupdate + lrelease) so that .ts files are kept up to date and .qm files
|
# target makes the dependency available to qwfassoc-standalone as well.
|
||||||
# are embedded as Qt resources.
|
# toml11 is only needed when the standalone executable is built, so it is
|
||||||
|
# looked up conditionally below.
|
||||||
find_package(Qt6 REQUIRED COMPONENTS Widgets LinguistTools)
|
find_package(Qt6 REQUIRED COMPONENTS Widgets LinguistTools)
|
||||||
find_package(toml11 REQUIRED)
|
|
||||||
# Findwfassoc.cmake requires the wfassoc_ROOT variable to be set.
|
|
||||||
find_package(wfassoc REQUIRED)
|
find_package(wfassoc REQUIRED)
|
||||||
|
|
||||||
set(QWFASSOC_SOURCES
|
# The standalone executable is optional: embedders may want only the library.
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp"
|
option(QWFASSOC_BUILD_STANDALONE "Build the qwfassoc-standalone executable" ON)
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/main_window.cpp"
|
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/manifest.cpp"
|
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/icon_utils.cpp"
|
|
||||||
)
|
|
||||||
|
|
||||||
set(QWFASSOC_HEADERS
|
add_subdirectory(qwfassoc)
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/main_window.h"
|
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/manifest.h"
|
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/icon_utils.h"
|
|
||||||
)
|
|
||||||
|
|
||||||
set(QWFASSOC_UI
|
if(QWFASSOC_BUILD_STANDALONE)
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/main_window.ui"
|
find_package(toml11 REQUIRED)
|
||||||
)
|
add_subdirectory(qwfassoc-standalone)
|
||||||
|
endif()
|
||||||
# WIN32 makes this a windowed application on Windows (no console window).
|
|
||||||
add_executable(qwfassoc WIN32
|
|
||||||
${QWFASSOC_SOURCES}
|
|
||||||
${QWFASSOC_HEADERS}
|
|
||||||
${QWFASSOC_UI}
|
|
||||||
)
|
|
||||||
|
|
||||||
target_include_directories(qwfassoc PRIVATE
|
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/src"
|
|
||||||
)
|
|
||||||
|
|
||||||
target_link_libraries(qwfassoc PRIVATE
|
|
||||||
Qt6::Widgets
|
|
||||||
toml11::toml11
|
|
||||||
wfassoc::wfassoc
|
|
||||||
)
|
|
||||||
|
|
||||||
# Translation pipeline.
|
|
||||||
#
|
|
||||||
# qt6_add_translations() runs lupdate against the target's sources (keeping
|
|
||||||
# the .ts files below in sync) and then runs lrelease to compile them into
|
|
||||||
# .qm files. The .qm files are embedded under the ":/i18n" resource prefix,
|
|
||||||
# where installTranslators() in main.cpp looks them up at runtime.
|
|
||||||
#
|
|
||||||
# The .ts files are intentionally shipped empty: translators are expected to
|
|
||||||
# fill them in. Re-running cmake / building the target refreshes the entries
|
|
||||||
# found by lupdate without dropping already-translated ones.
|
|
||||||
set(QWFASSOC_TS_FILES
|
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/i18n/qwfassoc_zh_CN.ts"
|
|
||||||
)
|
|
||||||
|
|
||||||
qt6_add_translations(qwfassoc
|
|
||||||
TS_FILES ${QWFASSOC_TS_FILES}
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -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
|
The project is organized as two CMake subprojects:
|
||||||
as the `wfassoc-exec` command line tool, but in a small tabbed dialog aimed at
|
|
||||||
end users rather than scripts.
|
|
||||||
|
|
||||||
## Layout
|
|
||||||
|
|
||||||
```
|
```
|
||||||
qwfassoc/
|
qwfassoc/ Parent directory (this README)
|
||||||
├── CMakeLists.txt CMake build script
|
├── CMakeLists.txt Top-level CMake; add_subdirectory's both
|
||||||
├── README.md This file
|
│ subprojects and finds Qt, wfassoc, toml11
|
||||||
├── cmake/
|
├── cmake/
|
||||||
│ ├── Findwfassoc.cmake Verbatim copy of wfassoc's Find module
|
│ ├── Findwfassoc.cmake Verbatim copy of wfassoc's Find module
|
||||||
│ └── README.md Provenance notes for the copy
|
│ └── README.md Provenance notes for the copy
|
||||||
├── i18n/
|
├── qwfassoc/ Shared library subproject
|
||||||
│ └── qwfassoc_zh_CN.ts Empty placeholder translation file
|
│ ├── CMakeLists.txt
|
||||||
└── src/
|
│ ├── i18n/
|
||||||
├── main.cpp Entry point, CLI parsing, translator loading
|
│ │ └── qwfassoc_zh_CN.ts Empty placeholder translation file
|
||||||
├── main_window.h/.cpp Main configuration dialog
|
│ └── src/
|
||||||
├── main_window.ui Qt Designer description of the dialog
|
│ ├── qwfassoc_global.h QWFASSOC_EXPORT macro
|
||||||
├── manifest.h/.cpp TOML manifest loader (toml11) and schema builder
|
│ ├── scope.h Shared TargetScope enum
|
||||||
└── icon_utils.h/.cpp wfassocpp::HICON -> QPixmap conversion helper
|
│ ├── 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
|
## Requirements
|
||||||
|
|
||||||
* **CMake** 3.20 or newer.
|
* **CMake** 3.20 or newer (3.21+ recommended for `qt6_add_translations`).
|
||||||
* A C++17 compiler.
|
* A C++17 compiler.
|
||||||
* **Qt 6** (6.3 or newer is recommended for `qt6_add_translations`). The
|
* **Qt 6** with the `Widgets` and `LinguistTools` components.
|
||||||
`Widgets` and `LinguistTools` components are required:
|
* **wfassoc**, with `wfassoc_ROOT` pointing at an installed tree (see
|
||||||
`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
|
|
||||||
[`cmake/Findwfassoc.cmake`](cmake/Findwfassoc.cmake) for the expected
|
[`cmake/Findwfassoc.cmake`](cmake/Findwfassoc.cmake) for the expected
|
||||||
directory layout).
|
directory layout).
|
||||||
|
* **toml11** — only required when building the standalone executable.
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
|
||||||
```bat
|
```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 ^
|
-Dwfassoc_ROOT=C:\path\to\wfassoc\install ^
|
||||||
-Dtoml11_DIR=C:\path\to\toml11\share\toml11\cmake
|
-Dtoml11_DIR=C:\path\to\toml11\share\toml11\cmake
|
||||||
cmake --build build --config Release
|
cmake --build build --config Release
|
||||||
```
|
```
|
||||||
|
|
||||||
The resulting executable is `build/Release/qwfassoc.exe` (or a similar path
|
To skip the standalone executable (and the toml11 dependency):
|
||||||
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:
|
|
||||||
|
|
||||||
```bat
|
```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
|
The standalone executable is `build/qwfassoc-standalone/Release/qwfassoc-standalone.exe`
|
||||||
read-only (cells render greyed out and clicks are ignored). Use `-f system`
|
(or similar, depending on the generator); the library is
|
||||||
(with appropriate administrator privileges) to make changes that affect all
|
`build/qwfassoc/Release/qwfassoc.dll`.
|
||||||
users.
|
|
||||||
|
## 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
|
## Internationalization
|
||||||
|
|
||||||
The user-facing strings shipped in the source code and the `.ui` file are
|
Source strings are English and every user-facing string is wrapped in `tr()`
|
||||||
written in English. Every translatable string is wrapped in `tr()` (in code)
|
(in code) or is a plain `<string>` element in the `.ui` file (which `uic`
|
||||||
or marked as a regular `<string>` (in the `.ui` file, which `uic` then wraps
|
wraps in `QCoreApplication::translate`).
|
||||||
in `QCoreApplication::translate`).
|
|
||||||
|
|
||||||
The CMake build wires up the standard Qt translation pipeline via
|
Each subproject ships its own empty placeholder `.ts` file under its
|
||||||
`qt6_add_translations()`:
|
`i18n/` directory and registers it with `qt6_add_translations()`:
|
||||||
|
|
||||||
* the listed `.ts` files under `i18n/` are kept in sync with the source by
|
* `qwfassoc/i18n/qwfassoc_zh_CN.ts` — covers the library widgets.
|
||||||
`lupdate`,
|
* `qwfassoc-standalone/i18n/qwfassoc-standalone_zh_CN.ts` — covers the
|
||||||
* `lrelease` compiles them into `.qm` files which are embedded under the
|
executable-specific messages (CLI errors, tab titles, dialog window
|
||||||
`:/i18n/` resource prefix,
|
title, etc.).
|
||||||
* `installTranslators()` in `src/main.cpp` loads the `.qm` file matching the
|
|
||||||
current locale at startup.
|
|
||||||
|
|
||||||
The repository ships `i18n/qwfassoc_zh_CN.ts` as an **empty placeholder**.
|
At runtime, `installTranslators()` in `qwfassoc-standalone/src/main.cpp`
|
||||||
Translators are expected to fill it in (or add new language files and list
|
loads both `.qm` files for the user's preferred UI language from the
|
||||||
them in `CMakeLists.txt`). No actual translation work is performed by the
|
`:/i18n/` resource prefix. Translators are expected to fill in the `.ts`
|
||||||
build on its own.
|
files; 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.
|
|
||||||
|
|
||||||
## Notes and Limitations
|
## 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
|
display name returned by wfassoc with the display name this program would
|
||||||
use. Two programs sharing the exact same display name could therefore be
|
use. Two programs sharing the exact same display name could therefore be
|
||||||
confused.
|
confused.
|
||||||
* The dialog uses `Qt::ItemIsSelectable` (without `Qt::ItemIsEnabled`) to
|
* The system column in `AssociationWidget` is rendered disabled (using
|
||||||
render disabled system-column cells in user mode; their text is still shown
|
`Qt::ItemIsSelectable` without `Qt::ItemIsEnabled`) when `TargetScope` is
|
||||||
but they cannot be clicked.
|
`User`; the cells stay visible but cannot be clicked.
|
||||||
* All errors originating from wfassoc are surfaced through `QMessageBox`
|
* All errors originating from wfassoc are surfaced through `QMessageBox`
|
||||||
dialogs; fatal errors during startup cause the process to exit with a
|
dialogs; fatal errors during startup cause the process to exit with a
|
||||||
non-zero status code.
|
non-zero status code.
|
||||||
|
|||||||
48
example/qwfassoc/qwfassoc-standalone/CMakeLists.txt
Normal file
48
example/qwfassoc/qwfassoc-standalone/CMakeLists.txt
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
# qwfassoc-standalone: executable that uses the qwfassoc library to reproduce
|
||||||
|
# the original tabbed wfassoc configurator.
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||||
|
|
||||||
|
set(QWFASSOC_STANDALONE_SOURCES
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp"
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/main_window.cpp"
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/manifest_parser.cpp"
|
||||||
|
)
|
||||||
|
|
||||||
|
set(QWFASSOC_STANDALONE_HEADERS
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/main_window.h"
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/manifest_parser.h"
|
||||||
|
)
|
||||||
|
|
||||||
|
set(QWFASSOC_STANDALONE_UI
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/main_window.ui"
|
||||||
|
)
|
||||||
|
|
||||||
|
add_executable(qwfassoc-standalone WIN32
|
||||||
|
${QWFASSOC_STANDALONE_SOURCES}
|
||||||
|
${QWFASSOC_STANDALONE_HEADERS}
|
||||||
|
${QWFASSOC_STANDALONE_UI}
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(qwfassoc-standalone PRIVATE
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/src"
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(qwfassoc-standalone PRIVATE
|
||||||
|
qwfassoc
|
||||||
|
Qt6::Widgets
|
||||||
|
toml11::toml11
|
||||||
|
)
|
||||||
|
|
||||||
|
# Translation pipeline for the standalone executable. The library's strings
|
||||||
|
# are translated by qwfassoc's own .ts file; this one only covers the
|
||||||
|
# executable-specific messages (CLI errors, tab titles, etc.).
|
||||||
|
set(QWFASSOC_STANDALONE_TS_FILES
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/i18n/qwfassoc-standalone_zh_CN.ts"
|
||||||
|
)
|
||||||
|
|
||||||
|
qt6_add_translations(qwfassoc-standalone
|
||||||
|
TS_FILES ${QWFASSOC_STANDALONE_TS_FILES}
|
||||||
|
)
|
||||||
@@ -13,21 +13,18 @@
|
|||||||
#include <wfassoc++.h>
|
#include <wfassoc++.h>
|
||||||
|
|
||||||
#include "main_window.h"
|
#include "main_window.h"
|
||||||
#include "manifest.h"
|
#include "manifest_parser.h"
|
||||||
|
#include "scope.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
// Context used for translatable strings that live outside of any QObject.
|
// Context used for translatable strings that live outside of any QObject.
|
||||||
// Keeping it stable lets translators find these messages under the same key
|
constexpr const char* kTranslationContext = "qwfassoc-standalone";
|
||||||
// across builds.
|
|
||||||
constexpr const char* kTranslationContext = "qwfassoc";
|
|
||||||
|
|
||||||
// Show a modal error dialog with the given message and return a non-zero
|
// Show a modal error dialog with the given message and return a non-zero
|
||||||
// exit code. Used for the various fatal conditions that may occur before the
|
// exit code. Used for the various fatal conditions that may occur before the
|
||||||
// main dialog can be shown.
|
// main dialog can be shown.
|
||||||
int fatal(QWidget* parent, const QString& message) {
|
int fatal(QWidget* parent, const QString& message) {
|
||||||
// The application name is treated as a brand identifier and is not
|
|
||||||
// translated; the message text itself is already translated by callers.
|
|
||||||
QMessageBox::critical(parent, QApplication::applicationName(), message);
|
QMessageBox::critical(parent, QApplication::applicationName(), message);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -46,18 +43,24 @@ qwfassoc::TargetScope parseScope(const QString& value) {
|
|||||||
"Invalid value for --for. Use \"user\" or \"system\".");
|
"Invalid value for --for. Use \"user\" or \"system\".");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the translation matching the user's preferred UI language, if any.
|
// Install the translation(s) matching the user's preferred UI language, if
|
||||||
// Qt's qt6_add_translations() compiles .ts files into .qm files and embeds
|
// any. Both the qwfassoc library's .qm and this executable's .qm are loaded
|
||||||
// them under the ":/i18n" resource prefix.
|
// (their .ts files live under the per-project i18n/ directories and are
|
||||||
|
// embedded under the ":/i18n" resource prefix by qt6_add_translations).
|
||||||
void installTranslators(QApplication& app) {
|
void installTranslators(QApplication& app) {
|
||||||
QTranslator* translator = new QTranslator(&app);
|
|
||||||
const QStringList uiLanguages = QLocale::system().uiLanguages();
|
const QStringList uiLanguages = QLocale::system().uiLanguages();
|
||||||
for (const QString& locale : uiLanguages) {
|
for (const QString& locale : uiLanguages) {
|
||||||
const QString baseName =
|
const QString name = QLocale(locale).name();
|
||||||
QStringLiteral("qwfassoc_") + QLocale(locale).name();
|
|
||||||
if (translator->load(QStringLiteral(":/i18n/") + baseName)) {
|
QTranslator* libTranslator = new QTranslator(&app);
|
||||||
app.installTranslator(translator);
|
if (libTranslator->load(QStringLiteral(":/i18n/qwfassoc_") + name)) {
|
||||||
return;
|
app.installTranslator(libTranslator);
|
||||||
|
}
|
||||||
|
|
||||||
|
QTranslator* appTranslator = new QTranslator(&app);
|
||||||
|
if (appTranslator->load(
|
||||||
|
QStringLiteral(":/i18n/qwfassoc-standalone_") + name)) {
|
||||||
|
app.installTranslator(appTranslator);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -66,11 +69,11 @@ void installTranslators(QApplication& app) {
|
|||||||
|
|
||||||
int main(int argc, char* argv[]) {
|
int main(int argc, char* argv[]) {
|
||||||
QApplication app(argc, argv);
|
QApplication app(argc, argv);
|
||||||
QApplication::setApplicationName(QStringLiteral("qwfassoc"));
|
QApplication::setApplicationName(QStringLiteral("qwfassoc-standalone"));
|
||||||
|
|
||||||
// Install available translations before any translatable string is
|
// Install available translations before any translatable string is
|
||||||
// resolved (including the application display name below) so that tr()
|
// resolved so that tr() and QCoreApplication::translate() pick up the
|
||||||
// and QCoreApplication::translate() pick up the right language.
|
// right language.
|
||||||
installTranslators(app);
|
installTranslators(app);
|
||||||
|
|
||||||
QApplication::setApplicationDisplayName(
|
QApplication::setApplicationDisplayName(
|
||||||
@@ -80,8 +83,9 @@ int main(int argc, char* argv[]) {
|
|||||||
// Parse command line arguments using Qt's built-in parser.
|
// Parse command line arguments using Qt's built-in parser.
|
||||||
QCommandLineParser parser;
|
QCommandLineParser parser;
|
||||||
parser.setApplicationDescription(
|
parser.setApplicationDescription(
|
||||||
QCoreApplication::translate(kTranslationContext,
|
QCoreApplication::translate(
|
||||||
"Qt-based GUI for the wfassoc library."));
|
kTranslationContext,
|
||||||
|
"Qt-based GUI executable for the wfassoc library."));
|
||||||
parser.addHelpOption();
|
parser.addHelpOption();
|
||||||
|
|
||||||
QCommandLineOption manifestOption(
|
QCommandLineOption manifestOption(
|
||||||
@@ -136,8 +140,8 @@ int main(int argc, char* argv[]) {
|
|||||||
int exitCode = 0;
|
int exitCode = 0;
|
||||||
try {
|
try {
|
||||||
qwfassoc::Manifest manifest =
|
qwfassoc::Manifest manifest =
|
||||||
qwfassoc::Manifest::fromFile(manifestPath.toStdString());
|
qwfassoc::parseManifestFile(manifestPath.toStdString());
|
||||||
wfassocpp::Schema schema = manifest.toSchema();
|
wfassocpp::Schema schema = qwfassoc::buildSchema(manifest);
|
||||||
wfassocpp::Program program(std::move(schema));
|
wfassocpp::Program program(std::move(schema));
|
||||||
|
|
||||||
qwfassoc::MainWindow window(std::move(program), scope);
|
qwfassoc::MainWindow window(std::move(program), scope);
|
||||||
79
example/qwfassoc/qwfassoc-standalone/src/main_window.cpp
Normal file
79
example/qwfassoc/qwfassoc-standalone/src/main_window.cpp
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
#include "main_window.h"
|
||||||
|
#include "ui_main_window.h"
|
||||||
|
|
||||||
|
#include "application_widget.h"
|
||||||
|
#include "association_widget.h"
|
||||||
|
#include "icon_utils.h"
|
||||||
|
|
||||||
|
#include <QTabWidget>
|
||||||
|
|
||||||
|
namespace qwfassoc {
|
||||||
|
|
||||||
|
MainWindow::MainWindow(wfassocpp::Program program,
|
||||||
|
TargetScope scope,
|
||||||
|
QWidget* parent)
|
||||||
|
: QDialog(parent),
|
||||||
|
ui_(new Ui::MainWindow),
|
||||||
|
appTab_(nullptr),
|
||||||
|
assocTab_(nullptr),
|
||||||
|
program_(std::move(program)),
|
||||||
|
scope_(scope) {
|
||||||
|
ui_->setupUi(this);
|
||||||
|
|
||||||
|
// Resolve program metadata that several labels depend on.
|
||||||
|
programName_ = QString::fromUtf8(program_.ResolveName());
|
||||||
|
|
||||||
|
{
|
||||||
|
auto iconRc = program_.ResolveIcon();
|
||||||
|
auto handle = iconRc.GetIcon();
|
||||||
|
programIcon_ = icon_utils::fromHicon(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compose the window title and icon.
|
||||||
|
setWindowTitle(tr("%1 Options").arg(programName_));
|
||||||
|
if (!programIcon_.isNull()) {
|
||||||
|
setWindowIcon(QIcon(programIcon_));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the two tab pages from the library widgets.
|
||||||
|
appTab_ = new ApplicationWidget(this);
|
||||||
|
assocTab_ = new AssociationWidget(this);
|
||||||
|
|
||||||
|
ui_->tabWidget->addTab(appTab_, tr("Applications"));
|
||||||
|
ui_->tabWidget->addTab(assocTab_, tr("File Associations"));
|
||||||
|
|
||||||
|
// Two-phase initialization. The standalone executable wants the OK and
|
||||||
|
// Cancel buttons visible because they drive dialog acceptance.
|
||||||
|
appTab_->setConfig({&program_, scope_});
|
||||||
|
assocTab_->setConfig({&program_, scope_, /*showOkCancelButtons=*/true});
|
||||||
|
|
||||||
|
// Wire widget signals so that any wfassoc change refreshes both pages,
|
||||||
|
// and the association widget can request dialog closure.
|
||||||
|
connect(appTab_, &ApplicationWidget::changed, this,
|
||||||
|
&MainWindow::onAnyChanged);
|
||||||
|
connect(assocTab_, &AssociationWidget::changed, this,
|
||||||
|
&MainWindow::onAnyChanged);
|
||||||
|
connect(assocTab_, &AssociationWidget::finished, this,
|
||||||
|
&MainWindow::onFinished);
|
||||||
|
}
|
||||||
|
|
||||||
|
MainWindow::~MainWindow() = default;
|
||||||
|
|
||||||
|
void MainWindow::onAnyChanged() {
|
||||||
|
if (appTab_ != nullptr) {
|
||||||
|
appTab_->refresh();
|
||||||
|
}
|
||||||
|
if (assocTab_ != nullptr) {
|
||||||
|
assocTab_->refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::onFinished(bool accepted) {
|
||||||
|
if (accepted) {
|
||||||
|
accept();
|
||||||
|
} else {
|
||||||
|
reject();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace qwfassoc
|
||||||
57
example/qwfassoc/qwfassoc-standalone/src/main_window.h
Normal file
57
example/qwfassoc/qwfassoc-standalone/src/main_window.h
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
#pragma once
|
||||||
|
#ifndef QWFASSOC_STANDALONE_MAIN_WINDOW_H_
|
||||||
|
#define QWFASSOC_STANDALONE_MAIN_WINDOW_H_
|
||||||
|
|
||||||
|
#include <QDialog>
|
||||||
|
#include <QPixmap>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
#include <wfassoc++.h>
|
||||||
|
|
||||||
|
#include "scope.h"
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class MainWindow;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace qwfassoc {
|
||||||
|
class ApplicationWidget;
|
||||||
|
class AssociationWidget;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace qwfassoc {
|
||||||
|
|
||||||
|
// Top-level dialog used by the qwfassoc-standalone executable. Hosts the two
|
||||||
|
// reusable widgets from the qwfassoc library inside a QTabWidget and wires
|
||||||
|
// their changed() / finished() signals together.
|
||||||
|
class MainWindow : public QDialog {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit MainWindow(wfassocpp::Program program,
|
||||||
|
TargetScope scope,
|
||||||
|
QWidget* parent = nullptr);
|
||||||
|
~MainWindow() override;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
// Called whenever one of the embedded widgets reports that wfassoc state
|
||||||
|
// has changed. Refreshes both widgets so they stay in sync.
|
||||||
|
void onAnyChanged();
|
||||||
|
// Called when the association widget asks the dialog to close.
|
||||||
|
void onFinished(bool accepted);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ui::MainWindow* ui_;
|
||||||
|
ApplicationWidget* appTab_;
|
||||||
|
AssociationWidget* assocTab_;
|
||||||
|
|
||||||
|
wfassocpp::Program program_;
|
||||||
|
TargetScope scope_;
|
||||||
|
|
||||||
|
QString programName_;
|
||||||
|
QPixmap programIcon_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace qwfassoc
|
||||||
|
|
||||||
|
#endif // QWFASSOC_STANDALONE_MAIN_WINDOW_H_
|
||||||
52
example/qwfassoc/qwfassoc-standalone/src/main_window.ui
Normal file
52
example/qwfassoc/qwfassoc-standalone/src/main_window.ui
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>MainWindow</class>
|
||||||
|
<widget class="QDialog" name="MainWindow">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>480</width>
|
||||||
|
<height>600</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>480</width>
|
||||||
|
<height>600</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>480</width>
|
||||||
|
<height>600</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Options</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="mainLayout">
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>9</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>9</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>9</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>9</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QTabWidget" name="tabWidget">
|
||||||
|
<property name="currentIndex">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
#include "manifest.h"
|
#include "manifest_parser.h"
|
||||||
|
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
@@ -6,9 +6,9 @@
|
|||||||
|
|
||||||
namespace qwfassoc {
|
namespace qwfassoc {
|
||||||
|
|
||||||
// region: Manifest Parsing
|
// region: TOML Parsing
|
||||||
|
|
||||||
Manifest Manifest::fromFile(const std::string& path) {
|
Manifest parseManifestFile(const std::string& path) {
|
||||||
toml::value root;
|
toml::value root;
|
||||||
try {
|
try {
|
||||||
root = toml::parse(path);
|
root = toml::parse(path);
|
||||||
@@ -103,30 +103,33 @@ Manifest Manifest::fromFile(const std::string& path) {
|
|||||||
|
|
||||||
// region: Schema Conversion
|
// region: Schema Conversion
|
||||||
|
|
||||||
wfassocpp::Schema Manifest::toSchema() const {
|
wfassocpp::Schema buildSchema(const Manifest& manifest) {
|
||||||
wfassocpp::Schema schema;
|
wfassocpp::Schema schema;
|
||||||
|
|
||||||
// The wfassocpp wrappers translate any underlying failure into a
|
// The wfassocpp wrappers translate any underlying failure into a
|
||||||
// std::runtime_error via _Check, so we let those propagate untouched.
|
// std::runtime_error via _Check, so we let those propagate untouched.
|
||||||
schema.SetIdentifier(identifier.c_str());
|
schema.SetIdentifier(manifest.identifier.c_str());
|
||||||
schema.SetPath(path.c_str());
|
schema.SetPath(manifest.path.c_str());
|
||||||
schema.SetClsid(clsid.c_str());
|
schema.SetClsid(manifest.clsid.c_str());
|
||||||
|
|
||||||
// Optional fields: passing nullptr tells wfassoc to clear the value.
|
// Optional fields: passing nullptr tells wfassoc to clear the value.
|
||||||
schema.SetName(name.has_value() ? name->c_str() : nullptr);
|
schema.SetName(manifest.name.has_value() ? manifest.name->c_str()
|
||||||
schema.SetIcon(icon.has_value() ? icon->c_str() : nullptr);
|
: nullptr);
|
||||||
schema.SetBehavior(behavior.has_value() ? behavior->c_str() : nullptr);
|
schema.SetIcon(manifest.icon.has_value() ? manifest.icon->c_str()
|
||||||
|
: nullptr);
|
||||||
|
schema.SetBehavior(manifest.behavior.has_value() ? manifest.behavior->c_str()
|
||||||
|
: nullptr);
|
||||||
|
|
||||||
for (const auto& [key, value] : strs) {
|
for (const auto& [key, value] : manifest.strs) {
|
||||||
schema.AddStr(key.c_str(), value.c_str());
|
schema.AddStr(key.c_str(), value.c_str());
|
||||||
}
|
}
|
||||||
for (const auto& [key, value] : icons) {
|
for (const auto& [key, value] : manifest.icons) {
|
||||||
schema.AddIcon(key.c_str(), value.c_str());
|
schema.AddIcon(key.c_str(), value.c_str());
|
||||||
}
|
}
|
||||||
for (const auto& [key, value] : behaviors) {
|
for (const auto& [key, value] : manifest.behaviors) {
|
||||||
schema.AddBehavior(key.c_str(), value.c_str());
|
schema.AddBehavior(key.c_str(), value.c_str());
|
||||||
}
|
}
|
||||||
for (const auto& [key, value] : exts) {
|
for (const auto& [key, value] : manifest.exts) {
|
||||||
schema.AddExt(key.c_str(),
|
schema.AddExt(key.c_str(),
|
||||||
value.name.c_str(),
|
value.name.c_str(),
|
||||||
value.icon.c_str(),
|
value.icon.c_str(),
|
||||||
25
example/qwfassoc/qwfassoc-standalone/src/manifest_parser.h
Normal file
25
example/qwfassoc/qwfassoc-standalone/src/manifest_parser.h
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
#pragma once
|
||||||
|
#ifndef QWFASSOC_STANDALONE_MANIFEST_PARSER_H_
|
||||||
|
#define QWFASSOC_STANDALONE_MANIFEST_PARSER_H_
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <wfassoc++.h>
|
||||||
|
|
||||||
|
#include "manifest.h"
|
||||||
|
|
||||||
|
namespace qwfassoc {
|
||||||
|
|
||||||
|
// Parse a manifest TOML file from disk into a Manifest value.
|
||||||
|
// Throws std::runtime_error on any IO or TOML syntax error.
|
||||||
|
Manifest parseManifestFile(const std::string& path);
|
||||||
|
|
||||||
|
// Build a wfassocpp::Schema from a manifest value.
|
||||||
|
// Throws std::runtime_error (originating from wfassocpp::_Check) when the
|
||||||
|
// wfassoc library rejects an operation, e.g. on duplicate keys or dangling
|
||||||
|
// references.
|
||||||
|
wfassocpp::Schema buildSchema(const Manifest& manifest);
|
||||||
|
|
||||||
|
} // namespace qwfassoc
|
||||||
|
|
||||||
|
#endif // QWFASSOC_STANDALONE_MANIFEST_PARSER_H_
|
||||||
58
example/qwfassoc/qwfassoc/CMakeLists.txt
Normal file
58
example/qwfassoc/qwfassoc/CMakeLists.txt
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
# qwfassoc: shared library exporting reusable Qt widgets that wrap wfassoc.
|
||||||
|
|
||||||
|
# Qt 6 requires C++17 at minimum.
|
||||||
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||||
|
|
||||||
|
set(QWFASSOC_SOURCES
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/application_widget.cpp"
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/association_widget.cpp"
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/icon_utils.cpp"
|
||||||
|
)
|
||||||
|
|
||||||
|
set(QWFASSOC_HEADERS
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/qwfassoc_global.h"
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/scope.h"
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/manifest.h"
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/application_widget.h"
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/association_widget.h"
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/icon_utils.h"
|
||||||
|
)
|
||||||
|
|
||||||
|
set(QWFASSOC_UI
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/application_widget.ui"
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/association_widget.ui"
|
||||||
|
)
|
||||||
|
|
||||||
|
add_library(qwfassoc SHARED
|
||||||
|
${QWFASSOC_SOURCES}
|
||||||
|
${QWFASSOC_HEADERS}
|
||||||
|
${QWFASSOC_UI}
|
||||||
|
)
|
||||||
|
|
||||||
|
# QWFASSOC_LIBRARY switches QWFASSOC_EXPORT from import to export mode.
|
||||||
|
target_compile_definitions(qwfassoc PRIVATE QWFASSOC_LIBRARY)
|
||||||
|
|
||||||
|
# Consumers (and the library itself) need to find the public headers under
|
||||||
|
# src/. PUBLIC propagates the include path to anyone linking against qwfassoc.
|
||||||
|
target_include_directories(qwfassoc PUBLIC
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/src"
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(qwfassoc PUBLIC
|
||||||
|
Qt6::Widgets
|
||||||
|
wfassoc::wfassoc
|
||||||
|
)
|
||||||
|
|
||||||
|
# Translation pipeline for the library. qt6_add_translations() runs lupdate
|
||||||
|
# against the target's sources and embeds the lrelease output under the
|
||||||
|
# ":/i18n" resource prefix, where installTranslators() in the executable
|
||||||
|
# looks it up at runtime.
|
||||||
|
set(QWFASSOC_TS_FILES
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/i18n/qwfassoc_zh_CN.ts"
|
||||||
|
)
|
||||||
|
|
||||||
|
qt6_add_translations(qwfassoc
|
||||||
|
TS_FILES ${QWFASSOC_TS_FILES}
|
||||||
|
)
|
||||||
4
example/qwfassoc/qwfassoc/i18n/qwfassoc_zh_CN.ts
Normal file
4
example/qwfassoc/qwfassoc/i18n/qwfassoc_zh_CN.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!DOCTYPE TS>
|
||||||
|
<TS version="2.1" language="zh_CN">
|
||||||
|
</TS>
|
||||||
119
example/qwfassoc/qwfassoc/src/application_widget.cpp
Normal file
119
example/qwfassoc/qwfassoc/src/application_widget.cpp
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
#include "application_widget.h"
|
||||||
|
#include "ui_application_widget.h"
|
||||||
|
#include "icon_utils.h"
|
||||||
|
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include <QPushButton>
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
namespace qwfassoc {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// Convert the library's TargetScope enum to the wfassocpp::Scope value used by
|
||||||
|
// the program APIs (register / unregister / link / unlink / is_registered).
|
||||||
|
wfassocpp::Scope toWfassocScope(TargetScope scope) {
|
||||||
|
return scope == TargetScope::User ? wfassocpp::Scope::User
|
||||||
|
: wfassocpp::Scope::System;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
ApplicationWidget::ApplicationWidget(QWidget* parent)
|
||||||
|
: QWidget(parent), ui_(new Ui::ApplicationWidget) {
|
||||||
|
ui_->setupUi(this);
|
||||||
|
|
||||||
|
connect(ui_->installButton, &QPushButton::clicked, this,
|
||||||
|
&ApplicationWidget::onInstallClicked);
|
||||||
|
connect(ui_->uninstallButton, &QPushButton::clicked, this,
|
||||||
|
&ApplicationWidget::onUninstallClicked);
|
||||||
|
|
||||||
|
// Until setConfig() is called we have no program to operate on; keep the
|
||||||
|
// whole widget disabled.
|
||||||
|
setEnabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplicationWidget::~ApplicationWidget() = default;
|
||||||
|
|
||||||
|
void ApplicationWidget::setConfig(const Config& config) {
|
||||||
|
program_ = config.program;
|
||||||
|
scope_ = config.scope;
|
||||||
|
|
||||||
|
if (program_ != nullptr) {
|
||||||
|
// Resolve program metadata that several labels depend on. These calls
|
||||||
|
// may throw std::runtime_error on failure; the caller is expected to
|
||||||
|
// wrap setConfig() in a try/catch and present an error dialog.
|
||||||
|
programName_ = QString::fromUtf8(program_->ResolveName());
|
||||||
|
|
||||||
|
auto iconRc = program_->ResolveIcon();
|
||||||
|
auto handle = iconRc.GetIcon();
|
||||||
|
programIcon_ = icon_utils::fromHicon(handle);
|
||||||
|
|
||||||
|
if (!programIcon_.isNull()) {
|
||||||
|
ui_->appIconLabel->setPixmap(
|
||||||
|
programIcon_.scaled(32, 32, Qt::KeepAspectRatio,
|
||||||
|
Qt::SmoothTransformation));
|
||||||
|
}
|
||||||
|
ui_->appDescLabel->setText(
|
||||||
|
tr("Install or uninstall %1 here.").arg(programName_));
|
||||||
|
|
||||||
|
setEnabled(true);
|
||||||
|
refresh();
|
||||||
|
} else {
|
||||||
|
programName_.clear();
|
||||||
|
programIcon_ = QPixmap();
|
||||||
|
ui_->appIconLabel->setPixmap(QPixmap());
|
||||||
|
ui_->appIconLabel->setText(QString());
|
||||||
|
ui_->appDescLabel->setText(QString());
|
||||||
|
setEnabled(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApplicationWidget::refresh() {
|
||||||
|
if (program_ == nullptr) {
|
||||||
|
ui_->installButton->setEnabled(false);
|
||||||
|
ui_->uninstallButton->setEnabled(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool registered = program_->IsRegistered(toWfassocScope(scope_));
|
||||||
|
ui_->installButton->setEnabled(!registered);
|
||||||
|
ui_->uninstallButton->setEnabled(registered);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApplicationWidget::onInstallClicked() {
|
||||||
|
if (program_ == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
program_->Register(toWfassocScope(scope_));
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
QMessageBox::critical(this, tr("Error"), QString::fromUtf8(e.what()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QMessageBox::information(this, tr("Information"),
|
||||||
|
tr("Application installed successfully."));
|
||||||
|
refresh();
|
||||||
|
emit changed();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApplicationWidget::onUninstallClicked() {
|
||||||
|
if (program_ == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
program_->Unregister(toWfassocScope(scope_));
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
QMessageBox::critical(this, tr("Error"), QString::fromUtf8(e.what()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QMessageBox::information(this, tr("Information"),
|
||||||
|
tr("Application uninstalled successfully."));
|
||||||
|
refresh();
|
||||||
|
emit changed();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace qwfassoc
|
||||||
74
example/qwfassoc/qwfassoc/src/application_widget.h
Normal file
74
example/qwfassoc/qwfassoc/src/application_widget.h
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
#pragma once
|
||||||
|
#ifndef QWFASSOC_APPLICATION_WIDGET_H_
|
||||||
|
#define QWFASSOC_APPLICATION_WIDGET_H_
|
||||||
|
|
||||||
|
#include <QPixmap>
|
||||||
|
#include <QString>
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
|
#include <wfassoc++.h>
|
||||||
|
|
||||||
|
#include "qwfassoc_global.h"
|
||||||
|
#include "scope.h"
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class ApplicationWidget;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace qwfassoc {
|
||||||
|
|
||||||
|
// Widget exposing install / uninstall actions for a single wfassoc program.
|
||||||
|
//
|
||||||
|
// The widget follows the two-phase initialization pattern required by Qt
|
||||||
|
// Designer promoted widgets: the constructor only takes a parent, and the
|
||||||
|
// caller must invoke setConfig() with the target program and scope before the
|
||||||
|
// widget becomes usable. Until setConfig() is called the widget is disabled.
|
||||||
|
class QWFASSOC_EXPORT ApplicationWidget : public QWidget {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Configuration bundle passed to setConfig().
|
||||||
|
struct Config {
|
||||||
|
// Non-owning pointer to the wfassoc program. Must outlive the widget.
|
||||||
|
wfassocpp::Program* program = nullptr;
|
||||||
|
// Scope that install/unregister operations apply to.
|
||||||
|
TargetScope scope = TargetScope::User;
|
||||||
|
};
|
||||||
|
|
||||||
|
explicit ApplicationWidget(QWidget* parent = nullptr);
|
||||||
|
~ApplicationWidget() override;
|
||||||
|
|
||||||
|
// Two-phase initialization. Calling this with a non-null program enables
|
||||||
|
// the widget and triggers an initial refresh. Calling it with a null
|
||||||
|
// program (or not calling it at all) leaves the widget disabled.
|
||||||
|
void setConfig(const Config& config);
|
||||||
|
|
||||||
|
// Re-query the live wfassoc state and update the enabled state of the
|
||||||
|
// install / uninstall buttons. Called automatically by setConfig() and
|
||||||
|
// also intended to be called by the host whenever another component has
|
||||||
|
// mutated wfassoc state.
|
||||||
|
void refresh();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
// Emitted whenever the user performs an action that mutates wfassoc
|
||||||
|
// state (i.e. install or uninstall). The host should refresh every
|
||||||
|
// widget that depends on wfassoc state in response.
|
||||||
|
void changed();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onInstallClicked();
|
||||||
|
void onUninstallClicked();
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ui::ApplicationWidget* ui_;
|
||||||
|
wfassocpp::Program* program_ = nullptr;
|
||||||
|
TargetScope scope_ = TargetScope::User;
|
||||||
|
|
||||||
|
// Cached metadata used to fill the widget labels.
|
||||||
|
QString programName_;
|
||||||
|
QPixmap programIcon_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace qwfassoc
|
||||||
|
|
||||||
|
#endif // QWFASSOC_APPLICATION_WIDGET_H_
|
||||||
89
example/qwfassoc/qwfassoc/src/application_widget.ui
Normal file
89
example/qwfassoc/qwfassoc/src/application_widget.ui
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>ApplicationWidget</class>
|
||||||
|
<widget class="QWidget" name="ApplicationWidget">
|
||||||
|
<layout class="QVBoxLayout" name="mainLayout">
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="groupBox">
|
||||||
|
<property name="title">
|
||||||
|
<string>Install and Uninstall</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="groupLayout">
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="headerLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="appIconLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string notr="true">[icon]</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="appDescLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>Install or uninstall the application here.</string>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="actionLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="installButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Install</string>
|
||||||
|
</property>
|
||||||
|
<property name="autoDefault">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="uninstallButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Uninstall</string>
|
||||||
|
</property>
|
||||||
|
<property name="autoDefault">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="verticalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>40</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "main_window.h"
|
#include "association_widget.h"
|
||||||
#include "ui_main_window.h"
|
#include "ui_association_widget.h"
|
||||||
|
#include "icon_utils.h"
|
||||||
|
|
||||||
#include <QHeaderView>
|
#include <QHeaderView>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
@@ -7,19 +8,13 @@
|
|||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
#include <QTableWidgetItem>
|
#include <QTableWidgetItem>
|
||||||
|
|
||||||
// icon_utils.h pulls in <qt_windows.h>, so it is included after all Qt
|
|
||||||
// headers to keep the Windows include ordering that Qt expects.
|
|
||||||
#include "icon_utils.h"
|
|
||||||
|
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
namespace qwfassoc {
|
namespace qwfassoc {
|
||||||
|
|
||||||
// region: Helpers
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
// Convert our internal TargetScope enum to the wfassocpp::Scope value used by
|
// Convert the library's TargetScope enum to the wfassocpp::Scope value used by
|
||||||
// the program APIs (register / unregister / link / unlink / is_registered).
|
// the program APIs (register / unregister / link / unlink / is_registered).
|
||||||
wfassocpp::Scope toWfassocScope(TargetScope scope) {
|
wfassocpp::Scope toWfassocScope(TargetScope scope) {
|
||||||
return scope == TargetScope::User ? wfassocpp::Scope::User
|
return scope == TargetScope::User ? wfassocpp::Scope::User
|
||||||
@@ -29,12 +24,16 @@ wfassocpp::Scope toWfassocScope(TargetScope scope) {
|
|||||||
// Decide which CellState a queried ExtStatus corresponds to, given the
|
// Decide which CellState a queried ExtStatus corresponds to, given the
|
||||||
// resolved "self" name for this extension. We treat the cell as Self when the
|
// resolved "self" name for this extension. We treat the cell as Self when the
|
||||||
// resolved display name matches our own; otherwise it is treated as Other.
|
// resolved display name matches our own; otherwise it is treated as Other.
|
||||||
CellState classifyCell(const QString& selfName, const QString& observedName) {
|
detail::CellState classifyCell(const QString& selfName,
|
||||||
return observedName == selfName ? CellState::Self : CellState::Other;
|
const QString& observedName) {
|
||||||
|
return observedName == selfName ? detail::CellState::Self
|
||||||
|
: detail::CellState::Other;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Effective icon to draw for a cell, based on its state.
|
// Effective icon to draw for a cell, based on its state.
|
||||||
QPixmap effectiveCellIcon(const ExtRow& row, const CellData& cell) {
|
QPixmap effectiveCellIcon(const detail::ExtRow& row,
|
||||||
|
const detail::CellData& cell) {
|
||||||
|
using detail::CellState;
|
||||||
switch (cell.state) {
|
switch (cell.state) {
|
||||||
case CellState::Blank:
|
case CellState::Blank:
|
||||||
return QPixmap();
|
return QPixmap();
|
||||||
@@ -48,30 +47,10 @@ QPixmap effectiveCellIcon(const ExtRow& row, const CellData& cell) {
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
// endregion
|
AssociationWidget::AssociationWidget(QWidget* parent)
|
||||||
|
: QWidget(parent), ui_(new Ui::AssociationWidget) {
|
||||||
// region: Construction
|
|
||||||
|
|
||||||
MainWindow::MainWindow(wfassocpp::Program program,
|
|
||||||
TargetScope scope,
|
|
||||||
QWidget* parent)
|
|
||||||
: QDialog(parent),
|
|
||||||
ui_(new Ui::MainWindow),
|
|
||||||
program_(std::move(program)),
|
|
||||||
scope_(scope) {
|
|
||||||
ui_->setupUi(this);
|
ui_->setupUi(this);
|
||||||
|
|
||||||
// Resolve program metadata that several labels depend on. These calls may
|
|
||||||
// throw std::runtime_error on failure; the caller (main.cpp) is expected
|
|
||||||
// to handle that and present an error dialog before exiting.
|
|
||||||
programName_ = QString::fromUtf8(program_.ResolveName());
|
|
||||||
|
|
||||||
{
|
|
||||||
auto iconRc = program_.ResolveIcon();
|
|
||||||
auto handle = iconRc.GetIcon();
|
|
||||||
programIcon_ = icon_utils::fromHicon(handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch the current user name for the second column header. The USERNAME
|
// Fetch the current user name for the second column header. The USERNAME
|
||||||
// environment variable is good enough on Windows; fall back to a static
|
// environment variable is good enough on Windows; fall back to a static
|
||||||
// translatable placeholder if it is unset for any reason.
|
// translatable placeholder if it is unset for any reason.
|
||||||
@@ -79,105 +58,114 @@ MainWindow::MainWindow(wfassocpp::Program program,
|
|||||||
QStringLiteral("USERNAME"), tr("User"));
|
QStringLiteral("USERNAME"), tr("User"));
|
||||||
|
|
||||||
// Give the table as much vertical room as possible inside its layout.
|
// Give the table as much vertical room as possible inside its layout.
|
||||||
ui_->associationsLayout->setStretch(2, 1);
|
ui_->mainLayout->setStretch(2, 1);
|
||||||
|
|
||||||
// Reasonable default column widths so dotted extensions and ProgId names
|
// Reasonable default column widths so dotted extensions and ProgId names
|
||||||
// stay readable in the 480px dialog.
|
// stay readable in the 480px dialog the widget typically lives in.
|
||||||
ui_->assocTable->setColumnWidth(0, 90);
|
ui_->assocTable->setColumnWidth(0, 90);
|
||||||
ui_->assocTable->setColumnWidth(1, 175);
|
ui_->assocTable->setColumnWidth(1, 175);
|
||||||
ui_->assocTable->setColumnWidth(2, 175);
|
ui_->assocTable->setColumnWidth(2, 175);
|
||||||
ui_->assocTable->verticalHeader()->setVisible(false);
|
ui_->assocTable->verticalHeader()->setVisible(false);
|
||||||
ui_->assocTable->setShowGrid(true);
|
ui_->assocTable->setShowGrid(true);
|
||||||
|
|
||||||
// Wire signals.
|
|
||||||
connect(ui_->installButton, &QPushButton::clicked, this,
|
|
||||||
&MainWindow::onInstallClicked);
|
|
||||||
connect(ui_->uninstallButton, &QPushButton::clicked, this,
|
|
||||||
&MainWindow::onUninstallClicked);
|
|
||||||
connect(ui_->selectUserButton, &QPushButton::clicked, this,
|
connect(ui_->selectUserButton, &QPushButton::clicked, this,
|
||||||
&MainWindow::onSelectUserClicked);
|
&AssociationWidget::onSelectUserClicked);
|
||||||
connect(ui_->selectSystemButton, &QPushButton::clicked, this,
|
connect(ui_->selectSystemButton, &QPushButton::clicked, this,
|
||||||
&MainWindow::onSelectSystemClicked);
|
&AssociationWidget::onSelectSystemClicked);
|
||||||
connect(ui_->assocTable, &QTableWidget::cellClicked, this,
|
connect(ui_->assocTable, &QTableWidget::cellClicked, this,
|
||||||
&MainWindow::onCellClicked);
|
&AssociationWidget::onCellClicked);
|
||||||
connect(ui_->okButton, &QPushButton::clicked, this,
|
connect(ui_->okButton, &QPushButton::clicked, this,
|
||||||
&MainWindow::onOkClicked);
|
&AssociationWidget::onOkClicked);
|
||||||
connect(ui_->cancelButton, &QPushButton::clicked, this,
|
connect(ui_->cancelButton, &QPushButton::clicked, this,
|
||||||
&MainWindow::onCancelClicked);
|
&AssociationWidget::onCancelClicked);
|
||||||
connect(ui_->applyButton, &QPushButton::clicked, this,
|
connect(ui_->applyButton, &QPushButton::clicked, this,
|
||||||
&MainWindow::onApplyClicked);
|
&AssociationWidget::onApplyClicked);
|
||||||
|
|
||||||
retranslateUi();
|
// Until setConfig() is called we have no program to operate on; keep the
|
||||||
refreshProgramState();
|
// whole widget disabled.
|
||||||
|
setEnabled(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
MainWindow::~MainWindow() = default;
|
AssociationWidget::~AssociationWidget() = default;
|
||||||
|
|
||||||
// endregion
|
void AssociationWidget::setConfig(const Config& config) {
|
||||||
|
program_ = config.program;
|
||||||
|
scope_ = config.scope;
|
||||||
|
showOkCancelButtons_ = config.showOkCancelButtons;
|
||||||
|
|
||||||
// region: UI Text
|
ui_->okButton->setVisible(showOkCancelButtons_);
|
||||||
|
ui_->cancelButton->setVisible(showOkCancelButtons_);
|
||||||
|
|
||||||
void MainWindow::retranslateUi() {
|
if (program_ != nullptr) {
|
||||||
// The window title embeds the program name, so it is composed at runtime
|
programName_ = QString::fromUtf8(program_->ResolveName());
|
||||||
// through tr() + QString::arg to stay translatable.
|
ui_->assocHeaderLabel->setText(
|
||||||
setWindowTitle(tr("%1 Options").arg(programName_));
|
tr("File types associated with %1:").arg(programName_));
|
||||||
|
|
||||||
// Application tab.
|
QStringList headers;
|
||||||
if (!programIcon_.isNull()) {
|
headers << tr("Type") << userName_ << tr("All Users");
|
||||||
ui_->appIconLabel->setPixmap(
|
ui_->assocTable->setHorizontalHeaderLabels(headers);
|
||||||
programIcon_.scaled(32, 32, Qt::KeepAspectRatio,
|
|
||||||
Qt::SmoothTransformation));
|
setEnabled(true);
|
||||||
|
refresh();
|
||||||
|
} else {
|
||||||
|
programName_.clear();
|
||||||
|
ui_->assocHeaderLabel->setText(QString());
|
||||||
|
ui_->assocTable->setRowCount(0);
|
||||||
|
rows_.clear();
|
||||||
|
setEnabled(false);
|
||||||
}
|
}
|
||||||
ui_->appDescLabel->setText(
|
|
||||||
tr("Install or uninstall %1 here.").arg(programName_));
|
|
||||||
|
|
||||||
// File association tab.
|
|
||||||
ui_->assocHeaderLabel->setText(
|
|
||||||
tr("File types associated with %1:").arg(programName_));
|
|
||||||
|
|
||||||
QStringList headers;
|
|
||||||
headers << tr("Type") << userName_ << tr("All Users");
|
|
||||||
ui_->assocTable->setHorizontalHeaderLabels(headers);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// endregion
|
void AssociationWidget::refresh() {
|
||||||
|
if (program_ == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
updateEnabledState();
|
||||||
|
rebuildTable();
|
||||||
|
}
|
||||||
|
|
||||||
// region: State Refresh
|
void AssociationWidget::updateEnabledState() {
|
||||||
|
if (program_ == nullptr) {
|
||||||
|
ui_->selectUserButton->setEnabled(false);
|
||||||
|
ui_->selectSystemButton->setEnabled(false);
|
||||||
|
ui_->assocTable->setEnabled(false);
|
||||||
|
ui_->applyButton->setEnabled(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
void MainWindow::refreshProgramState() {
|
const bool registered = program_->IsRegistered(toWfassocScope(scope_));
|
||||||
const bool registered = program_.IsRegistered(toWfassocScope(scope_));
|
|
||||||
|
|
||||||
ui_->installButton->setEnabled(!registered);
|
|
||||||
ui_->uninstallButton->setEnabled(registered);
|
|
||||||
|
|
||||||
// The whole file-association tab is disabled until the application has
|
|
||||||
// been registered in the active scope. Additionally, the system column is
|
|
||||||
// permanently disabled when running in user mode.
|
|
||||||
const bool userColumnActive = registered;
|
const bool userColumnActive = registered;
|
||||||
const bool systemColumnActive = registered && isSystemColumnEnabled();
|
const bool systemColumnActive = registered && isSystemColumnEnabled();
|
||||||
|
|
||||||
ui_->selectUserButton->setEnabled(userColumnActive);
|
ui_->selectUserButton->setEnabled(userColumnActive);
|
||||||
ui_->selectSystemButton->setEnabled(systemColumnActive);
|
ui_->selectSystemButton->setEnabled(systemColumnActive);
|
||||||
ui_->assocTable->setEnabled(registered);
|
ui_->assocTable->setEnabled(registered);
|
||||||
|
// The Apply button enable state is driven by pending changes too; only
|
||||||
rebuildTable();
|
// touch it here to make sure it's disabled when nothing is registered.
|
||||||
|
if (!registered) {
|
||||||
|
ui_->applyButton->setEnabled(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::rebuildTable() {
|
void AssociationWidget::rebuildTable() {
|
||||||
|
if (program_ == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
refreshing_ = true;
|
refreshing_ = true;
|
||||||
|
|
||||||
rows_.clear();
|
rows_.clear();
|
||||||
const size_t count = program_.ExtsLen();
|
const size_t count = program_->ExtsLen();
|
||||||
rows_.reserve(count);
|
rows_.reserve(count);
|
||||||
|
|
||||||
ui_->assocTable->setRowCount(static_cast<int>(count));
|
ui_->assocTable->setRowCount(static_cast<int>(count));
|
||||||
|
|
||||||
for (size_t i = 0; i < count; ++i) {
|
for (size_t i = 0; i < count; ++i) {
|
||||||
ExtRow row;
|
detail::ExtRow row;
|
||||||
row.index = i;
|
row.index = i;
|
||||||
|
|
||||||
// Self extension info: dotted body, display name and cached icon.
|
// Self extension info: dotted body, display name and cached icon.
|
||||||
auto selfExt = program_.ResolveExt(i);
|
auto selfExt = program_->ResolveExt(i);
|
||||||
row.extBody = QString::fromUtf8(selfExt.GetExt());
|
row.extBody = QString::fromUtf8(selfExt.GetExt());
|
||||||
row.dottedExt = QString::fromUtf8(selfExt.GetDottedExt());
|
row.dottedExt = QString::fromUtf8(selfExt.GetDottedExt());
|
||||||
row.selfName = QString::fromUtf8(selfExt.GetName());
|
row.selfName = QString::fromUtf8(selfExt.GetName());
|
||||||
@@ -186,7 +174,7 @@ void MainWindow::rebuildTable() {
|
|||||||
// Query the user-view and system-view states. None means blank;
|
// Query the user-view and system-view states. None means blank;
|
||||||
// a match against our self name means Self; anything else is Other
|
// a match against our self name means Self; anything else is Other
|
||||||
// and we keep the original name/icon around for display.
|
// and we keep the original name/icon around for display.
|
||||||
auto userStatus = program_.QueryExt(wfassocpp::View::User, i);
|
auto userStatus = program_->QueryExt(wfassocpp::View::User, i);
|
||||||
if (userStatus) {
|
if (userStatus) {
|
||||||
const QString observedName =
|
const QString observedName =
|
||||||
QString::fromUtf8(userStatus->GetName());
|
QString::fromUtf8(userStatus->GetName());
|
||||||
@@ -196,7 +184,7 @@ void MainWindow::rebuildTable() {
|
|||||||
icon_utils::fromHicon(userStatus->GetIcon());
|
icon_utils::fromHicon(userStatus->GetIcon());
|
||||||
}
|
}
|
||||||
|
|
||||||
auto systemStatus = program_.QueryExt(wfassocpp::View::System, i);
|
auto systemStatus = program_->QueryExt(wfassocpp::View::System, i);
|
||||||
if (systemStatus) {
|
if (systemStatus) {
|
||||||
const QString observedName =
|
const QString observedName =
|
||||||
QString::fromUtf8(systemStatus->GetName());
|
QString::fromUtf8(systemStatus->GetName());
|
||||||
@@ -216,7 +204,7 @@ void MainWindow::rebuildTable() {
|
|||||||
// update their text/icon and flags.
|
// update their text/icon and flags.
|
||||||
const int rowIdx = static_cast<int>(i);
|
const int rowIdx = static_cast<int>(i);
|
||||||
|
|
||||||
auto* typeItem = new QTableWidgetItem(row.dottedExt);
|
auto* typeItem = new QTableWidgetItem(rows_.back().dottedExt);
|
||||||
ui_->assocTable->setItem(rowIdx, 0, typeItem);
|
ui_->assocTable->setItem(rowIdx, 0, typeItem);
|
||||||
|
|
||||||
auto* userItem = new QTableWidgetItem;
|
auto* userItem = new QTableWidgetItem;
|
||||||
@@ -235,11 +223,13 @@ void MainWindow::rebuildTable() {
|
|||||||
updateApplyButtonEnabled();
|
updateApplyButtonEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::refreshRowDisplay(int row) {
|
void AssociationWidget::refreshRowDisplay(int row) {
|
||||||
|
using detail::CellState;
|
||||||
|
|
||||||
if (row < 0 || row >= static_cast<int>(rows_.size())) {
|
if (row < 0 || row >= static_cast<int>(rows_.size())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const ExtRow& r = rows_[row];
|
const detail::ExtRow& r = rows_[row];
|
||||||
|
|
||||||
// Column 0: hybrid icon (user-preferred) + dotted extension.
|
// Column 0: hybrid icon (user-preferred) + dotted extension.
|
||||||
QPixmap hybridIcon;
|
QPixmap hybridIcon;
|
||||||
@@ -285,9 +275,9 @@ void MainWindow::refreshRowDisplay(int row) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::updateApplyButtonEnabled() {
|
void AssociationWidget::updateApplyButtonEnabled() {
|
||||||
bool dirty = false;
|
bool dirty = false;
|
||||||
for (const ExtRow& r : rows_) {
|
for (const detail::ExtRow& r : rows_) {
|
||||||
if (r.pendingUser.state != r.initialUser.state ||
|
if (r.pendingUser.state != r.initialUser.state ||
|
||||||
r.pendingSystem.state != r.initialSystem.state) {
|
r.pendingSystem.state != r.initialSystem.state) {
|
||||||
dirty = true;
|
dirty = true;
|
||||||
@@ -297,11 +287,9 @@ void MainWindow::updateApplyButtonEnabled() {
|
|||||||
ui_->applyButton->setEnabled(dirty);
|
ui_->applyButton->setEnabled(dirty);
|
||||||
}
|
}
|
||||||
|
|
||||||
// endregion
|
void AssociationWidget::toggleCell(int row, int column) {
|
||||||
|
using detail::CellState;
|
||||||
|
|
||||||
// region: Cell Interaction
|
|
||||||
|
|
||||||
void MainWindow::toggleCell(int row, int column) {
|
|
||||||
if (refreshing_) {
|
if (refreshing_) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -309,8 +297,8 @@ void MainWindow::toggleCell(int row, int column) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
CellData* cell = nullptr;
|
detail::CellData* cell = nullptr;
|
||||||
const ExtRow* rowPtr = &rows_[row];
|
const detail::ExtRow* rowPtr = &rows_[row];
|
||||||
if (column == 1) {
|
if (column == 1) {
|
||||||
cell = &rows_[row].pendingUser;
|
cell = &rows_[row].pendingUser;
|
||||||
} else if (column == 2) {
|
} else if (column == 2) {
|
||||||
@@ -337,7 +325,9 @@ void MainWindow::toggleCell(int row, int column) {
|
|||||||
updateApplyButtonEnabled();
|
updateApplyButtonEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::selectAllInScope(bool isUser) {
|
void AssociationWidget::selectAllInScope(bool isUser) {
|
||||||
|
using detail::CellState;
|
||||||
|
|
||||||
if (!isUser && !isSystemColumnEnabled()) {
|
if (!isUser && !isSystemColumnEnabled()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -346,16 +336,16 @@ void MainWindow::selectAllInScope(bool isUser) {
|
|||||||
// first click only fills blanks; otherwise the click overrides cells
|
// first click only fills blanks; otherwise the click overrides cells
|
||||||
// pointing at other handlers as well.
|
// pointing at other handlers as well.
|
||||||
bool hasBlank = false;
|
bool hasBlank = false;
|
||||||
for (ExtRow& r : rows_) {
|
for (detail::ExtRow& r : rows_) {
|
||||||
const CellData& cell = isUser ? r.pendingUser : r.pendingSystem;
|
const detail::CellData& cell = isUser ? r.pendingUser : r.pendingSystem;
|
||||||
if (cell.state == CellState::Blank) {
|
if (cell.state == CellState::Blank) {
|
||||||
hasBlank = true;
|
hasBlank = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (ExtRow& r : rows_) {
|
for (detail::ExtRow& r : rows_) {
|
||||||
CellData& cell = isUser ? r.pendingUser : r.pendingSystem;
|
detail::CellData& cell = isUser ? r.pendingUser : r.pendingSystem;
|
||||||
if (hasBlank) {
|
if (hasBlank) {
|
||||||
if (cell.state == CellState::Blank) {
|
if (cell.state == CellState::Blank) {
|
||||||
cell.state = CellState::Self;
|
cell.state = CellState::Self;
|
||||||
@@ -375,27 +365,23 @@ void MainWindow::selectAllInScope(bool isUser) {
|
|||||||
updateApplyButtonEnabled();
|
updateApplyButtonEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
// endregion
|
void AssociationWidget::applyAllChanges() {
|
||||||
|
|
||||||
// region: Apply
|
|
||||||
|
|
||||||
void MainWindow::applyAllChanges() {
|
|
||||||
// Walk through every row and commit any cell whose pending state differs
|
// Walk through every row and commit any cell whose pending state differs
|
||||||
// from the initial snapshot. wfassoc's link/unlink take an index rather
|
// from the initial snapshot. wfassoc's link/unlink take an index rather
|
||||||
// than a scope/view, so we map columns back to (scope, index) pairs.
|
// than a scope/view, so we map columns back to (scope, index) pairs.
|
||||||
for (const ExtRow& r : rows_) {
|
for (const detail::ExtRow& r : rows_) {
|
||||||
if (r.pendingUser.state != r.initialUser.state) {
|
if (r.pendingUser.state != r.initialUser.state) {
|
||||||
if (r.pendingUser.state == CellState::Self) {
|
if (r.pendingUser.state == detail::CellState::Self) {
|
||||||
program_.LinkExt(wfassocpp::Scope::User, r.index);
|
program_->LinkExt(wfassocpp::Scope::User, r.index);
|
||||||
} else {
|
} else {
|
||||||
program_.UnlinkExt(wfassocpp::Scope::User, r.index);
|
program_->UnlinkExt(wfassocpp::Scope::User, r.index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (r.pendingSystem.state != r.initialSystem.state) {
|
if (r.pendingSystem.state != r.initialSystem.state) {
|
||||||
if (r.pendingSystem.state == CellState::Self) {
|
if (r.pendingSystem.state == detail::CellState::Self) {
|
||||||
program_.LinkExt(wfassocpp::Scope::System, r.index);
|
program_->LinkExt(wfassocpp::Scope::System, r.index);
|
||||||
} else {
|
} else {
|
||||||
program_.UnlinkExt(wfassocpp::Scope::System, r.index);
|
program_->UnlinkExt(wfassocpp::Scope::System, r.index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -404,83 +390,49 @@ void MainWindow::applyAllChanges() {
|
|||||||
rebuildTable();
|
rebuildTable();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MainWindow::isSystemColumnEnabled() const {
|
bool AssociationWidget::isSystemColumnEnabled() const {
|
||||||
return scope_ == TargetScope::System;
|
return scope_ == TargetScope::System;
|
||||||
}
|
}
|
||||||
|
|
||||||
// endregion
|
void AssociationWidget::onSelectUserClicked() {
|
||||||
|
|
||||||
// region: Slots
|
|
||||||
|
|
||||||
void MainWindow::onInstallClicked() {
|
|
||||||
try {
|
|
||||||
program_.Register(toWfassocScope(scope_));
|
|
||||||
} catch (const std::exception& e) {
|
|
||||||
QMessageBox::critical(this, windowTitle(),
|
|
||||||
QString::fromUtf8(e.what()));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QMessageBox::information(this, windowTitle(),
|
|
||||||
tr("Application installed successfully."));
|
|
||||||
refreshProgramState();
|
|
||||||
}
|
|
||||||
|
|
||||||
void MainWindow::onUninstallClicked() {
|
|
||||||
try {
|
|
||||||
program_.Unregister(toWfassocScope(scope_));
|
|
||||||
} catch (const std::exception& e) {
|
|
||||||
QMessageBox::critical(this, windowTitle(),
|
|
||||||
QString::fromUtf8(e.what()));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QMessageBox::information(this, windowTitle(),
|
|
||||||
tr("Application uninstalled successfully."));
|
|
||||||
refreshProgramState();
|
|
||||||
}
|
|
||||||
|
|
||||||
void MainWindow::onSelectUserClicked() {
|
|
||||||
selectAllInScope(/*isUser=*/true);
|
selectAllInScope(/*isUser=*/true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::onSelectSystemClicked() {
|
void AssociationWidget::onSelectSystemClicked() {
|
||||||
selectAllInScope(/*isUser=*/false);
|
selectAllInScope(/*isUser=*/false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::onCellClicked(int row, int column) {
|
void AssociationWidget::onCellClicked(int row, int column) {
|
||||||
toggleCell(row, column);
|
toggleCell(row, column);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::onOkClicked() {
|
void AssociationWidget::onOkClicked() {
|
||||||
try {
|
try {
|
||||||
applyAllChanges();
|
applyAllChanges();
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
QMessageBox::critical(this, windowTitle(),
|
QMessageBox::critical(this, tr("Error"), QString::fromUtf8(e.what()));
|
||||||
QString::fromUtf8(e.what()));
|
|
||||||
// Sync the table with the live registry, since some changes may have
|
// Sync the table with the live registry, since some changes may have
|
||||||
// been committed before the failure.
|
// been committed before the failure.
|
||||||
refreshProgramState();
|
refresh();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
accept();
|
emit changed();
|
||||||
|
emit finished(/*accepted=*/true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::onCancelClicked() {
|
void AssociationWidget::onCancelClicked() {
|
||||||
reject();
|
emit finished(/*accepted=*/false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::onApplyClicked() {
|
void AssociationWidget::onApplyClicked() {
|
||||||
try {
|
try {
|
||||||
applyAllChanges();
|
applyAllChanges();
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
QMessageBox::critical(this, windowTitle(),
|
QMessageBox::critical(this, tr("Error"), QString::fromUtf8(e.what()));
|
||||||
QString::fromUtf8(e.what()));
|
refresh();
|
||||||
refreshProgramState();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
emit changed();
|
||||||
}
|
}
|
||||||
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
} // namespace qwfassoc
|
} // namespace qwfassoc
|
||||||
@@ -1,32 +1,29 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#ifndef QWFASSOC_MAIN_WINDOW_H_
|
#ifndef QWFASSOC_ASSOCIATION_WIDGET_H_
|
||||||
#define QWFASSOC_MAIN_WINDOW_H_
|
#define QWFASSOC_ASSOCIATION_WIDGET_H_
|
||||||
|
|
||||||
#include <QDialog>
|
|
||||||
#include <QPixmap>
|
#include <QPixmap>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <wfassoc++.h>
|
#include <wfassoc++.h>
|
||||||
|
|
||||||
|
#include "qwfassoc_global.h"
|
||||||
|
#include "scope.h"
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
class MainWindow;
|
class AssociationWidget;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace qwfassoc {
|
namespace qwfassoc {
|
||||||
|
|
||||||
// The target scope the application is being managed for.
|
// Internal helper types used by AssociationWidget. They live in a `detail`
|
||||||
// This value comes from the --for command line argument and decides which
|
// namespace to signal that they are not part of the public API even though
|
||||||
// columns in the file association table are interactive, as well as which
|
// they need to be visible in the header.
|
||||||
// scope install/uninstall operate on.
|
namespace detail {
|
||||||
enum class TargetScope {
|
|
||||||
User,
|
|
||||||
System,
|
|
||||||
};
|
|
||||||
|
|
||||||
// The pending state of a single (extension, scope) cell.
|
|
||||||
enum class CellState {
|
enum class CellState {
|
||||||
// The extension has no associated handler in this scope.
|
// The extension has no associated handler in this scope.
|
||||||
Blank,
|
Blank,
|
||||||
@@ -68,42 +65,64 @@ struct ExtRow {
|
|||||||
CellData pendingSystem;
|
CellData pendingSystem;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Main configuration dialog. Owns the wfassoc Program for its lifetime.
|
} // namespace detail
|
||||||
class MainWindow : public QDialog {
|
|
||||||
|
// Widget showing the per-extension file-association status of a wfassoc
|
||||||
|
// program, and letting the user stage link / unlink operations.
|
||||||
|
//
|
||||||
|
// The widget follows the two-phase initialization pattern: the constructor
|
||||||
|
// only takes a parent, and the caller invokes setConfig() with the target
|
||||||
|
// program and scope before the widget becomes usable.
|
||||||
|
//
|
||||||
|
// By default the OK and Cancel buttons are hidden because they imply a
|
||||||
|
// dialog-level operation (close). Hosts that embed this widget in a dialog
|
||||||
|
// can enable them through Config::showOkCancelButtons and react to the
|
||||||
|
// finished() signal.
|
||||||
|
class QWFASSOC_EXPORT AssociationWidget : public QWidget {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit MainWindow(wfassocpp::Program program,
|
struct Config {
|
||||||
TargetScope scope,
|
// Non-owning pointer to the wfassoc program. Must outlive the widget.
|
||||||
QWidget* parent = nullptr);
|
wfassocpp::Program* program = nullptr;
|
||||||
~MainWindow() override;
|
// Scope that link / unlink operations apply to. Also controls whether
|
||||||
|
// the system column is interactive (only System scope unlocks it).
|
||||||
|
TargetScope scope = TargetScope::User;
|
||||||
|
// Whether the OK and Cancel buttons are visible. They are hidden by
|
||||||
|
// default since closing the host window is a host-level decision.
|
||||||
|
bool showOkCancelButtons = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
explicit AssociationWidget(QWidget* parent = nullptr);
|
||||||
|
~AssociationWidget() override;
|
||||||
|
|
||||||
|
// Two-phase initialization.
|
||||||
|
void setConfig(const Config& config);
|
||||||
|
|
||||||
|
// Re-query the live wfassoc state and rebuild the table.
|
||||||
|
void refresh();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
// Emitted after the user applies pending changes (OK or Apply). The host
|
||||||
|
// should refresh every widget that depends on wfassoc state in response.
|
||||||
|
void changed();
|
||||||
|
// Emitted when the widget wants its host window to close. `accepted` is
|
||||||
|
// true when the OK button was used (after the changes were applied and
|
||||||
|
// changed() was emitted) and false when the Cancel button was used.
|
||||||
|
void finished(bool accepted);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
// Slot connected to the "Install" button.
|
|
||||||
void onInstallClicked();
|
|
||||||
// Slot connected to the "Uninstall" button.
|
|
||||||
void onUninstallClicked();
|
|
||||||
// Slot connected to the "+" button above the user column.
|
|
||||||
void onSelectUserClicked();
|
void onSelectUserClicked();
|
||||||
// Slot connected to the "+" button above the system column.
|
|
||||||
void onSelectSystemClicked();
|
void onSelectSystemClicked();
|
||||||
// Slot connected to QTableWidget::cellClicked.
|
|
||||||
void onCellClicked(int row, int column);
|
void onCellClicked(int row, int column);
|
||||||
// Slot connected to the "OK" button: apply pending changes then close.
|
|
||||||
void onOkClicked();
|
void onOkClicked();
|
||||||
// Slot connected to the "Cancel" button: close without applying.
|
|
||||||
void onCancelClicked();
|
void onCancelClicked();
|
||||||
// Slot connected to the "Apply" button: apply pending changes, stay open.
|
|
||||||
void onApplyClicked();
|
void onApplyClicked();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Re-apply labels that depend on the resolved program name and the
|
// Refresh install / apply button enable state based on the live registry
|
||||||
// current user name. Translatable strings use tr() so they get picked up
|
// and the pending edits.
|
||||||
// by Qt's translation tooling.
|
void updateEnabledState();
|
||||||
void retranslateUi();
|
|
||||||
// Refresh install/uninstall button enable state and the file association
|
|
||||||
// tab enable state from the live registry.
|
|
||||||
void refreshProgramState();
|
|
||||||
// Drop and rebuild the table contents from the live registry.
|
// Drop and rebuild the table contents from the live registry.
|
||||||
void rebuildTable();
|
void rebuildTable();
|
||||||
// Refresh a single row's displayed cells from its pending state.
|
// Refresh a single row's displayed cells from its pending state.
|
||||||
@@ -119,19 +138,18 @@ private:
|
|||||||
void selectAllInScope(bool isUser);
|
void selectAllInScope(bool isUser);
|
||||||
// Commit every pending change to the registry via wfassoc.
|
// Commit every pending change to the registry via wfassoc.
|
||||||
void applyAllChanges();
|
void applyAllChanges();
|
||||||
|
|
||||||
// True when the system column should be interactive.
|
// True when the system column should be interactive.
|
||||||
bool isSystemColumnEnabled() const;
|
bool isSystemColumnEnabled() const;
|
||||||
|
|
||||||
std::unique_ptr<Ui::MainWindow> ui_;
|
Ui::AssociationWidget* ui_;
|
||||||
wfassocpp::Program program_;
|
wfassocpp::Program* program_ = nullptr;
|
||||||
TargetScope scope_;
|
TargetScope scope_ = TargetScope::User;
|
||||||
|
bool showOkCancelButtons_ = false;
|
||||||
|
|
||||||
QString programName_;
|
QString programName_;
|
||||||
QPixmap programIcon_;
|
|
||||||
QString userName_;
|
QString userName_;
|
||||||
|
|
||||||
std::vector<ExtRow> rows_;
|
std::vector<detail::ExtRow> rows_;
|
||||||
// Re-entrancy guard used while rebuilding the table to avoid feeding
|
// Re-entrancy guard used while rebuilding the table to avoid feeding
|
||||||
// model-change signals back into toggleCell().
|
// model-change signals back into toggleCell().
|
||||||
bool refreshing_ = false;
|
bool refreshing_ = false;
|
||||||
@@ -139,4 +157,4 @@ private:
|
|||||||
|
|
||||||
} // namespace qwfassoc
|
} // namespace qwfassoc
|
||||||
|
|
||||||
#endif // QWFASSOC_MAIN_WINDOW_H_
|
#endif // QWFASSOC_ASSOCIATION_WIDGET_H_
|
||||||
162
example/qwfassoc/qwfassoc/src/association_widget.ui
Normal file
162
example/qwfassoc/qwfassoc/src/association_widget.ui
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>AssociationWidget</class>
|
||||||
|
<widget class="QWidget" name="AssociationWidget">
|
||||||
|
<layout class="QVBoxLayout" name="mainLayout">
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="assocHeaderLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>File types associated with this application:</string>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="selectButtonsLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="selectUserButton">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Select all for current user</string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string notr="true">+</string>
|
||||||
|
</property>
|
||||||
|
<property name="autoDefault">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="selectSystemButton">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Select all for all users</string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string notr="true">+</string>
|
||||||
|
</property>
|
||||||
|
<property name="autoDefault">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="selectButtonsSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QTableWidget" name="assocTable">
|
||||||
|
<property name="editTriggers">
|
||||||
|
<set>QAbstractItemView::NoEditTriggers</set>
|
||||||
|
</property>
|
||||||
|
<property name="selectionMode">
|
||||||
|
<enum>QAbstractItemView::SingleSelection</enum>
|
||||||
|
</property>
|
||||||
|
<property name="selectionBehavior">
|
||||||
|
<enum>QAbstractItemView::SelectItems</enum>
|
||||||
|
</property>
|
||||||
|
<column>
|
||||||
|
<property name="text">
|
||||||
|
<string>Type</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
|
<column>
|
||||||
|
<property name="text">
|
||||||
|
<string>User</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
|
<column>
|
||||||
|
<property name="text">
|
||||||
|
<string>All Users</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="bottomButtonsLayout">
|
||||||
|
<item>
|
||||||
|
<spacer name="bottomButtonsSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="okButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>OK</string>
|
||||||
|
</property>
|
||||||
|
<property name="autoDefault">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="default">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="visible">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="cancelButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Cancel</string>
|
||||||
|
</property>
|
||||||
|
<property name="autoDefault">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="visible">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="applyButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Apply</string>
|
||||||
|
</property>
|
||||||
|
<property name="autoDefault">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
||||||
@@ -6,6 +6,8 @@
|
|||||||
|
|
||||||
#include <wfassoc++.h>
|
#include <wfassoc++.h>
|
||||||
|
|
||||||
|
#include "qwfassoc_global.h"
|
||||||
|
|
||||||
namespace qwfassoc {
|
namespace qwfassoc {
|
||||||
namespace icon_utils {
|
namespace icon_utils {
|
||||||
|
|
||||||
@@ -16,7 +18,7 @@ namespace icon_utils {
|
|||||||
// Windows) and then QPixmap::fromImage() using its rvalue-reference overload
|
// Windows) and then QPixmap::fromImage() using its rvalue-reference overload
|
||||||
// so that no extra pixel buffer copy is performed. The returned pixmap is null
|
// so that no extra pixel buffer copy is performed. The returned pixmap is null
|
||||||
// if the input handle is null.
|
// if the input handle is null.
|
||||||
QPixmap fromHicon(wfassocpp::HICON handle);
|
QWFASSOC_EXPORT QPixmap fromHicon(wfassocpp::HICON handle);
|
||||||
|
|
||||||
} // namespace icon_utils
|
} // namespace icon_utils
|
||||||
} // namespace qwfassoc
|
} // namespace qwfassoc
|
||||||
@@ -6,8 +6,6 @@
|
|||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include <wfassoc++.h>
|
|
||||||
|
|
||||||
namespace qwfassoc {
|
namespace qwfassoc {
|
||||||
|
|
||||||
// Description of a single extension declared in the manifest.
|
// Description of a single extension declared in the manifest.
|
||||||
@@ -23,9 +21,11 @@ struct ManifestExt {
|
|||||||
// In-memory representation of a wfassoc manifest TOML file.
|
// In-memory representation of a wfassoc manifest TOML file.
|
||||||
//
|
//
|
||||||
// This struct mirrors the Rust `Manifest` type defined in
|
// This struct mirrors the Rust `Manifest` type defined in
|
||||||
// `wfassoc-exec/src/manifest.rs`. All validation that requires the wfassoc
|
// `wfassoc-exec/src/manifest.rs`. It is a plain data struct: parsing from
|
||||||
// library itself (identifier regex, dangling references, etc.) is deferred to
|
// TOML and conversion into a wfassocpp::Schema are intentionally kept out of
|
||||||
// `wfassocpp::Program` construction; this struct only performs TOML parsing.
|
// the library so that the library itself does not depend on a TOML parser.
|
||||||
|
// Consumers (such as qwfassoc-standalone) are responsible for filling the
|
||||||
|
// fields in.
|
||||||
struct Manifest {
|
struct Manifest {
|
||||||
std::string identifier;
|
std::string identifier;
|
||||||
std::string path;
|
std::string path;
|
||||||
@@ -39,15 +39,6 @@ struct Manifest {
|
|||||||
std::map<std::string, std::string> icons;
|
std::map<std::string, std::string> icons;
|
||||||
std::map<std::string, std::string> behaviors;
|
std::map<std::string, std::string> behaviors;
|
||||||
std::map<std::string, ManifestExt> exts;
|
std::map<std::string, ManifestExt> exts;
|
||||||
|
|
||||||
// Parse a manifest TOML file from disk.
|
|
||||||
// Throws std::runtime_error on any IO or TOML syntax error.
|
|
||||||
static Manifest fromFile(const std::string& path);
|
|
||||||
|
|
||||||
// Build a wfassocpp::Schema from this manifest.
|
|
||||||
// Throws std::runtime_error (originating from wfassocpp::_Check) when the
|
|
||||||
// wfassoc library rejects an operation, e.g. on duplicate keys.
|
|
||||||
wfassocpp::Schema toSchema() const;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace qwfassoc
|
} // namespace qwfassoc
|
||||||
20
example/qwfassoc/qwfassoc/src/qwfassoc_global.h
Normal file
20
example/qwfassoc/qwfassoc/src/qwfassoc_global.h
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
#pragma once
|
||||||
|
#ifndef QWFASSOC_GLOBAL_H_
|
||||||
|
#define QWFASSOC_GLOBAL_H_
|
||||||
|
|
||||||
|
#include <qglobal.h>
|
||||||
|
|
||||||
|
// Standard Qt shared-library export/import macros.
|
||||||
|
//
|
||||||
|
// When the qwfassoc library itself is being built, QWFASSOC_LIBRARY is
|
||||||
|
// defined (see the library's CMakeLists.txt) and QWFASSOC_EXPORT expands to
|
||||||
|
// Q_DECL_EXPORT so that symbols are exported from the .dll. Consumers of the
|
||||||
|
// library leave QWFASSOC_LIBRARY undefined, so QWFASSOC_EXPORT expands to
|
||||||
|
// Q_DECL_IMPORT and the same symbols are imported.
|
||||||
|
#if defined(QWFASSOC_LIBRARY)
|
||||||
|
# define QWFASSOC_EXPORT Q_DECL_EXPORT
|
||||||
|
#else
|
||||||
|
# define QWFASSOC_EXPORT Q_DECL_IMPORT
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // QWFASSOC_GLOBAL_H_
|
||||||
20
example/qwfassoc/qwfassoc/src/scope.h
Normal file
20
example/qwfassoc/qwfassoc/src/scope.h
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
#pragma once
|
||||||
|
#ifndef QWFASSOC_SCOPE_H_
|
||||||
|
#define QWFASSOC_SCOPE_H_
|
||||||
|
|
||||||
|
namespace qwfassoc {
|
||||||
|
|
||||||
|
// The target scope an application is being managed for.
|
||||||
|
//
|
||||||
|
// This value typically comes from a `--for user` / `--for system` command
|
||||||
|
// line argument and decides which columns of the file-association table are
|
||||||
|
// interactive, as well as which scope install / unregister operations apply
|
||||||
|
// to.
|
||||||
|
enum class TargetScope {
|
||||||
|
User,
|
||||||
|
System,
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace qwfassoc
|
||||||
|
|
||||||
|
#endif // QWFASSOC_SCOPE_H_
|
||||||
@@ -1,267 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<ui version="4.0">
|
|
||||||
<class>MainWindow</class>
|
|
||||||
<widget class="QDialog" name="MainWindow">
|
|
||||||
<property name="geometry">
|
|
||||||
<rect>
|
|
||||||
<x>0</x>
|
|
||||||
<y>0</y>
|
|
||||||
<width>480</width>
|
|
||||||
<height>600</height>
|
|
||||||
</rect>
|
|
||||||
</property>
|
|
||||||
<property name="minimumSize">
|
|
||||||
<size>
|
|
||||||
<width>480</width>
|
|
||||||
<height>600</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="maximumSize">
|
|
||||||
<size>
|
|
||||||
<width>480</width>
|
|
||||||
<height>600</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="windowTitle">
|
|
||||||
<string>Options</string>
|
|
||||||
</property>
|
|
||||||
<layout class="QVBoxLayout" name="mainLayout">
|
|
||||||
<property name="leftMargin">
|
|
||||||
<number>9</number>
|
|
||||||
</property>
|
|
||||||
<property name="topMargin">
|
|
||||||
<number>9</number>
|
|
||||||
</property>
|
|
||||||
<property name="rightMargin">
|
|
||||||
<number>9</number>
|
|
||||||
</property>
|
|
||||||
<property name="bottomMargin">
|
|
||||||
<number>9</number>
|
|
||||||
</property>
|
|
||||||
<item>
|
|
||||||
<widget class="QTabWidget" name="tabWidget">
|
|
||||||
<property name="currentIndex">
|
|
||||||
<number>0</number>
|
|
||||||
</property>
|
|
||||||
<widget class="QWidget" name="tabApplications">
|
|
||||||
<attribute name="title">
|
|
||||||
<string>Applications</string>
|
|
||||||
</attribute>
|
|
||||||
<layout class="QVBoxLayout" name="applicationsLayout">
|
|
||||||
<item>
|
|
||||||
<widget class="QGroupBox" name="groupBox">
|
|
||||||
<property name="title">
|
|
||||||
<string>Install and Uninstall</string>
|
|
||||||
</property>
|
|
||||||
<layout class="QVBoxLayout" name="groupLayout">
|
|
||||||
<item>
|
|
||||||
<layout class="QHBoxLayout" name="headerLayout">
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="appIconLabel">
|
|
||||||
<property name="text">
|
|
||||||
<string notr="true">[icon]</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="appDescLabel">
|
|
||||||
<property name="text">
|
|
||||||
<string>Install or uninstall the application here.</string>
|
|
||||||
</property>
|
|
||||||
<property name="wordWrap">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<layout class="QHBoxLayout" name="actionLayout">
|
|
||||||
<item>
|
|
||||||
<widget class="QPushButton" name="installButton">
|
|
||||||
<property name="text">
|
|
||||||
<string>Install</string>
|
|
||||||
</property>
|
|
||||||
<property name="autoDefault">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QPushButton" name="uninstallButton">
|
|
||||||
<property name="text">
|
|
||||||
<string>Uninstall</string>
|
|
||||||
</property>
|
|
||||||
<property name="autoDefault">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<spacer name="applicationsVerticalSpacer">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Vertical</enum>
|
|
||||||
</property>
|
|
||||||
<property name="sizeHint" stdset="0">
|
|
||||||
<size>
|
|
||||||
<width>20</width>
|
|
||||||
<height>40</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
</spacer>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
<widget class="QWidget" name="tabAssociations">
|
|
||||||
<attribute name="title">
|
|
||||||
<string>File Associations</string>
|
|
||||||
</attribute>
|
|
||||||
<layout class="QVBoxLayout" name="associationsLayout">
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="assocHeaderLabel">
|
|
||||||
<property name="text">
|
|
||||||
<string>File types associated with this application:</string>
|
|
||||||
</property>
|
|
||||||
<property name="wordWrap">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<layout class="QHBoxLayout" name="selectButtonsLayout">
|
|
||||||
<item>
|
|
||||||
<widget class="QPushButton" name="selectUserButton">
|
|
||||||
<property name="toolTip">
|
|
||||||
<string>Select all for current user</string>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string notr="true">+</string>
|
|
||||||
</property>
|
|
||||||
<property name="autoDefault">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QPushButton" name="selectSystemButton">
|
|
||||||
<property name="toolTip">
|
|
||||||
<string>Select all for all users</string>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string notr="true">+</string>
|
|
||||||
</property>
|
|
||||||
<property name="autoDefault">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<spacer name="selectButtonsSpacer">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Horizontal</enum>
|
|
||||||
</property>
|
|
||||||
<property name="sizeHint" stdset="0">
|
|
||||||
<size>
|
|
||||||
<width>40</width>
|
|
||||||
<height>20</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
</spacer>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QTableWidget" name="assocTable">
|
|
||||||
<property name="editTriggers">
|
|
||||||
<set>QAbstractItemView::NoEditTriggers</set>
|
|
||||||
</property>
|
|
||||||
<property name="selectionMode">
|
|
||||||
<enum>QAbstractItemView::SingleSelection</enum>
|
|
||||||
</property>
|
|
||||||
<property name="selectionBehavior">
|
|
||||||
<enum>QAbstractItemView::SelectItems</enum>
|
|
||||||
</property>
|
|
||||||
<column>
|
|
||||||
<property name="text">
|
|
||||||
<string>Type</string>
|
|
||||||
</property>
|
|
||||||
</column>
|
|
||||||
<column>
|
|
||||||
<property name="text">
|
|
||||||
<string>User</string>
|
|
||||||
</property>
|
|
||||||
</column>
|
|
||||||
<column>
|
|
||||||
<property name="text">
|
|
||||||
<string>All Users</string>
|
|
||||||
</property>
|
|
||||||
</column>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<layout class="QHBoxLayout" name="bottomButtonsLayout">
|
|
||||||
<item>
|
|
||||||
<spacer name="bottomButtonsSpacer">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Horizontal</enum>
|
|
||||||
</property>
|
|
||||||
<property name="sizeHint" stdset="0">
|
|
||||||
<size>
|
|
||||||
<width>40</width>
|
|
||||||
<height>20</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
</spacer>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QPushButton" name="okButton">
|
|
||||||
<property name="text">
|
|
||||||
<string>OK</string>
|
|
||||||
</property>
|
|
||||||
<property name="autoDefault">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
<property name="default">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QPushButton" name="cancelButton">
|
|
||||||
<property name="text">
|
|
||||||
<string>Cancel</string>
|
|
||||||
</property>
|
|
||||||
<property name="autoDefault">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QPushButton" name="applyButton">
|
|
||||||
<property name="text">
|
|
||||||
<string>Apply</string>
|
|
||||||
</property>
|
|
||||||
<property name="autoDefault">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
<property name="enabled">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
<resources/>
|
|
||||||
<connections/>
|
|
||||||
</ui>
|
|
||||||
Reference in New Issue
Block a user