#include "mainwindow.h" #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" #ifdef __linux__ #include #include #include #endif int MainWindow::socketPair[2] = {0, 0}; MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { qRegisterMetaType("ImageInfoData"); qRegisterMetaType("RawImage"); 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(800, 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_imageGL = new ImageScrollAreaGL(m_database, this); setCentralWidget(m_imageGL); StatusBar *statusBar = new StatusBar(this); setStatusBar(statusBar); connect(m_imageGL->imageWidget(), &ImageWidget::status, statusBar, &StatusBar::newStatus); m_stretchPanel = new StretchToolbar(this); connect(m_stretchPanel, SIGNAL(paramChanged(float,float,float)), m_imageGL->imageWidget(), SLOT(setMTFParams(float,float,float))); connect(m_stretchPanel, &StretchToolbar::autoStretch, [&](){ m_stretchPanel->stretchImage(m_ringList->currentImage().get()); }); connect(m_stretchPanel, &StretchToolbar::invert, m_imageGL->imageWidget(), &ImageWidget::invert); connect(m_stretchPanel, &StretchToolbar::superPixel, m_imageGL->imageWidget(), &ImageWidget::superPixel); m_ringList = new ImageRingList(m_database, this); m_filesystem = new FilesystemWidget(m_ringList, this); connect(m_filesystem, SIGNAL(fileSelected(int)), this, SLOT(loadFile(int))); 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))); 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); setWindowTitle(tr("Tenmon")); connect(m_ringList, SIGNAL(pixmapLoaded(Image*)), this, SLOT(pixmapLoaded(Image*))); connect(m_ringList, SIGNAL(currentImageChanged(int)), this, SLOT(updateWindowTitle())); connect(m_ringList, SIGNAL(infoLoaded(ImageInfoData)), m_info, SLOT(setInfo(const ImageInfoData&))); connect(m_ringList, SIGNAL(currentImageChanged(int)), m_filesystem, SLOT(selectFile(int))); connect(m_ringList, &ImageRingList::thumbnailLoaded, m_imageGL->imageWidget(), &ImageWidget::thumbnailLoaded); connect(m_ringList, &ImageRingList::pixmapLoaded, m_stretchPanel, &StretchToolbar::imageLoaded); connect(m_imageGL->imageWidget(), &ImageWidget::fileDropped, this, static_cast(&MainWindow::loadFile)); QMenu *fileMenu = new QMenu(tr("File"), this); fileMenu->addAction(tr("Open"), this, SLOT(loadFile()), QKeySequence::Open); fileMenu->addAction(tr("Save as"), this, SLOT(saveAs()), QKeySequence::Save); fileMenu->addSeparator(); fileMenu->addAction(tr("Copy marked files"), this, SLOT(copyMarked())); fileMenu->addAction(tr("Move marked files"), this, SLOT(moveMarked())); fileMenu->addSeparator(); fileMenu->addAction(tr("Index directory"), this, SLOT(indexDir())); fileMenu->addAction(tr("Reindex files"), this, SLOT(reindex())); 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 *viewMenu = new QMenu(tr("View"), this); viewMenu->addAction(tr("Zoom In"), m_imageGL, SLOT(zoomIn()), QKeySequence::ZoomIn); viewMenu->addAction(tr("Zoom Out"), m_imageGL, SLOT(zoomOut()), QKeySequence::ZoomOut); viewMenu->addAction(tr("Best Fit"), m_imageGL, SLOT(bestFit()), QKeySequence("Ctrl+1")); viewMenu->addAction(tr("100%"), m_imageGL, SLOT(oneToOne())); viewMenu->addAction(tr("Fullscreen"), this, SLOT(toggleFullScreen()), Qt::CTRL + Qt::Key_F11); QAction *thumbnailsAction = viewMenu->addAction(tr("Thumbnails"), [this](bool checked){ m_imageGL->imageWidget()->allocateThumbnails(m_ringList->imageNames()); m_imageGL->imageWidget()->showThumbnail(checked); m_imageGL->setThumbnails(checked ? m_ringList->imageCount() : 0); if(checked)m_ringList->loadThumbnails(); else m_ringList->stopLoading(); }, Qt::Key_F2); thumbnailsAction->setCheckable(true); menuBar()->addMenu(viewMenu); QMenu *selectMenu = new QMenu(tr("Select"), this); selectMenu->addAction(tr("Mark"), this, SLOT(markImage()), Qt::Key_F5); selectMenu->addAction(tr("Unmark"), this, SLOT(unmarkImage()), Qt::Key_F8); selectMenu->addSeparator(); selectMenu->addAction(tr("Mark and next"), this, SLOT(markAndNext()), Qt::Key_M); selectMenu->addAction(tr("Unmark and next"), this, SLOT(unmarkAndNext()), Qt::Key_X); selectMenu->addAction(tr("Show marked"), this, &MainWindow::showMarkFilesDialog); menuBar()->addMenu(selectMenu); 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()); menuBar()->addMenu(dockMenu); QMenu *helpMenu = menuBar()->addMenu(tr("Help")); helpMenu->addAction(tr("Help"), [this]{ HelpDialog help(this); help.exec(); }, QKeySequence::HelpContents); helpMenu->addAction(tr("About Tenmon"), [this]{ About about(this); about.exec(); }); helpMenu->addAction(tr("About Qt"), [this](){ QMessageBox::aboutQt(this); }); setupSigterm(); QSettings settings; restoreGeometry(settings.value("mainwindow/geometry").toByteArray()); restoreState(settings.value("mainwindow/state").toByteArray()); QStringList standardLocations = QStandardPaths::standardLocations(QStandardPaths::PicturesLocation); if(standardLocations.size()) _lastDir = standardLocations.first(); _lastDir = settings.value("mainwindow/lastdir", _lastDir).toString(); m_filesystem->setDir(_lastDir); QStringList args = QCoreApplication::arguments(); args.removeFirst(); for(auto &arg : args) { QFileInfo info(arg); if(info.exists()) { m_ringList->setFile(info.canonicalFilePath()); updateWindowTitle(); _lastDir = info.absoluteDir().absolutePath(); settings.setValue("mainwindow/lastdir", _lastDir); break; } } m_imageGL->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); copyOrMove(copy, dest); } void MainWindow::copyOrMove(bool copy, const QString &dest) { QDir dir(dest); if(!dest.isEmpty() && dir.exists()) { int i = 0; QStringList files = m_database->getMarkedFiles(); QProgressDialog progress(copy ? tr("Copying") : tr("Moving"), tr("Cancel"), 0, files.size(), this); progress.setWindowModality(Qt::WindowModal); progress.show(); foreach(const QString &file, files) { QFileInfo info(file); QFile srcFile(file); QFile dstFile(dir.absoluteFilePath(info.fileName())); if(dstFile.exists()) continue; if(progress.wasCanceled()) break; #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(); qDebug() << dstFile.fileName(); srcFile.copy(dstFile.fileName()); } } else { srcFile.rename(dstFile.fileName()); } #else if(copy) srcFile.copy(dstFile.fileName()); else srcFile.rename(dstFile.fileName()); #endif progress.setValue(i++); } m_database->clearMarkedFiles(); } } void MainWindow::socketNotify() { socketNotifier->setEnabled(false); char tmp; read(socketPair[1], &tmp, sizeof(tmp)); close(); socketNotifier->setEnabled(true); } void MainWindow::pixmapLoaded(Image *image) { if(image->rawImage()) { m_imageGL->setImage(image); } } void MainWindow::loadFile() { QString file = QFileDialog::getOpenFileName(this, tr("Open file"), _lastDir, tr("Images (*.jpg *.jpeg *.png *.cr2 *.nef *.dng *.fit *.fits *.xisf *.JPG *.JPEG *.PNG *.CR2 *.NEF *.DNG *.FIT *.FITS *.XISF)")); 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); m_filesystem->setDir(_lastDir); } } void MainWindow::loadFile(int row) { m_ringList->loadFile(row); } void MainWindow::indexDir() { QString dir = QFileDialog::getExistingDirectory(this, tr("Index directory"), _lastDir); 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, tr("JPEG (*.jpg *.JPG);; PNG (*.png *.PNG);;FITS (*.fits *.FITS);;XISF (*.xisf *.XISF)"), &selectedFilter); if(!file.isEmpty()) { QFileInfo info(file); if(info.suffix().isEmpty()) { if(selectedFilter.contains("jpg"))file += ".jpg"; if(selectedFilter.contains("png"))file += ".png"; if(selectedFilter.contains("fits"))file += ".fits"; if(selectedFilter.contains("xisf"))file += ".xisf"; } if(file.endsWith(".fits") || file.endsWith(".xisf")) { convert(file); } else { QImage img = m_imageGL->imageWidget()->renderToImage(); if(!img.isNull()) img.save(file); } } } void MainWindow::convert(const QString &outfile) { QString file = m_ringList->currentImage()->name(); QThreadPool::globalInstance()->start(new ConvertRunable(file, outfile)); } 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::toggleFullScreen() { if(isFullScreen()) { showNormal(); if(_maximized)showMaximized(); } else { _maximized = isMaximized(); showFullScreen(); } } 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; markedFiles.exec(); } 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); } }