Erste Version

This commit is contained in:
diabolus committed 2026-11-01 15:38:43 +01:00
commit e5944d9b97
74 files changed
+30486

No files matched your search

+770
View File
@@ -0,0 +1,770 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2026 Dany Thinnes
#include "mainwindow.h"
#include <QApplication>
#include <QCheckBox>
#include <QCloseEvent>
#include <QDialog>
#include <QDir>
#include <QFileDialog>
#include <QFileInfo>
#include <QFileSystemModel>
#include <QGridLayout>
#include <QHBoxLayout>
#include <QHeaderView>
#include <QIcon>
#include <QInputDialog>
#include <QLabel>
#include <QLineEdit>
#include <QMenuBar>
#include <QMessageBox>
#include <QPainter>
#include <QPainterPath>
#include <QPlainTextEdit>
#include <QProcess>
#include <QPushButton>
#include <QSettings>
#include <QSplitter>
#include <QStyle>
#include <QTreeView>
#include <QTreeWidget>
#include <QVBoxLayout>
#include <algorithm>
#ifndef APP_VERSION
#define APP_VERSION "0.2.0"
#endif
namespace {
constexpr int RolePath = Qt::UserRole;
constexpr int RoleIsDir = Qt::UserRole + 1;
constexpr int RoleUp = Qt::UserRole + 2;
QString humanSize(qint64 s)
{
if (s < 1024)
return QString::number(s) + QStringLiteral(" B");
return QString::number(s / 1024.0, 'f', 1) + QStringLiteral(" KiB");
}
QString parentOf(const QString &path)
{
const int i = path.lastIndexOf('/');
return i < 0 ? QString() : path.left(i);
}
// Einfaches Diskettensymbol als Platzhalter-Logo (kann spaeter durch ein PNG ersetzt werden)
QPixmap makeLogo(int size)
{
QPixmap pm(size, size);
pm.fill(Qt::transparent);
QPainter p(&pm);
p.setRenderHint(QPainter::Antialiasing);
p.scale(size / 72.0, size / 72.0);
p.setPen(Qt::NoPen);
QPainterPath body;
body.addRoundedRect(QRectF(6, 6, 60, 60), 5, 5);
QPainterPath cut;
cut.moveTo(48, 6);
cut.lineTo(66, 24);
cut.lineTo(66, 6);
cut.closeSubpath();
p.setBrush(QColor("#56b6c2"));
p.drawPath(body.subtracted(cut));
p.setBrush(QColor("#1e1e1e"));
p.drawRect(QRectF(20, 6, 26, 18));
p.setBrush(QColor("#d4d4d4"));
p.drawRect(QRectF(37, 9, 6, 12));
p.drawRoundedRect(QRectF(14, 38, 44, 24), 2, 2);
p.setBrush(QColor("#56b6c2"));
p.drawRect(QRectF(19, 44, 34, 2));
p.drawRect(QRectF(19, 50, 26, 2));
return pm;
}
bool runUdisks(const QStringList &args, QString &output)
{
QProcess p;
p.start(QStringLiteral("udisksctl"), args);
if (!p.waitForStarted(5000)) {
output = QStringLiteral("udisksctl konnte nicht gestartet werden (Paket udisks2 installiert?)");
return false;
}
p.waitForFinished(30000);
output = (QString::fromUtf8(p.readAllStandardOutput()) + QString::fromUtf8(p.readAllStandardError())).trimmed();
return p.exitStatus() == QProcess::NormalExit && p.exitCode() == 0;
}
} // namespace
MainWindow::MainWindow()
{
setWindowTitle(QStringLiteral("ADF Manager"));
setWindowIcon(QIcon(makeLogo(64)));
resize(1200, 700);
QSettings s;
m_tool.setProgram(s.value("xdftool", "xdftool").toString());
// ---- Menue ----
QMenu *fileMenu = menuBar()->addMenu(tr("&Datei"));
fileMenu->addAction(tr("ADF &öffnen …"), QKeySequence::Open, this, &MainWindow::openAdf);
fileMenu->addAction(tr("&Neu laden"), QKeySequence::Refresh, this, &MainWindow::reload);
m_ejectAct = fileMenu->addAction(tr("ADF-Medium &auswerfen"), this, &MainWindow::ejectDevice);
m_ejectAct->setEnabled(false);
fileMenu->addSeparator();
fileMenu->addAction(tr("xdftool-&Pfad …"), this, &MainWindow::setToolPath);
fileMenu->addSeparator();
fileMenu->addAction(tr("&Beenden"), QKeySequence::Quit, this, &QWidget::close);
QMenu *helpMenu = menuBar()->addMenu(tr("&Hilfe"));
helpMenu->addAction(tr("Über ADF Manager"), this, &MainWindow::showAbout);
helpMenu->addAction(tr("Über &Qt"), this, [] { QApplication::aboutQt(); });
// ---- Linke Seite: ADF ----
m_adfLabel = new QLabel(tr("Keine ADF geöffnet"));
auto *btnOpen = new QPushButton(tr("ADF öffnen …"));
connect(btnOpen, &QPushButton::clicked, this, &MainWindow::openAdf);
m_ejectBtn = new QPushButton(tr("⏏ Auswerfen"));
m_ejectBtn->setEnabled(false);
m_ejectBtn->setToolTip(tr("Nur aktiv, wenn die ADF auf einer SD-Karte / einem Wechseldatenträger liegt"));
connect(m_ejectBtn, &QPushButton::clicked, this, &MainWindow::ejectDevice);
m_cwdLabel = new QLabel;
m_adfTree = new QTreeWidget;
m_adfTree->setColumnCount(3);
m_adfTree->setHeaderLabels({ tr("Name"), tr("Größe"), tr("Typ") });
m_adfTree->setSelectionMode(QAbstractItemView::ExtendedSelection);
m_adfTree->setAlternatingRowColors(true);
m_adfTree->setRootIsDecorated(false);
m_adfTree->setUniformRowHeights(true);
m_adfTree->header()->setSectionResizeMode(QHeaderView::Interactive);
m_adfTree->header()->setStretchLastSection(false);
m_adfTree->setColumnWidth(0, 320);
m_adfTree->setColumnWidth(1, 90);
m_adfTree->setColumnWidth(2, 80);
connect(m_adfTree, &QTreeWidget::itemActivated, this, &MainWindow::adfActivated);
auto *btnDelete = new QPushButton(tr("Löschen"));
auto *btnRename = new QPushButton(tr("Umbenennen"));
auto *btnMkdir = new QPushButton(tr("Neuer Ordner"));
connect(btnDelete, &QPushButton::clicked, this, &MainWindow::deleteSelected);
connect(btnRename, &QPushButton::clicked, this, &MainWindow::renameSelected);
connect(btnMkdir, &QPushButton::clicked, this, &MainWindow::makeDir);
auto *leftTop = new QHBoxLayout;
leftTop->addWidget(btnOpen);
leftTop->addWidget(m_adfLabel, 1);
leftTop->addWidget(m_ejectBtn);
auto *leftBottom = new QHBoxLayout;
leftBottom->addWidget(btnDelete);
leftBottom->addWidget(btnRename);
leftBottom->addWidget(btnMkdir);
leftBottom->addStretch(1);
auto *left = new QWidget;
auto *leftLay = new QVBoxLayout(left);
leftLay->addLayout(leftTop);
leftLay->addWidget(m_cwdLabel);
leftLay->addWidget(m_adfTree, 1);
leftLay->addLayout(leftBottom);
// ---- Mitte: Kopier-Buttons ----
auto *btnToPc = new QPushButton(tr("→\nAuf PC\nkopieren"));
auto *btnToAdf = new QPushButton(tr("←\nIn ADF\nkopieren"));
btnToPc->setMinimumHeight(70);
btnToAdf->setMinimumHeight(70);
connect(btnToPc, &QPushButton::clicked, this, &MainWindow::adfToPc);
connect(btnToAdf, &QPushButton::clicked, this, &MainWindow::pcToAdf);
auto *mid = new QWidget;
auto *midLay = new QVBoxLayout(mid);
midLay->addStretch(1);
midLay->addWidget(btnToPc);
midLay->addWidget(btnToAdf);
midLay->addStretch(1);
mid->setFixedWidth(100);
// ---- Rechte Seite: PC-Ordner ----
m_localEdit = new QLineEdit;
m_localEdit->setReadOnly(true);
auto *btnChoose = new QPushButton(tr("Ordner wählen …"));
auto *btnUp = new QPushButton(tr("↑"));
btnUp->setToolTip(tr("Eine Ebene höher"));
btnUp->setFixedWidth(36);
connect(btnChoose, &QPushButton::clicked, this, &MainWindow::chooseLocalDir);
connect(btnUp, &QPushButton::clicked, this, &MainWindow::localUp);
const bool showHidden = s.value("showHidden", false).toBool();
m_localModel = new QFileSystemModel(this);
m_localModel->setFilter(QDir::AllEntries | QDir::NoDotAndDotDot
| (showHidden ? QDir::Hidden : QDir::Filters()));
m_localView = new QTreeView;
m_localView->setModel(m_localModel);
m_localView->setSelectionMode(QAbstractItemView::ExtendedSelection);
m_localView->setRootIsDecorated(false);
m_localView->setSortingEnabled(true);
m_localView->sortByColumn(0, Qt::AscendingOrder);
m_localView->setAlternatingRowColors(true);
m_localView->header()->setSectionResizeMode(QHeaderView::Interactive);
m_localView->header()->setStretchLastSection(false);
m_localView->setColumnWidth(0, 320);
m_localView->setColumnWidth(1, 90);
m_localView->setColumnWidth(2, 90);
m_localView->setColumnWidth(3, 140);
connect(m_localView, &QTreeView::doubleClicked, this, &MainWindow::localActivated);
auto *chkHidden = new QCheckBox(tr("Versteckte Dateien anzeigen"));
chkHidden->setChecked(showHidden);
connect(chkHidden, &QCheckBox::toggled, this, [this](bool on) {
m_localModel->setFilter(QDir::AllEntries | QDir::NoDotAndDotDot
| (on ? QDir::Hidden : QDir::Filters()));
QSettings().setValue("showHidden", on);
});
auto *rightTop = new QHBoxLayout;
rightTop->addWidget(btnChoose);
rightTop->addWidget(btnUp);
rightTop->addWidget(m_localEdit, 1);
auto *right = new QWidget;
auto *rightLay = new QVBoxLayout(right);
rightLay->addLayout(rightTop);
rightLay->addWidget(chkHidden);
rightLay->addWidget(m_localView, 1);
// ---- Zusammenbau ----
auto *top = new QWidget;
auto *topLay = new QHBoxLayout(top);
topLay->setContentsMargins(0, 0, 0, 0);
topLay->addWidget(left, 1);
topLay->addWidget(mid);
topLay->addWidget(right, 1);
m_log = new QPlainTextEdit;
m_log->setReadOnly(true);
m_log->setMaximumBlockCount(2000);
auto *split = new QSplitter(Qt::Vertical);
split->addWidget(top);
split->addWidget(m_log);
split->setStretchFactor(0, 5);
split->setStretchFactor(1, 1);
setCentralWidget(split);
setLocalDir(s.value("localDir", QDir::homePath()).toString());
updatePathLabel();
// Fenstergeometrie und Spaltenbreiten wiederherstellen
if (s.contains("geometry"))
restoreGeometry(s.value("geometry").toByteArray());
if (s.contains("adfHeader"))
m_adfTree->header()->restoreState(s.value("adfHeader").toByteArray());
if (s.contains("localHeader"))
m_localView->header()->restoreState(s.value("localHeader").toByteArray());
}
void MainWindow::closeEvent(QCloseEvent *e)
{
QSettings s;
s.setValue("geometry", saveGeometry());
s.setValue("adfHeader", m_adfTree->header()->saveState());
s.setValue("localHeader", m_localView->header()->saveState());
QMainWindow::closeEvent(e);
}
void MainWindow::log(const QString &text)
{
m_log->appendPlainText(text);
}
bool MainWindow::haveAdf()
{
if (m_adf.isEmpty()) {
QMessageBox::information(this, tr("ADF Manager"), tr("Bitte zuerst eine ADF öffnen."));
return false;
}
return true;
}
// ------------------------------------------------------------------ ADF laden
void MainWindow::openAdf()
{
QSettings s;
const QString file = QFileDialog::getOpenFileName(
this, tr("ADF auswählen"), s.value("adfDir", QDir::homePath()).toString(),
tr("Amiga-Disk-Images (*.adf *.ADF *.adz);;Alle Dateien (*)"));
if (file.isEmpty())
return;
s.setValue("adfDir", QFileInfo(file).absolutePath());
loadAdf(file);
}
void MainWindow::loadAdf(const QString &file)
{
m_adf = file;
m_cwd.clear();
m_adfLabel->setText(QFileInfo(file).fileName());
setWindowTitle(QStringLiteral("ADF Manager – %1").arg(QFileInfo(file).fileName()));
updateEjectState();
reload();
}
void MainWindow::reload()
{
if (m_adf.isEmpty())
return;
QApplication::setOverrideCursor(Qt::WaitCursor);
QList<AdfEntry> entries;
const XdfTool::Result r = m_tool.list(m_adf, entries);
QApplication::restoreOverrideCursor();
log(QStringLiteral("$ ") + r.commandLine);
if (!r.ok) {
log(r.err.isEmpty() ? r.out : r.err);
QMessageBox::warning(this, tr("xdftool"),
tr("Inhalt konnte nicht gelesen werden:\n%1").arg(r.err.isEmpty() ? r.out : r.err));
return;
}
if (entries.isEmpty())
log(tr("(keine Einträge erkannt – Rohausgabe:)\n") + r.out);
m_entries = entries;
m_known.clear();
m_dirs.clear();
for (const AdfEntry &e : m_entries) {
m_known.insert(e.path.toLower());
if (e.isDir)
m_dirs.insert(e.path.toLower());
}
// Aktuellen Ordner beibehalten, falls er noch existiert
while (!m_cwd.isEmpty() && !m_dirs.contains(m_cwd.toLower()))
m_cwd = parentOf(m_cwd);
showDir();
}
// Zeigt den Inhalt des aktuellen ADF-Ordners (m_cwd)
void MainWindow::showDir()
{
m_adfTree->clear();
if (!m_cwd.isEmpty()) {
auto *up = new QTreeWidgetItem(m_adfTree);
up->setText(0, QStringLiteral(".."));
up->setText(2, tr("Ordner"));
up->setIcon(0, style()->standardIcon(QStyle::SP_FileDialogToParent));
up->setData(0, RoleUp, true);
up->setData(0, RoleIsDir, true);
up->setData(0, RolePath, parentOf(m_cwd));
up->setFlags(Qt::ItemIsEnabled); // nicht auswaehlbar, aber aktivierbar
}
QList<AdfEntry> children;
for (const AdfEntry &e : m_entries) {
if (parentOf(e.path) == m_cwd)
children << e;
}
std::sort(children.begin(), children.end(), [](const AdfEntry &a, const AdfEntry &b) {
if (a.isDir != b.isDir)
return a.isDir;
return a.name.compare(b.name, Qt::CaseInsensitive) < 0;
});
for (const AdfEntry &e : children) {
auto *item = new QTreeWidgetItem(m_adfTree);
item->setText(0, e.name);
item->setToolTip(0, e.name);
item->setText(1, e.isDir ? QString() : humanSize(e.size));
item->setText(2, e.isDir ? tr("Ordner") : tr("Datei"));
item->setTextAlignment(1, Qt::AlignRight | Qt::AlignVCenter);
item->setIcon(0, style()->standardIcon(e.isDir ? QStyle::SP_DirIcon : QStyle::SP_FileIcon));
item->setData(0, RolePath, e.path);
item->setData(0, RoleIsDir, e.isDir);
item->setData(0, RoleUp, false);
}
updatePathLabel();
}
void MainWindow::updatePathLabel()
{
if (m_adf.isEmpty())
m_cwdLabel->setText(tr("Ziel/Quelle in der ADF: –"));
else
m_cwdLabel->setText(tr("Aktueller ADF-Ordner: <b>/%1</b> (Doppelklick öffnet Ordner)").arg(m_cwd.toHtmlEscaped()));
}
void MainWindow::adfActivated(QTreeWidgetItem *item, int)
{
if (!item || !item->data(0, RoleIsDir).toBool())
return;
m_cwd = item->data(0, RolePath).toString();
showDir();
}
QList<QTreeWidgetItem *> MainWindow::selectedEntries() const
{
QList<QTreeWidgetItem *> result;
for (QTreeWidgetItem *item : m_adfTree->selectedItems()) {
if (!item->data(0, RoleUp).toBool())
result << item;
}
return result;
}
// ------------------------------------------------------------------ xdftool ausfuehren
bool MainWindow::runBatch(const QList<QStringList> &cmds)
{
QApplication::setOverrideCursor(Qt::WaitCursor);
const XdfTool::Result r = m_tool.runBatch(m_adf, cmds);
QApplication::restoreOverrideCursor();
log(QStringLiteral("$ ") + r.commandLine);
if (!r.out.trimmed().isEmpty())
log(r.out.trimmed());
if (!r.err.trimmed().isEmpty())
log(r.err.trimmed());
if (!r.ok) {
QMessageBox::warning(this, tr("xdftool"),
tr("xdftool meldet einen Fehler:\n%1").arg(r.err.isEmpty() ? r.out : r.err));
}
return r.ok;
}
// ------------------------------------------------------------------ ADF -> PC
void MainWindow::adfToPc()
{
if (!haveAdf())
return;
const QList<QTreeWidgetItem *> items = selectedEntries();
if (items.isEmpty()) {
QMessageBox::information(this, tr("ADF Manager"), tr("Links nichts ausgewählt."));
return;
}
QList<QStringList> cmds;
for (QTreeWidgetItem *item : items) {
const QString path = item->data(0, RolePath).toString();
cmds << QStringList{ QStringLiteral("read"), path, QDir(m_localDir).filePath(item->text(0)) };
}
runBatch(cmds);
}
// ------------------------------------------------------------------ PC -> ADF
void MainWindow::pcToAdf()
{
if (!haveAdf())
return;
const QModelIndexList rows = m_localView->selectionModel()->selectedRows(0);
if (rows.isEmpty()) {
QMessageBox::information(this, tr("ADF Manager"), tr("Rechts nichts ausgewählt."));
return;
}
// Ziel ist immer der links angezeigte ADF-Ordner
const QString dir = m_cwd;
QList<QStringList> cmds;
QStringList overwritten;
for (const QModelIndex &idx : rows) {
const QString local = m_localModel->filePath(idx);
const QString name = QFileInfo(local).fileName();
const QString ami = dir.isEmpty() ? name : dir + QLatin1Char('/') + name;
if (m_known.contains(ami.toLower())) {
overwritten << ami;
cmds << QStringList{ QStringLiteral("delete"), ami, QStringLiteral("all") };
}
cmds << QStringList{ QStringLiteral("write"), local, ami };
}
if (!overwritten.isEmpty()) {
const auto answer = QMessageBox::question(
this, tr("Vorhandene Einträge ersetzen"),
tr("Diese Einträge existieren bereits in der ADF und werden vorher gelöscht:\n\n%1\n\nFortfahren?")
.arg(overwritten.join('\n')));
if (answer != QMessageBox::Yes)
return;
}
runBatch(cmds);
reload();
}
// ------------------------------------------------------------------ Loeschen / Umbenennen / Ordner
void MainWindow::deleteSelected()
{
if (!haveAdf())
return;
const QList<QTreeWidgetItem *> items = selectedEntries();
if (items.isEmpty())
return;
QStringList names;
QList<QStringList> cmds;
for (QTreeWidgetItem *item : items) {
const QString path = item->data(0, RolePath).toString();
names << path;
cmds << QStringList{ QStringLiteral("delete"), path, QStringLiteral("all") };
}
if (QMessageBox::question(this, tr("Löschen"),
tr("Wirklich aus der ADF löschen?\n\n%1").arg(names.join('\n')))
!= QMessageBox::Yes)
return;
runBatch(cmds);
reload();
}
void MainWindow::renameSelected()
{
if (!haveAdf())
return;
const QList<QTreeWidgetItem *> items = selectedEntries();
if (items.size() != 1) {
QMessageBox::information(this, tr("Umbenennen"), tr("Bitte genau einen Eintrag auswählen."));
return;
}
QTreeWidgetItem *item = items.first();
bool ok = false;
const QString newName = QInputDialog::getText(this, tr("Umbenennen"), tr("Neuer Name:"),
QLineEdit::Normal, item->text(0), &ok);
if (!ok || newName.isEmpty() || newName == item->text(0))
return;
const QString oldPath = item->data(0, RolePath).toString();
const QString parent = parentOf(oldPath);
const QString newPath = parent.isEmpty() ? newName : parent + QLatin1Char('/') + newName;
runBatch({ QStringList{ QStringLiteral("rename"), oldPath, newPath } });
reload();
}
void MainWindow::makeDir()
{
if (!haveAdf())
return;
bool ok = false;
const QString name = QInputDialog::getText(this, tr("Neuer Ordner"), tr("Name:"),
QLineEdit::Normal, QString(), &ok);
if (!ok || name.isEmpty())
return;
const QString path = m_cwd.isEmpty() ? name : m_cwd + QLatin1Char('/') + name;
runBatch({ QStringList{ QStringLiteral("makedir"), path } });
reload();
}
// ------------------------------------------------------------------ PC-Seite
void MainWindow::setLocalDir(const QString &dir)
{
if (!QFileInfo(dir).isDir())
return;
m_localDir = QDir(dir).absolutePath();
m_localEdit->setText(m_localDir);
m_localModel->setRootPath(m_localDir);
m_localView->setRootIndex(m_localModel->index(m_localDir));
QSettings().setValue("localDir", m_localDir);
}
void MainWindow::chooseLocalDir()
{
const QString dir = QFileDialog::getExistingDirectory(this, tr("Ordner auf dem PC"), m_localDir);
if (!dir.isEmpty())
setLocalDir(dir);
}
void MainWindow::localUp()
{
QDir d(m_localDir);
if (d.cdUp())
setLocalDir(d.absolutePath());
}
void MainWindow::localActivated(const QModelIndex &idx)
{
if (m_localModel->isDir(idx))
setLocalDir(m_localModel->filePath(idx));
}
void MainWindow::setToolPath()
{
bool ok = false;
const QString p = QInputDialog::getText(this, tr("xdftool"), tr("Programm / Pfad zu xdftool:"),
QLineEdit::Normal, m_tool.program(), &ok);
if (ok && !p.isEmpty()) {
m_tool.setProgram(p);
QSettings().setValue("xdftool", p);
}
}
// ------------------------------------------------------------------ Auswerfen
void MainWindow::updateEjectState()
{
m_removable = m_adf.isEmpty() ? RemovableInfo() : detectRemovable(m_adf);
m_ejectBtn->setEnabled(m_removable.removable);
m_ejectAct->setEnabled(m_removable.removable);
if (m_removable.removable) {
const QString tip = tr("%1 auswerfen (%2)")
.arg(m_removable.label.isEmpty() ? m_removable.partition : m_removable.label,
m_removable.partition);
m_ejectBtn->setToolTip(tip);
log(tr("ADF liegt auf Wechseldatenträger: %1 (%2)").arg(m_removable.partition, m_removable.mountPoint));
} else {
m_ejectBtn->setToolTip(tr("Nur aktiv, wenn die ADF auf einer SD-Karte / einem Wechseldatenträger liegt"));
}
}
void MainWindow::ejectDevice()
{
if (!m_removable.removable)
return;
const RemovableInfo dev = m_removable;
// PC-Ansicht verlassen, falls sie auf dem Medium steht
if (m_localDir == dev.mountPoint || m_localDir.startsWith(dev.mountPoint + QLatin1Char('/')))
setLocalDir(QDir::homePath());
QApplication::setOverrideCursor(Qt::WaitCursor);
QString out;
const bool unmounted = runUdisks({ QStringLiteral("unmount"), QStringLiteral("-b"), dev.partition }, out);
log(QStringLiteral("$ udisksctl unmount -b %1\n%2").arg(dev.partition, out));
bool poweredOff = false;
QString out2;
if (unmounted) {
poweredOff = runUdisks({ QStringLiteral("power-off"), QStringLiteral("-b"), dev.disk }, out2);
log(QStringLiteral("$ udisksctl power-off -b %1\n%2").arg(dev.disk, out2));
}
QApplication::restoreOverrideCursor();
if (!unmounted) {
QMessageBox::warning(this, tr("Auswerfen"), tr("Das Medium konnte nicht ausgehängt werden:\n%1").arg(out));
return;
}
// ADF-Ansicht leeren, die Datei ist nicht mehr erreichbar
m_adf.clear();
m_entries.clear();
m_known.clear();
m_dirs.clear();
m_cwd.clear();
m_adfTree->clear();
m_adfLabel->setText(tr("Keine ADF geöffnet"));
setWindowTitle(QStringLiteral("ADF Manager"));
updatePathLabel();
updateEjectState();
if (poweredOff) {
QMessageBox::information(this, tr("Auswerfen"), tr("Das Medium wurde ausgehängt und kann entfernt werden."));
} else {
QMessageBox::information(this, tr("Auswerfen"),
tr("Das Medium wurde ausgehängt.\nAbschalten war nicht möglich:\n%1").arg(out2));
}
}
// ------------------------------------------------------------------ Ueber-Dialog
void MainWindow::showAbout()
{
auto *dlg = new QDialog(this);
dlg->setWindowTitle(tr("Über ADF Manager"));
dlg->setFixedWidth(400);
auto *root = new QVBoxLayout(dlg);
root->setContentsMargins(0, 0, 0, 0);
root->setSpacing(0);
auto *header = new QWidget(dlg);
header->setFixedHeight(110);
header->setStyleSheet("background-color: #1e1e1e;");
auto *headerLayout = new QHBoxLayout(header);
headerLayout->setContentsMargins(20, 14, 24, 14);
headerLayout->setSpacing(16);
auto *logoLabel = new QLabel(header);
logoLabel->setPixmap(makeLogo(72));
logoLabel->setStyleSheet("background: transparent;");
logoLabel->setFixedSize(72, 72);
auto *titleBlock = new QVBoxLayout();
titleBlock->setSpacing(4);
auto *titleLabel = new QLabel("ADF Manager", header);
titleLabel->setStyleSheet(
"color: #ffffff; font-size: 26px; font-weight: bold; background: transparent;");
auto *subtitleLabel = new QLabel(tr("Frontend für Amiga-Disk-Images (xdftool)"), header);
subtitleLabel->setStyleSheet(
"color: #56b6c2; font-size: 11px; background: transparent;");
titleBlock->addWidget(titleLabel);
titleBlock->addWidget(subtitleLabel);
headerLayout->addWidget(logoLabel);
headerLayout->addLayout(titleBlock);
root->addWidget(header);
auto *body = new QWidget(dlg);
body->setStyleSheet("background-color: #252526;");
auto *grid = new QGridLayout(body);
grid->setContentsMargins(24, 20, 24, 20);
grid->setVerticalSpacing(10);
grid->setHorizontalSpacing(16);
grid->setColumnMinimumWidth(0, 90);
const QString labelStyle = "color: #888888; font-weight: bold; background: transparent;";
const QString valueStyle = "color: #d4d4d4; background: transparent;";
auto addRow = [&](int row, const QString &label, const QString &value) {
auto *l = new QLabel(label, body);
auto *v = new QLabel(value, body);
l->setStyleSheet(labelStyle);
v->setStyleSheet(valueStyle);
l->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
grid->addWidget(l, row, 0);
grid->addWidget(v, row, 1);
};
addRow(0, tr("Version"), APP_VERSION);
addRow(1, tr("Entwickler"), "Dany Thinnes");
addRow(2, tr("Projekt"), "Projekt Hirnfrei");
addRow(3, tr("Framework"), QString("Qt %1").arg(QT_VERSION_STR));
addRow(4, tr("Sprache"), "C++17");
root->addWidget(body, 1);
auto *footer = new QWidget(dlg);
footer->setStyleSheet("background-color: #252526; border-top: 1px solid #3c3c3c;");
auto *footerLayout = new QHBoxLayout(footer);
footerLayout->setContentsMargins(16, 10, 16, 10);
auto *closeBtn = new QPushButton(tr("Schließen"), footer);
closeBtn->setDefault(true);
closeBtn->setMinimumWidth(90);
connect(closeBtn, &QPushButton::clicked, dlg, &QDialog::accept);
footerLayout->addStretch();
footerLayout->addWidget(closeBtn);
root->addWidget(footer);
dlg->exec();
dlg->deleteLater();
}