изменено: CMakeLists.txt
новый файл: appimage-build.nix новый файл: assets/icons/erlu.ico изменено: default.nix новый файл: packaging/windows/erlu.rc изменено: src/MainWindow.cpp изменено: src/MainWindow.h изменено: src/items/BlockItem.cpp изменено: src/items/HeaderFooterItem.cpp изменено: src/main.cpp изменено: src/plugins/color/ColorsPlugin.cpp изменено: src/plugins/color/translations/colors_en.ts изменено: src/plugins/color/translations/colors_fr.ts изменено: src/plugins/color/translations/colors_ru.ts
This commit is contained in:
parent
17f793f334
commit
58198c6ecd
14 changed files with 428 additions and 139 deletions
|
|
@ -8,6 +8,43 @@ find_package(Qt6 REQUIRED COMPONENTS Widgets Svg)
|
||||||
|
|
||||||
qt_standard_project_setup()
|
qt_standard_project_setup()
|
||||||
|
|
||||||
|
set(APP_TS_FILES
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/translations/idef0_en.ts
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/translations/idef0_fr.ts
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/translations/idef0_ru.ts
|
||||||
|
)
|
||||||
|
|
||||||
|
set(COLORS_TS_FILES
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/plugins/color/translations/colors_en.ts
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/plugins/color/translations/colors_fr.ts
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/plugins/color/translations/colors_ru.ts
|
||||||
|
)
|
||||||
|
|
||||||
|
find_program(QT_LRELEASE_EXECUTABLE NAMES lrelease-qt6 lrelease REQUIRED)
|
||||||
|
|
||||||
|
function(generate_qm_files OUT_VAR OUT_DIR)
|
||||||
|
set(_qm_files "")
|
||||||
|
foreach(_ts_file IN LISTS ARGN)
|
||||||
|
get_filename_component(_ts_name "${_ts_file}" NAME_WE)
|
||||||
|
set(_qm_file "${OUT_DIR}/${_ts_name}.qm")
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT "${_qm_file}"
|
||||||
|
COMMAND "${CMAKE_COMMAND}" -E make_directory "${OUT_DIR}"
|
||||||
|
COMMAND "${QT_LRELEASE_EXECUTABLE}" "${_ts_file}" -qm "${_qm_file}"
|
||||||
|
DEPENDS "${_ts_file}"
|
||||||
|
VERBATIM
|
||||||
|
)
|
||||||
|
list(APPEND _qm_files "${_qm_file}")
|
||||||
|
endforeach()
|
||||||
|
set(${OUT_VAR} "${_qm_files}" PARENT_SCOPE)
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
generate_qm_files(
|
||||||
|
APP_QM_FILES
|
||||||
|
"${CMAKE_CURRENT_BINARY_DIR}/translations"
|
||||||
|
${APP_TS_FILES}
|
||||||
|
)
|
||||||
|
|
||||||
qt_add_library(idef0_core SHARED
|
qt_add_library(idef0_core SHARED
|
||||||
src/MainWindow.h src/MainWindow.cpp
|
src/MainWindow.h src/MainWindow.cpp
|
||||||
src/items/DiagramScene.h src/items/DiagramScene.cpp
|
src/items/DiagramScene.h src/items/DiagramScene.cpp
|
||||||
|
|
@ -25,6 +62,12 @@ qt_add_executable(erlu_idef0_editor
|
||||||
src/main.cpp
|
src/main.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if(WIN32)
|
||||||
|
target_sources(erlu_idef0_editor PRIVATE
|
||||||
|
packaging/windows/erlu.rc
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
qt_add_resources(erlu_idef0_editor "app_icons"
|
qt_add_resources(erlu_idef0_editor "app_icons"
|
||||||
PREFIX "/icons"
|
PREFIX "/icons"
|
||||||
BASE assets/icons
|
BASE assets/icons
|
||||||
|
|
@ -79,6 +122,17 @@ if(BUILD_COLORS_PLUGIN)
|
||||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/plugins/color"
|
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/plugins/color"
|
||||||
INSTALL_RPATH "\$ORIGIN/../../lib"
|
INSTALL_RPATH "\$ORIGIN/../../lib"
|
||||||
)
|
)
|
||||||
|
generate_qm_files(
|
||||||
|
COLORS_QM_FILES
|
||||||
|
"${CMAKE_CURRENT_BINARY_DIR}/plugins/color/translations"
|
||||||
|
${COLORS_TS_FILES}
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_custom_target(translations ALL DEPENDS ${APP_QM_FILES} ${COLORS_QM_FILES})
|
||||||
|
add_dependencies(erlu_idef0_editor translations)
|
||||||
|
if(BUILD_COLORS_PLUGIN)
|
||||||
|
add_dependencies(colorsplugin translations)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
set_target_properties(erlu_idef0_editor PROPERTIES
|
set_target_properties(erlu_idef0_editor PROPERTIES
|
||||||
|
|
@ -101,6 +155,15 @@ if(BUILD_COLORS_PLUGIN)
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
install(FILES ${APP_QM_FILES}
|
||||||
|
DESTINATION translations
|
||||||
|
)
|
||||||
|
if(BUILD_COLORS_PLUGIN)
|
||||||
|
install(FILES ${COLORS_QM_FILES}
|
||||||
|
DESTINATION plugins/color/translations
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
if(UNIX AND NOT APPLE)
|
if(UNIX AND NOT APPLE)
|
||||||
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/packaging/linux/erlu-idef0-editor.desktop
|
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/packaging/linux/erlu-idef0-editor.desktop
|
||||||
DESTINATION share/applications)
|
DESTINATION share/applications)
|
||||||
|
|
|
||||||
121
appimage-build.nix
Normal file
121
appimage-build.nix
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
{ pkgs ? import <nixpkgs> {}
|
||||||
|
, buildColorsPlugin ? true
|
||||||
|
}:
|
||||||
|
|
||||||
|
# Builds a distributable AppImage from the local sources (see default.nix).
|
||||||
|
# Uses linuxdeployqt + appimagetool AppImages to bundle Qt runtime bits and
|
||||||
|
# generate the final .AppImage artifact.
|
||||||
|
let
|
||||||
|
inherit (pkgs) lib;
|
||||||
|
|
||||||
|
# Reuse the main package but skip the nixpkgs Qt wrapper so we can lay down
|
||||||
|
# the raw binaries and their closure into the AppDir.
|
||||||
|
erlu = (import ./default.nix {
|
||||||
|
inherit pkgs buildColorsPlugin;
|
||||||
|
}).overrideAttrs (_: {
|
||||||
|
dontWrapQtApps = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
appimagetoolSrc = pkgs.fetchurl {
|
||||||
|
url = "https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage";
|
||||||
|
sha256 = "sha256-ptceK2zWb46NFsN60WRliYXgz1/KqVDJCkgokMudE+A=";
|
||||||
|
};
|
||||||
|
in
|
||||||
|
pkgs.runCommand "erlu-idef0-editor-appimage"
|
||||||
|
{
|
||||||
|
nativeBuildInputs = [
|
||||||
|
pkgs.squashfsTools
|
||||||
|
pkgs.patchelf
|
||||||
|
pkgs.binutils
|
||||||
|
pkgs.findutils
|
||||||
|
pkgs.coreutils
|
||||||
|
pkgs.file
|
||||||
|
pkgs.imagemagick
|
||||||
|
pkgs.nix
|
||||||
|
];
|
||||||
|
} ''
|
||||||
|
set -euo pipefail
|
||||||
|
export HOME=$PWD/home
|
||||||
|
mkdir -p "$HOME"
|
||||||
|
|
||||||
|
# Extract appimagetool AppImage without running it
|
||||||
|
extract_appimage() {
|
||||||
|
local src="$1"
|
||||||
|
local dest="$2"
|
||||||
|
mkdir -p "$dest"
|
||||||
|
local offset
|
||||||
|
offset=$(LC_ALL=C readelf -h "$src" | awk 'NR==13{e_shoff=$5} NR==18{e_shentsize=$5} NR==19{e_shnum=$5} END{print e_shoff+e_shentsize*e_shnum}')
|
||||||
|
unsquashfs -q -d "$dest" -o "$offset" "$src"
|
||||||
|
chmod -R u+w "$dest"
|
||||||
|
}
|
||||||
|
|
||||||
|
extract_appimage ${appimagetoolSrc} appimagetool
|
||||||
|
|
||||||
|
appdir=$PWD/AppDir
|
||||||
|
mkdir -p "$appdir"/usr/{bin,share/applications,share/icons/hicolor/scalable/apps,share/mime/packages}
|
||||||
|
|
||||||
|
# Core binary and runtime data
|
||||||
|
cp ${erlu}/bin/erlu_idef0_editor "$appdir/usr/bin/erlu_idef0_editor"
|
||||||
|
chmod +x "$appdir/usr/bin/erlu_idef0_editor"
|
||||||
|
|
||||||
|
if [ -d ${erlu}/share ]; then
|
||||||
|
cp -r ${erlu}/share/* "$appdir/usr/share/"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -d ${erlu}/plugins ]; then
|
||||||
|
cp -r ${erlu}/plugins "$appdir/usr/"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Ensure we can amend the copied tree (Nix store files are read-only)
|
||||||
|
chmod -R u+w "$appdir"
|
||||||
|
|
||||||
|
# Copy the full dependency closure into /nix/store inside the AppDir so
|
||||||
|
# the existing RPATHs and interpreter paths keep working at runtime.
|
||||||
|
mkdir -p "$appdir/nix/store"
|
||||||
|
for p in $(nix-store -qR ${erlu}); do
|
||||||
|
cp -a "$p" "$appdir/nix/store/"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Desktop integration bits
|
||||||
|
cp ${./packaging/linux/erlu-idef0-editor.desktop} "$appdir/usr/share/applications/erlu-idef0-editor.desktop"
|
||||||
|
cp ${./assets/icons/erlu.svg} "$appdir/usr/share/icons/hicolor/scalable/apps/erlu.svg"
|
||||||
|
cp ${./packaging/linux/idef0.xml} "$appdir/usr/share/mime/packages/idef0.xml"
|
||||||
|
|
||||||
|
# linuxdeployqt expects the .desktop and icon at the AppDir root too
|
||||||
|
cp "$appdir/usr/share/applications/erlu-idef0-editor.desktop" "$appdir/"
|
||||||
|
cp "$appdir/usr/share/icons/hicolor/scalable/apps/erlu.svg" "$appdir/erlu.svg"
|
||||||
|
|
||||||
|
# Minimal AppRun launcher
|
||||||
|
cat > "$appdir/AppRun" <<'EOF'
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
HERE="$(dirname "$(readlink -f "$0")")"
|
||||||
|
export PATH="$HERE/usr/bin:$PATH"
|
||||||
|
exec "$HERE/usr/bin/erlu_idef0_editor" "$@"
|
||||||
|
EOF
|
||||||
|
chmod +x "$appdir/AppRun"
|
||||||
|
|
||||||
|
# Derive the type-2 runtime from the appimagetool AppImage (avoids network fetch)
|
||||||
|
runtime=$PWD/runtime
|
||||||
|
runtime_offset=$(LC_ALL=C readelf -h ${appimagetoolSrc} | awk 'NR==13{e_shoff=$5} NR==18{e_shentsize=$5} NR==19{e_shnum=$5} END{print e_shoff+e_shentsize*e_shnum}')
|
||||||
|
head -c "$runtime_offset" ${appimagetoolSrc} > "$runtime"
|
||||||
|
chmod +x "$runtime"
|
||||||
|
|
||||||
|
# Wrapper to force appimagetool to use the local runtime (and log its use)
|
||||||
|
cat > appimagetool-wrapper <<EOF
|
||||||
|
#!${pkgs.coreutils}/bin/env bash
|
||||||
|
set -euxo pipefail
|
||||||
|
echo "appimagetool-wrapper: runtime=$runtime pwd=\$PWD args=\$@" >&2
|
||||||
|
ls -l "$PWD/appimagetool/usr/bin" >&2 || true
|
||||||
|
exec "$PWD/appimagetool/usr/bin/appimagetool" --runtime-file "$runtime" "\$@"
|
||||||
|
EOF
|
||||||
|
chmod +x appimagetool-wrapper
|
||||||
|
|
||||||
|
export PATH="$PWD/appimagetool/usr/bin:${pkgs.file}/bin:$PATH"
|
||||||
|
|
||||||
|
# Build the AppImage directly with appimagetool
|
||||||
|
./appimagetool-wrapper "$appdir"
|
||||||
|
|
||||||
|
mkdir -p "$out"
|
||||||
|
mv ./*.AppImage "$out/erlu-idef0-editor-${erlu.version}.AppImage"
|
||||||
|
''
|
||||||
BIN
assets/icons/erlu.ico
Normal file
BIN
assets/icons/erlu.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
|
|
@ -8,7 +8,7 @@ let
|
||||||
in
|
in
|
||||||
stdenv.mkDerivation rec {
|
stdenv.mkDerivation rec {
|
||||||
pname = "erlu_idef0_editor";
|
pname = "erlu_idef0_editor";
|
||||||
version = "0.1.0";
|
version = "1.0.1";
|
||||||
|
|
||||||
src = lib.cleanSourceWith {
|
src = lib.cleanSourceWith {
|
||||||
src = ./.;
|
src = ./.;
|
||||||
|
|
|
||||||
1
packaging/windows/erlu.rc
Normal file
1
packaging/windows/erlu.rc
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
IDI_APP_ICON ICON "../../assets/icons/erlu.ico"
|
||||||
|
|
@ -54,6 +54,14 @@ static const char* kDiagramFileFilter = QT_TR_NOOP("IDEF0 Diagram (*.idef0);;JSO
|
||||||
static const char* kPdfFileFilter = QT_TR_NOOP("PDF (*.pdf)");
|
static const char* kPdfFileFilter = QT_TR_NOOP("PDF (*.pdf)");
|
||||||
static const char* kMarkdownFileFilter = QT_TR_NOOP("Markdown (*.md)");
|
static const char* kMarkdownFileFilter = QT_TR_NOOP("Markdown (*.md)");
|
||||||
|
|
||||||
|
static QFileDialog::Options themedFileDialogOptions() {
|
||||||
|
QFileDialog::Options options;
|
||||||
|
#if defined(Q_OS_WIN)
|
||||||
|
options |= QFileDialog::DontUseNativeDialog;
|
||||||
|
#endif
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
static QPageSize::PageSizeId pageSizeFromMeta(const QVariantMap& meta) {
|
static QPageSize::PageSizeId pageSizeFromMeta(const QVariantMap& meta) {
|
||||||
const QString size = meta.value("pageSize", "A4").toString();
|
const QString size = meta.value("pageSize", "A4").toString();
|
||||||
if (size == "A1") return QPageSize::A1;
|
if (size == "A1") return QPageSize::A1;
|
||||||
|
|
@ -95,7 +103,7 @@ static QVariantMap mergeWithDiagramState(QVariantMap globalMeta, const QVariantM
|
||||||
return globalMeta;
|
return globalMeta;
|
||||||
}
|
}
|
||||||
|
|
||||||
MainWindow::MainWindow(const QString& startupPath, QWidget* parent)
|
MainWindow::MainWindow(const QString& startupPath, QWidget* parent, bool skipStartupPrompt)
|
||||||
: QMainWindow(parent)
|
: QMainWindow(parent)
|
||||||
{
|
{
|
||||||
setupUi();
|
setupUi();
|
||||||
|
|
@ -106,7 +114,7 @@ MainWindow::MainWindow(const QString& startupPath, QWidget* parent)
|
||||||
QTimer::singleShot(0, this, &QWidget::close);
|
QTimer::singleShot(0, this, &QWidget::close);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else if (!promptStartup()) {
|
} else if (!skipStartupPrompt && !promptStartup()) {
|
||||||
QTimer::singleShot(0, this, &QWidget::close);
|
QTimer::singleShot(0, this, &QWidget::close);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -262,7 +270,8 @@ void MainWindow::setupActions() {
|
||||||
|
|
||||||
connect(actNew, &QAction::triggered, this, [this]{ newDiagram(); });
|
connect(actNew, &QAction::triggered, this, [this]{ newDiagram(); });
|
||||||
connect(actOpen, &QAction::triggered, this, [this]{
|
connect(actOpen, &QAction::triggered, this, [this]{
|
||||||
const QString path = QFileDialog::getOpenFileName(this, tr("Open diagram"), QString(), tr(kDiagramFileFilter));
|
const QString path = QFileDialog::getOpenFileName(
|
||||||
|
this, tr("Open diagram"), QString(), tr(kDiagramFileFilter), nullptr, themedFileDialogOptions());
|
||||||
if (!path.isEmpty()) {
|
if (!path.isEmpty()) {
|
||||||
loadDiagramFromPath(path);
|
loadDiagramFromPath(path);
|
||||||
}
|
}
|
||||||
|
|
@ -293,14 +302,16 @@ void MainWindow::setupActions() {
|
||||||
|
|
||||||
connect(actSave, &QAction::triggered, this, [this, saveTo]{
|
connect(actSave, &QAction::triggered, this, [this, saveTo]{
|
||||||
if (m_currentFile.isEmpty()) {
|
if (m_currentFile.isEmpty()) {
|
||||||
const QString path = QFileDialog::getSaveFileName(this, tr("Save diagram"), QString(), tr(kDiagramFileFilter));
|
const QString path = QFileDialog::getSaveFileName(
|
||||||
|
this, tr("Save diagram"), QString(), tr(kDiagramFileFilter), nullptr, themedFileDialogOptions());
|
||||||
if (!path.isEmpty()) saveTo(path);
|
if (!path.isEmpty()) saveTo(path);
|
||||||
} else {
|
} else {
|
||||||
saveTo(m_currentFile);
|
saveTo(m_currentFile);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
connect(actSaveAs, &QAction::triggered, this, [this, saveTo]{
|
connect(actSaveAs, &QAction::triggered, this, [this, saveTo]{
|
||||||
const QString path = QFileDialog::getSaveFileName(this, tr("Save diagram as"), QString(), tr(kDiagramFileFilter));
|
const QString path = QFileDialog::getSaveFileName(
|
||||||
|
this, tr("Save diagram as"), QString(), tr(kDiagramFileFilter), nullptr, themedFileDialogOptions());
|
||||||
if (!path.isEmpty()) saveTo(path);
|
if (!path.isEmpty()) saveTo(path);
|
||||||
});
|
});
|
||||||
connect(actExportPdf, &QAction::triggered, this, [this]{
|
connect(actExportPdf, &QAction::triggered, this, [this]{
|
||||||
|
|
@ -411,9 +422,77 @@ static QString symbolPlacementDefault(const QLocale& loc, const QString& sym) {
|
||||||
return "?1";
|
return "?1";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void applyWindowsMenuPaletteFix(QMainWindow* window) {
|
||||||
|
#if defined(Q_OS_WIN)
|
||||||
|
if (!window || !window->menuBar()) return;
|
||||||
|
const QPalette pal = qApp->palette();
|
||||||
|
const QString menuBg = pal.color(QPalette::Window).name(QColor::HexRgb);
|
||||||
|
const QString popupBg = pal.color(QPalette::Base).name(QColor::HexRgb);
|
||||||
|
const QString text = pal.color(QPalette::Active, QPalette::WindowText).name(QColor::HexRgb);
|
||||||
|
const QString selectedBg = pal.color(QPalette::Highlight).name(QColor::HexRgb);
|
||||||
|
const QString selectedText = pal.color(QPalette::HighlightedText).name(QColor::HexRgb);
|
||||||
|
const QString disabledText = pal.color(QPalette::Disabled, QPalette::WindowText).name(QColor::HexRgb);
|
||||||
|
|
||||||
|
window->menuBar()->setStyleSheet(QStringLiteral(
|
||||||
|
"QMenuBar { background-color: %1; color: %2; }"
|
||||||
|
"QMenuBar::item { background: transparent; color: %2; padding: 4px 8px; }"
|
||||||
|
"QMenuBar::item:selected { background-color: %3; color: %4; }"
|
||||||
|
"QMenuBar::item:disabled { color: %5; }"
|
||||||
|
"QMenu { background-color: %6; color: %2; border: 1px solid %1; }"
|
||||||
|
"QMenu::item { color: %2; }"
|
||||||
|
"QMenu::item:selected { background-color: %3; color: %4; }"
|
||||||
|
"QMenu::item:disabled { color: %5; }")
|
||||||
|
.arg(menuBg, text, selectedBg, selectedText, disabledText, popupBg));
|
||||||
|
#else
|
||||||
|
Q_UNUSED(window);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static void applyWindowsDialogPaletteFix(bool darkMode) {
|
||||||
|
#if defined(Q_OS_WIN)
|
||||||
|
if (!darkMode) {
|
||||||
|
qApp->setStyleSheet(QString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const QPalette pal = qApp->palette();
|
||||||
|
const QString window = pal.color(QPalette::Window).name(QColor::HexRgb);
|
||||||
|
const QString base = pal.color(QPalette::Base).name(QColor::HexRgb);
|
||||||
|
const QString text = pal.color(QPalette::Active, QPalette::WindowText).name(QColor::HexRgb);
|
||||||
|
const QString border = pal.color(QPalette::Mid).name(QColor::HexRgb);
|
||||||
|
const QString btn = pal.color(QPalette::Button).name(QColor::HexRgb);
|
||||||
|
const QString btnText = pal.color(QPalette::Active, QPalette::ButtonText).name(QColor::HexRgb);
|
||||||
|
const QString disabled = pal.color(QPalette::Disabled, QPalette::WindowText).name(QColor::HexRgb);
|
||||||
|
const QString highlight = pal.color(QPalette::Highlight).name(QColor::HexRgb);
|
||||||
|
const QString highlightedText = pal.color(QPalette::HighlightedText).name(QColor::HexRgb);
|
||||||
|
|
||||||
|
qApp->setStyleSheet(QStringLiteral(
|
||||||
|
"QDialog, QMessageBox, QInputDialog, QFileDialog, QColorDialog { background-color: %1; color: %2; }"
|
||||||
|
"QTabWidget::pane { background-color: %1; border: 1px solid %5; }"
|
||||||
|
"QTabBar::tab { background-color: %3; color: %4; border: 1px solid %5; padding: 4px 10px; }"
|
||||||
|
"QTabBar::tab:selected { background-color: %1; color: %2; }"
|
||||||
|
"QTabBar::tab:disabled { color: %6; }"
|
||||||
|
"QTabWidget QWidget { background-color: %1; color: %2; }"
|
||||||
|
"QLabel, QCheckBox, QRadioButton, QGroupBox { color: %2; }"
|
||||||
|
"QPushButton { background-color: %3; color: %4; border: 1px solid %5; padding: 4px 10px; }"
|
||||||
|
"QPushButton:disabled { color: %6; }"
|
||||||
|
"QLineEdit, QPlainTextEdit, QTextEdit, QAbstractSpinBox, QComboBox {"
|
||||||
|
" background-color: %7; color: %2; border: 1px solid %5; selection-background-color: %8; selection-color: %9; }"
|
||||||
|
"QAbstractItemView, QListView, QTreeView, QTableView {"
|
||||||
|
" background-color: %7; color: %2; border: 1px solid %5; selection-background-color: %8; selection-color: %9; }"
|
||||||
|
"QDialogButtonBox QPushButton { min-width: 80px; }")
|
||||||
|
.arg(window, text, btn, btnText, border, disabled, base, highlight, highlightedText));
|
||||||
|
#else
|
||||||
|
Q_UNUSED(darkMode);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
bool MainWindow::showWelcome() {
|
bool MainWindow::showWelcome() {
|
||||||
auto* dlg = new QDialog(this);
|
auto* dlg = new QDialog(this);
|
||||||
dlg->setWindowTitle(tr("Welcome"));
|
dlg->setWindowTitle(tr("Welcome"));
|
||||||
|
dlg->setPalette(qApp->palette());
|
||||||
|
#if defined(Q_OS_WIN)
|
||||||
|
dlg->setStyleSheet(qApp->styleSheet());
|
||||||
|
#endif
|
||||||
auto* tabs = new QTabWidget(dlg);
|
auto* tabs = new QTabWidget(dlg);
|
||||||
|
|
||||||
// General tab
|
// General tab
|
||||||
|
|
@ -445,20 +524,6 @@ bool MainWindow::showWelcome() {
|
||||||
general->setLayout(genForm);
|
general->setLayout(genForm);
|
||||||
tabs->addTab(general, tr("General"));
|
tabs->addTab(general, tr("General"));
|
||||||
|
|
||||||
// Numbering tab (placeholder)
|
|
||||||
auto* numbering = new QWidget(dlg);
|
|
||||||
auto* numLayout = new QVBoxLayout(numbering);
|
|
||||||
numLayout->addStretch();
|
|
||||||
numbering->setLayout(numLayout);
|
|
||||||
tabs->addTab(numbering, tr("Numbering"));
|
|
||||||
|
|
||||||
// Display tab (placeholder)
|
|
||||||
auto* display = new QWidget(dlg);
|
|
||||||
auto* disLayout = new QVBoxLayout(display);
|
|
||||||
disLayout->addStretch();
|
|
||||||
display->setLayout(disLayout);
|
|
||||||
tabs->addTab(display, tr("Display"));
|
|
||||||
|
|
||||||
// ABC Units tab
|
// ABC Units tab
|
||||||
auto* units = new QWidget(dlg);
|
auto* units = new QWidget(dlg);
|
||||||
auto* unitsForm = new QFormLayout(units);
|
auto* unitsForm = new QFormLayout(units);
|
||||||
|
|
@ -577,17 +642,31 @@ void MainWindow::resetScene() {
|
||||||
|
|
||||||
void MainWindow::newDiagram() {
|
void MainWindow::newDiagram() {
|
||||||
if (!showWelcome()) return;
|
if (!showWelcome()) return;
|
||||||
resetScene();
|
auto* win = new MainWindow(QString(), nullptr, true);
|
||||||
const QRectF bounds = m_scene->contentRect().isNull() ? m_scene->sceneRect() : m_scene->contentRect();
|
win->m_welcomeState = m_welcomeState;
|
||||||
m_scene->createBlockAt(bounds.center());
|
|
||||||
m_currentFile.clear();
|
if (win->m_actDarkMode && win->m_actFollowSystemTheme) {
|
||||||
markDirty(false);
|
const QSignalBlocker b2(win->m_actDarkMode);
|
||||||
// Keep main window visible and focused after closing modal creation dialogs.
|
const QSignalBlocker b3(win->m_actFollowSystemTheme);
|
||||||
QTimer::singleShot(0, this, [this]{
|
win->m_actDarkMode->setChecked(win->m_welcomeState.value("darkMode", false).toBool());
|
||||||
setWindowState(windowState() & ~Qt::WindowMinimized);
|
win->m_actFollowSystemTheme->setChecked(win->m_welcomeState.value("followSystemTheme", false).toBool());
|
||||||
showNormal();
|
win->m_actDarkMode->setEnabled(!win->m_actFollowSystemTheme->isChecked());
|
||||||
raise();
|
}
|
||||||
activateWindow();
|
|
||||||
|
win->m_welcomeState["resolvedDarkMode"] = win->effectiveDarkMode();
|
||||||
|
win->applyAppPalette(win->m_welcomeState.value("resolvedDarkMode").toBool());
|
||||||
|
win->resetScene();
|
||||||
|
const QRectF bounds = win->m_scene->contentRect().isNull() ? win->m_scene->sceneRect() : win->m_scene->contentRect();
|
||||||
|
win->m_scene->createBlockAt(bounds.center());
|
||||||
|
win->m_currentFile.clear();
|
||||||
|
win->markDirty(false);
|
||||||
|
win->resize(size());
|
||||||
|
win->show();
|
||||||
|
QTimer::singleShot(0, win, [win]{
|
||||||
|
win->setWindowState(win->windowState() & ~Qt::WindowMinimized);
|
||||||
|
win->showNormal();
|
||||||
|
win->raise();
|
||||||
|
win->activateWindow();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -601,7 +680,8 @@ bool MainWindow::promptStartup() {
|
||||||
box.exec();
|
box.exec();
|
||||||
if (box.clickedButton() == cancelBtn) return false;
|
if (box.clickedButton() == cancelBtn) return false;
|
||||||
if (box.clickedButton() == openBtn) {
|
if (box.clickedButton() == openBtn) {
|
||||||
const QString path = QFileDialog::getOpenFileName(this, tr("Open diagram"), QString(), tr(kDiagramFileFilter));
|
const QString path = QFileDialog::getOpenFileName(
|
||||||
|
this, tr("Open diagram"), QString(), tr(kDiagramFileFilter), nullptr, themedFileDialogOptions());
|
||||||
if (path.isEmpty()) return false;
|
if (path.isEmpty()) return false;
|
||||||
return loadDiagramFromPath(path);
|
return loadDiagramFromPath(path);
|
||||||
}
|
}
|
||||||
|
|
@ -659,7 +739,8 @@ bool MainWindow::loadDiagramFromPath(const QString& path) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MainWindow::exportPdf(bool allDiagrams, bool numberPages, bool forceLightTheme) {
|
bool MainWindow::exportPdf(bool allDiagrams, bool numberPages, bool forceLightTheme) {
|
||||||
QString path = QFileDialog::getSaveFileName(this, tr("Export to PDF"), QString(), tr(kPdfFileFilter));
|
QString path = QFileDialog::getSaveFileName(
|
||||||
|
this, tr("Export to PDF"), QString(), tr(kPdfFileFilter), nullptr, themedFileDialogOptions());
|
||||||
if (path.isEmpty()) return false;
|
if (path.isEmpty()) return false;
|
||||||
if (QFileInfo(path).suffix().isEmpty()) path += ".pdf";
|
if (QFileInfo(path).suffix().isEmpty()) path += ".pdf";
|
||||||
|
|
||||||
|
|
@ -765,7 +846,8 @@ bool MainWindow::exportPdf(bool allDiagrams, bool numberPages, bool forceLightTh
|
||||||
|
|
||||||
bool MainWindow::exportMarkdownExplanation() {
|
bool MainWindow::exportMarkdownExplanation() {
|
||||||
if (!m_scene) return false;
|
if (!m_scene) return false;
|
||||||
QString path = QFileDialog::getSaveFileName(this, tr("Export to Markdown"), QString(), tr(kMarkdownFileFilter));
|
QString path = QFileDialog::getSaveFileName(
|
||||||
|
this, tr("Export to Markdown"), QString(), tr(kMarkdownFileFilter), nullptr, themedFileDialogOptions());
|
||||||
if (path.isEmpty()) return false;
|
if (path.isEmpty()) return false;
|
||||||
if (QFileInfo(path).suffix().isEmpty()) path += ".md";
|
if (QFileInfo(path).suffix().isEmpty()) path += ".md";
|
||||||
|
|
||||||
|
|
@ -909,6 +991,8 @@ void MainWindow::applyAppPalette(bool darkMode) {
|
||||||
static const QPalette defaultPalette = qApp->palette();
|
static const QPalette defaultPalette = qApp->palette();
|
||||||
if (!darkMode) {
|
if (!darkMode) {
|
||||||
qApp->setPalette(defaultPalette);
|
qApp->setPalette(defaultPalette);
|
||||||
|
applyWindowsDialogPaletteFix(false);
|
||||||
|
applyWindowsMenuPaletteFix(this);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
QPalette p;
|
QPalette p;
|
||||||
|
|
@ -925,6 +1009,8 @@ void MainWindow::applyAppPalette(bool darkMode) {
|
||||||
p.setColor(QPalette::Highlight, QColor(75, 110, 180));
|
p.setColor(QPalette::Highlight, QColor(75, 110, 180));
|
||||||
p.setColor(QPalette::HighlightedText, QColor(255, 255, 255));
|
p.setColor(QPalette::HighlightedText, QColor(255, 255, 255));
|
||||||
qApp->setPalette(p);
|
qApp->setPalette(p);
|
||||||
|
applyWindowsDialogPaletteFix(true);
|
||||||
|
applyWindowsMenuPaletteFix(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MainWindow::openDiagramPath(const QString& path) {
|
bool MainWindow::openDiagramPath(const QString& path) {
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ class PluginManager;
|
||||||
class MainWindow final : public QMainWindow {
|
class MainWindow final : public QMainWindow {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit MainWindow(const QString& startupPath = QString(), QWidget* parent = nullptr);
|
explicit MainWindow(const QString& startupPath = QString(), QWidget* parent = nullptr, bool skipStartupPrompt = false);
|
||||||
bool openDiagramPath(const QString& path);
|
bool openDiagramPath(const QString& path);
|
||||||
DiagramScene* scene() const { return m_scene; }
|
DiagramScene* scene() const { return m_scene; }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
#include <QCheckBox>
|
#include <QCheckBox>
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
#include <QLocale>
|
#include <QLocale>
|
||||||
|
#include <QApplication>
|
||||||
|
|
||||||
#include "DiagramScene.h"
|
#include "DiagramScene.h"
|
||||||
|
|
||||||
|
|
@ -281,7 +282,7 @@ QString BlockItem::formattedPrice() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
void BlockItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent* e) {
|
void BlockItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent* e) {
|
||||||
auto* dlg = new QDialog();
|
auto* dlg = new QDialog(QApplication::activeWindow());
|
||||||
dlg->setWindowTitle(tr("Edit block"));
|
dlg->setWindowTitle(tr("Edit block"));
|
||||||
|
|
||||||
auto* layout = new QVBoxLayout(dlg);
|
auto* layout = new QVBoxLayout(dlg);
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
#include <QButtonGroup>
|
#include <QButtonGroup>
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
#include <QGraphicsSceneMouseEvent>
|
#include <QGraphicsSceneMouseEvent>
|
||||||
|
#include <QApplication>
|
||||||
|
|
||||||
HeaderFooterItem::HeaderFooterItem(DiagramScene* scene)
|
HeaderFooterItem::HeaderFooterItem(DiagramScene* scene)
|
||||||
: QGraphicsObject(nullptr)
|
: QGraphicsObject(nullptr)
|
||||||
|
|
@ -221,7 +222,7 @@ void HeaderFooterItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent* e) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto* dlg = new QDialog();
|
auto* dlg = new QDialog(QApplication::activeWindow());
|
||||||
dlg->setWindowTitle(tr("Edit header/footer"));
|
dlg->setWindowTitle(tr("Edit header/footer"));
|
||||||
auto* layout = new QVBoxLayout(dlg);
|
auto* layout = new QVBoxLayout(dlg);
|
||||||
auto* form = new QFormLayout();
|
auto* form = new QFormLayout();
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,9 @@ int main(int argc, char** argv) {
|
||||||
|
|
||||||
const QStringList searchPaths = {
|
const QStringList searchPaths = {
|
||||||
QDir::currentPath() + "/translations",
|
QDir::currentPath() + "/translations",
|
||||||
|
QCoreApplication::applicationDirPath() + "/translations",
|
||||||
|
QCoreApplication::applicationDirPath() + "/../translations",
|
||||||
|
QCoreApplication::applicationDirPath() + "/../Resources/translations",
|
||||||
QCoreApplication::applicationDirPath() + "/../share/idef0/translations",
|
QCoreApplication::applicationDirPath() + "/../share/idef0/translations",
|
||||||
":/i18n"
|
":/i18n"
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,94 +1,98 @@
|
||||||
#include "plugins/color/ColorsPlugin.h"
|
#include "plugins/color/ColorsPlugin.h"
|
||||||
|
|
||||||
#include <QColorDialog>
|
#include <QColorDialog>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QTranslator>
|
#include <QTranslator>
|
||||||
#include <QLocale>
|
#include <QLocale>
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
|
||||||
static void ensureTranslator(Idef0Host* host) {
|
static void ensureTranslator(Idef0Host* host) {
|
||||||
static bool loaded = false;
|
static bool loaded = false;
|
||||||
static QTranslator* translator = nullptr;
|
static QTranslator* translator = nullptr;
|
||||||
if (loaded || !host || !host->plugin_dir) return;
|
if (loaded || !host || !host->plugin_dir) return;
|
||||||
const char* dirC = host->plugin_dir(host->opaque);
|
const char* dirC = host->plugin_dir(host->opaque);
|
||||||
if (!dirC) return;
|
if (!dirC) return;
|
||||||
const QString dir = QString::fromUtf8(dirC);
|
const QString dir = QString::fromUtf8(dirC);
|
||||||
const QString baseLocale = QLocale().name().replace('-', '_');
|
const QString baseLocale = QLocale().name().replace('-', '_');
|
||||||
const QString shortLocale = baseLocale.section('_', 0, 0);
|
const QString shortLocale = baseLocale.section('_', 0, 0);
|
||||||
|
|
||||||
QStringList candidates;
|
QStringList candidates;
|
||||||
auto addCandidate = [&](const QString& c) {
|
auto addCandidate = [&](const QString& c) {
|
||||||
if (c.isEmpty()) return;
|
if (c.isEmpty()) return;
|
||||||
if (c == QStringLiteral("C") || c == QStringLiteral("POSIX")) return;
|
if (c == QStringLiteral("C") || c == QStringLiteral("POSIX")) return;
|
||||||
candidates << c;
|
candidates << c;
|
||||||
};
|
};
|
||||||
addCandidate(baseLocale);
|
addCandidate(baseLocale);
|
||||||
addCandidate(shortLocale);
|
addCandidate(shortLocale);
|
||||||
const QString sysName = QLocale::system().name().replace('-', '_');
|
const QString sysName = QLocale::system().name().replace('-', '_');
|
||||||
addCandidate(sysName);
|
addCandidate(sysName);
|
||||||
addCandidate(sysName.section('_', 0, 0));
|
addCandidate(sysName.section('_', 0, 0));
|
||||||
for (const QString& lang : QLocale::system().uiLanguages()) {
|
for (const QString& lang : QLocale::system().uiLanguages()) {
|
||||||
const QString norm = QString(lang).replace('-', '_');
|
const QString norm = QString(lang).replace('-', '_');
|
||||||
addCandidate(norm);
|
addCandidate(norm);
|
||||||
addCandidate(norm.section('_', 0, 0));
|
addCandidate(norm.section('_', 0, 0));
|
||||||
}
|
}
|
||||||
const QString envLang = QString::fromLocal8Bit(qgetenv("LANG")).section('.', 0, 0).replace('-', '_');
|
const QString envLang = QString::fromLocal8Bit(qgetenv("LANG")).section('.', 0, 0).replace('-', '_');
|
||||||
addCandidate(envLang);
|
addCandidate(envLang);
|
||||||
addCandidate(envLang.section('_', 0, 0));
|
addCandidate(envLang.section('_', 0, 0));
|
||||||
|
|
||||||
candidates.removeDuplicates();
|
candidates.removeDuplicates();
|
||||||
if (candidates.isEmpty()) candidates << QStringLiteral("en");
|
if (candidates.isEmpty()) candidates << QStringLiteral("en");
|
||||||
|
|
||||||
translator = new QTranslator(qApp);
|
translator = new QTranslator(qApp);
|
||||||
qInfo() << "[colors plugin] translator search in" << dir << "candidates" << candidates;
|
qInfo() << "[colors plugin] translator search in" << dir << "candidates" << candidates;
|
||||||
for (const QString& loc : candidates) {
|
for (const QString& loc : candidates) {
|
||||||
if (loc.isEmpty()) continue;
|
if (loc.isEmpty()) continue;
|
||||||
const QString baseName = QStringLiteral("colors_%1").arg(loc);
|
const QString baseName = QStringLiteral("colors_%1").arg(loc);
|
||||||
if (translator->load(baseName, dir + "/translations")) {
|
if (translator->load(baseName, dir + "/translations")) {
|
||||||
qApp->installTranslator(translator);
|
qApp->installTranslator(translator);
|
||||||
qInfo() << "[colors plugin] translator loaded" << baseName;
|
qInfo() << "[colors plugin] translator loaded" << baseName;
|
||||||
loaded = true;
|
loaded = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!loaded) {
|
if (!loaded) {
|
||||||
qWarning() << "[colors plugin] translator not found, falling back to default language";
|
qWarning() << "[colors plugin] translator not found, falling back to default language";
|
||||||
delete translator;
|
delete translator;
|
||||||
translator = nullptr;
|
translator = nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void onSetColor(void*, Idef0Host* host) {
|
static void onSetColor(void*, Idef0Host* host) {
|
||||||
if (!host || !host->selected_items || !host->set_item_color) return;
|
if (!host || !host->selected_items || !host->set_item_color) return;
|
||||||
ensureTranslator(host);
|
ensureTranslator(host);
|
||||||
Idef0SelectedItem items[64];
|
Idef0SelectedItem items[64];
|
||||||
const size_t count = host->selected_items(host->opaque, items, 64);
|
const size_t count = host->selected_items(host->opaque, items, 64);
|
||||||
if (count == 0) return;
|
if (count == 0) return;
|
||||||
QColor initial("#2b6ee6");
|
QColor initial("#2b6ee6");
|
||||||
const QColor chosen = QColorDialog::getColor(initial, nullptr, QObject::tr("Select item color"));
|
QColorDialog::ColorDialogOptions options;
|
||||||
|
#if defined(Q_OS_WIN)
|
||||||
|
options |= QColorDialog::DontUseNativeDialog;
|
||||||
|
#endif
|
||||||
|
const QColor chosen = QColorDialog::getColor(initial, nullptr, QObject::tr("Select item color"), options);
|
||||||
if (!chosen.isValid()) return;
|
if (!chosen.isValid()) return;
|
||||||
const QByteArray hex = chosen.name(QColor::HexRgb).toUtf8();
|
const QByteArray hex = chosen.name(QColor::HexRgb).toUtf8();
|
||||||
for (size_t i = 0; i < count; ++i) {
|
for (size_t i = 0; i < count; ++i) {
|
||||||
host->set_item_color(host->opaque, items[i].kind, items[i].id, hex.constData());
|
host->set_item_color(host->opaque, items[i].kind, items[i].id, hex.constData());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void onClearColor(void*, Idef0Host* host) {
|
static void onClearColor(void*, Idef0Host* host) {
|
||||||
if (!host || !host->selected_items || !host->clear_item_color) return;
|
if (!host || !host->selected_items || !host->clear_item_color) return;
|
||||||
ensureTranslator(host);
|
ensureTranslator(host);
|
||||||
Idef0SelectedItem items[64];
|
Idef0SelectedItem items[64];
|
||||||
const size_t count = host->selected_items(host->opaque, items, 64);
|
const size_t count = host->selected_items(host->opaque, items, 64);
|
||||||
for (size_t i = 0; i < count; ++i) {
|
for (size_t i = 0; i < count; ++i) {
|
||||||
host->clear_item_color(host->opaque, items[i].kind, items[i].id);
|
host->clear_item_color(host->opaque, items[i].kind, items[i].id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" bool idef0_plugin_init_v1(Idef0Host* host) {
|
extern "C" bool idef0_plugin_init_v1(Idef0Host* host) {
|
||||||
if (!host || !host->add_menu_action) return false;
|
if (!host || !host->add_menu_action) return false;
|
||||||
ensureTranslator(host);
|
ensureTranslator(host);
|
||||||
qInfo() << "[colors plugin] init, plugin dir:" << (host->plugin_dir ? host->plugin_dir(host->opaque) : "(none)");
|
qInfo() << "[colors plugin] init, plugin dir:" << (host->plugin_dir ? host->plugin_dir(host->opaque) : "(none)");
|
||||||
host->add_menu_action(host->opaque, QObject::tr("Set item color…").toUtf8().constData(), &onSetColor, nullptr);
|
host->add_menu_action(host->opaque, QObject::tr("Set item color...").toUtf8().constData(), &onSetColor, nullptr);
|
||||||
host->add_menu_action(host->opaque, QObject::tr("Clear item colors").toUtf8().constData(), &onClearColor, nullptr);
|
host->add_menu_action(host->opaque, QObject::tr("Clear item colors").toUtf8().constData(), &onClearColor, nullptr);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!DOCTYPE TS>
|
<!DOCTYPE TS>
|
||||||
<TS version="2.1" language="en_US">
|
<TS version="2.1" language="en_US">
|
||||||
<context>
|
<context>
|
||||||
|
|
@ -8,8 +8,8 @@
|
||||||
<translation>Select item color</translation>
|
<translation>Select item color</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>Set item color…</source>
|
<source>Set item color...</source>
|
||||||
<translation>Set item color…</translation>
|
<translation>Set item color...</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>Clear item colors</source>
|
<source>Clear item colors</source>
|
||||||
|
|
@ -17,3 +17,6 @@
|
||||||
</message>
|
</message>
|
||||||
</context>
|
</context>
|
||||||
</TS>
|
</TS>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,22 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!DOCTYPE TS>
|
<!DOCTYPE TS>
|
||||||
<TS version="2.1" language="fr_FR">
|
<TS version="2.1" language="fr_FR">
|
||||||
<context>
|
<context>
|
||||||
<name>QObject</name>
|
<name>QObject</name>
|
||||||
<message>
|
<message>
|
||||||
<source>Select item color</source>
|
<source>Select item color</source>
|
||||||
<translation>Choisir la couleur de l'élément</translation>
|
<translation>Choisir la couleur de l'Г©lГ©ment</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>Set item color…</source>
|
<source>Set item color...</source>
|
||||||
<translation>Définir la couleur de l'élément…</translation>
|
<translation>Définir la couleur de l'élément...</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>Clear item colors</source>
|
<source>Clear item colors</source>
|
||||||
<translation>Réinitialiser les couleurs des éléments</translation>
|
<translation>RГ©initialiser les couleurs des Г©lГ©ments</translation>
|
||||||
</message>
|
</message>
|
||||||
</context>
|
</context>
|
||||||
</TS>
|
</TS>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,22 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!DOCTYPE TS>
|
<!DOCTYPE TS>
|
||||||
<TS version="2.1" language="ru_RU">
|
<TS version="2.1" language="ru_RU">
|
||||||
<context>
|
<context>
|
||||||
<name>QObject</name>
|
<name>QObject</name>
|
||||||
<message>
|
<message>
|
||||||
<source>Select item color</source>
|
<source>Select item color</source>
|
||||||
<translation>Выберите цвет элемента</translation>
|
<translation>Задать цвет элемента</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>Set item color…</source>
|
<source>Set item color...</source>
|
||||||
<translation>Задать цвет элемента…</translation>
|
<translation>Задать цвет элемента...</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>Clear item colors</source>
|
<source>Clear item colors</source>
|
||||||
<translation>Сбросить цвета элементов</translation>
|
<translation>Очистить цвета элементов</translation>
|
||||||
</message>
|
</message>
|
||||||
</context>
|
</context>
|
||||||
</TS>
|
</TS>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue