771 lines
26 KiB
C++
771 lines
26 KiB
C++
// 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();
|
||
}
|