Compare commits
20 Commits
20250915
...
3f88e5fe83
| Author | SHA1 | Date | |
|---|---|---|---|
| 3f88e5fe83 | |||
| 6a537642ab | |||
| b7f1a0abc9 | |||
| 33c976d3c9 | |||
| a17001cdf9 | |||
| 305c1d1f55 | |||
| 95808b094d | |||
| 2b56af27fe | |||
| 8edf746827 | |||
| 729a330e6c | |||
| 1ac5a4e42a | |||
| 83d212aa91 | |||
| bd24fba407 | |||
| 3448f62f31 | |||
| 567e66acb5 | |||
| 9e79133464 | |||
| e08107aa13 | |||
| 6eda2c4e48 | |||
| b16ae3a9ee | |||
| 56bba27ae3 |
+2
-2
@@ -17,7 +17,7 @@ if(SANITIZE_ADDRESS_LEAK)
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=address -fsanitize=leak")
|
||||
endif(SANITIZE_ADDRESS_LEAK)
|
||||
|
||||
find_package(Qt6 COMPONENTS Widgets Sql OpenGLWidgets Qml Charts REQUIRED)
|
||||
find_package(Qt6 COMPONENTS Widgets Sql OpenGLWidgets Qml Charts Svg REQUIRED)
|
||||
find_library(EXIF_LIB exif REQUIRED)
|
||||
find_library(FITS_LIB cfitsio REQUIRED)
|
||||
find_library(RAW_LIB NAMES raw_r REQUIRED)
|
||||
@@ -105,7 +105,7 @@ if(STELLARSOLVER_INCLUDE AND STELLARSOLVER_LIB)
|
||||
message(STATUS "Found stellarsolver ${STELLARSOLVER_INCLUDE} ${STELLARSOLVER_LIB}")
|
||||
endif(STELLARSOLVER_INCLUDE AND STELLARSOLVER_LIB)
|
||||
|
||||
target_link_libraries(tenmon PRIVATE Qt6::Widgets Qt6::Sql Qt6::OpenGLWidgets Qt6::Qml Qt6::Charts ${EXIF_LIB} ${FITS_LIB} ${RAW_LIB} ${WCS_LIB} ${LCMS2_LIB} XISF)
|
||||
target_link_libraries(tenmon PRIVATE Qt6::Widgets Qt6::Sql Qt6::OpenGLWidgets Qt6::Qml Qt6::Charts Qt6::Svg ${EXIF_LIB} ${FITS_LIB} ${RAW_LIB} ${WCS_LIB} ${LCMS2_LIB} XISF)
|
||||
if(APPLE)
|
||||
target_link_libraries(tenmon PRIVATE Qt6::DBus "-framework CoreFoundation")
|
||||
elseif(UNIX)
|
||||
|
||||
@@ -2,7 +2,7 @@ FITS/XISF image viewer with multithreaded image loading
|
||||
|
||||
To get all dependencies install these packages
|
||||
|
||||
sudo apt install qt6-base-dev qt6-declarative-dev libqt6opengl6-dev libraw-dev libexif-dev libcfitsio-dev wcslib-dev cmake libzstd-dev libqt6sql6-sqlite
|
||||
sudo apt install qt6-base-dev qt6-declarative-dev qt6-charts-dev libqt6opengl6-dev libraw-dev libexif-dev libcfitsio-dev wcslib-dev cmake libzstd-dev libqt6sql6-sqlite
|
||||
|
||||
on OpenSUSE
|
||||
|
||||
@@ -26,6 +26,10 @@ Then to build run standard cmake sequence
|
||||
cmake --build build
|
||||
./build/tenmon
|
||||
|
||||
To install it to system run this command as root
|
||||
|
||||
cmake --install build
|
||||
|
||||
For working plate solving you must have compiled and installed StellarSolver https://github.com/rlancaste/stellarsolver
|
||||
It is important that you compile StellarSolver with Qt6. By default it use Qt5 but when linked with Qt6 program it will
|
||||
crash.
|
||||
|
||||
+1
-1
Submodule libXISF updated: c6581e1122...7b70b6a081
@@ -47,6 +47,7 @@
|
||||
</keywords>
|
||||
<url type="homepage">https://nouspiro.space/?page_id=206</url>
|
||||
<url type="bugtracker">https://github.com/flathub/space.nouspiro.tenmon/issues</url>
|
||||
<url type="vcs-browser">https://gitea.nouspiro.space/nou/tenmon</url>
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<caption>Main window with image</caption>
|
||||
@@ -59,6 +60,23 @@
|
||||
</screenshots>
|
||||
<content_rating type="oars-1.1"/>
|
||||
<releases>
|
||||
<release version="20260217" date="2026-02-17">
|
||||
<description>
|
||||
<ul>
|
||||
<li>Fix potentional crash</li>
|
||||
<li>Enable sorting of FITS info</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="20251101" date="2025-11-01">
|
||||
<description>
|
||||
<ul>
|
||||
<li>Better image Save as</li>
|
||||
<li>Fix xisf file corruption when platesolving</li>
|
||||
<li>Add selecting language</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="20250915" date="2025-09-15">
|
||||
<description>
|
||||
<ul>
|
||||
|
||||
@@ -113,12 +113,8 @@ BatchProcessing::BatchProcessing(Database *database, QWidget *parent) : QDialog(
|
||||
|
||||
_engine = new Script::ScriptEngine(_database, this);
|
||||
connect(_engine, &Script::ScriptEngine::newMessage, this, &BatchProcessing::newMessage);
|
||||
QStringList apiList;
|
||||
apiList << "core.log" << "core.mark" << "core.unmark" << "core.isMarked" << "core.getObjects" << "core.setMaxthread";
|
||||
apiList << "core.sync" << "core.getString" << "core.getInt" << "core.getFloat" << "core.question" << "core.plot";
|
||||
apiList << "fileName" << "absoluteFileName";
|
||||
|
||||
_completerModel = new QStringListModel(this);
|
||||
_completerModel->setStringList(apiList);
|
||||
_completer = new QCompleter(_completerModel, this);
|
||||
_ui->consoleLineEdit->setCompleter(_completer);
|
||||
connect(_ui->executeButton, &QPushButton::clicked, _ui->consoleLineEdit, &QLineEdit::returnPressed);
|
||||
@@ -128,13 +124,13 @@ BatchProcessing::BatchProcessing(Database *database, QWidget *parent) : QDialog(
|
||||
QString program = _ui->consoleLineEdit->text();
|
||||
QJSValue val = _engine->eval(program);
|
||||
_ui->consoleLineEdit->addLine();
|
||||
qDebug() << val.toString();
|
||||
//qDebug() << val.toString();
|
||||
}
|
||||
});
|
||||
|
||||
connect(_ui->consoleLineEdit, &QLineEdit::textEdited, [this](const QString &text){
|
||||
QStringList comp = _engine->complete(text);
|
||||
qDebug() << comp;
|
||||
//qDebug() << comp;
|
||||
_completerModel->setStringList(comp);
|
||||
});
|
||||
|
||||
|
||||
+13
-1
@@ -61,8 +61,15 @@ bool Database::init(const QLatin1String &connectionName)
|
||||
query.exec("CREATE INDEX IF NOT EXISTS maxRa_idx ON fits_files(maxRa)");
|
||||
query.exec("CREATE INDEX IF NOT EXISTS minDec_idx ON fits_files(minDec)");
|
||||
query.exec("CREATE INDEX IF NOT EXISTS maxDec_idx ON fits_files(maxDec)");
|
||||
version = 1;
|
||||
}
|
||||
else if(version > 1)
|
||||
if(version == 1)
|
||||
{
|
||||
query.exec("CREATE INDEX IF NOT EXISTS id_file_key ON fits_headers(id_file, key)");
|
||||
query.exec("PRAGMA user_version = 2");
|
||||
version = 2;
|
||||
}
|
||||
if(version > 2)
|
||||
{
|
||||
qDebug() << "Database version is too new";
|
||||
return false;
|
||||
@@ -307,6 +314,11 @@ QVector<SkyObject> Database::getObjects(double minRa, double maxRa, double minDe
|
||||
return objects;
|
||||
}
|
||||
|
||||
const QSqlDatabase &Database::db() const
|
||||
{
|
||||
return database;
|
||||
}
|
||||
|
||||
bool Database::indexDir2(const QDir &dir, QProgressDialog *progress, QStringList &scannedDirs)
|
||||
{
|
||||
if(scannedDirs.contains(dir.canonicalPath()))return true;
|
||||
|
||||
@@ -42,6 +42,7 @@ public:
|
||||
void reindex(QProgressDialog *progress);
|
||||
QStringList getFitsKeywords();
|
||||
QVector<SkyObject> getObjects(double minRa, double maxRa, double minDec, double maxDec);
|
||||
const QSqlDatabase& db() const;
|
||||
protected:
|
||||
bool indexDir2(const QDir &dir, QProgressDialog *progress, QStringList &scannedDirs);
|
||||
bool indexFile(const QFileInfo &file);
|
||||
|
||||
+87
-9
@@ -9,6 +9,9 @@
|
||||
#include <QMenu>
|
||||
#include <QContextMenuEvent>
|
||||
#include <QRegularExpression>
|
||||
#include <QGuiApplication>
|
||||
#include <QClipboard>
|
||||
#include <QMimeData>
|
||||
#include <iostream>
|
||||
#include "batchprocessing.h"
|
||||
|
||||
@@ -157,32 +160,67 @@ void FITSFileModel::filesUnmarked(const QModelIndexList &indexes)
|
||||
}
|
||||
}
|
||||
|
||||
void FITSFileModel::load()
|
||||
{
|
||||
if(!m_loaded)
|
||||
{
|
||||
m_loaded = true;
|
||||
prepareQuery();
|
||||
}
|
||||
}
|
||||
|
||||
void FITSFileModel::prepareQuery()
|
||||
{
|
||||
if(!m_loaded)return;
|
||||
|
||||
QString cols;
|
||||
QString join;
|
||||
QStringList where;
|
||||
QString sql = m_columns.size() ? "SELECT f.file," : "SELECT f.file";
|
||||
QVariantList bindValues;
|
||||
QVariantList bindValuesJoin;
|
||||
for(int i=0; i<m_value.size(); i++)
|
||||
{
|
||||
if(m_key[i] == "file")
|
||||
where.append(QString(" f.file LIKE '%1' ").arg(m_value[i]));
|
||||
{
|
||||
where.append(" f.file LIKE ? ");
|
||||
bindValues.append(m_value[i]);
|
||||
}
|
||||
else if(m_key[i] == "RA pos")
|
||||
where.append(QString(" %1 BETWEEN f.minRa AND f.maxRa ").arg(RA(m_value[i])));
|
||||
{
|
||||
where.append(" ? BETWEEN f.minRa AND f.maxRa ");
|
||||
bindValues.append(RA(m_value[i]));
|
||||
}
|
||||
else if(m_key[i] == "DEC pos")
|
||||
where.append(QString(" %1 BETWEEN f.minDec AND f.maxDec ").arg(DEC(m_value[i])));
|
||||
{
|
||||
where.append(" ? BETWEEN f.minDec AND f.maxDec ");
|
||||
bindValues.append(DEC(m_value[i]));
|
||||
}
|
||||
else if(m_key[i] == "RA range")
|
||||
where.append(QString(" crVal1 BETWEEN %1 AND %2 ").arg(RA(m_value[i])).arg(RA(m_limit[i])));
|
||||
{
|
||||
where.append(" crVal1 BETWEEN ? AND ? ");
|
||||
bindValues.append(RA(m_value[i]));
|
||||
bindValues.append(RA(m_limit[i]));
|
||||
}
|
||||
else if(m_key[i] == "DEC range")
|
||||
where.append(QString(" crVal2 BETWEEN %1 AND %2 ").arg(DEC(m_value[i])).arg(DEC(m_limit[i])));
|
||||
{
|
||||
where.append(" crVal2 BETWEEN ? AND ? ");
|
||||
bindValues.append(DEC(m_value[i]));
|
||||
bindValues.append(DEC(m_limit[i]));
|
||||
}
|
||||
else
|
||||
join += QString(" JOIN fits_headers AS s%1 ON f.id=s%1.id_file AND s%1.key='%2' AND s%1.value LIKE '%3'").arg(i).arg(m_key[i]).arg(m_value[i]);
|
||||
{
|
||||
join += QString(" JOIN fits_headers AS s%1 ON f.id=s%1.id_file AND s%1.key=? AND s%1.value LIKE ? ").arg(i);
|
||||
bindValuesJoin.append(m_key[i]);
|
||||
bindValuesJoin.append(m_value[i]);
|
||||
}
|
||||
}
|
||||
int i=0;
|
||||
for(auto &column : m_columns)
|
||||
{
|
||||
cols += QString("GROUP_CONCAT(h%1.value) AS h%1_value,").arg(i);
|
||||
join += QString(" LEFT JOIN fits_headers AS h%1 ON f.id=h%1.id_file AND h%1.key='%2'").arg(i).arg(column);
|
||||
join += QString(" LEFT JOIN fits_headers AS h%1 ON f.id=h%1.id_file AND h%1.key=?").arg(i);
|
||||
bindValuesJoin.append(column);
|
||||
i++;
|
||||
}
|
||||
cols.chop(1);
|
||||
@@ -191,7 +229,18 @@ void FITSFileModel::prepareQuery()
|
||||
sql += join;
|
||||
if(!where.isEmpty())sql += " WHERE " + where.join("AND");
|
||||
sql += " GROUP BY f.id" + m_sort;
|
||||
setQuery(sql);
|
||||
|
||||
QSqlQuery query(m_database->db());
|
||||
query.prepare(sql);
|
||||
for(auto &val : bindValuesJoin)
|
||||
query.addBindValue(val);
|
||||
for(auto &val : bindValues)
|
||||
query.addBindValue(val);
|
||||
|
||||
if(!query.exec())
|
||||
qWarning() << "Failed to exectute query" << query.lastQuery() << bindValuesJoin << bindValues;
|
||||
setQuery(std::move(query));
|
||||
|
||||
setHeaderData(0, Qt::Horizontal, tr("File name"));
|
||||
i = 1;
|
||||
for(auto &column : m_columns)
|
||||
@@ -217,6 +266,7 @@ void DatabaseTableView::contextMenuEvent(QContextMenuEvent *event)
|
||||
QAction *unmark = menu.addAction(tr("Unmark"));
|
||||
QAction *open = menu.addAction(tr("Open"));
|
||||
QAction *openDirAction = menu.addAction(tr("Open file location"));
|
||||
QAction *copyPath = menu.addAction(tr("Copy files"));
|
||||
|
||||
QAction *a = menu.exec(event->globalPos());
|
||||
if(a == nullptr)
|
||||
@@ -232,6 +282,22 @@ void DatabaseTableView::contextMenuEvent(QContextMenuEvent *event)
|
||||
emit openFile(indexes);
|
||||
else if(a == openDirAction)
|
||||
emit openDir(indexes);
|
||||
else if(a == copyPath)
|
||||
{
|
||||
QStringList paths;
|
||||
QList<QUrl> urls;
|
||||
for(auto &index : indexes)
|
||||
{
|
||||
QString path = index.siblingAtColumn(0).data().toString();
|
||||
paths.append(path);
|
||||
urls.append(QUrl::fromLocalFile(path));
|
||||
}
|
||||
QMimeData *data = new QMimeData;
|
||||
data->setUrls(urls);
|
||||
data->setText(paths.join('\n'));
|
||||
QClipboard *clipboard = QGuiApplication::clipboard();
|
||||
clipboard->setMimeData(data);
|
||||
}
|
||||
}
|
||||
|
||||
DataBaseView::DataBaseView(Database *database, QWidget *parent) : QWidget(parent)
|
||||
@@ -300,12 +366,13 @@ DataBaseView::DataBaseView(Database *database, QWidget *parent) : QWidget(parent
|
||||
};
|
||||
|
||||
QStringList fitsKeywords = m_database->getFitsKeywords();
|
||||
QStringList filterKey = settings.value("databaseview/filterKey", QStringList{"file", "file", "file"}).toStringList();
|
||||
for(int i=0; i<3; i++)
|
||||
{
|
||||
m_filterKeyword[i] = new QComboBox(this);
|
||||
m_filterKeyword[i]->setMaximumWidth(300);
|
||||
addFilterItems(m_filterKeyword[i], fitsKeywords);
|
||||
|
||||
m_filterKeyword[i]->setCurrentText(filterKey[i]);
|
||||
|
||||
m_search[i] = new QLineEdit(this);
|
||||
m_search[i]->setPlaceholderText(tr("Text to search, you can % as wildcard"));
|
||||
@@ -339,8 +406,13 @@ DataBaseView::DataBaseView(Database *database, QWidget *parent) : QWidget(parent
|
||||
|
||||
DataBaseView::~DataBaseView()
|
||||
{
|
||||
QStringList filterKey;
|
||||
for(int i = 0; i < 3; i++)
|
||||
filterKey.append(m_filterKeyword[i]->currentText());
|
||||
|
||||
QSettings settings;
|
||||
settings.setValue("databaseview/header", m_tableView->horizontalHeader()->saveState());
|
||||
settings.setValue("databaseview/filterKey", filterKey);
|
||||
}
|
||||
|
||||
void DataBaseView::selectColumns()
|
||||
@@ -392,6 +464,7 @@ bool DataBaseView::exportCSV(const QString &path)
|
||||
if(!csv.open(QIODevice::WriteOnly | QIODevice::Text))
|
||||
return false;
|
||||
|
||||
m_model->load();
|
||||
QSqlQuery sql(m_model->query().lastQuery());
|
||||
int colCount = m_model->columnCount();
|
||||
QStringList header;
|
||||
@@ -420,3 +493,8 @@ bool DataBaseView::exportCSV(const QString &path)
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void DataBaseView::visible(bool visible)
|
||||
{
|
||||
if(visible)m_model->load();
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ class FITSFileModel : public QSqlQueryModel
|
||||
QStringList m_limit;
|
||||
QSet<QString> m_markedFiles;
|
||||
Database *m_database;
|
||||
bool m_loaded = false;
|
||||
public:
|
||||
explicit FITSFileModel(Database *database, QObject *parent = nullptr);
|
||||
void sort(int column, Qt::SortOrder order) override;
|
||||
@@ -38,6 +39,7 @@ public:
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
void filesMarked(const QModelIndexList &indexes);
|
||||
void filesUnmarked(const QModelIndexList &indexes);
|
||||
void load();
|
||||
protected:
|
||||
void prepareQuery();
|
||||
};
|
||||
@@ -74,6 +76,7 @@ public slots:
|
||||
void itemActivated(const QModelIndex &index);
|
||||
void applyFilter();
|
||||
bool exportCSV(const QString &path);
|
||||
void visible(bool visible);
|
||||
signals:
|
||||
void loadFile(QString file);
|
||||
};
|
||||
|
||||
@@ -11,6 +11,8 @@ ImageInfo::ImageInfo(QWidget *parent) : QTreeWidget(parent)
|
||||
setIndentation(5);
|
||||
QSettings settings;
|
||||
header()->restoreState(settings.value("imageinfo/headerstate").toByteArray());
|
||||
setSortingEnabled(true);
|
||||
header()->setSortIndicatorClearable(true);
|
||||
}
|
||||
|
||||
ImageInfo::~ImageInfo()
|
||||
|
||||
@@ -30,6 +30,7 @@ FITSRecord::FITSRecord(const LibXISF::FITSKeyword &record)
|
||||
string.chop(1);
|
||||
string.remove(0, 1);
|
||||
}
|
||||
string = string.trimmed();
|
||||
bool isint;
|
||||
bool isdouble;
|
||||
double vald = string.toDouble(&isdouble);
|
||||
|
||||
+24
-20
@@ -111,7 +111,7 @@ void Image::thumbnailLoadFinish(std::shared_ptr<RawImage> rawImage)
|
||||
emit thumbnailLoaded(this);
|
||||
}
|
||||
|
||||
ImageRingList::ImageRingList(Database *database, const QStringList &nameFilter, QObject *parent) : QAbstractItemModel(parent)
|
||||
ImageRingList::ImageRingList(Database *database, const QStringList &nameFilter, QObject *parent) : QAbstractListModel(parent)
|
||||
, m_liveMode(false)
|
||||
, m_analyzeLevel(None)
|
||||
, m_database(database)
|
||||
@@ -412,7 +412,7 @@ void ImageRingList::clearThumbnails()
|
||||
img->clearThumbnail();
|
||||
}
|
||||
|
||||
QModelIndex ImageRingList::index(int row, int column, const QModelIndex &parent) const
|
||||
/*QModelIndex ImageRingList::index(int row, int column, const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent);
|
||||
return createIndex(row, column, m_images.at(row).get());
|
||||
@@ -422,7 +422,7 @@ QModelIndex ImageRingList::parent(const QModelIndex &child) const
|
||||
{
|
||||
Q_UNUSED(child);
|
||||
return QModelIndex();
|
||||
}
|
||||
}*/
|
||||
|
||||
int ImageRingList::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
@@ -432,31 +432,35 @@ int ImageRingList::rowCount(const QModelIndex &parent) const
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ImageRingList::columnCount(const QModelIndex &parent) const
|
||||
/*int ImageRingList::columnCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent);
|
||||
return 1;
|
||||
}
|
||||
}*/
|
||||
|
||||
QVariant ImageRingList::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
switch(role)
|
||||
if(index.isValid() && index.row() >= 0 && index.row() < m_images.size())
|
||||
{
|
||||
case Qt::DisplayRole:
|
||||
{
|
||||
QFileInfo info(m_images.at(index.row())->name());
|
||||
return info.fileName();
|
||||
}
|
||||
case Qt::FontRole:
|
||||
{
|
||||
bool marked = m_database->isMarked(m_images.at(index.row())->name());
|
||||
QFont font;
|
||||
font.setBold(marked);
|
||||
return font;
|
||||
}
|
||||
default:
|
||||
return QVariant();
|
||||
switch(role)
|
||||
{
|
||||
case Qt::DisplayRole:
|
||||
{
|
||||
QFileInfo info(m_images.at(index.row())->name());
|
||||
return info.fileName();
|
||||
}
|
||||
case Qt::FontRole:
|
||||
{
|
||||
bool marked = m_database->isMarked(m_images.at(index.row())->name());
|
||||
QFont font;
|
||||
font.setBold(marked);
|
||||
return font;
|
||||
}
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
QVariant ImageRingList::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
|
||||
+4
-4
@@ -51,7 +51,7 @@ typedef std::shared_ptr<Image> ImagePtr;
|
||||
|
||||
class Database;
|
||||
|
||||
class ImageRingList : public QAbstractItemModel
|
||||
class ImageRingList : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
int m_width;
|
||||
@@ -93,10 +93,10 @@ public:
|
||||
void updateMark();
|
||||
void clearThumbnails();
|
||||
|
||||
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
|
||||
QModelIndex parent(const QModelIndex &child) const override;
|
||||
//QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
|
||||
//QModelIndex parent(const QModelIndex &child) const override;
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
//int columnCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
||||
public slots:
|
||||
|
||||
+5
-1
@@ -193,7 +193,11 @@ void ConvertRunable::run()
|
||||
QFileInfo info(m_outfile);
|
||||
info.dir().mkpath(".");
|
||||
|
||||
if(m_params.autostretch)
|
||||
if(m_params.stretch)
|
||||
{
|
||||
rawimage->applySTF(m_params.mtf);
|
||||
}
|
||||
else if(m_params.autostretch)
|
||||
{
|
||||
rawimage->calcStats();
|
||||
MTFParam mtfParam = rawimage->calcMTFParams();
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <QSemaphore>
|
||||
#include <QSize>
|
||||
#include "imageinfodata.h"
|
||||
#include "mtfparam.h"
|
||||
|
||||
class Image;
|
||||
|
||||
@@ -33,6 +34,8 @@ public:
|
||||
QSize resize;
|
||||
Qt::AspectRatioMode aspect = Qt::KeepAspectRatio;
|
||||
bool autostretch = false;
|
||||
bool stretch = false;
|
||||
MTFParam mtf;
|
||||
ConvertParams(){}
|
||||
ConvertParams(const QVariantMap &map);
|
||||
};
|
||||
|
||||
+25
-2
@@ -3,8 +3,12 @@
|
||||
#include <QSurfaceFormat>
|
||||
#include <QTranslator>
|
||||
#include <QCommandLineParser>
|
||||
#include <QSettings>
|
||||
#include <stdlib.h>
|
||||
#include "../thumbnailer/genthumbnail.h"
|
||||
#ifdef Q_OS_WIN64
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
@@ -18,6 +22,14 @@ int main(int argc, char *argv[])
|
||||
bool useGLES = true;
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_WIN64
|
||||
if(AttachConsole(ATTACH_PARENT_PROCESS))
|
||||
{
|
||||
freopen("CONOUT$", "w", stdout);
|
||||
freopen("CONOUT$", "w", stderr);
|
||||
}
|
||||
#endif
|
||||
|
||||
QCommandLineParser cmd;
|
||||
cmd.addOption({"gl", "Use desktop OpenGL. This is default on x86 and MacOS platform."});
|
||||
cmd.addOption({"gles", "Use OpenGL ES. This is default on ARM platform."});
|
||||
@@ -76,8 +88,19 @@ int main(int argc, char *argv[])
|
||||
|
||||
QTranslator translator;
|
||||
QTranslator translator2;
|
||||
if(translator.load(QLocale(), "tenmon", "_", ":/translations"))
|
||||
a.installTranslator(&translator);
|
||||
QSettings settings;
|
||||
QString lang = settings.value("settings/lang").toString();
|
||||
if(lang.isEmpty())
|
||||
{
|
||||
if(translator.load(QLocale(), "tenmon", "_", ":/translations"))
|
||||
a.installTranslator(&translator);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(translator.load("tenmon_" + lang, ":/translations"))
|
||||
a.installTranslator(&translator);
|
||||
}
|
||||
|
||||
if(translator2.load(QLocale(), "tenmon", "_", a.applicationDirPath()))
|
||||
a.installTranslator(&translator2);
|
||||
|
||||
|
||||
+34
-16
@@ -18,6 +18,7 @@
|
||||
#include <QThreadPool>
|
||||
#include <QStatusBar>
|
||||
#include <QImageReader>
|
||||
#include <QImageWriter>
|
||||
#include <QMimeDatabase>
|
||||
#include <QDesktopServices>
|
||||
#include <QJsonDocument>
|
||||
@@ -57,12 +58,18 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
|
||||
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());
|
||||
}
|
||||
auto supportedWrite = QImageWriter::supportedMimeTypes();
|
||||
for(auto format : supportedWrite)
|
||||
{
|
||||
QMimeType mimeType = db.mimeTypeForName(format);
|
||||
_saveFilter.append(mimeType.filterString() + ";;");
|
||||
}
|
||||
|
||||
_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"});
|
||||
@@ -146,6 +153,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
|
||||
databaseViewDock->setWidget(m_databaseView);
|
||||
databaseViewDock->setObjectName("databaseViewDock");
|
||||
databaseViewDock->hide();
|
||||
connect(databaseViewDock, &QDockWidget::visibilityChanged, m_databaseView, &DataBaseView::visible);
|
||||
addDockWidget(Qt::BottomDockWidgetArea, databaseViewDock);
|
||||
|
||||
QDockWidget *filetreeDock = nullptr;
|
||||
@@ -189,7 +197,12 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
|
||||
#endif
|
||||
fileMenu->addAction(tr("Copy marked files"), Qt::Key_F5, this, &MainWindow::copyMarked);
|
||||
fileMenu->addAction(tr("Move marked files"), Qt::Key_F6, this, &MainWindow::moveMarked);
|
||||
fileMenu->addAction(tr("Move marked files to trash"), QKeySequence::Delete, this, &MainWindow::deleteMarked);
|
||||
QAction *deleteAction = fileMenu->addAction(tr("Move marked files to trash"), QKeySequence::Delete, this, &MainWindow::deleteMarked);
|
||||
#ifdef Q_OS_MACOS
|
||||
deleteAction->setShortcuts(QList<QKeySequence>({Qt::Key_Backspace, QKeySequence::Delete}));
|
||||
#else
|
||||
deleteAction->setShortcuts(QKeySequence::Delete);
|
||||
#endif
|
||||
fileMenu->addSeparator();
|
||||
fileMenu->addAction(tr("Index directory"), this, static_cast<void (MainWindow::*)()>(&MainWindow::indexDir));
|
||||
fileMenu->addAction(tr("Reindex files"), this, &MainWindow::reindex);
|
||||
@@ -613,12 +626,16 @@ void MainWindow::reindex()
|
||||
void MainWindow::saveAs()
|
||||
{
|
||||
QString selectedFilter;
|
||||
ImagePtr ptr = m_ringList->currentImage();
|
||||
if(!ptr)return;
|
||||
|
||||
QFileInfo srcFile(ptr->name());
|
||||
QString file = QFileDialog::getSaveFileName(this,
|
||||
tr("Save as"),
|
||||
_lastDir,
|
||||
_lastDir + "/" + srcFile.baseName(),
|
||||
_saveFilter,
|
||||
&selectedFilter);
|
||||
auto filterToFormat = [](const QString &file, const QString &filter) -> const char*
|
||||
auto filterToFormat = [](const QString &file, const QString &filter) -> const QString
|
||||
{
|
||||
QString suffix = QFileInfo(file).suffix();
|
||||
if(!suffix.compare("jpg", Qt::CaseInsensitive) || !suffix.compare("jpeg", Qt::CaseInsensitive))return "jpeg";
|
||||
@@ -628,30 +645,31 @@ void MainWindow::saveAs()
|
||||
if(filter.contains("png"))return "png";
|
||||
if(filter.contains("fits"))return "fits";
|
||||
if(filter.contains("xisf"))return "xisf";
|
||||
QRegularExpression suf("\\(\\*\\.([a-zA-Z]+).*\\)");
|
||||
auto match = suf.match(filter);
|
||||
if(match.hasMatch())
|
||||
return match.captured(1);
|
||||
return "jpeg";
|
||||
};
|
||||
|
||||
if(!file.isEmpty())
|
||||
{
|
||||
auto button = QMessageBox::question(this, tr("Apply stretch?"), tr("Apply current stretch function to image?"));
|
||||
|
||||
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));
|
||||
}
|
||||
convert(file, format, button == QMessageBox::Yes);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::convert(const QString &outfile, const QString &format)
|
||||
void MainWindow::convert(const QString &outfile, const QString &format, bool stretch)
|
||||
{
|
||||
QString file = m_ringList->currentImage()->name();
|
||||
QThreadPool::globalInstance()->start(new ConvertRunable(file, outfile, format));
|
||||
ConvertRunable::ConvertParams param;
|
||||
param.stretch = stretch;
|
||||
param.mtf = m_stretchPanel->params();
|
||||
|
||||
QThreadPool::globalInstance()->start(new ConvertRunable(file, outfile, format, param));
|
||||
}
|
||||
|
||||
void MainWindow::markImage()
|
||||
|
||||
+1
-1
@@ -52,7 +52,7 @@ public slots:
|
||||
void indexDir(const QString &dir);
|
||||
void reindex();
|
||||
void saveAs();
|
||||
void convert(const QString &outfile, const QString &format);
|
||||
void convert(const QString &outfile, const QString &format, bool stretch);
|
||||
void markImage();
|
||||
void unmarkImage();
|
||||
void markAndNext();
|
||||
|
||||
+22
-6
@@ -1195,12 +1195,28 @@ void RawImage::applySTF(const MTFParam &mtfParams)
|
||||
for(size_t i = 0; i < len; i++)
|
||||
{
|
||||
float x;
|
||||
if constexpr(std::numeric_limits<std::remove_reference_t<decltype(*src)>>::is_integer)x = src[i] * iscale;
|
||||
else x = src[i] * unit.first + unit.second;
|
||||
x = (x - mtfParams.blackPoint[0]) / (mtfParams.whitePoint[0] - mtfParams.blackPoint[0]);
|
||||
x = std::clamp(x, 0.0f, 1.0f);
|
||||
x = ((mtfParams.midPoint[0] - 1.0f) * x) / ((2.0f * mtfParams.midPoint[0] - 1.0f) * x - mtfParams.midPoint[0]);
|
||||
src[i] = x * s;
|
||||
if(m_ch == 4)
|
||||
{
|
||||
size_t c = i & 0x3;
|
||||
if(c < 3)
|
||||
{
|
||||
if constexpr(std::numeric_limits<std::remove_reference_t<decltype(*src)>>::is_integer)x = src[i] * iscale;
|
||||
else x = src[i] * unit.first + unit.second;
|
||||
x = (x - mtfParams.blackPoint[c]) / (mtfParams.whitePoint[c] - mtfParams.blackPoint[c]);
|
||||
x = std::clamp(x, 0.0f, 1.0f);
|
||||
x = ((mtfParams.midPoint[c] - 1.0f) * x) / ((2.0f * mtfParams.midPoint[c] - 1.0f) * x - mtfParams.midPoint[c]);
|
||||
src[i] = x * s;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if constexpr(std::numeric_limits<std::remove_reference_t<decltype(*src)>>::is_integer)x = src[i] * iscale;
|
||||
else x = src[i] * unit.first + unit.second;
|
||||
x = (x - mtfParams.blackPoint[0]) / (mtfParams.whitePoint[0] - mtfParams.blackPoint[0]);
|
||||
x = std::clamp(x, 0.0f, 1.0f);
|
||||
x = ((mtfParams.midPoint[0] - 1.0f) * x) / ((2.0f * mtfParams.midPoint[0] - 1.0f) * x - mtfParams.midPoint[0]);
|
||||
src[i] = x * s;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+29
-1
@@ -129,11 +129,30 @@ SettingsDialog::SettingsDialog(QWidget *parent) : QDialog(parent)
|
||||
delete item;
|
||||
});
|
||||
|
||||
m_lang = new QComboBox(this);
|
||||
m_lang->addItems({"English", "Français", "Slovenčina", "Português"});
|
||||
QString lang;
|
||||
switch(QLocale().language())
|
||||
{
|
||||
default:
|
||||
case QLocale::English: lang = "en"; break;
|
||||
case QLocale::French: lang = "fr"; break;
|
||||
case QLocale::Slovak: lang = "sk"; break;
|
||||
case QLocale::Portuguese: lang = "pt_BR"; break;
|
||||
}
|
||||
|
||||
lang = settings.value("settings/lang", lang).toString();
|
||||
if(lang == "en")m_lang->setCurrentIndex(0);
|
||||
else if(lang == "fr")m_lang->setCurrentIndex(1);
|
||||
else if(lang == "sk")m_lang->setCurrentIndex(2);
|
||||
else if(lang == "pt_BR")m_lang->setCurrentIndex(3);
|
||||
|
||||
layout->addRow(tr("Image preload count"), m_preloadImages);
|
||||
layout->addRow(tr("Thumbnails size"), m_thumSize);
|
||||
layout->addRow(tr("Saturation"), m_saturation);
|
||||
layout->addRow(tr("Slideshow interval"), m_slideShowTime);
|
||||
layout->addRow(tr("Image interpolation"), m_filtering);
|
||||
layout->addRow(tr("Language"), m_lang);
|
||||
layout->addRow(m_qualityThumbnail);
|
||||
layout->addRow(m_useNativeDialog);
|
||||
layout->addRow(m_bestFit);
|
||||
@@ -150,7 +169,6 @@ SettingsDialog::SettingsDialog(QWidget *parent) : QDialog(parent)
|
||||
#endif
|
||||
//layout->addRow(new QLabel(tr("Changes in settings will take effect after program restart.")));
|
||||
|
||||
|
||||
QDialogButtonBox *buttonBox = new QDialogButtonBox(this);
|
||||
buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||
@@ -237,4 +255,14 @@ void SettingsDialog::saveSettings()
|
||||
}
|
||||
settings.setValue("settings/headerhighlightkeywords", headerHighlight.keys());
|
||||
settings.setValue("settings/headerhighlightcolors", colors);
|
||||
QString lang;
|
||||
int langIdx = m_lang->currentIndex();
|
||||
switch(langIdx)
|
||||
{
|
||||
case 0: lang = "en"; break;
|
||||
case 1: lang = "fr"; break;
|
||||
case 2: lang = "sk"; break;
|
||||
case 3: lang = "pt_BR"; break;
|
||||
}
|
||||
settings.setValue("settings/lang", lang);
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ private:
|
||||
QListWidget *m_headerHighlight;
|
||||
QColor m_color = Qt::yellow;
|
||||
QLineEdit *m_keyword;
|
||||
QComboBox *m_lang;
|
||||
};
|
||||
|
||||
#endif // SETTINGSDIALOG_H
|
||||
|
||||
@@ -104,6 +104,11 @@ StretchToolbar::~StretchToolbar()
|
||||
settings.setValue("stretchtoolbar/autostretch", m_autoStretchOnLoad->isChecked());
|
||||
}
|
||||
|
||||
const MTFParam &StretchToolbar::params() const
|
||||
{
|
||||
return m_mtfParam;
|
||||
}
|
||||
|
||||
void StretchToolbar::stretchImage(Image *img)
|
||||
{
|
||||
if(img && img->rawImage())
|
||||
|
||||
@@ -22,6 +22,7 @@ class StretchToolbar : public QToolBar
|
||||
public:
|
||||
explicit StretchToolbar(QWidget *parent = nullptr);
|
||||
~StretchToolbar();
|
||||
const MTFParam& params() const;
|
||||
public slots:
|
||||
void stretchImage(Image *img);
|
||||
void resetMTF();
|
||||
|
||||
Reference in New Issue
Block a user