Files
tenmon/mainwindow.cpp
T
2022-04-26 22:32:38 +02:00

510 lines
16 KiB
C++

#include "mainwindow.h"
#include <QScrollArea>
#include <QDir>
#include <QKeyEvent>
#include <QMenu>
#include <QMenuBar>
#include <QFileDialog>
#include <QStandardPaths>
#include <QMessageBox>
#include <QProgressDialog>
#include <QDebug>
#include <QDockWidget>
#include <signal.h>
#include <unistd.h>
#include <QSettings>
#include <QCoreApplication>
#include <QThreadPool>
#include "loadrunable.h"
#include "markedfiles.h"
#include "about.h"
#ifdef __linux__
#include <sys/ioctl.h>
#include <linux/btrfs.h>
#include <sys/socket.h>
#endif
int MainWindow::socketPair[2] = {0, 0};
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
{
qRegisterMetaType<ImageInfoData>("ImageInfoData");
qRegisterMetaType<RawImage*>("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);
//m_image = new ImageScrollArea(this);
//m_image->resize(0,0);
//setCentralWidget(m_image);
resize(800, 600);
m_imageGL = new ImageScrollAreaGL(this);
setCentralWidget(m_imageGL);
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(this);
m_filesystem = new FilesystemWidget(m_ringList, this);
connect(m_filesystem, SIGNAL(fileSelected(int)), this, SLOT(loadFile(int)));
m_database = new Database(this);
if(!m_database->init())
QMessageBox::critical(this, tr("Can't open DB"), tr("Can't open SQLITE database"));
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);
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<void (MainWindow::*)(const QString &)>(&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->imageCount());
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("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());
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();
}
MainWindow::~MainWindow()
{
delete m_database;
}
void MainWindow::keyPressEvent(QKeyEvent *event)
{
switch (event->key())
{
case Qt::Key_Left:
m_ringList->decrement();
break;
case Qt::Key_Right:
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);
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)
{
//m_image->setImage(image->pixmap());
if(image->rawImage())
{
m_imageGL->setImage(image->rawImage());
}
}
void MainWindow::loadFile()
{
QString file = QFileDialog::getOpenFileName(this, tr("Open file"), _lastDir, tr("Images (*.jpg *.jpeg *.png *.cr2 *.fit *.fits *.xisf *.JPG *.JPEG *.PNG *.CR2 *.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);
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);
updateWindowTitle();
}
}
void MainWindow::unmarkImage()
{
ImagePtr ptr = m_ringList->currentImage();
if(ptr)
{
QString file = ptr->name();
if(!file.isEmpty())
m_database->unmark(file);
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);
}
}