f31522130f
Build system (5 gaps hardened): - COOKBOOK_OFFLINE defaults to true (fork-mode) - normalize_patch handles diff -ruN format - New 'repo validate-patches' command (25/25 relibc patches) - 14 patched Qt/Wayland/display recipes added to protected list - relibc archive regenerated with current patch chain Boot fixes (fixable): - Full ISO EFI partition: 16 MiB → 1 MiB (matches mini, BIOS hardcoded 2 MiB offset) - D-Bus system bus: absolute /usr/bin/dbus-daemon path (was skipped) - redbear-sessiond: absolute /usr/bin/redbear-sessiond path (was skipped) - daemon framework: silenced spurious INIT_NOTIFY warnings for oneshot_async services (P0-daemon-silence-init-notify.patch) - udev-shim: demoted INIT_NOTIFY warning to INFO (expected for oneshot_async) - relibc: comprehensive named semaphores (sem_open/close/unlink) replacing upstream todo!() stubs - greeterd: Wayland socket timeout 15s → 30s (compositor DRM wait) - greeter-ui: built and linked (header guard unification, sem_compat stubs removed) - mc: un-ignored in both configs, fixed glib/libiconv/pcre2 transitive deps - greeter config: removed stale keymapd dependency from display/greeter services - prefix toolchain: relibc headers synced, _RELIBC_STDLIB_H guard unified Unfixable (diagnosed, upstream): - i2c-hidd: abort on no-I2C-hardware (QEMU) — process::exit → relibc abort - kded6/greeter-ui: page fault 0x8 — Qt library null deref - Thread panics fd != -1 — Rust std library on Redox - DHCP timeout / eth0 MAC — QEMU user-mode networking - hwrngd/thermald — no hardware RNG/thermal in VM - live preload allocation — BIOS memory fragmentation, continues on demand
230 lines
7.6 KiB
C++
230 lines
7.6 KiB
C++
// Copyright (C) 2025 The Qt Company Ltd.
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
|
|
|
#include "mainwindow.h"
|
|
#include "ui_mainwindow.h"
|
|
#include "exportdialog.h"
|
|
|
|
#include <QStandardPaths>
|
|
#include <QFileDialog>
|
|
#include <QLabel>
|
|
#include <QAction>
|
|
#include <QActionGroup>
|
|
#include <QToolBar>
|
|
#include <QMessageBox>
|
|
#include <QSvgRenderer>
|
|
#include <QWheelEvent>
|
|
|
|
static inline QString picturesLocation()
|
|
{
|
|
return QStandardPaths::standardLocations(QStandardPaths::PicturesLocation).value(0, QDir::currentPath());
|
|
}
|
|
|
|
MainWindow::MainWindow()
|
|
: QMainWindow()
|
|
, ui(new Ui::MainWindow)
|
|
, m_svgView(new SvgView)
|
|
, m_zoomLabel(new QLabel)
|
|
{
|
|
ui->setupUi(this);
|
|
|
|
ui->scrollArea->setWidget(m_svgView);
|
|
ui->scrollArea->widget()->installEventFilter(this);
|
|
|
|
ui->actionBlack->setIcon(QIcon(QLatin1String(":/files/black.png")));
|
|
ui->actionWhite->setIcon(QIcon(QLatin1String(":/files/white.png")));
|
|
ui->actionChequered->setIcon(QIcon(QLatin1String(":/files/chequered.png")));
|
|
|
|
QActionGroup *backgroundActionGroup = new QActionGroup(this);
|
|
backgroundActionGroup->addAction(ui->actionBlack);
|
|
backgroundActionGroup->addAction(ui->actionWhite);
|
|
backgroundActionGroup->addAction(ui->actionChequered);
|
|
|
|
connect(backgroundActionGroup, &QActionGroup::triggered, this, &MainWindow::setBackground);
|
|
connect(ui->actionOutline, &QAction::toggled, m_svgView, &SvgView::setOutline);
|
|
|
|
|
|
connect(ui->actionAnimations, &QAction::toggled, m_svgView, &SvgView::setAnimations);
|
|
connect(ui->actionRestart_Animations, &QAction::triggered, m_svgView, &SvgView::restartAnimations);
|
|
|
|
QActionGroup *featuresActionGroup = new QActionGroup(this);
|
|
featuresActionGroup->addAction(ui->actionExtended);
|
|
featuresActionGroup->addAction(ui->actionTiny_1_2);
|
|
connect(featuresActionGroup, &QActionGroup::triggered, this, &MainWindow::setFeature);
|
|
|
|
connect(ui->actionOpen, &QAction::triggered, this, &MainWindow::openFile);
|
|
connect(ui->actionExport, &QAction::triggered, this, &MainWindow::exportImage);
|
|
|
|
connect(ui->actionZoom_In, &QAction::triggered, this, &MainWindow::zoomIn);
|
|
connect(ui->actionZoom_Out, &QAction::triggered, this, &MainWindow::zoomOut);
|
|
connect(ui->actionReset_Zoom, &QAction::triggered, this, &MainWindow::resetZoom);
|
|
connect(ui->actionRefresh, &QAction::triggered, m_svgView, &SvgView::reload);
|
|
|
|
connect(ui->actionAssume_Trusted_Source, &QAction::toggled, m_svgView, &SvgView::setAssumeTrustedSource);
|
|
|
|
QToolBar *toolBar = new QToolBar(this);
|
|
addToolBar(Qt::TopToolBarArea, toolBar);
|
|
|
|
toolBar->addAction(ui->actionOpen);
|
|
toolBar->addAction(ui->actionExport);
|
|
|
|
toolBar->insertSeparator(ui->actionZoom_In);
|
|
toolBar->addAction(ui->actionZoom_In);
|
|
toolBar->addAction(ui->actionZoom_Out);
|
|
toolBar->addAction(ui->actionRefresh);
|
|
|
|
toolBar->insertSeparator(ui->actionWhite);
|
|
toolBar->addAction(ui->actionWhite);
|
|
toolBar->addAction(ui->actionBlack);
|
|
toolBar->addAction(ui->actionChequered);
|
|
|
|
m_zoomLabel->setToolTip(tr("Use the mouse wheel to zoom"));
|
|
statusBar()->addPermanentWidget(m_zoomLabel);
|
|
updateZoomLabel();
|
|
|
|
connect(&m_watcher, &QFileSystemWatcher::fileChanged, this, &MainWindow::fileModified);
|
|
}
|
|
|
|
void MainWindow::openFile()
|
|
{
|
|
QFileDialog fileDialog(this);
|
|
fileDialog.setAcceptMode(QFileDialog::AcceptOpen);
|
|
fileDialog.setMimeTypeFilters(QStringList() << "image/svg+xml" << "image/svg+xml-compressed");
|
|
fileDialog.setWindowTitle(tr("Open SVG File"));
|
|
if (m_currentPath.isEmpty())
|
|
fileDialog.setDirectory(picturesLocation());
|
|
|
|
while (fileDialog.exec() == QDialog::Accepted && !loadFile(fileDialog.selectedFiles().constFirst()))
|
|
;
|
|
}
|
|
|
|
bool MainWindow::loadFile(const QString &fileName)
|
|
{
|
|
if (!QFileInfo::exists(fileName)) {
|
|
QMessageBox::critical(this, tr("Open SVG File"),
|
|
tr("Could not open file '%1'.").arg(QDir::toNativeSeparators(fileName)));
|
|
return false;
|
|
}
|
|
|
|
if (!m_svgView->currentFile().empty())
|
|
m_watcher.removePath(m_svgView->currentFile().toString());
|
|
m_watcher.addPath(fileName);
|
|
m_svgView->load(fileName);
|
|
|
|
resetZoom();
|
|
|
|
if (!fileName.startsWith(":/")) {
|
|
m_currentPath = fileName;
|
|
setWindowFilePath(fileName);
|
|
const QSize size = m_svgView->fileSize();
|
|
const QString message =
|
|
tr("Opened %1, %2x%3").arg(QFileInfo(fileName).fileName()).arg(size.width()).arg(size.width());
|
|
statusBar()->showMessage(message);
|
|
}
|
|
|
|
const QSize availableSize = this->screen()->availableGeometry().size();
|
|
resize(m_svgView->fileSize().expandedTo(availableSize / 4) + QSize(80, 80 + menuBar()->height()));
|
|
|
|
return true;
|
|
}
|
|
|
|
void MainWindow::fileModified(const QString &path)
|
|
{
|
|
if (!ui->actionAuto_Refresh->isChecked())
|
|
return;
|
|
|
|
if (m_watcher.files().contains(path) && m_svgView->currentFile() == path)
|
|
m_svgView->reload();
|
|
}
|
|
|
|
void MainWindow::setBackground(QAction *action)
|
|
{
|
|
if (action == ui->actionWhite)
|
|
m_svgView->setBackgroundType(SvgView::Background::White);
|
|
else if (action == ui->actionBlack)
|
|
m_svgView->setBackgroundType(SvgView::Background::Black);
|
|
else if (action == ui->actionChequered)
|
|
m_svgView->setBackgroundType(SvgView::Background::Chequered);
|
|
}
|
|
|
|
void MainWindow::setFeature(QAction *action)
|
|
{
|
|
if (action == ui->actionTiny_1_2)
|
|
m_svgView->setFeature(SvgView::Feature::Tiny_12);
|
|
else
|
|
m_svgView->setFeature(SvgView::Feature::Extended);
|
|
}
|
|
|
|
void MainWindow::exportImage()
|
|
{
|
|
ExportDialog exportDialog(this);
|
|
exportDialog.setExportSize(m_svgView->fileSize());
|
|
|
|
QString fileName;
|
|
if (m_currentPath.isEmpty()) {
|
|
fileName = picturesLocation() + QLatin1String("/export.png");
|
|
} else {
|
|
const QFileInfo fi(m_currentPath);
|
|
fileName = fi.absolutePath() + QLatin1Char('/') + fi.baseName() + QLatin1String(".png");
|
|
}
|
|
|
|
exportDialog.setExportFileName(fileName);
|
|
exportDialog.exec();
|
|
|
|
if (exportDialog.result() == QDialog::Accepted) {
|
|
const QSize imageSize = exportDialog.exportSize();
|
|
QImage renderedSvg = m_svgView->renderAsImage(imageSize);
|
|
|
|
fileName = exportDialog.exportFileName();
|
|
if (renderedSvg.save(fileName)) {
|
|
const QString message = tr("Exported %1, %2x%3, %4 bytes")
|
|
.arg(QDir::toNativeSeparators(fileName)).arg(imageSize.width()).arg(imageSize.height())
|
|
.arg(QFileInfo(fileName).size());
|
|
statusBar()->showMessage(message);
|
|
} else {
|
|
QMessageBox::critical(this, tr("Export Image"),
|
|
tr("Could not write file '%1'.").arg(QDir::toNativeSeparators(fileName)));
|
|
}
|
|
}
|
|
}
|
|
|
|
void MainWindow::updateZoomLabel()
|
|
{
|
|
const int percent = qRound(m_scale * qreal(100));
|
|
m_zoomLabel->setText(QString::number(percent) + QLatin1Char('%'));
|
|
}
|
|
|
|
void MainWindow::zoomIn()
|
|
{
|
|
m_scale = qMin(m_scale * 2, 10.);
|
|
m_svgView->setScale(m_scale);
|
|
updateZoomLabel();
|
|
}
|
|
|
|
void MainWindow::zoomOut()
|
|
{
|
|
m_scale = qMax(m_scale / 2, 0.5);
|
|
m_svgView->setScale(m_scale);
|
|
updateZoomLabel();
|
|
}
|
|
|
|
void MainWindow::resetZoom()
|
|
{
|
|
m_scale = 1.;
|
|
m_svgView->setScale(m_scale);
|
|
updateZoomLabel();
|
|
}
|
|
|
|
bool MainWindow::eventFilter(QObject *object, QEvent *event)
|
|
{
|
|
if (object == ui->scrollArea->widget() && event->type() == QEvent::Wheel) {
|
|
QWheelEvent *wheelEvent = static_cast<QWheelEvent *>(event);
|
|
m_scale *= qBound(0., qPow(1.2, wheelEvent->angleDelta().y() / 240.), 2.);
|
|
m_scale = qBound(0.1, m_scale, 10.);
|
|
m_svgView->setScale(m_scale);
|
|
updateZoomLabel();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|