изменено: 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()
|
||||
|
||||
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
|
||||
src/MainWindow.h src/MainWindow.cpp
|
||||
src/items/DiagramScene.h src/items/DiagramScene.cpp
|
||||
|
|
@ -25,6 +62,12 @@ qt_add_executable(erlu_idef0_editor
|
|||
src/main.cpp
|
||||
)
|
||||
|
||||
if(WIN32)
|
||||
target_sources(erlu_idef0_editor PRIVATE
|
||||
packaging/windows/erlu.rc
|
||||
)
|
||||
endif()
|
||||
|
||||
qt_add_resources(erlu_idef0_editor "app_icons"
|
||||
PREFIX "/icons"
|
||||
BASE assets/icons
|
||||
|
|
@ -79,6 +122,17 @@ if(BUILD_COLORS_PLUGIN)
|
|||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/plugins/color"
|
||||
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()
|
||||
|
||||
set_target_properties(erlu_idef0_editor PROPERTIES
|
||||
|
|
@ -101,6 +155,15 @@ if(BUILD_COLORS_PLUGIN)
|
|||
)
|
||||
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)
|
||||
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/packaging/linux/erlu-idef0-editor.desktop
|
||||
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
|
||||
stdenv.mkDerivation rec {
|
||||
pname = "erlu_idef0_editor";
|
||||
version = "0.1.0";
|
||||
version = "1.0.1";
|
||||
|
||||
src = lib.cleanSourceWith {
|
||||
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* 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) {
|
||||
const QString size = meta.value("pageSize", "A4").toString();
|
||||
if (size == "A1") return QPageSize::A1;
|
||||
|
|
@ -95,7 +103,7 @@ static QVariantMap mergeWithDiagramState(QVariantMap globalMeta, const QVariantM
|
|||
return globalMeta;
|
||||
}
|
||||
|
||||
MainWindow::MainWindow(const QString& startupPath, QWidget* parent)
|
||||
MainWindow::MainWindow(const QString& startupPath, QWidget* parent, bool skipStartupPrompt)
|
||||
: QMainWindow(parent)
|
||||
{
|
||||
setupUi();
|
||||
|
|
@ -106,7 +114,7 @@ MainWindow::MainWindow(const QString& startupPath, QWidget* parent)
|
|||
QTimer::singleShot(0, this, &QWidget::close);
|
||||
return;
|
||||
}
|
||||
} else if (!promptStartup()) {
|
||||
} else if (!skipStartupPrompt && !promptStartup()) {
|
||||
QTimer::singleShot(0, this, &QWidget::close);
|
||||
return;
|
||||
}
|
||||
|
|
@ -262,7 +270,8 @@ void MainWindow::setupActions() {
|
|||
|
||||
connect(actNew, &QAction::triggered, this, [this]{ newDiagram(); });
|
||||
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()) {
|
||||
loadDiagramFromPath(path);
|
||||
}
|
||||
|
|
@ -293,14 +302,16 @@ void MainWindow::setupActions() {
|
|||
|
||||
connect(actSave, &QAction::triggered, this, [this, saveTo]{
|
||||
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);
|
||||
} else {
|
||||
saveTo(m_currentFile);
|
||||
}
|
||||
});
|
||||
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);
|
||||
});
|
||||
connect(actExportPdf, &QAction::triggered, this, [this]{
|
||||
|
|
@ -411,9 +422,77 @@ static QString symbolPlacementDefault(const QLocale& loc, const QString& sym) {
|
|||
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() {
|
||||
auto* dlg = new QDialog(this);
|
||||
dlg->setWindowTitle(tr("Welcome"));
|
||||
dlg->setPalette(qApp->palette());
|
||||
#if defined(Q_OS_WIN)
|
||||
dlg->setStyleSheet(qApp->styleSheet());
|
||||
#endif
|
||||
auto* tabs = new QTabWidget(dlg);
|
||||
|
||||
// General tab
|
||||
|
|
@ -445,20 +524,6 @@ bool MainWindow::showWelcome() {
|
|||
general->setLayout(genForm);
|
||||
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
|
||||
auto* units = new QWidget(dlg);
|
||||
auto* unitsForm = new QFormLayout(units);
|
||||
|
|
@ -577,17 +642,31 @@ void MainWindow::resetScene() {
|
|||
|
||||
void MainWindow::newDiagram() {
|
||||
if (!showWelcome()) return;
|
||||
resetScene();
|
||||
const QRectF bounds = m_scene->contentRect().isNull() ? m_scene->sceneRect() : m_scene->contentRect();
|
||||
m_scene->createBlockAt(bounds.center());
|
||||
m_currentFile.clear();
|
||||
markDirty(false);
|
||||
// Keep main window visible and focused after closing modal creation dialogs.
|
||||
QTimer::singleShot(0, this, [this]{
|
||||
setWindowState(windowState() & ~Qt::WindowMinimized);
|
||||
showNormal();
|
||||
raise();
|
||||
activateWindow();
|
||||
auto* win = new MainWindow(QString(), nullptr, true);
|
||||
win->m_welcomeState = m_welcomeState;
|
||||
|
||||
if (win->m_actDarkMode && win->m_actFollowSystemTheme) {
|
||||
const QSignalBlocker b2(win->m_actDarkMode);
|
||||
const QSignalBlocker b3(win->m_actFollowSystemTheme);
|
||||
win->m_actDarkMode->setChecked(win->m_welcomeState.value("darkMode", false).toBool());
|
||||
win->m_actFollowSystemTheme->setChecked(win->m_welcomeState.value("followSystemTheme", false).toBool());
|
||||
win->m_actDarkMode->setEnabled(!win->m_actFollowSystemTheme->isChecked());
|
||||
}
|
||||
|
||||
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();
|
||||
if (box.clickedButton() == cancelBtn) return false;
|
||||
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;
|
||||
return loadDiagramFromPath(path);
|
||||
}
|
||||
|
|
@ -659,7 +739,8 @@ bool MainWindow::loadDiagramFromPath(const QString& path) {
|
|||
}
|
||||
|
||||
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 (QFileInfo(path).suffix().isEmpty()) path += ".pdf";
|
||||
|
||||
|
|
@ -765,7 +846,8 @@ bool MainWindow::exportPdf(bool allDiagrams, bool numberPages, bool forceLightTh
|
|||
|
||||
bool MainWindow::exportMarkdownExplanation() {
|
||||
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 (QFileInfo(path).suffix().isEmpty()) path += ".md";
|
||||
|
||||
|
|
@ -909,6 +991,8 @@ void MainWindow::applyAppPalette(bool darkMode) {
|
|||
static const QPalette defaultPalette = qApp->palette();
|
||||
if (!darkMode) {
|
||||
qApp->setPalette(defaultPalette);
|
||||
applyWindowsDialogPaletteFix(false);
|
||||
applyWindowsMenuPaletteFix(this);
|
||||
return;
|
||||
}
|
||||
QPalette p;
|
||||
|
|
@ -925,6 +1009,8 @@ void MainWindow::applyAppPalette(bool darkMode) {
|
|||
p.setColor(QPalette::Highlight, QColor(75, 110, 180));
|
||||
p.setColor(QPalette::HighlightedText, QColor(255, 255, 255));
|
||||
qApp->setPalette(p);
|
||||
applyWindowsDialogPaletteFix(true);
|
||||
applyWindowsMenuPaletteFix(this);
|
||||
}
|
||||
|
||||
bool MainWindow::openDiagramPath(const QString& path) {
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ class PluginManager;
|
|||
class MainWindow final : public QMainWindow {
|
||||
Q_OBJECT
|
||||
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);
|
||||
DiagramScene* scene() const { return m_scene; }
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
#include <QCheckBox>
|
||||
#include <QVBoxLayout>
|
||||
#include <QLocale>
|
||||
#include <QApplication>
|
||||
|
||||
#include "DiagramScene.h"
|
||||
|
||||
|
|
@ -281,7 +282,7 @@ QString BlockItem::formattedPrice() const {
|
|||
}
|
||||
|
||||
void BlockItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent* e) {
|
||||
auto* dlg = new QDialog();
|
||||
auto* dlg = new QDialog(QApplication::activeWindow());
|
||||
dlg->setWindowTitle(tr("Edit block"));
|
||||
|
||||
auto* layout = new QVBoxLayout(dlg);
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
#include <QButtonGroup>
|
||||
#include <QVBoxLayout>
|
||||
#include <QGraphicsSceneMouseEvent>
|
||||
#include <QApplication>
|
||||
|
||||
HeaderFooterItem::HeaderFooterItem(DiagramScene* scene)
|
||||
: QGraphicsObject(nullptr)
|
||||
|
|
@ -221,7 +222,7 @@ void HeaderFooterItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent* e) {
|
|||
return;
|
||||
}
|
||||
|
||||
auto* dlg = new QDialog();
|
||||
auto* dlg = new QDialog(QApplication::activeWindow());
|
||||
dlg->setWindowTitle(tr("Edit header/footer"));
|
||||
auto* layout = new QVBoxLayout(dlg);
|
||||
auto* form = new QFormLayout();
|
||||
|
|
|
|||
|
|
@ -73,6 +73,9 @@ int main(int argc, char** argv) {
|
|||
|
||||
const QStringList searchPaths = {
|
||||
QDir::currentPath() + "/translations",
|
||||
QCoreApplication::applicationDirPath() + "/translations",
|
||||
QCoreApplication::applicationDirPath() + "/../translations",
|
||||
QCoreApplication::applicationDirPath() + "/../Resources/translations",
|
||||
QCoreApplication::applicationDirPath() + "/../share/idef0/translations",
|
||||
":/i18n"
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,94 +1,98 @@
|
|||
#include "plugins/color/ColorsPlugin.h"
|
||||
|
||||
#include <QColorDialog>
|
||||
#include <QObject>
|
||||
#include <QTranslator>
|
||||
#include <QLocale>
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
|
||||
static void ensureTranslator(Idef0Host* host) {
|
||||
static bool loaded = false;
|
||||
static QTranslator* translator = nullptr;
|
||||
if (loaded || !host || !host->plugin_dir) return;
|
||||
const char* dirC = host->plugin_dir(host->opaque);
|
||||
if (!dirC) return;
|
||||
const QString dir = QString::fromUtf8(dirC);
|
||||
const QString baseLocale = QLocale().name().replace('-', '_');
|
||||
const QString shortLocale = baseLocale.section('_', 0, 0);
|
||||
|
||||
QStringList candidates;
|
||||
auto addCandidate = [&](const QString& c) {
|
||||
if (c.isEmpty()) return;
|
||||
if (c == QStringLiteral("C") || c == QStringLiteral("POSIX")) return;
|
||||
candidates << c;
|
||||
};
|
||||
addCandidate(baseLocale);
|
||||
addCandidate(shortLocale);
|
||||
const QString sysName = QLocale::system().name().replace('-', '_');
|
||||
addCandidate(sysName);
|
||||
addCandidate(sysName.section('_', 0, 0));
|
||||
for (const QString& lang : QLocale::system().uiLanguages()) {
|
||||
const QString norm = QString(lang).replace('-', '_');
|
||||
addCandidate(norm);
|
||||
addCandidate(norm.section('_', 0, 0));
|
||||
}
|
||||
const QString envLang = QString::fromLocal8Bit(qgetenv("LANG")).section('.', 0, 0).replace('-', '_');
|
||||
addCandidate(envLang);
|
||||
addCandidate(envLang.section('_', 0, 0));
|
||||
|
||||
candidates.removeDuplicates();
|
||||
if (candidates.isEmpty()) candidates << QStringLiteral("en");
|
||||
|
||||
translator = new QTranslator(qApp);
|
||||
qInfo() << "[colors plugin] translator search in" << dir << "candidates" << candidates;
|
||||
for (const QString& loc : candidates) {
|
||||
if (loc.isEmpty()) continue;
|
||||
const QString baseName = QStringLiteral("colors_%1").arg(loc);
|
||||
if (translator->load(baseName, dir + "/translations")) {
|
||||
qApp->installTranslator(translator);
|
||||
qInfo() << "[colors plugin] translator loaded" << baseName;
|
||||
loaded = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!loaded) {
|
||||
qWarning() << "[colors plugin] translator not found, falling back to default language";
|
||||
delete translator;
|
||||
translator = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
static void onSetColor(void*, Idef0Host* host) {
|
||||
if (!host || !host->selected_items || !host->set_item_color) return;
|
||||
ensureTranslator(host);
|
||||
#include "plugins/color/ColorsPlugin.h"
|
||||
|
||||
#include <QColorDialog>
|
||||
#include <QObject>
|
||||
#include <QTranslator>
|
||||
#include <QLocale>
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
|
||||
static void ensureTranslator(Idef0Host* host) {
|
||||
static bool loaded = false;
|
||||
static QTranslator* translator = nullptr;
|
||||
if (loaded || !host || !host->plugin_dir) return;
|
||||
const char* dirC = host->plugin_dir(host->opaque);
|
||||
if (!dirC) return;
|
||||
const QString dir = QString::fromUtf8(dirC);
|
||||
const QString baseLocale = QLocale().name().replace('-', '_');
|
||||
const QString shortLocale = baseLocale.section('_', 0, 0);
|
||||
|
||||
QStringList candidates;
|
||||
auto addCandidate = [&](const QString& c) {
|
||||
if (c.isEmpty()) return;
|
||||
if (c == QStringLiteral("C") || c == QStringLiteral("POSIX")) return;
|
||||
candidates << c;
|
||||
};
|
||||
addCandidate(baseLocale);
|
||||
addCandidate(shortLocale);
|
||||
const QString sysName = QLocale::system().name().replace('-', '_');
|
||||
addCandidate(sysName);
|
||||
addCandidate(sysName.section('_', 0, 0));
|
||||
for (const QString& lang : QLocale::system().uiLanguages()) {
|
||||
const QString norm = QString(lang).replace('-', '_');
|
||||
addCandidate(norm);
|
||||
addCandidate(norm.section('_', 0, 0));
|
||||
}
|
||||
const QString envLang = QString::fromLocal8Bit(qgetenv("LANG")).section('.', 0, 0).replace('-', '_');
|
||||
addCandidate(envLang);
|
||||
addCandidate(envLang.section('_', 0, 0));
|
||||
|
||||
candidates.removeDuplicates();
|
||||
if (candidates.isEmpty()) candidates << QStringLiteral("en");
|
||||
|
||||
translator = new QTranslator(qApp);
|
||||
qInfo() << "[colors plugin] translator search in" << dir << "candidates" << candidates;
|
||||
for (const QString& loc : candidates) {
|
||||
if (loc.isEmpty()) continue;
|
||||
const QString baseName = QStringLiteral("colors_%1").arg(loc);
|
||||
if (translator->load(baseName, dir + "/translations")) {
|
||||
qApp->installTranslator(translator);
|
||||
qInfo() << "[colors plugin] translator loaded" << baseName;
|
||||
loaded = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!loaded) {
|
||||
qWarning() << "[colors plugin] translator not found, falling back to default language";
|
||||
delete translator;
|
||||
translator = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
static void onSetColor(void*, Idef0Host* host) {
|
||||
if (!host || !host->selected_items || !host->set_item_color) return;
|
||||
ensureTranslator(host);
|
||||
Idef0SelectedItem items[64];
|
||||
const size_t count = host->selected_items(host->opaque, items, 64);
|
||||
if (count == 0) return;
|
||||
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;
|
||||
const QByteArray hex = chosen.name(QColor::HexRgb).toUtf8();
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
host->set_item_color(host->opaque, items[i].kind, items[i].id, hex.constData());
|
||||
}
|
||||
}
|
||||
|
||||
static void onClearColor(void*, Idef0Host* host) {
|
||||
if (!host || !host->selected_items || !host->clear_item_color) return;
|
||||
ensureTranslator(host);
|
||||
Idef0SelectedItem items[64];
|
||||
const size_t count = host->selected_items(host->opaque, items, 64);
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
host->clear_item_color(host->opaque, items[i].kind, items[i].id);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" bool idef0_plugin_init_v1(Idef0Host* host) {
|
||||
if (!host || !host->add_menu_action) return false;
|
||||
ensureTranslator(host);
|
||||
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("Clear item colors").toUtf8().constData(), &onClearColor, nullptr);
|
||||
return true;
|
||||
}
|
||||
const QByteArray hex = chosen.name(QColor::HexRgb).toUtf8();
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
host->set_item_color(host->opaque, items[i].kind, items[i].id, hex.constData());
|
||||
}
|
||||
}
|
||||
|
||||
static void onClearColor(void*, Idef0Host* host) {
|
||||
if (!host || !host->selected_items || !host->clear_item_color) return;
|
||||
ensureTranslator(host);
|
||||
Idef0SelectedItem items[64];
|
||||
const size_t count = host->selected_items(host->opaque, items, 64);
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
host->clear_item_color(host->opaque, items[i].kind, items[i].id);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" bool idef0_plugin_init_v1(Idef0Host* host) {
|
||||
if (!host || !host->add_menu_action) return false;
|
||||
ensureTranslator(host);
|
||||
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("Clear item colors").toUtf8().constData(), &onClearColor, nullptr);
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS>
|
||||
<TS version="2.1" language="en_US">
|
||||
<context>
|
||||
|
|
@ -8,8 +8,8 @@
|
|||
<translation>Select item color</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Set item color…</source>
|
||||
<translation>Set item color…</translation>
|
||||
<source>Set item color...</source>
|
||||
<translation>Set item color...</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Clear item colors</source>
|
||||
|
|
@ -17,3 +17,6 @@
|
|||
</message>
|
||||
</context>
|
||||
</TS>
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,19 +1,22 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS>
|
||||
<TS version="2.1" language="fr_FR">
|
||||
<context>
|
||||
<name>QObject</name>
|
||||
<message>
|
||||
<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>
|
||||
<source>Set item color…</source>
|
||||
<translation>Définir la couleur de l'élément…</translation>
|
||||
<source>Set item color...</source>
|
||||
<translation>Définir la couleur de l'élément...</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Clear item colors</source>
|
||||
<translation>Réinitialiser les couleurs des éléments</translation>
|
||||
<translation>RГ©initialiser les couleurs des Г©lГ©ments</translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,19 +1,22 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS>
|
||||
<TS version="2.1" language="ru_RU">
|
||||
<context>
|
||||
<name>QObject</name>
|
||||
<message>
|
||||
<source>Select item color</source>
|
||||
<translation>Выберите цвет элемента</translation>
|
||||
<translation>Задать цвет элемента</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Set item color…</source>
|
||||
<translation>Задать цвет элемента…</translation>
|
||||
<source>Set item color...</source>
|
||||
<translation>Задать цвет элемента...</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Clear item colors</source>
|
||||
<translation>Сбросить цвета элементов</translation>
|
||||
<translation>Очистить цвета элементов</translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue