Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ on:
- 'LICENSE'
- '.gitignore'

env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true

jobs:
build:
strategy:
Expand All @@ -31,7 +34,7 @@ jobs:
name: Build (${{ matrix.name }})

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5

- name: Install dependencies (Linux)
if: runner.os == 'Linux'
Expand Down
7 changes: 5 additions & 2 deletions .github/workflows/msstore.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,16 @@ on:
permissions:
contents: write

env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true

jobs:
msix:
runs-on: windows-latest
name: Build MSIX

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5

- name: Install Qt6
uses: jurplel/install-qt-action@v4
Expand All @@ -44,7 +47,7 @@ jobs:
echo "Version: ${VER}, MSIX: ${MSIX_VER}"

- name: Configure
run: cmake -B build -DCMAKE_BUILD_TYPE=Release
run: cmake -B build -DCMAKE_BUILD_TYPE=Release -DAPP_VERSION=${{ steps.version.outputs.version }}

- name: Build
run: cmake --build build --config Release
Expand Down
14 changes: 12 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ on:
permissions:
contents: write

env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true

jobs:
build:
strategy:
Expand All @@ -24,7 +27,14 @@ jobs:
name: Build (${{ matrix.name }})

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5

- name: Determine version
id: version
shell: bash
run: |
VER="${GITHUB_REF#refs/tags/v}"
echo "version=${VER}" >> "$GITHUB_OUTPUT"

- name: Install dependencies (Linux)
if: runner.os == 'Linux'
Expand All @@ -36,7 +46,7 @@ jobs:
version: '6.7.*'

- name: Configure
run: cmake -B build -DBUILD_TESTING=ON -DCMAKE_BUILD_TYPE=Release
run: cmake -B build -DBUILD_TESTING=ON -DCMAKE_BUILD_TYPE=Release -DAPP_VERSION=${{ steps.version.outputs.version }}

- name: Build
run: cmake --build build --config Release
Expand Down
18 changes: 16 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ endif()

# ---- Dependencies ------------------------------------------------------------

find_package(Qt6 REQUIRED COMPONENTS Widgets Svg PrintSupport)
find_package(Qt6 REQUIRED COMPONENTS Widgets Svg PrintSupport Network LinguistTools)

# ---- Library (all sources except main.cpp) -----------------------------------

Expand All @@ -40,6 +40,7 @@ add_library(ymind_lib OBJECT
src/core/SettingsDialog.h src/core/SettingsDialog.cpp
src/core/TemplateDescriptor.h src/core/TemplateDescriptor.cpp
src/core/TemplateRegistry.h src/core/TemplateRegistry.cpp
src/core/UpdateChecker.h src/core/UpdateChecker.cpp

# Scene – graphics-scene items
src/scene/EdgeItem.h src/scene/EdgeItem.cpp
Expand Down Expand Up @@ -68,12 +69,25 @@ add_library(ymind_lib OBJECT

target_include_directories(ymind_lib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src)

target_compile_definitions(ymind_lib PUBLIC YMIND_VERSION="${PROJECT_VERSION}")
set(APP_VERSION "${PROJECT_VERSION}" CACHE STRING "Application version string")
target_compile_definitions(ymind_lib PUBLIC YMIND_VERSION="${APP_VERSION}")

target_link_libraries(ymind_lib PUBLIC
Qt6::Widgets
Qt6::Svg
Qt6::PrintSupport
Qt6::Network
)

# ---- Translations ------------------------------------------------------------

set(TS_FILES
translations/ymind_zh_CN.ts
)

qt_add_translations(ymind_lib
TS_FILES ${TS_FILES}
RESOURCE_PREFIX "/translations"
)

# ---- Executable --------------------------------------------------------------
Expand Down
29 changes: 16 additions & 13 deletions src/core/AboutDialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
#include <QVBoxLayout>

AboutDialog::AboutDialog(QWidget* parent) : QDialog(parent) {
setWindowTitle("About YMind");
setWindowTitle(tr("About YMind"));
setFixedWidth(420);

QString linkColor = ThemeManager::isDark() ? "#5cacee" : "#0563C1";
Expand All @@ -26,18 +26,20 @@ AboutDialog::AboutDialog(QWidget* parent) : QDialog(parent) {
appLabel->setWordWrap(true);
appLabel->setText(
QString("<h2>YMind</h2>"
"<p>Version %1</p>"
"<p>A desktop mind map editor.</p>")
.arg(QCoreApplication::applicationVersion()));
"<p>%1</p>"
"<p>%2</p>")
.arg(tr("Version %1").arg(QCoreApplication::applicationVersion()),
tr("A desktop mind map editor.")));
layout->addWidget(appLabel);

// License
auto* licenseLabel = new QLabel(this);
licenseLabel->setTextFormat(Qt::RichText);
licenseLabel->setWordWrap(true);
licenseLabel->setText(QString("<p>Licensed under the %1.</p>")
.arg(makeLink("https://www.apache.org/licenses/LICENSE-2.0",
"Apache License 2.0")));
licenseLabel->setText(QString("<p>%1</p>")
.arg(tr("Licensed under the %1.")
.arg(makeLink("https://www.apache.org/licenses/LICENSE-2.0",
"Apache License 2.0"))));
licenseLabel->setOpenExternalLinks(true);
layout->addWidget(licenseLabel);

Expand All @@ -47,18 +49,19 @@ AboutDialog::AboutDialog(QWidget* parent) : QDialog(parent) {
qtLabel->setWordWrap(true);
QString qtOpenSource = "https://www.qt.io/download/open-source";
qtLabel->setText(
QString("<p>This application uses Qt %1, licensed under the %2. "
"Qt source code and re-linking instructions are available at: %3.</p>")
.arg(qVersion(),
makeLink("https://www.gnu.org/licenses/lgpl-3.0.html", "GNU LGPL v3"),
makeLink(qtOpenSource, qtOpenSource)));
QString("<p>%1</p>")
.arg(tr("This application uses Qt %1, licensed under the %2. "
"Qt source code and re-linking instructions are available at: %3.")
.arg(qVersion(),
makeLink("https://www.gnu.org/licenses/lgpl-3.0.html", "GNU LGPL v3"),
makeLink(qtOpenSource, qtOpenSource))));
qtLabel->setOpenExternalLinks(true);
layout->addWidget(qtLabel);

layout->addStretch();

// Close button
auto* closeBtn = new QPushButton("Close", this);
auto* closeBtn = new QPushButton(tr("Close"), this);
closeBtn->setDefault(true);
connect(closeBtn, &QPushButton::clicked, this, &QDialog::accept);

Expand Down
16 changes: 16 additions & 0 deletions src/core/AppSettings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,19 @@ QByteArray AppSettings::windowState() const {
void AppSettings::setWindowState(const QByteArray& state) {
m_settings->setValue("window/state", state);
}

bool AppSettings::checkForUpdatesEnabled() const {
return m_settings->value("updates/checkOnStartup", false).toBool();
}

void AppSettings::setCheckForUpdatesEnabled(bool enabled) {
m_settings->setValue("updates/checkOnStartup", enabled);
}

QString AppSettings::language() const {
return m_settings->value("appearance/language", "en").toString();
}

void AppSettings::setLanguage(const QString& lang) {
m_settings->setValue("appearance/language", lang);
}
6 changes: 6 additions & 0 deletions src/core/AppSettings.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ class AppSettings : public QObject {
QByteArray windowState() const;
void setWindowState(const QByteArray& state);

bool checkForUpdatesEnabled() const;
void setCheckForUpdatesEnabled(bool enabled);

QString language() const;
void setLanguage(const QString& lang);

signals:
void themeChanged(AppTheme theme);
void autoSaveSettingsChanged();
Expand Down
48 changes: 25 additions & 23 deletions src/core/FileManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ void FileManager::newFile() {

void FileManager::openFile() {
QString filePath =
QFileDialog::getOpenFileName(m_window, "Open Mind Map", QString(),
"YMind Files (*.ymind);;JSON Files (*.json);;All Files (*)");
QFileDialog::getOpenFileName(m_window, tr("Open Mind Map"), QString(),
tr("YMind Files (*.ymind);;JSON Files (*.json);;All Files (*)"));
if (filePath.isEmpty())
return;

Expand All @@ -39,7 +39,7 @@ void FileManager::openFile() {
auto* scene = m_tabManager->currentScene();
auto* view = m_tabManager->currentView();
if (!scene->loadFromFile(filePath)) {
QMessageBox::warning(m_window, "YMind", "Could not open file:\n" + filePath);
QMessageBox::warning(m_window, "YMind", tr("Could not open file:\n%1").arg(filePath));
return;
}
m_tabManager->setCurrentFilePath(filePath);
Expand All @@ -55,7 +55,7 @@ void FileManager::openFile() {
view->setScene(scene);

if (!scene->loadFromFile(filePath)) {
QMessageBox::warning(m_window, "YMind", "Could not open file:\n" + filePath);
QMessageBox::warning(m_window, "YMind", tr("Could not open file:\n%1").arg(filePath));
delete scene;
delete view;
return;
Expand All @@ -80,7 +80,7 @@ void FileManager::saveFile() {
QString path = m_tabManager->currentFilePath();

if (!scene->saveToFile(path)) {
QMessageBox::warning(m_window, "YMind", "Could not save file:\n" + path);
QMessageBox::warning(m_window, "YMind", tr("Could not save file:\n%1").arg(path));
}

int cur = m_tabManager->currentIndex();
Expand All @@ -92,8 +92,8 @@ void FileManager::saveFile() {

void FileManager::saveFileAs() {
QString filePath =
QFileDialog::getSaveFileName(m_window, "Save Mind Map", QString(),
"YMind Files (*.ymind);;JSON Files (*.json);;All Files (*)");
QFileDialog::getSaveFileName(m_window, tr("Save Mind Map"), QString(),
tr("YMind Files (*.ymind);;JSON Files (*.json);;All Files (*)"));
if (filePath.isEmpty())
return;

Expand All @@ -102,7 +102,7 @@ void FileManager::saveFileAs() {

auto* scene = m_tabManager->currentScene();
if (!scene->saveToFile(filePath)) {
QMessageBox::warning(m_window, "YMind", "Could not save file:\n" + filePath);
QMessageBox::warning(m_window, "YMind", tr("Could not save file:\n%1").arg(filePath));
return;
}

Expand Down Expand Up @@ -130,15 +130,15 @@ void FileManager::doExport(const QString& dialogTitle, const QString& filter,

if (!exporter(filePath)) {
QMessageBox::warning(m_window, "YMind",
QString("Could not export %1:\n%2").arg(errorLabel, filePath));
tr("Could not export %1:\n%2").arg(errorLabel, filePath));
return;
}
if (auto* mw = qobject_cast<QMainWindow*>(m_window))
mw->statusBar()->showMessage("Exported to " + filePath, 3000);
mw->statusBar()->showMessage(tr("Exported to %1").arg(filePath), 3000);
}

void FileManager::exportAsText() {
doExport("Export as Text", "Text Files (*.txt);;All Files (*)", ".txt",
doExport(tr("Export as Text"), tr("Text Files (*.txt);;All Files (*)"), ".txt",
[this](const QString& path) {
QFile file(path);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
Expand All @@ -147,11 +147,11 @@ void FileManager::exportAsText() {
file.close();
return true;
},
"file");
tr("file"));
}

void FileManager::exportAsMarkdown() {
doExport("Export as Markdown", "Markdown Files (*.md);;All Files (*)", ".md",
doExport(tr("Export as Markdown"), tr("Markdown Files (*.md);;All Files (*)"), ".md",
[this](const QString& path) {
QFile file(path);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
Expand All @@ -160,42 +160,42 @@ void FileManager::exportAsMarkdown() {
file.close();
return true;
},
"file");
tr("file"));
}

void FileManager::exportAsPng() {
doExport("Export as PNG", "PNG Images (*.png);;All Files (*)", ".png",
doExport(tr("Export as PNG"), tr("PNG Images (*.png);;All Files (*)"), ".png",
[this](const QString& path) {
return m_tabManager->currentScene()->exportToPng(path);
},
"PNG");
}

void FileManager::exportAsSvg() {
doExport("Export as SVG", "SVG Files (*.svg);;All Files (*)", ".svg",
doExport(tr("Export as SVG"), tr("SVG Files (*.svg);;All Files (*)"), ".svg",
[this](const QString& path) {
return m_tabManager->currentScene()->exportToSvg(path);
},
"SVG");
}

void FileManager::exportAsPdf() {
doExport("Export as PDF", "PDF Files (*.pdf);;All Files (*)", ".pdf",
doExport(tr("Export as PDF"), tr("PDF Files (*.pdf);;All Files (*)"), ".pdf",
[this](const QString& path) {
return m_tabManager->currentScene()->exportToPdf(path);
},
"PDF");
}

void FileManager::importFromText() {
QString filePath = QFileDialog::getOpenFileName(m_window, "Import from Text", QString(),
"Text Files (*.txt);;All Files (*)");
QString filePath = QFileDialog::getOpenFileName(m_window, tr("Import from Text"), QString(),
tr("Text Files (*.txt);;All Files (*)"));
if (filePath.isEmpty())
return;

QFile file(filePath);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
QMessageBox::warning(m_window, "YMind", "Could not read file:\n" + filePath);
QMessageBox::warning(m_window, "YMind", tr("Could not read file:\n%1").arg(filePath));
return;
}
QString text = QString::fromUtf8(file.readAll());
Expand All @@ -206,7 +206,8 @@ void FileManager::importFromText() {
auto* scene = m_tabManager->currentScene();
auto* view = m_tabManager->currentView();
if (!scene->importFromText(text)) {
QMessageBox::warning(m_window, "YMind", "Could not parse text file:\n" + filePath);
QMessageBox::warning(m_window, "YMind",
tr("Could not parse text file:\n%1").arg(filePath));
return;
}
m_tabManager->setCurrentFilePath(QString());
Expand All @@ -221,7 +222,8 @@ void FileManager::importFromText() {
view->setScene(scene);

if (!scene->importFromText(text)) {
QMessageBox::warning(m_window, "YMind", "Could not parse text file:\n" + filePath);
QMessageBox::warning(m_window, "YMind",
tr("Could not parse text file:\n%1").arg(filePath));
delete scene;
delete view;
return;
Expand All @@ -236,5 +238,5 @@ void FileManager::importFromText() {
}

if (auto* mw = qobject_cast<QMainWindow*>(m_window))
mw->statusBar()->showMessage("Imported from " + filePath, 3000);
mw->statusBar()->showMessage(tr("Imported from %1").arg(filePath), 3000);
}
Loading
Loading