Erste Version
This commit is contained in:
commit
e5944d9b97
74 files changed
+30486
No files matched your search
+770
@@ -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();
|
||||
}
|
||||
Reference in new issue
Block a user