#include "mainwindow.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "loadrunable.h" #include "markedfiles.h" #include "about.h" #include "statusbar.h" #include "settingsdialog.h" #include "histogram.h" #include "batchprocessing.h" #ifdef __linux__ #include #include #include #endif bool moveToTrash(const QString &path); int MainWindow::socketPair[2] = {0, 0}; MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { qRegisterMetaType("ImageInfoData"); qRegisterMetaType>("std::shared_ptr"); SettingsDialog::loadSettings(); QStringList nameFilter; _saveFilter = tr("FITS (*.fits *.fit);;XISF (*.xisf);;"); _openFilter = tr("Images ("); QMimeDatabase db; auto supportedFormats = QImageReader::supportedMimeTypes(); QStringList filters; for(auto format : supportedFormats) { QMimeType mimeType = db.mimeTypeForName(format); _saveFilter.append(mimeType.filterString() + ";;"); _openFilter.append("*."); _openFilter.append(mimeType.suffixes().join(" *.")); _openFilter.append(" "); nameFilter.append(mimeType.suffixes()); } _openFilter.append("*.fit *.fits *.fts *.fz *.xisf *.cr2 *.cr3 *.nef *.dng)"); _openFilter.append(tr(";;All files (*)")); nameFilter.append({"fit", "fits", "fts", "fz", "xisf", "cr2", "cr3", "nef", "dng"}); QImageReader::setAllocationLimit(0); m_info = new ImageInfo(this); QDockWidget *infoDock = new QDockWidget(tr("Image info"), this); infoDock->setWidget(m_info); infoDock->setObjectName("infoDock"); addDockWidget(Qt::LeftDockWidgetArea, infoDock); resize(1024, 600); setStatusBar(new QStatusBar(this)); m_database = new Database(this); if(!m_database->init()) QMessageBox::critical(this, tr("Can't open DB"), tr("Can't open SQLITE database")); m_image = new ImageScrollArea(m_database, this); setCentralWidget(m_image); StatusBar *statusBar = new StatusBar(this); setStatusBar(statusBar); connect(m_image, &ImageScrollArea::status, statusBar, &StatusBar::newStatus); m_stretchPanel = new StretchToolbar(this); connect(m_stretchPanel, &StretchToolbar::paramChanged, m_image, &ImageScrollArea::setMTFParams); connect(m_stretchPanel, &StretchToolbar::autoStretch, [&](){ m_stretchPanel->stretchImage(m_ringList->currentImage().get()); }); connect(m_stretchPanel, &StretchToolbar::invert, m_image, &ImageScrollArea::invert); connect(m_stretchPanel, &StretchToolbar::superPixel, m_image, &ImageScrollArea::superPixel); connect(m_stretchPanel, &StretchToolbar::falseColor, m_image, &ImageScrollArea::falseColor); m_ringList = new ImageRingList(m_database, nameFilter, this); m_filesystem = new FilesystemWidget(m_ringList, this); connect(m_filesystem, SIGNAL(fileSelected(int)), this, SLOT(loadFile(int))); connect(m_filesystem, &FilesystemWidget::sortChanged, m_ringList, &ImageRingList::setSort); connect(m_filesystem, &FilesystemWidget::reverseSort, m_ringList, &ImageRingList::reverseSort); m_filetree = new Filetree(this); connect(m_filetree, &Filetree::fileSelected, this, static_cast(&MainWindow::loadFile)); connect(m_filetree, &Filetree::copyFiles, [this](const QString &path){ copyOrMove(true, path); }); connect(m_filetree, &Filetree::moveFiles, [this](const QString &path){ copyOrMove(false, path); }); connect(m_filetree, &Filetree::indexDirectory, this, static_cast(&MainWindow::indexDir)); m_databaseView = new DataBaseView(m_database, this); connect(m_databaseView, SIGNAL(loadFile(QString)), this, SLOT(loadFile(QString))); #ifdef PLATESOLVER _plateSolving = new PlateSolving(this); addDockWidget(Qt::RightDockWidgetArea, _plateSolving); _plateSolving->hide(); #endif addToolBar(Qt::TopToolBarArea, m_stretchPanel); QDockWidget *filesystemDock = new QDockWidget(tr("Filesystem"), this); filesystemDock->setWidget(m_filesystem); filesystemDock->setObjectName("filesystemDock"); addDockWidget(Qt::LeftDockWidgetArea, filesystemDock); QDockWidget *databaseViewDock = new QDockWidget(tr("FITS/XISF files database"), this); databaseViewDock->setWidget(m_databaseView); databaseViewDock->setObjectName("databaseViewDock"); databaseViewDock->hide(); addDockWidget(Qt::BottomDockWidgetArea, databaseViewDock); QDockWidget *filetreeDock = new QDockWidget(tr("File tree"), this); filetreeDock->setWidget(m_filetree); filetreeDock->setObjectName("filetreeDock"); databaseViewDock->hide(); addDockWidget(Qt::LeftDockWidgetArea, filetreeDock); Histogram *histogram = new Histogram(this); QDockWidget *histogramDock = new QDockWidget(tr("Histogram"), this); histogramDock->setWidget(histogram); histogramDock->setObjectName("histogramDock"); histogramDock->hide(); addDockWidget(Qt::LeftDockWidgetArea, histogramDock); setWindowTitle(tr("Tenmon")); connect(m_ringList, &ImageRingList::pixmapLoaded, m_image, &ImageScrollArea::imageLoaded); connect(m_ringList, &ImageRingList::currentImageChanged, this, &MainWindow::updateWindowTitle); connect(m_ringList, &ImageRingList::infoLoaded, m_info, &ImageInfo::setInfo); connect(m_ringList, &ImageRingList::currentImageChanged, m_filesystem, &FilesystemWidget::selectFile); connect(m_ringList, &ImageRingList::thumbnailLoaded, m_image, &ImageScrollArea::thumbnailLoaded); connect(m_ringList, &ImageRingList::pixmapLoaded, m_stretchPanel, &StretchToolbar::imageLoaded); connect(m_ringList, &ImageRingList::pixmapLoaded, histogram, &Histogram::imageLoaded); #ifdef PLATESOLVER connect(m_ringList, &ImageRingList::pixmapLoaded, _plateSolving, &PlateSolving::imageLoaded); #endif connect(m_image, &ImageScrollArea::fileDropped, this, static_cast(&MainWindow::loadFile)); QMenu *fileMenu = new QMenu(tr("File"), this); fileMenu->addAction(tr("Open"), QKeySequence::Open, this, SLOT(loadFile())); fileMenu->addAction(tr("Open directory recursively"), this, &MainWindow::loadDir); QAction *saveAs = fileMenu->addAction(tr("Save as"), QKeySequence::Save, this, SLOT(saveAs())); fileMenu->addSeparator(); fileMenu->addAction(tr("Copy marked files"), Qt::Key_F5, this, SLOT(copyMarked())); fileMenu->addAction(tr("Move marked files"), Qt::Key_F6, this, SLOT(moveMarked())); fileMenu->addAction(tr("Move marked files to trash"), QKeySequence::Delete, this, &MainWindow::deleteMarked); fileMenu->addSeparator(); fileMenu->addAction(tr("Index directory"), this, SLOT(indexDir())); fileMenu->addAction(tr("Reindex files"), this, SLOT(reindex())); fileMenu->addAction(tr("Export database to CSV"), this, &MainWindow::exportCSV); fileMenu->addAction(tr("Batch processing"), Qt::Key_B | Qt::CTRL, [this](){ BatchProcessing *batchProcessing = new BatchProcessing(this); batchProcessing->exec(); delete batchProcessing; }); fileMenu->addSeparator(); QAction *liveModeAction = fileMenu->addAction(tr("Live mode"), this, SLOT(liveMode(bool))); liveModeAction->setCheckable(true); QAction *exitAction = fileMenu->addAction(tr("Exit"), this, SLOT(close())); exitAction->setShortcut(QKeySequence::Quit); menuBar()->addMenu(fileMenu); QMenu *editMenu = new QMenu(tr("Edit"), this); editMenu->addAction(tr("Settings"), this, &MainWindow::showSettingsDialog); menuBar()->addMenu(editMenu); QMenu *viewMenu = new QMenu(tr("View"), this); viewMenu->addAction(tr("Zoom In"), QKeySequence::ZoomIn, m_image, &ImageScrollArea::zoomIn); viewMenu->addAction(tr("Zoom Out"), QKeySequence::ZoomOut, m_image, &ImageScrollArea::zoomOut); viewMenu->addAction(tr("Best Fit"), QKeySequence("Ctrl+1"), m_image, &ImageScrollArea::bestFit); viewMenu->addAction(tr("100%"), QKeySequence("Ctrl+0"), m_image, &ImageScrollArea::oneToOne); viewMenu->addSeparator(); QMenu *bayerMenu = viewMenu->addMenu(tr("Bayer mask")); QActionGroup *bayerActionGroup = new QActionGroup(this); QAction *rggbAction = bayerActionGroup->addAction(tr("RGGB"));//0 0 QAction *grbgAction = bayerActionGroup->addAction(tr("GRBG"));//1 0 QAction *gbrgAction = bayerActionGroup->addAction(tr("GBRG"));//0 1 QAction *bggrAction = bayerActionGroup->addAction(tr("BGGR"));//1 1 rggbAction->setCheckable(true); rggbAction->setData(0); rggbAction->setIcon(QIcon(":/bayer.png")); grbgAction->setCheckable(true); grbgAction->setData(1); grbgAction->setIcon(QIcon(":/grbg.png")); gbrgAction->setCheckable(true); gbrgAction->setData(2); gbrgAction->setIcon(QIcon(":/gbrg.png")); bggrAction->setCheckable(true); bggrAction->setData(3); bggrAction->setIcon(QIcon(":/bggr.png")); bayerMenu->addActions({rggbAction, grbgAction, gbrgAction, bggrAction}); viewMenu->addMenu(bayerMenu); connect(bayerActionGroup, &QActionGroup::triggered, [this](QAction *action){ int data = action->data().toInt(); m_image->setBayerMask(data); QSettings settings; settings.setValue("mainwindow/bayermask", data); }); QStringList colormaps = {"Autumn", "Bone", "Jet", "Winter", "Rainbow", "Ocean", "Summer", "Spring", "Cool", "HSV", "Pink", "Hot", "Parula", "Magma", "Inferno", "Plasma", "Viridis", "Cividis", "Twilight", "Twilight shifted", "Turbo", "Deepgreen"}; QMenu *colormapMenu = viewMenu->addMenu(tr("Colormap")); QActionGroup *colormapActionGroup = new QActionGroup(this); QImage cmImg = ImageWidget::loadColormap(); for(int i=0; iaddAction(i < colormaps.size() ? colormaps[i] : tr("User %1").arg(i - colormaps.size() + 1)); action->setIcon(QPixmap::fromImage(icon)); action->setCheckable(true); action->setData(i); colormapMenu->addAction(action); } viewMenu->addMenu(colormapMenu); connect(colormapActionGroup, &QActionGroup::triggered, [this](QAction *action){ int data = action->data().toInt(); m_image->setColormap(data); QSettings settings; settings.setValue("mainwindow/colormap", data); }); QAction *thumbnailsAction = viewMenu->addAction(tr("Thumbnails"), Qt::Key_F2, [this](bool checked){ if(SettingsDialog::loadThumbsizes())m_ringList->clearThumbnails(); m_image->allocateThumbnails(m_ringList->imageNames()); m_image->showThumbnail(checked); if(checked)m_ringList->loadThumbnails(); else m_ringList->stopLoading(); }); thumbnailsAction->setCheckable(true); QAction *slideshowAction = viewMenu->addAction(tr("Slideshow"), Qt::Key_F3, m_ringList, &ImageRingList::toggleSlideshow); slideshowAction->setCheckable(true); menuBar()->addMenu(viewMenu); QMenu *selectMenu = new QMenu(tr("Select"), this); selectMenu->addAction(tr("Mark"), Qt::Key_F7, this, SLOT(markImage())); selectMenu->addAction(tr("Unmark"), Qt::Key_F8, this, SLOT(unmarkImage())); selectMenu->addSeparator(); selectMenu->addAction(tr("Mark and next"), Qt::Key_M, this, SLOT(markAndNext())); selectMenu->addAction(tr("Unmark and next"), Qt::Key_X, this, SLOT(unmarkAndNext())); selectMenu->addSeparator(); selectMenu->addAction(tr("Show marked list"), this, &MainWindow::showMarkFilesDialog); QAction *openMarked = selectMenu->addAction(tr("Open marked"), m_ringList, &ImageRingList::setMarked); menuBar()->addMenu(selectMenu); fileMenu->insertAction(saveAs, openMarked); /*QMenu *analyzeMenu = new QMenu(tr("Analyze"), this); QActionGroup *analyzeGroup = new QActionGroup(this); connect(analyzeGroup, &QActionGroup::triggered, [](QAction* action) { static QAction* lastAction = nullptr; if(action == lastAction) { action->setChecked(false); lastAction = nullptr; } else lastAction = action; }); QAction *statsAction = analyzeGroup->addAction(tr("Image statistics")); QAction *peakAction = analyzeGroup->addAction(tr("Peak finder")); QAction *starAction = analyzeGroup->addAction(tr("Star finder")); statsAction->setCheckable(true); peakAction->setCheckable(true); starAction->setCheckable(true); connect(statsAction, SIGNAL(toggled(bool)), this, SLOT(imageStats(bool))); connect(peakAction, SIGNAL(toggled(bool)), this, SLOT(peakFinder(bool))); connect(starAction, SIGNAL(toggled(bool)), this, SLOT(starFinder(bool))); analyzeMenu->addActions({statsAction, peakAction, starAction}); menuBar()->addMenu(analyzeMenu);*/ QMenu *dockMenu = new QMenu(tr("Docks"), this); dockMenu->addAction(infoDock->toggleViewAction()); dockMenu->addAction(m_stretchPanel->toggleViewAction()); dockMenu->addAction(filesystemDock->toggleViewAction()); dockMenu->addAction(databaseViewDock->toggleViewAction()); dockMenu->addAction(filetreeDock->toggleViewAction()); dockMenu->addAction(histogramDock->toggleViewAction()); #ifdef PLATESOLVER dockMenu->addAction(_plateSolving->toggleViewAction()); #endif menuBar()->addMenu(dockMenu); QMenu *helpMenu = menuBar()->addMenu(tr("Help")); helpMenu->addAction(tr("Help"), QKeySequence::HelpContents, [this]{ HelpDialog help(this); help.exec(); }); helpMenu->addAction(tr("About Tenmon"), [this]{ About about(this); about.exec(); }); helpMenu->addAction(tr("About Qt"), [this](){ QMessageBox::aboutQt(this); }); helpMenu->addAction(tr("Check for update"), this, &MainWindow::checkNewVersion); setupSigterm(); QSettings settings; restoreGeometry(settings.value("mainwindow/geometry").toByteArray()); restoreState(settings.value("mainwindow/state").toByteArray()); int bayermask = settings.value("mainwindow/bayermask", 0).toInt(); switch(bayermask) { default: case 0: rggbAction->setChecked(true); break; case 1: grbgAction->setChecked(true); break; case 2: gbrgAction->setChecked(true); break; case 3: bggrAction->setChecked(true); break; } int colormap = settings.value("mainwindow/colormap", 4).toInt(); if(colormap >= 0 && colormap < colormapActionGroup->actions().size()) colormapActionGroup->actions().at(colormap)->setChecked(true); m_image->setBayerMask(bayermask); m_image->setColormap(colormap); QStringList standardLocations = QStandardPaths::standardLocations(QStandardPaths::PicturesLocation); if(standardLocations.size()) _lastDir = standardLocations.first(); _lastDir = settings.value("mainwindow/lastdir", _lastDir).toString(); m_image->setFocus(); // workaround for nasty wayland backend bug https://bugreports.qt.io/browse/QTBUG-87332 if(static_cast(QCoreApplication::instance())->platformName() == "wayland") { infoDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable); filesystemDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable); databaseViewDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable); filetreeDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable); m_stretchPanel->setFloatable(false); } } MainWindow::~MainWindow() { delete m_database; } void MainWindow::keyPressEvent(QKeyEvent *event) { switch (event->key()) { case Qt::Key_Left: case Qt::Key_Up: m_ringList->decrement(); break; case Qt::Key_Right: case Qt::Key_Down: m_ringList->increment(); break; default: event->ignore(); break; } if(event->isAccepted()) updateWindowTitle(); } void MainWindow::keyReleaseEvent(QKeyEvent *event) { event->ignore(); } void MainWindow::setupSigterm() { #ifdef __linux__ struct sigaction signal; signal.sa_handler = MainWindow::signalHandler; sigemptyset(&signal.sa_mask); signal.sa_flags = 0; signal.sa_flags |= SA_RESTART; sigaction(SIGHUP, &signal, 0); sigaction(SIGTERM, &signal, 0); sigaction(SIGINT, &signal, 0); ::socketpair(AF_UNIX, SOCK_STREAM, 0, socketPair); socketNotifier = new QSocketNotifier(socketPair[1], QSocketNotifier::Read, this); connect(socketNotifier, SIGNAL(activated(int)), this, SLOT(socketNotify())); #endif } void MainWindow::signalHandler(int) { char a = 1; ::write(socketPair[0], &a, sizeof(a)); } void MainWindow::closeEvent(QCloseEvent *event) { QSettings settings; settings.setValue("mainwindow/geometry", saveGeometry()); settings.setValue("mainwindow/state", saveState()); QMainWindow::closeEvent(event); } void MainWindow::copyOrMove(bool copy) { QString dest = QFileDialog::getExistingDirectory(this, tr("Select destination"), _lastDir, QFileDialog::ShowDirsOnly); copyOrMove(copy, dest); } void MainWindow::copyOrMove(bool copy, const QString &dest) { QDir dir(dest); if(!dest.isEmpty() && dir.exists()) { int i = 0; int missing = 0; bool overwriteAll = false; bool skipAll = false; QStringList files = m_database->getMarkedFiles(); QProgressDialog progress(copy ? tr("Copying") : tr("Moving"), tr("Cancel"), 0, files.size(), this); progress.setWindowModality(Qt::WindowModal); progress.show(); for(const QString &file : files) { bool result = false; QFileInfo info(file); QFile srcFile(file); QFile dstFile(dir.absoluteFilePath(info.fileName())); if(!srcFile.exists()) { missing++; continue; } if(dstFile.exists()) { if(skipAll) { continue; } else if(overwriteAll) { dstFile.remove(); } else { QMessageBox::StandardButton button = QMessageBox::question(this, tr("Overwrite file?"), tr("Destination file %1 already exists. Overwrite?").arg(dstFile.fileName()), QMessageBox::Yes | QMessageBox::YesToAll | QMessageBox::No | QMessageBox::NoToAll); switch (button) { case QMessageBox::YesToAll: overwriteAll = true; case QMessageBox::Yes: dstFile.remove(); break; case QMessageBox::NoToAll: skipAll = true; case QMessageBox::No: continue; default: break; } } } if(progress.wasCanceled()) return; #ifdef __linux__ if(copy) { srcFile.open(QIODevice::ReadOnly); dstFile.open(QIODevice::WriteOnly); if(ioctl(dstFile.handle(), BTRFS_IOC_CLONE, srcFile.handle()) < 0) { dstFile.remove(); dstFile.close(); result = srcFile.copy(dstFile.fileName()); } else result = true; } else { result = srcFile.rename(dstFile.fileName()); } #else if(copy) result = srcFile.copy(dstFile.fileName()); else result = srcFile.rename(dstFile.fileName()); #endif if(!result) { QString t = copy ? tr("Failed to copy") : tr("Failed to move"); QString m = copy ? tr("Failed to copy from %1 to %2") : tr("Failed to move from %1 to %2"); QMessageBox::StandardButton button = QMessageBox::warning(this, t, m.arg(srcFile.fileName()).arg(dir.absolutePath()), QMessageBox::Ignore | QMessageBox::Abort); if(button == QMessageBox::Abort)return; } progress.setValue(i++); } m_database->clearMarkedFiles(); if(missing) QMessageBox::information(this, tr("Missing marked files"), tr("%1 marked files were missing. They were skipped.").arg(missing)); } } void MainWindow::socketNotify() { socketNotifier->setEnabled(false); char tmp; read(socketPair[1], &tmp, sizeof(tmp)); close(); socketNotifier->setEnabled(true); } void MainWindow::loadFile() { QString file = QFileDialog::getOpenFileName(this, tr("Open file"), _lastDir, _openFilter); loadFile(file); } void MainWindow::loadFile(const QString &path) { if(!path.isEmpty()) { QFileInfo info(path); m_ringList->setFile(info.canonicalFilePath()); updateWindowTitle(); if(info.isDir()) _lastDir = info.absolutePath(); else _lastDir = info.canonicalPath(); QSettings settings; settings.setValue("mainwindow/lastdir", _lastDir); if(settings.value("settings/bestfit", false).toBool()) m_image->bestFit(); } } void MainWindow::loadFiles(const QStringList &paths) { m_ringList->setFiles(paths); } void MainWindow::loadFile(int row) { m_ringList->loadFile(row); } void MainWindow::loadDir() { QString dir = QFileDialog::getExistingDirectory(this, tr("Open directory recursively"), _lastDir); if(!dir.isEmpty()) { _lastDir = dir; m_ringList->setDir(dir, QString(), true); QSettings settings; settings.setValue("mainwindow/lastdir", _lastDir); } } void MainWindow::indexDir() { QString dir = QFileDialog::getExistingDirectory(this, tr("Index directory"), _lastDir, QFileDialog::ShowDirsOnly); indexDir(dir); } void MainWindow::indexDir(const QString &dir) { if(!dir.isEmpty()) { QProgressDialog progressDialog(tr("Indexing FITS files"), tr("Cancel"), 0, 1, this); progressDialog.setModal(true); m_database->indexDir(dir, &progressDialog); } } void MainWindow::reindex() { QProgressDialog progressDialog(tr("Indexing FITS files"), tr("Cancel"), 0, 1, this); progressDialog.setModal(true); m_database->reindex(&progressDialog); } void MainWindow::saveAs() { QString selectedFilter; QString file = QFileDialog::getSaveFileName(this, tr("Save as"), _lastDir, _saveFilter, &selectedFilter); auto filterToFormat = [](const QString &file, const QString &filter) -> const char* { QString suffix = QFileInfo(file).suffix(); if(!suffix.compare("jpg", Qt::CaseInsensitive) || !suffix.compare("jpeg", Qt::CaseInsensitive))return "jpeg"; if(!suffix.compare("png", Qt::CaseInsensitive))return "png"; if(!suffix.compare("fits", Qt::CaseInsensitive) || !suffix.compare("fit", Qt::CaseInsensitive))return "fits"; if(!suffix.compare("xisf", Qt::CaseInsensitive))return "xisf"; if(filter.contains("png"))return "png"; if(filter.contains("fits"))return "fits"; if(filter.contains("xisf"))return "xisf"; return "jpeg"; }; if(!file.isEmpty()) { QString format = filterToFormat(file, selectedFilter); if(format == "fits" || format == "xisf") { convert(file, format); } else { QImage img = m_image->renderToImage(); if(!img.isNull()) img.save(file, filterToFormat(file, selectedFilter)); } } } void MainWindow::convert(const QString &outfile, const QString &format) { QString file = m_ringList->currentImage()->name(); QThreadPool::globalInstance()->start(new ConvertRunable(file, outfile, format)); } void MainWindow::markImage() { ImagePtr ptr = m_ringList->currentImage(); if(ptr) { QString file = ptr->name(); if(!file.isEmpty()) { m_database->mark(file); m_ringList->updateMark(); } updateWindowTitle(); } } void MainWindow::unmarkImage() { ImagePtr ptr = m_ringList->currentImage(); if(ptr) { QString file = ptr->name(); if(!file.isEmpty()) { m_database->unmark(file); m_ringList->updateMark(); } updateWindowTitle(); } } void MainWindow::markAndNext() { markImage(); m_ringList->increment(); updateWindowTitle(); } void MainWindow::unmarkAndNext() { unmarkImage(); m_ringList->increment(); updateWindowTitle(); } void MainWindow::copyMarked() { copyOrMove(true); } void MainWindow::moveMarked() { copyOrMove(false); } void MainWindow::deleteMarked() { QStringList files = m_database->getMarkedFiles(); if(QMessageBox::question(this, tr("Move files to trash?"), tr("Do you want to move %1 files to trash?").arg(files.size())) != QMessageBox::Yes) return; QProgressDialog progress(tr("Moving marked files to trash"), tr("Cancel"), 0, files.size(), this); progress.setWindowModality(Qt::WindowModal); progress.show(); int i = 0; for(const QString &file : files) { if(!QFile::exists(file)) continue; if(progress.wasCanceled()) return; if(!moveToTrash(file)) { QMessageBox::warning(this, tr("Failed to move file to trash"), tr("Failed to move file to trash %1").arg(file)); return; } progress.setValue(i++); } m_database->clearMarkedFiles(); } void MainWindow::liveMode(bool active) { m_ringList->setLiveMode(active); } void MainWindow::imageStats(bool imageStats) { m_ringList->setCalculateStats(imageStats); } void MainWindow::peakFinder(bool findPeaks) { m_ringList->setFindPeaks(findPeaks); } void MainWindow::starFinder(bool findStars) { m_ringList->setFindStars(findStars); } void MainWindow::showMarkFilesDialog() { MarkedFiles markedFiles(this); markedFiles.exec(); } void MainWindow::showSettingsDialog() { SettingsDialog settingsDialog(this); connect(&settingsDialog, &SettingsDialog::preloadChanged, m_ringList, &ImageRingList::setPreload); settingsDialog.exec(); } void MainWindow::exportCSV() { QStringList documentDir = QStandardPaths::standardLocations(QStandardPaths::DocumentsLocation); if(documentDir.empty())documentDir.append(""); QString file = QFileDialog::getSaveFileName(this, tr("Save as"), documentDir.first(), tr("CSV file (*.csv)")); if(!file.isEmpty()) m_databaseView->exportCSV(file); } void MainWindow::checkNewVersion() { QNetworkAccessManager *manager = new QNetworkAccessManager(this); QNetworkRequest request(QUrl("https://gitea.nouspiro.space/api/v1/repos/nou/tenmon/releases/latest")); request.setRawHeader("accept", "application/json"); QNetworkReply *reply = manager->get(request); connect(reply, &QNetworkReply::finished, [this, manager, reply](){ QJsonParseError error; QJsonDocument json = QJsonDocument::fromJson(reply->readAll(), &error); if(json.isObject() && json.object().contains("tag_name")) { QString tag = json.object().value("tag_name").toString(); QString version = getVersion(); if(version >= tag) QMessageBox::information(this, tr("Update check"), tr("You have newest version")); else { if(QMessageBox::question(this, tr("Update check"), tr("New version %1 is available. Do you want to download it now?").arg(tag)) == QMessageBox::Yes) { QUrl url(json.object().value("html_url").toString()); qDebug() << url; if(url.host() == "gitea.nouspiro.space") QDesktopServices::openUrl(url); } } } else { QMessageBox::warning(this, tr("Update check"), tr("Failed to check version")); } reply->deleteLater(); manager->deleteLater(); }); } void MainWindow::updateWindowTitle() { ImagePtr ptr = m_ringList->currentImage(); if(ptr) { QFileInfo info(ptr->name()); QString title = info.fileName(); if(m_database->isMarked(ptr->name())) title += " *"; setWindowTitle(title); } }