Compare commits

..

17 Commits

Author SHA1 Message Date
nou cb1237cbc4 Fix Info.plist 2026-05-08 11:42:54 +02:00
nou dbc9baba13 Fix Info.plist icon 2026-05-08 09:09:55 +02:00
nou bd99501616 Add support for open with on MacOS 2026-05-08 08:44:46 +02:00
nou fccbf4d810 Update icons 2026-05-05 19:12:32 +02:00
nou 0a11289f34 Add automatic check for new versions 2026-04-28 17:09:49 +02:00
nou 53167e7bb5 Implement fits_memfile workaround
CFITSIO doesn't support non-ASCI characters in file paths
2026-04-27 18:40:57 +02:00
nou 1e940de31b Use std::filesystem::path for XISF files 2026-04-27 18:39:41 +02:00
nou f1b5ec81c0 Workaround for CFITSIO failing to load path containing non-ASCI chars 2026-04-19 13:25:32 +02:00
nou 8b8759facb Delay restoring state of database header 2026-04-13 13:30:54 +02:00
nou 468862ad35 Do not print debug logs by default 2026-04-13 13:29:54 +02:00
nou 974d482d5b Update metainfo 2026-04-12 18:32:15 +02:00
nou d45bf37e50 Update translation 2026-04-12 18:23:48 +02:00
nou 56a8a0e789 Special handling of COUNT aggregate function 2026-04-12 18:23:39 +02:00
nou 27afb2ea5f Add table view to database tree 2026-04-12 13:41:29 +02:00
nou 28016ada8d Improve logging 2026-04-12 10:26:03 +02:00
nou 885a5b4c6d Add support copy to clipboard for table 2026-04-12 10:20:43 +02:00
nou 63149745ed Handle return value of QFile::open 2026-04-12 10:19:40 +02:00
50 changed files with 833 additions and 249 deletions
+11 -1
View File
@@ -29,6 +29,7 @@ add_subdirectory(libXISF)
set(TENMON_SRC
src/about.cpp src/about.h
src/application.cpp src/application.h
src/batchprocessing.cpp src/batchprocessing.h src/batchprocessing.ui
src/chartgraph.h src/chartgraph.cpp
src/database.cpp src/database.h
@@ -39,6 +40,7 @@ set(TENMON_SRC
src/filemanager.h src/filemanager.cpp src/filemanager.ui
src/filesystemwidget.cpp src/filesystemwidget.h
src/fitskeyword.ui
src/fitswrapper.h src/fitswrapper.cpp
src/histogram.cpp src/histogram.h
src/httpdownloader.h src/httpdownloader.cpp
src/imageinfo.cpp src/imageinfo.h
@@ -73,6 +75,9 @@ elseif(APPLE)
set(tenmon_ICON ${CMAKE_CURRENT_SOURCE_DIR}/resources/tenmon.icns)
find_package(Qt6 COMPONENTS DBus REQUIRED)
set_source_files_properties(${tenmon_ICON} PROPERTIES MACOSX_PACKAGE_LOCATION "Resources")
set(MACOSX_BUNDLE_ICON_FILE "tenmon.icns")
set(MACOSX_BUNDLE_BUNDLE_NAME "tenmon")
set(MACOSX_BUNDLE_GUI_IDENTIFIER "space.nouspiro.tenmon")
else()
set(tenmon_ICON "")
find_package(Qt6 COMPONENTS DBus REQUIRED)
@@ -111,6 +116,7 @@ endif(STELLARSOLVER_INCLUDE AND STELLARSOLVER_LIB)
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")
set_target_properties(tenmon PROPERTIES MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.in")
elseif(UNIX)
target_link_libraries(tenmon PRIVATE Qt6::DBus)
endif(APPLE)
@@ -133,8 +139,12 @@ if(UNIX AND NOT APPLE)
install(SCRIPT install.cmake)
else()
install(FILES space.nouspiro.tenmon.desktop DESTINATION "${CMAKE_INSTALL_DATADIR}/applications")
install(FILES resources/space.nouspiro.tenmon.png DESTINATION "${CMAKE_INSTALL_DATADIR}/icons/hicolor/64x64/apps")
install(FILES resources/space.nouspiro.tenmon_16.png DESTINATION "${CMAKE_INSTALL_DATADIR}/icons/hicolor/16x16/apps" RENAME space.nouspiro.tenmon.png)
install(FILES resources/space.nouspiro.tenmon_32.png DESTINATION "${CMAKE_INSTALL_DATADIR}/icons/hicolor/32x32/apps" RENAME space.nouspiro.tenmon.png)
install(FILES resources/space.nouspiro.tenmon_64.png DESTINATION "${CMAKE_INSTALL_DATADIR}/icons/hicolor/64x64/apps" RENAME space.nouspiro.tenmon.png)
install(FILES resources/space.nouspiro.tenmon_128.png DESTINATION "${CMAKE_INSTALL_DATADIR}/icons/hicolor/128x128/apps" RENAME space.nouspiro.tenmon.png)
install(FILES resources/space.nouspiro.tenmon_256.png DESTINATION "${CMAKE_INSTALL_DATADIR}/icons/hicolor/256x256/apps" RENAME space.nouspiro.tenmon.png)
install(FILES resources/space.nouspiro.tenmon_512.png DESTINATION "${CMAKE_INSTALL_DATADIR}/icons/hicolor/512x512/apps" RENAME space.nouspiro.tenmon.png)
install(FILES space.nouspiro.tenmon.xisf.xml DESTINATION "${CMAKE_INSTALL_DATADIR}/mime/packages")
endif()
install(FILES space.nouspiro.tenmon.metainfo.xml DESTINATION "${CMAKE_INSTALL_DATADIR}/metainfo")
+50
View File
@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>tenmon</string>
<key>CFBundleGetInfoString</key>
<string>${MACOSX_BUNDLE_INFO_STRING}</string>
<key>CFBundleIconFile</key>
<string>${MACOSX_BUNDLE_ICON_FILE}</string>
<key>CFBundleIdentifier</key>
<string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleLongVersionString</key>
<string>${MACOSX_BUNDLE_LONG_VERSION_STRING}</string>
<key>CFBundleName</key>
<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
<key>CFBundleVersion</key>
<string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
<key>CSResourcesFileMapped</key>
<true/>
<key>NSHumanReadableCopyright</key>
<string>${MACOSX_BUNDLE_COPYRIGHT}</string>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>fits</string>
<string>fit</string>
<string>xisf</string>
<string>png</string>
<string>jpg</string>
<string>jpeg</string>
<string>tiff</string>
<string>tif</string>
</array>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
</dict>
</array>
</dict>
</plist>
+5 -2
View File
@@ -123,8 +123,11 @@ This example filters for files where: "Bias" is in the file name, the OBJECT pro
</p>
<h3>Database tree</h3>
<p>This is another view that show indexed database as tree. You can add or remove tree filter that construct a tree structure from FITS keywords. Each level of tree
will be based on this filter. You can specify one keywords multiple times.</p>
<p>This is another view that show indexed database as tree. You can add or remove tree grouping that construct a tree structure from FITS keywords. Each level of tree
will be based on this grouping. You can specify one keywords multiple times. When adding a grouping you can also specify aggregation function that is applied to last level.
SUM will add up all numbers in that group usefull for example for EXPTIME. COUNT will show how many files are in that group. AVG will show average value, MIN,MAX,MEDIAN will calculate
minimum, maximum and media value in that group. So if you select CCD-TEMP as last with AVG it will calculate average temperature. Double click on file in tree view will open
that file.</p>
<h3>Plate Solving</h3>
<p>This module can plate solve images and update FITS header with solution for FITS and XISF images.
+5 -2
View File
@@ -103,8 +103,11 @@ Cet exemple filtre les fichiers où : "Bias" figure dans le nom de fichier, la p
</p>
<h3>Database tree</h3>
<p>This is another view that show indexed database as tree. You can add or remove tree filter that construct a tree structure from FITS keywords. Each level of tree
will be based on this filter. You can specify one keywords multiple times.</p>
<p>This is another view that show indexed database as tree. You can add or remove tree grouping that construct a tree structure from FITS keywords. Each level of tree
will be based on this grouping. You can specify one keywords multiple times. When adding a grouping you can also specify aggregation function that is applied to last level.
SUM will add up all numbers in that group usefull for example for EXPTIME. COUNT will show how many files are in that group. AVG will show average value, MIN,MAX,MEDIAN will calculate
minimum, maximum and media value in that group. So if you select CCD-TEMP as last with AVG it will calculate average temperature. Double click on file in tree view will open
that file.</p>
<h3>Plate Solving</h3>
<p>This module can plate solve images and update FITS header with solution for FITS and XISF images.
+5 -2
View File
@@ -106,8 +106,11 @@ zástupný znak za hocijaký reťazec znakov aj žiadny. Znak _ je tiež zástup
Bez použitia zástupných znakov sa vyhľadá iba presný výskyt.</p>
<h3>Database tree</h3>
<p>This is another view that show indexed database as tree. You can add or remove tree filter that construct a tree structure from FITS keywords. Each level of tree
will be based on this filter. You can specify one keywords multiple times.</p>
<p>This is another view that show indexed database as tree. You can add or remove tree grouping that construct a tree structure from FITS keywords. Each level of tree
will be based on this grouping. You can specify one keywords multiple times. When adding a grouping you can also specify aggregation function that is applied to last level.
SUM will add up all numbers in that group usefull for example for EXPTIME. COUNT will show how many files are in that group. AVG will show average value, MIN,MAX,MEDIAN will calculate
minimum, maximum and media value in that group. So if you select CCD-TEMP as last with AVG it will calculate average temperature. Double click on file in tree view will open
that file.</p>
<h3>Plate Solving</h3>
<p>Tento modul umožnuje vyriešiť obrázok a určiť RA, DEC koordináty a aktualizovať FITS a XISF súbory s WCS dátami.
+1 -1
View File
@@ -1,5 +1,5 @@
<table><tr>
<td style="padding-right:10px"><img src=":/space.nouspiro.tenmon.png"></td>
<td style="padding-right:10px"><img src=":/space.nouspiro.tenmon_64.png"></td>
<td><h3>Tenmon</h3>
Tenmon is FITS/XISF image viewer and converter. It also index FITS keywords.<br>
v@GITVERSION@ Copyright © 2026 Dušan Poizl<br><br>
+5 -1
View File
@@ -2,6 +2,10 @@ find_program(XDG-DESKTOP-MENU_EXECUTABLE xdg-desktop-menu)
find_program(XDG-ICON-RESOURCE_EXECUTABLE xdg-icon-resource)
find_program(XDG-MIME xdg-mime)
execute_process(COMMAND ${XDG-DESKTOP-MENU_EXECUTABLE} install --novendor space.nouspiro.tenmon.desktop WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
execute_process(COMMAND ${XDG-ICON-RESOURCE_EXECUTABLE} install --novendor --size 64 resources/space.nouspiro.tenmon.png space.nouspiro.tenmon WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
execute_process(COMMAND ${XDG-ICON-RESOURCE_EXECUTABLE} install --novendor --size 16 resources/space.nouspiro.tenmon_16.png space.nouspiro.tenmon WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
execute_process(COMMAND ${XDG-ICON-RESOURCE_EXECUTABLE} install --novendor --size 32 resources/space.nouspiro.tenmon_32.png space.nouspiro.tenmon WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
execute_process(COMMAND ${XDG-ICON-RESOURCE_EXECUTABLE} install --novendor --size 64 resources/space.nouspiro.tenmon_64.png space.nouspiro.tenmon WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
execute_process(COMMAND ${XDG-ICON-RESOURCE_EXECUTABLE} install --novendor --size 128 resources/space.nouspiro.tenmon_128.png space.nouspiro.tenmon WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
execute_process(COMMAND ${XDG-ICON-RESOURCE_EXECUTABLE} install --novendor --size 256 resources/space.nouspiro.tenmon_256.png space.nouspiro.tenmon WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
execute_process(COMMAND ${XDG-ICON-RESOURCE_EXECUTABLE} install --novendor --size 512 resources/space.nouspiro.tenmon_512.png space.nouspiro.tenmon WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
execute_process(COMMAND ${XDG-MIME} install --novendor space.nouspiro.tenmon.xisf.xml WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
+1 -1
Submodule libXISF updated: 7b70b6a081...de757840b3
Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 1.4 MiB

+1 -1
View File
@@ -15,7 +15,7 @@
<file>bggr.png</file>
<file>grbg.png</file>
<file>gbrg.png</file>
<file>space.nouspiro.tenmon.png</file>
<file>space.nouspiro.tenmon_64.png</file>
<file>../translations/tenmon_pt_BR.qm</file>
<file alias="help">../about/help_en</file>
<file>colormap.png</file>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 247 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.
+11
View File
@@ -60,6 +60,17 @@
</screenshots>
<content_rating type="oars-1.1"/>
<releases>
<release version="20260412" date="2026-04-12">
<description>
<ul>
<li>Add database tree to show database entries grouped in tree structure</li>
<li>Add database summary table. This can show total exposure time for OBJECT and FILTER for example</li>
<li>Copy files to clipboard from database view</li>
<li>Copy to clipboard text from database tables so they can be pasted to Excel</li>
<li>Add CLI option to run scripts and generate thumbnails</li>
</ul>
</description>
</release>
<release version="20260217" date="2026-02-17">
<description>
<ul>
+9 -7
View File
@@ -15,11 +15,13 @@ About::About(QWidget *parent) : QDialog(parent)
QLabel *label = new QLabel(this);
QFile tenmonText(":/about/tenmon");
tenmonText.open(QIODevice::ReadOnly);
QByteArray text = tenmonText.readAll();
text.replace("@GITVERSION@", GITVERSION);
label->setText(text);
label->setOpenExternalLinks(true);
if(tenmonText.open(QIODevice::ReadOnly))
{
QByteArray text = tenmonText.readAll();
text.replace("@GITVERSION@", GITVERSION);
label->setText(text);
label->setOpenExternalLinks(true);
}
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok);
connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
@@ -41,8 +43,8 @@ HelpDialog::HelpDialog(QWidget *parent) : QDialog(parent)
layout->addWidget(helpText);
QFile tenmonText(":/help");
tenmonText.open(QIODevice::ReadOnly);
helpText->setHtml(tenmonText.readAll());
if(tenmonText.open(QIODevice::ReadOnly))
helpText->setHtml(tenmonText.readAll());
}
QString getVersion()
+19
View File
@@ -0,0 +1,19 @@
#include "application.h"
#include <QFileOpenEvent>
Application::Application(int &argc, char **argv) : QApplication(argc, argv)
{
}
bool Application::event(QEvent *event)
{
if (event->type() == QEvent::FileOpen) {
QFileOpenEvent *openEvent = static_cast<QFileOpenEvent *>(event);
emit openFileEvent(openEvent->file());
}
return QApplication::event(event);
}
+16
View File
@@ -0,0 +1,16 @@
#ifndef APPLICATION_H
#define APPLICATION_H
#include <QApplication>
class Application : public QApplication
{
Q_OBJECT
public:
Application(int &argc, char **argv);
bool event(QEvent *event) override;
signals:
void openFileEvent(const QString &file);
};
#endif // APPLICATION_H
+1 -1
View File
@@ -337,7 +337,7 @@ void BatchProcessing::newMessageCli(const QString &message, bool error)
if(error)
qWarning() << message;
else
qDebug() << message;
qInfo() << message;
}
QJSValue BatchProcessing::getString(const QString &label, const QString &text)
+9 -9
View File
@@ -35,7 +35,7 @@ bool Database::init(const QLatin1String &connectionName)
}
else
{
qDebug() << "Could not open NGC database";
qWarning() << "Could not open NGC database";
}
}
@@ -71,7 +71,7 @@ bool Database::init(const QLatin1String &connectionName)
}
if(version > 2)
{
qDebug() << "Database version is too new";
qWarning() << "Database version is too new";
return false;
}
@@ -100,16 +100,16 @@ bool Database::init(const QLatin1String &connectionName)
m_deleteFile.prepare("DELETE FROM fits_files WHERE id=?");
return true;
}
qDebug() << error.text();
qWarning() << error.text();
}
else
{
qDebug() << "Failed to open database" << connectionName;
qWarning() << "Failed to open database" << connectionName;
}
}
else
{
qDebug() << "Database is invalid";
qWarning() << "Database is invalid";
}
return false;
}
@@ -175,7 +175,7 @@ bool Database::checkError(QSqlQuery &query)
return true;
else
{
qDebug() << error.text();
qWarning() << error.text();
return false;
}
}
@@ -383,7 +383,7 @@ bool Database::indexFile(const QFileInfo &file)
m_insertFileWcs.bindValue(7, crVal2);
if(!m_insertFileWcs.exec())
{
qDebug() << "Database error" << m_insertFileWcs.lastError();
qWarning() << "Database error" << m_insertFileWcs.lastError();
return false;
}
last_id = m_insertFileWcs.lastInsertId().toLongLong();
@@ -394,7 +394,7 @@ bool Database::indexFile(const QFileInfo &file)
m_insertFile.bindValue(1, mtime);
if(!m_insertFile.exec())
{
qDebug() << "Database error" << m_insertFile.lastError();
qWarning() << "Database error" << m_insertFile.lastError();
return false;
}
last_id = m_insertFile.lastInsertId().toLongLong();
@@ -415,7 +415,7 @@ bool Database::indexFile(const QFileInfo &file)
m_insertFitsHeader.bindValue(3, comments);
if(!m_insertFitsHeader.execBatch())
{
qDebug() << "Database error" << m_insertFitsHeader.lastError();
qWarning() << "Database error" << m_insertFitsHeader.lastError();
return false;
}
}
+189 -26
View File
@@ -1,20 +1,31 @@
#include "databasetree.h"
#include "database.h"
#include "databaseview.h"
#include <QComboBox>
#include <QDialogButtonBox>
#include <QLabel>
#include <QPushButton>
#include <QSettings>
#include <QSqlError>
#include <QStackedWidget>
#include <QVBoxLayout>
DatabaseTreeSettings::DatabaseTreeSettings(const QString &filter, const QStringList &keywords, QWidget *parent) : QDialog(parent)
DatabaseTreeSettings::DatabaseTreeSettings(const QStringList &data, QStringList keywords, QWidget *parent) : QDialog(parent)
{
setWindowTitle(tr("Add tree filter"));
setWindowTitle(tr("Add tree grouping"));
QVBoxLayout *vlayout = new QVBoxLayout(this);
setLayout(vlayout);
QStringList key = filter.split('/');
QStringList key = data[0].split('/');
qsizetype dateobsindex = keywords.indexOf("DATE-OBS");
if(dateobsindex != -1)
{
keywords.insert(dateobsindex + 1, "DATE-OBS_YEAR-MONTH-DAY");
keywords.insert(dateobsindex + 1, "DATE-OBS_YEAR-MONTH");
keywords.insert(dateobsindex + 1, "DATE-OBS_YEAR");
}
for(int i = 0; i < 10; i++)
{
@@ -27,8 +38,15 @@ DatabaseTreeSettings::DatabaseTreeSettings(const QString &filter, const QStringL
comboxBox->setCurrentText(key[i]);
}
vlayout->addWidget(new QLabel(tr("Aggregate function"), this));
_aggregateFunction = new QComboBox(this);
_aggregateFunction->addItems({"", "SUM", "COUNT", "AVG", "MIN", "MAX", "MEDIAN"});
vlayout->addWidget(_aggregateFunction);
_aggregateFunction->setToolTip(tr("This aggregate function will be applied to last level of grouping"));
_aggregateFunction->setCurrentText(data[1]);
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
connect(buttonBox, &QDialogButtonBox::accepted, this, &DatabaseTreeSettings::accept);
connect(buttonBox, &QDialogButtonBox::accepted, this, &DatabaseTreeSettings::acceptButton);
connect(buttonBox, &QDialogButtonBox::rejected, this, &DatabaseTreeSettings::reject);
vlayout->addWidget(buttonBox);
}
@@ -44,6 +62,24 @@ QString DatabaseTreeSettings::keywords() const
return keywords.join('/');
}
QString DatabaseTreeSettings::aggregrationFunc() const
{
return _aggregateFunction->currentText();
}
void DatabaseTreeSettings::acceptButton()
{
for(QComboBox *box : _keywordsSelect)
{
if(box->currentIndex() > 0)
{
QDialog::accept();
return;
}
}
QDialog::reject();
}
class TreeNode
{
public:
@@ -130,6 +166,11 @@ void DatabaseTree::setKeys(const QStringList &keys)
endResetModel();
}
QStringList DatabaseTree::keys() const
{
return _keys;
}
QModelIndex DatabaseTree::index(int row, int column, const QModelIndex &parent) const
{
if(!hasIndex(row, column, parent))
@@ -262,6 +303,54 @@ void DatabaseTree::load()
}
}
QSqlQuery DatabaseTree::getGroupQuery(const QString &aggregateFunc) const
{
QStringList cols;
QString join;
QString sum;
for(int i = 0; i < _keys.size(); i++)
{
join += QString(" LEFT JOIN fits_headers AS h%1 ON f.id = h%1.id_file AND h%1.key = ?").arg(i);
if(_keys[i] == "DATE-OBS_YEAR")
cols.append(QString("STRFTIME('%Y', h%1.value)").arg(i));
else if(_keys[i] == "DATE-OBS_YEAR-MONTH")
cols.append(QString("STRFTIME('%Y-%m', h%1.value)").arg(i));
else if(_keys[i] == "DATE-OBS_YEAR-MONTH-DAY")
cols.append(QString("STRFTIME('%Y-%m-%d', h%1.value)").arg(i));
else
cols.append(QString("h%1.value").arg(i));
if(i == _keys.size() - 1)
{
QString tmp = aggregateFunc + "(" + cols.last() + ")";
if(aggregateFunc == "COUNT")
cols.append("COUNT(*)");
else
cols.last() = tmp;
}
}
QStringList group = cols;
group.removeLast();
QString sql = "SELECT " + cols.join(',') + " FROM fits_files AS f" + join + " GROUP BY " + group.join(',') + " ORDER BY " + cols.join(" NULLS LAST,") + " NULLS LAST";
QSqlQuery query(sql, _database->db());
for(auto &val : _keys)
{
if(val.startsWith("DATE-OBS_"))
query.addBindValue("DATE-OBS");
else
query.addBindValue(val);
}
qDebug() << "Group query" << sql;
if(!query.exec())
qWarning() << "Group query failed" << query.lastError();
return query;
}
void DatabaseTree::prepareQueries()
{
if(!_loaded)return;
@@ -276,22 +365,42 @@ void DatabaseTree::prepareQueries()
for(int i = 0; i < _keys.size(); i++)
{
QString sql = QString("SELECT h%1.value FROM fits_files AS f").arg(i) + join + where + QString(" GROUP BY h%1.value ORDER BY h%1.value").arg(i);
qDebug() << _keys[i] << sql;
QString sql;
QString col = QString("h%1.value").arg(i);
if(_keys[i] == "DATE-OBS_YEAR")
col = QString("STRFTIME('%Y', h%1.value)").arg(i);
else if(_keys[i] == "DATE-OBS_YEAR-MONTH")
col = QString("STRFTIME('%Y-%m', h%1.value)").arg(i);
else if(_keys[i] == "DATE-OBS_YEAR-MONTH-DAY")
col = QString("STRFTIME('%Y-%m-%d', h%1.value)").arg(i);
sql = QString("SELECT %1 FROM fits_files AS f").arg(col) + join + where + QString(" GROUP BY %1 ORDER BY %1 NULLS LAST").arg(col);
qDebug() << "Tree query for" << _keys[i] << sql;
QSqlQuery query(sql, _database->db());
for(auto &val : _keys)
query.addBindValue(val);
{
if(val.startsWith("DATE-OBS_"))
query.addBindValue("DATE-OBS");
else
query.addBindValue(val);
}
if(where.isEmpty())
where += QString(" WHERE h%1.value IS ?").arg(i);
where += QString(" WHERE %1 IS ?").arg(col);
else
where += QString(" AND h%1.value IS ?").arg(i);
where += QString(" AND %1 IS ?").arg(col);
_queries.append(std::move(query));
}
QSqlQuery files("SELECT f.file FROM fits_files AS f " + join + where + " GROUP BY f.id ORDER BY f.file", _database->db());
QSqlQuery files("SELECT f.file FROM fits_files AS f" + join + where + " GROUP BY f.id ORDER BY f.file", _database->db());
for(auto &val : _keys)
files.addBindValue(val);
{
if(val.startsWith("DATE-OBS_"))
files.addBindValue("DATE-OBS");
else
files.addBindValue(val);
}
qDebug() << files.lastQuery();
_queries.append(std::move(files));
}
@@ -322,7 +431,7 @@ void DatabaseTree::fillNode(TreeNode *node)
q.bindValue(i + _keys.size(), vals[i]);
if(!q.exec())
{
qWarning() << "Failed to execute query" << q.lastError();
qWarning() << "Failed to execute query" << q.lastError() << q.lastQuery() << q.boundValues();
node->fill({});
return;
}
@@ -338,32 +447,51 @@ DatabaseTreeView::DatabaseTreeView(Database *database, QWidget *parent) : QWidge
,_database(database)
{
QVBoxLayout *vlayout = new QVBoxLayout(this);
QHBoxLayout *hlayout = new QHBoxLayout(this);
QHBoxLayout *hlayout = new QHBoxLayout;
_model = new DatabaseTree(database, this);
_treeView = new QTreeView(this);
_treeView->setModel(_model);
_treeView->setHeaderHidden(true);
_tableView = new CopyTableView(this);
_sqlModel = new QSqlQueryModel(this);
_tableView->setModel(_sqlModel);
QSettings settings;
QStringList filters = settings.value("databasetreeview/filters", QStringList{"OBJECT", "OBJECT/IMAGETYP", "OBJECT/IMAGETYP/FILTER", "OBJECT/IMAGETYP/FILTER/EXPTIME", "IMAGETYP/OBJECT/IMAGETYP/FILTER/EXPTIME"}).toStringList();
QStringList filters = settings.value("databasetreeview/filters", QStringList{"OBJECT", "OBJECT/IMAGETYP", "OBJECT/IMAGETYP/FILTER", "OBJECT/IMAGETYP/FILTER/EXPTIME",
"IMAGETYP/OBJECT/IMAGETYP/FILTER/EXPTIME", "IMAGETYP/DATE-OBS_YEAR/EXPTIME"}).toStringList();
QStringList aggrFuncs = settings.value("databasetreeview/aggrFuncs", QStringList{"", "", "", "SUM", "SUM", "SUM"}).toStringList();
int selectedFilter = settings.value("databasetreeview/selectedFilter", 2).toInt();
_filters = new QComboBox(this);
_filters->addItems(filters);
for(int i = 0; i < std::min(filters.size(), aggrFuncs.size()); i++)
{
_filters->addItem(filters[i] + " " + aggrFuncs[i], QStringList{filters[i], aggrFuncs[i]});
}
_filters->setCurrentIndex(selectedFilter);
connect(_filters, &QComboBox::currentTextChanged, this, &DatabaseTreeView::filterChanged);
filterChanged(_filters->currentText());
connect(_filters, &QComboBox::currentIndexChanged, this, &DatabaseTreeView::filterChanged);
filterChanged(_filters->currentIndex());
QStackedWidget *stackedWidget = new QStackedWidget(this);
stackedWidget->addWidget(_treeView);
stackedWidget->addWidget(_tableView);
QPushButton *addButton = new QPushButton(tr("Add"), this);
QPushButton *removeButton = new QPushButton(tr("Remove"), this);
QPushButton *treeTableButton = new QPushButton(tr("Tree/Table"), this);
treeTableButton->setCheckable(true);
connect(treeTableButton, &QPushButton::clicked, [stackedWidget](bool checked){
stackedWidget->setCurrentIndex(checked ? 1 : 0);
});
hlayout->addWidget(_filters, 1);
hlayout->addWidget(addButton);
hlayout->addWidget(removeButton);
hlayout->addWidget(treeTableButton);
vlayout->addLayout(hlayout);
vlayout->addWidget(_treeView);
vlayout->addWidget(stackedWidget);
connect(_treeView, &QTreeView::activated, [this](const QModelIndex &index){
if(!_model->hasChildren(index))
@@ -380,27 +508,36 @@ DatabaseTreeView::DatabaseTreeView(Database *database, QWidget *parent) : QWidge
DatabaseTreeView::~DatabaseTreeView()
{
QStringList filters;
QStringList aggrFuncs;
for(int i = 0; i < _filters->count(); i++)
filters.append(_filters->itemText(i));
{
QStringList data = _filters->itemData(i).toStringList();
filters.append(data[0]);
aggrFuncs.append(data[1]);
}
QSettings settings;
settings.setValue("databasetreeview/filters", filters);
settings.setValue("databasetreeview/aggrFuncs", aggrFuncs);
settings.setValue("databasetreeview/selectedFilter", _filters->currentIndex());
}
void DatabaseTreeView::addFilter()
{
QStringList keywords = _database->getFitsKeywords();
DatabaseTreeSettings settings(_filters->currentText(), keywords, this);
QStringList data = _filters->currentData().toStringList();
DatabaseTreeSettings settings(data, keywords, this);
int result = settings.exec();
if(result == QDialog::Accepted)
{
QString keywords = settings.keywords();
int idx = _filters->findText(keywords);
QString aggrFunc = settings.aggregrationFunc();
QString text = keywords + " " + aggrFunc;
int idx = _filters->findText(text);
if(idx == -1)
{
_filters->addItem(keywords);
_filters->setCurrentText(keywords);
_filters->addItem(text, QStringList{keywords, aggrFunc});
_filters->setCurrentText(text);
}
else
{
@@ -415,14 +552,40 @@ void DatabaseTreeView::removeFilter()
_filters->removeItem(_filters->currentIndex());
}
void DatabaseTreeView::filterChanged(const QString &filter)
void DatabaseTreeView::filterChanged(int index)
{
QStringList keys = filter.split('/');
QStringList data = _filters->itemData(index).toStringList();
QStringList keys = data[0].split('/');
_model->setKeys(keys);
setQuery(data[1]);
}
void DatabaseTreeView::visible(bool visible)
{
if(visible)
if(visible && !_loaded)
{
_loaded = true;
_model->load();
QStringList data = _filters->currentData().toStringList();
setQuery(data[1]);
}
}
void DatabaseTreeView::setQuery(const QString &func)
{
QStringList keys = _model->keys();
int i = 0;
_sqlModel->setQuery(_model->getGroupQuery(func));
if(!func.isEmpty())
{
QString tmp = func + "(" + keys.last() + ")";
if(func == "COUNT")
keys.append("COUNT");
else
keys.last() = tmp;
}
for(auto &key : keys)
_sqlModel->setHeaderData(i++, Qt::Horizontal, key);
_tableView->resizeColumnsToContents();
}
+20 -6
View File
@@ -2,11 +2,13 @@
#define DATABASETREE_H
#include <QAbstractItemModel>
#include <QTreeView>
#include <QSqlQuery>
#include <QFont>
#include <QDialog>
#include <QComboBox>
#include <QDialog>
#include <QFont>
#include <QSqlQuery>
#include <QSqlQueryModel>
#include <QTableView>
#include <QTreeView>
#include <memory>
class Database;
@@ -14,11 +16,16 @@ class TreeNode;
class DatabaseTreeSettings : public QDialog
{
Q_OBJECT
public:
explicit DatabaseTreeSettings(const QString &filter, const QStringList &keywords, QWidget *parent = nullptr);
explicit DatabaseTreeSettings(const QStringList &data, QStringList keywords, QWidget *parent = nullptr);
QString keywords() const;
QString aggregrationFunc() const;
public slots:
void acceptButton();
private:
QVector<QComboBox*> _keywordsSelect;
QComboBox *_aggregateFunction;
};
class DatabaseTree : public QAbstractItemModel
@@ -26,6 +33,7 @@ class DatabaseTree : public QAbstractItemModel
public:
explicit DatabaseTree(Database *database, QObject *parent = nullptr);
void setKeys(const QStringList &keys);
QStringList keys() const;
QModelIndex index(int row, int column, const QModelIndex &parent) const override;
QModelIndex parent(const QModelIndex &index) const override;
int rowCount(const QModelIndex &index) const override;
@@ -35,6 +43,7 @@ public:
void fetchMore(const QModelIndex &parent) override;
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
void load();
QSqlQuery getGroupQuery(const QString &aggregateFunc) const;
private:
void prepareQueries();
void fillNode(TreeNode *node);
@@ -55,15 +64,20 @@ public:
public slots:
void addFilter();
void removeFilter();
void filterChanged(const QString &filter);
void filterChanged(int index);
void visible(bool visible);
private:
void setQuery(const QString &func);
signals:
void loadFile(const QString &file);
private:
QComboBox *_filters = nullptr;
QTreeView *_treeView = nullptr;
QTableView *_tableView = nullptr;
DatabaseTree *_model = nullptr;
QSqlQueryModel *_sqlModel = nullptr;
Database *_database = nullptr;
bool _loaded = false;
};
#endif // DATABASETREE_H
+59 -20
View File
@@ -1,19 +1,20 @@
#include "databaseview.h"
#include <QVBoxLayout>
#include <QPushButton>
#include <QSettings>
#include <QDialogButtonBox>
#include <QHeaderView>
#include <QSqlError>
#include <QDebug>
#include <QMenu>
#include <QContextMenuEvent>
#include <QRegularExpression>
#include <QGuiApplication>
#include <QClipboard>
#include <QMimeData>
#include <iostream>
#include "batchprocessing.h"
#include "databaseview.h"
#include <QClipboard>
#include <QContextMenuEvent>
#include <QDebug>
#include <QDialogButtonBox>
#include <QGuiApplication>
#include <QHeaderView>
#include <QMenu>
#include <QMimeData>
#include <QPushButton>
#include <QRegularExpression>
#include <QSettings>
#include <QSqlError>
#include <QTimer>
#include <QVBoxLayout>
#include <iostream>
const QStringList DEFAULT_COLUMNS = {"EXPTIME", "OBJECT", "RA", "DEC"};
@@ -239,7 +240,8 @@ void FITSFileModel::prepareQuery()
if(!query.exec())
qWarning() << "Failed to exectute query" << query.lastQuery() << bindValuesJoin << bindValues;
setQuery(std::move(query));
else
setQuery(std::move(query));
setHeaderData(0, Qt::Horizontal, tr("File name"));
i = 1;
@@ -247,7 +249,7 @@ void FITSFileModel::prepareQuery()
{
setHeaderData(i++, Qt::Horizontal, column);
}
std::cout << sql.toStdString() << std::endl;
qDebug() << "DB SQL" << sql;
if(lastError().type() != QSqlError::NoError)
qDebug() << "Database error" << lastError();
@@ -255,7 +257,7 @@ void FITSFileModel::prepareQuery()
m_markedFiles = QSet<QString>(list.begin(), list.end());
}
DatabaseTableView::DatabaseTableView(QWidget *parent) : QTableView(parent)
DatabaseTableView::DatabaseTableView(QWidget *parent) : CopyTableView(parent)
{
}
@@ -317,9 +319,12 @@ DataBaseView::DataBaseView(Database *database, QWidget *parent) : QWidget(parent
m_model = new FITSFileModel(m_database, this);
QSettings settings;
m_tableView->setModel(m_model);
m_model->setColumns(settings.value("databaseview/selectedColumns", DEFAULT_COLUMNS).toStringList());
m_tableView->horizontalHeader()->restoreState(settings.value("databaseview/header").toByteArray());
m_tableView->setModel(m_model);
QTimer::singleShot(200, [this](){
QSettings settings;
m_tableView->horizontalHeader()->restoreState(settings.value("databaseview/header").toByteArray());
});
QHBoxLayout *hlayout = new QHBoxLayout();
layout->addLayout(hlayout);
@@ -498,3 +503,37 @@ void DataBaseView::visible(bool visible)
{
if(visible)m_model->load();
}
CopyTableView::CopyTableView(QWidget *parent) : QTableView(parent)
{
}
void CopyTableView::keyPressEvent(QKeyEvent *event)
{
if(event->matches(QKeySequence::Copy))
{
QModelIndexList list = selectedIndexes();
QString table;
if(list.size() == 0)return;
int row = list.first().row();
int col = list.first().column();
for(auto &index : list)
{
if(row != index.row())
table.append('\n');
else if(col != index.column())
table.append('\t');
table.append(index.data().toString());
row = index.row();
col = index.column();
}
qApp->clipboard()->setText(table);
event->accept();
}
else
{
QTableView::keyPressEvent(event);
}
}
+9 -1
View File
@@ -44,7 +44,15 @@ protected:
void prepareQuery();
};
class DatabaseTableView : public QTableView
class CopyTableView : public QTableView
{
Q_OBJECT
public:
explicit CopyTableView(QWidget *parent = nullptr);
void keyPressEvent(QKeyEvent *event);
};
class DatabaseTableView : public CopyTableView
{
Q_OBJECT
public:
+89
View File
@@ -0,0 +1,89 @@
#include "fitswrapper.h"
#include <QDebug>
#include <QFile>
FITSWrapper::FITSWrapper(const QString &path, int mode, bool open)
{
_path = path;
_mode = mode;
if(open)
fits_open_diskfile(&_file, path.toLocal8Bit().data(), mode, &_status);
else
fits_create_diskfile(&_file, path.toLocal8Bit().data(), &_status);
if(_status == FILE_NOT_OPENED || _status == FILE_NOT_CREATED)
{
qWarning() << "Could not open file directly trying memfile workaround" << path;
if(open)
{
QFile fr(path);
if(fr.open(QIODevice::ReadOnly))
{
_bufferSize = fr.size();
_bufferPtr = malloc(_bufferSize);
fr.read((char*)_bufferPtr, _bufferSize);
_status = 0;
fits_open_memfile(&_file, "memfile", mode, &_bufferPtr, &_bufferSize, 0, realloc, &_status);
if(_status)
{
free(_bufferPtr);
_bufferPtr = nullptr;
_bufferSize = 0;
qWarning() << "fits_open_memfile failed";
}
}
else
{
qWarning() << "QFile failed to open file" << path;
_status = FILE_NOT_OPENED;
}
}
else
{
_bufferSize = 2880;
_bufferPtr = malloc(_bufferSize);
fits_create_memfile(&_file, &_bufferPtr, &_bufferSize, 0, realloc, &_status);
if(_status)
{
free(_bufferPtr);
_bufferPtr = nullptr;
_bufferSize = 0;
}
}
}
}
FITSWrapper::~FITSWrapper()
{
if(_file)
{
_status = 0;
fits_close_file(_file, &_status);
}
if(_mode == READWRITE && _bufferPtr)
{
qDebug() << "Writing FITS memfile" << _bufferSize;
QFile fw(_path);
if(fw.open(QIODevice::WriteOnly | QIODevice::Truncate))
{
if(fw.write((char*)_bufferPtr, _bufferSize) != (qint64)_bufferSize)
qWarning() << "Failed to write to file";
}
else
qWarning() << "Failed to open fits file for writing";
}
free(_bufferPtr);
}
int FITSWrapper::status() const
{
return _status;
}
FITSWrapper::operator fitsfile *()
{
return _file;
}
+25
View File
@@ -0,0 +1,25 @@
#ifndef FITSWRAPPER_H
#define FITSWRAPPER_H
#include <QByteArray>
#include <QString>
#include <fitsio.h>
class FITSWrapper
{
public:
explicit FITSWrapper(const QString &path, int mode, bool open);
~FITSWrapper();
FITSWrapper(FITSWrapper &other) = delete;
int status() const;
operator fitsfile*();
private:
QString _path;
int _status = 0;
int _mode = 0;
fitsfile *_file;
size_t _bufferSize = 0;
void *_bufferPtr = nullptr;
};
#endif // FITSWRAPPER_H
+10 -6
View File
@@ -345,18 +345,22 @@ Download::Download(QNetworkReply *reply, const QString indexPath, QObject *paren
filename.remove(QRegularExpression("\\.zst$"));
_fw.setFileName(indexPath + "/" + filename);
_fw.open(QIODevice::WriteOnly | QIODevice::Truncate);
if(_fw.isOpen())
if(_fw.open(QIODevice::WriteOnly | QIODevice::Truncate))
{
qDebug() << "open file" << _fw.fileName();
_dstream = ZSTD_createDStream();
}
else
{
qWarning() << "Failed to open file" << _fw.fileName();
abort();
}
_dstream = ZSTD_createDStream();
}
Download::~Download()
{
ZSTD_freeDStream(_dstream);
if(_dstream)
ZSTD_freeDStream(_dstream);
}
void Download::abort()
@@ -402,7 +406,7 @@ void Download::finished()
void Download::decompress(QByteArray &data)
{
if(data.isEmpty())return;
if(data.isEmpty() || _dstream == nullptr)return;
_hash.addData(data);
+2 -2
View File
@@ -11,8 +11,8 @@
class Download : public QObject
{
Q_OBJECT
QNetworkReply *_reply;
ZSTD_DStream *_dstream;
QNetworkReply *_reply = nullptr;
ZSTD_DStream *_dstream = nullptr;
QFile _fw;
QCryptographicHash _hash;
public:
+6 -6
View File
@@ -154,7 +154,7 @@ void ImageWidgetGL::setImage(std::shared_ptr<RawImage> image, int index)
m_image->bind();
f->glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
f->glGenerateMipmap(GL_TEXTURE_2D);
qDebug() << "setImage" << timer.elapsed();
qDebug() << "ImageWidgetGL::setImage" << timer.elapsed() << "ms";
m_swPaint = f->glGetError() != GL_NO_ERROR;
}
@@ -721,19 +721,19 @@ void ImageWidgetGL::initializeGL()
logger->startLogging();
connect(logger, &QOpenGLDebugLogger::messageLogged, [](const QOpenGLDebugMessage &message)
{
qDebug() << message;
qDebug() << "OpenGL debug" << message;
});
qDebug() << "Vendor:" << (char*)f->glGetString(GL_VENDOR);
qDebug() << "Renderer:" << (char*)f->glGetString(GL_RENDERER);
qDebug() << "Version:" << (char*)f->glGetString(GL_VERSION);
qDebug() << "OpenGL Vendor:" << (char*)f->glGetString(GL_VENDOR);
qDebug() << "OpenGL Renderer:" << (char*)f->glGetString(GL_RENDERER);
qDebug() << "OpenGL Version:" << (char*)f->glGetString(GL_VERSION);
f->glGetIntegerv(GL_MAX_TEXTURE_SIZE, &m_maxTextureSize);
f->glGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS, &m_maxArrayLayers);
qDebug() << "Max texture size:" << m_maxTextureSize << "max layers:" << m_maxArrayLayers;
//MANUAL_MIPMAP_GEN = QString((const char*)f->glGetString(GL_VENDOR)).startsWith("ATI Technologies Inc", Qt::CaseInsensitive);
qDebug() << context()->format();
qDebug() << "OpenGL context format" << context()->format();
// each vertex is x,y 2D position and s,t texture coordinates
float vertexs[] = {-1.0f, -1.0f, 0.0f, 1.0f,
+26 -25
View File
@@ -1,13 +1,13 @@
#include "loadimage.h"
#include <QElapsedTimer>
#include <QDebug>
#include <QFileInfo>
#include <QDir>
#include <libraw/libraw.h>
#include <fitsio2.h>
#include "libxisf.h"
#include <libexif/exif-data.h>
#include "rawimage.h"
#include <QDebug>
#include <QDir>
#include <QElapsedTimer>
#include <QFileInfo>
#include <libexif/exif-data.h>
#include <libraw/libraw.h>
#include "fitswrapper.h"
QString makeUNCPath(const QString &path)
{
@@ -85,7 +85,6 @@ int loadFITSHeader(fitsfile *file, ImageInfoData &info)
bool loadFITS(const QString path, ImageInfoData &info, std::shared_ptr<RawImage> &image, bool planar, uint32_t index)
{
fitsfile *file;
int status = 0;
int num = 0;
long naxes[3] = {0};
@@ -95,11 +94,13 @@ bool loadFITS(const QString path, ImageInfoData &info, std::shared_ptr<RawImage>
char err[100];
fits_get_errstatus(status, err);
info.info.append({QObject::tr("Error"), QString(err)});
qDebug() << "Failed to load FITS file" << err;
qWarning() << "Failed to load FITS file" << err;
return false;
};
fits_open_diskfile(&file, path.toLocal8Bit().data(), READONLY, &status);
FITSWrapper file(path, READONLY, true);
status = file.status();
if(status)return checkError();
fits_get_num_hdus(file, &num, &status);
if(status)return checkError();
@@ -222,7 +223,6 @@ noload:
}
}
fits_close_file(file, &status);
return true;
}
@@ -231,7 +231,8 @@ bool loadXISF(const QString &path, ImageInfoData &info, std::shared_ptr<RawImage
try
{
LibXISF::XISFReader xisf;
xisf.open(path.toLocal8Bit().data());
QFileInfo fileInfo(path);
xisf.open(fileInfo.filesystemFilePath());
if(index >= (uint32_t)xisf.imagesCount())return false;
const LibXISF::Image &xisfImage = xisf.getImage(index);
@@ -335,7 +336,7 @@ bool loadXISF(const QString &path, ImageInfoData &info, std::shared_ptr<RawImage
catch (LibXISF::Error &err)
{
info.info.append(QPair<QString, QString>("Error", err.what()));
qDebug() << "Failed to load XISF" << err.what();
qWarning() << "Failed to load XISF" << err.what();
return false;
}
info.info.append({QObject::tr("Error"), QObject::tr("Unsupported sample format")});
@@ -344,15 +345,14 @@ bool loadXISF(const QString &path, ImageInfoData &info, std::shared_ptr<RawImage
bool readFITSHeader(const QString &path, ImageInfoData &info)
{
fitsfile *fr;
int status = 0;
QString path2 = makeUNCPath(path);
fits_open_diskfile(&fr, path2.toLocal8Bit().data(), READONLY, &status);
if(fr && status == 0)
FITSWrapper file(path, READONLY, true);
int status = file.status();
if(file && status == 0)
{
status = loadFITSHeader(fr, info);
fits_close_file(fr, &status);
status = loadFITSHeader(file, info);
}
return status == 0;
}
@@ -363,7 +363,8 @@ bool readXISFHeader(const QString &path, ImageInfoData &info)
try
{
LibXISF::XISFReader xisf;
xisf.open(path2.toLocal8Bit().data());
QFileInfo fileInfo(path2);
xisf.open(fileInfo.filesystemFilePath());
const LibXISF::Image &image = xisf.getImage(0, false);
auto fitskeywords = image.fitsKeywords();
@@ -382,7 +383,7 @@ bool readXISFHeader(const QString &path, ImageInfoData &info)
}
catch (LibXISF::Error &err)
{
qDebug() << err.what();
qWarning() << "LibXISF error" << err.what();
return false;
}
return true;
@@ -455,17 +456,17 @@ bool loadImage(const QString &path, ImageInfoData &info, std::shared_ptr<RawImag
if(path.endsWith(".CR2", Qt::CaseInsensitive) || path.endsWith(".CR3", Qt::CaseInsensitive) || path.endsWith(".NEF", Qt::CaseInsensitive) || path.endsWith(".DNG", Qt::CaseInsensitive))
{
ret = loadRAW(path, info, rawImage);
qDebug() << "LoadRAW" << timer.elapsed();
qDebug() << "LoadRAW" << timer.elapsed() << "ms";
}
else if(isFITS(fileInfo.suffix()))
{
ret = loadFITS(path, info, rawImage, planar, index);
qDebug() << "LoadFITS" << timer.elapsed();
qDebug() << "LoadFITS" << timer.elapsed() << "ms";
}
else if(isXISF(fileInfo.suffix()))
{
ret = loadXISF(path, info, rawImage, planar, index);
qDebug() << "LoadXISF" << timer.elapsed();
qDebug() << "LoadXISF" << timer.elapsed() << "ms";
}
else
{
@@ -481,7 +482,7 @@ bool loadImage(const QString &path, ImageInfoData &info, std::shared_ptr<RawImag
exif_data_free(exif);
}
rawImage = std::make_shared<RawImage>(img);
qDebug() << "LoadQImage" << timer.elapsed();
qDebug() << "LoadQImage" << timer.elapsed() << "ms";
ret = !img.isNull();
}
return ret;
+10 -8
View File
@@ -1,13 +1,14 @@
#include "loadrunable.h"
#include "fitswrapper.h"
#include "imageringlist.h"
#include "loadimage.h"
#include "rawimage.h"
#include <QDebug>
#include <QElapsedTimer>
#include <QFileInfo>
#include <QPainter>
#include <QElapsedTimer>
#include <QDebug>
#include <algorithm>
#include <fitsio2.h>
#include "rawimage.h"
#include "loadimage.h"
#include <lcms2.h>
LoadRunable::LoadRunable(const QString &file, Image *receiver, AnalyzeLevel level, int index, bool thumbnail) :
@@ -269,7 +270,8 @@ void ConvertRunable::run()
image.setByteshuffling(true);
xisf.writeImage(image);
xisf.save(m_outfile.toLocal8Bit().data());
QFileInfo fileInfo(makeUNCPath(m_outfile));
xisf.save(fileInfo.filesystemFilePath());
}
catch(LibXISF::Error &err)
{
@@ -281,9 +283,10 @@ void ConvertRunable::run()
if(m_format == "fits")
{
int status = 0;
fitsfile *fw;
if(QFileInfo(m_outfile).exists())QFile::remove(m_outfile);
fits_create_diskfile(&fw, m_outfile.toLocal8Bit().data(), &status);
FITSWrapper fw(m_outfile, READWRITE, false);
if(fw.status())return;
if(!m_params.compressionType.isEmpty())
{
if(m_params.compressionType == "gzip")
@@ -292,7 +295,6 @@ void ConvertRunable::run()
fits_set_compression_type(fw, RICE_1, &status);
}
writeFITSImage(fw, rawimage, imageinfo);
fits_close_file(fw, &status);
return;
}
+24 -6
View File
@@ -1,15 +1,28 @@
#include "mainwindow.h"
#include <QApplication>
#include <QSurfaceFormat>
#include <QTranslator>
#include "application.h"
#include <QCommandLineParser>
#include <QSettings>
#include <QSurfaceFormat>
#include <QTranslator>
#include <stdlib.h>
#include "../thumbnailer/genthumbnail.h"
#ifdef Q_OS_WIN64
#include <windows.h>
#endif
bool DEBUG_LOG = false;
QtMessageHandler defaultHandler = nullptr;
void messageHandler(QtMsgType type, const QMessageLogContext &ctx, const QString &message)
{
if(defaultHandler)
{
if(DEBUG_LOG || type != QtMsgType::QtDebugMsg)
defaultHandler(type, ctx, message);
}
}
int main(int argc, char *argv[])
{
#ifdef __linux__
@@ -29,6 +42,7 @@ int main(int argc, char *argv[])
freopen("CONOUT$", "w", stderr);
}
#endif
defaultHandler = qInstallMessageHandler(messageHandler);
QCommandLineParser cmd;
cmd.addOption({"gl", "Use desktop OpenGL. This is default on x86 and MacOS platform."});
@@ -40,6 +54,7 @@ int main(int argc, char *argv[])
cmd.addOption({"scriptarg", "String that will be passed to script as variable \"scriparg\"", "arg"});
cmd.addOption({"outdir", "Output dir for script (default: CWD)", "dir", "."});
cmd.addOption({"noexit", "Do not exit application when script finish"});
cmd.addOption({"debug", "Print debug info"});
cmd.addHelpOption();
QStringList cmdArgs;
for(int i = 0; i < argc; i++)
@@ -51,6 +66,9 @@ int main(int argc, char *argv[])
if(cmd.isSet("gles"))
useGLES = true;
if(cmd.isSet("debug"))
DEBUG_LOG = true;
if(cmd.isSet("thumb"))
{
QCoreApplication app(argc, argv);
@@ -84,11 +102,10 @@ int main(int argc, char *argv[])
}
QSurfaceFormat::setDefaultFormat(format);
QApplication a(argc, argv);
Application a(argc, argv);
a.setOrganizationName("nou");
a.setApplicationName("Tenmon");
a.setWindowIcon(QIcon(":/space.nouspiro.tenmon.png"));
a.setWindowIcon(QIcon(":/space.nouspiro.tenmon_64.png"));
QTranslator translator;
QTranslator translator2;
@@ -109,6 +126,7 @@ int main(int argc, char *argv[])
a.installTranslator(&translator2);
MainWindow w;
QObject::connect(&a, &Application::openFileEvent, &w, static_cast<void (MainWindow::*)(const QString&)>(&MainWindow::loadFile));
w.show();
if(!cmd.positionalArguments().isEmpty() && !cmd.isSet("script"))
+62 -39
View File
@@ -1,37 +1,38 @@
#include "about.h"
#include "batchprocessing.h"
#include "filemanager.h"
#include "histogram.h"
#include "loadrunable.h"
#include "mainwindow.h"
#include <QScrollArea>
#include "markedfiles.h"
#include "settingsdialog.h"
#include "statusbar.h"
#include <QActionGroup>
#include <QDebug>
#include <QDesktopServices>
#include <QDir>
#include <QDockWidget>
#include <QFileDialog>
#include <QGuiApplication>
#include <QImageReader>
#include <QImageWriter>
#include <QJsonDocument>
#include <QKeyEvent>
#include <QMenu>
#include <QMenuBar>
#include <QFileDialog>
#include <QStandardPaths>
#include <QMessageBox>
#include <QMimeDatabase>
#include <QNetworkReply>
#include <QProgressDialog>
#include <QDebug>
#include <QDockWidget>
#include <QActionGroup>
#include <QScrollArea>
#include <QSettings>
#include <QStandardPaths>
#include <QStatusBar>
#include <QThreadPool>
#include <QSessionManager>
#include <QTimer>
#include <signal.h>
#include <unistd.h>
#include <QSettings>
#include <QGuiApplication>
#include <QThreadPool>
#include <QStatusBar>
#include <QImageReader>
#include <QImageWriter>
#include <QMimeDatabase>
#include <QDesktopServices>
#include <QJsonDocument>
#include <QNetworkReply>
#include <QTimer>
#include "loadrunable.h"
#include "markedfiles.h"
#include "about.h"
#include "statusbar.h"
#include "settingsdialog.h"
#include "histogram.h"
#include "batchprocessing.h"
#include "filemanager.h"
#ifdef __linux__
#include <sys/ioctl.h>
@@ -404,6 +405,14 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
if(_plateSolving)_plateSolving->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable);
m_stretchPanel->setFloatable(false);
}
if(settings.value("settings/checkupdate", false).toBool())
{
QDateTime lastcheck = settings.value("settings/lastcheck").toDateTime();
QDateTime weekago = QDateTime::currentDateTimeUtc().addDays(-7);
if(!lastcheck.isValid() || lastcheck < weekago)
checkNewVersion(true);
}
}
MainWindow::~MainWindow()
@@ -576,16 +585,23 @@ void MainWindow::loadFile(const QString &path)
if(!path.isEmpty())
{
QFileInfo info(path);
m_ringList->setFile(info.canonicalFilePath());
updateWindowTitle();
if(info.isDir())
_lastDir = info.absolutePath();
if(info.exists() && info.isReadable())
{
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();
}
else
_lastDir = info.canonicalPath();
QSettings settings;
settings.setValue("mainwindow/lastdir", _lastDir);
if(settings.value("settings/bestfit", false).toBool())
m_image->bestFit();
{
qWarning() << "File doesn't exist or is not readable";
}
}
}
@@ -815,27 +831,33 @@ void MainWindow::exportCSV()
m_databaseView->exportCSV(file);
}
void MainWindow::checkNewVersion()
void MainWindow::checkNewVersion(bool autocheck)
{
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](){
connect(reply, &QNetworkReply::finished, [this, manager, reply, autocheck](){
QJsonParseError error;
QJsonDocument json = QJsonDocument::fromJson(reply->readAll(), &error);
if(json.isObject() && json.object().contains("tag_name"))
{
QSettings settings;
settings.setValue("settings/lastcheck", QDateTime::currentDateTimeUtc());
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"));
{
if(!autocheck)
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;
qDebug() << "Opening url" << url;
if(url.host() == "gitea.nouspiro.space")
QDesktopServices::openUrl(url);
}
@@ -843,7 +865,8 @@ void MainWindow::checkNewVersion()
}
else
{
QMessageBox::warning(this, tr("Update check"), tr("Failed to check version"));
if(!autocheck)
QMessageBox::warning(this, tr("Update check"), tr("Failed to check version"));
}
reply->deleteLater();
+1 -1
View File
@@ -69,7 +69,7 @@ public slots:
void showMarkFilesDialog();
void showSettingsDialog();
void exportCSV();
void checkNewVersion();
void checkNewVersion(bool autocheck = false);
void openFileManager();
void runScript(const QString &script, const QString &outdir, const QStringList &paths, const QString &arg, bool exit);
};
+19 -18
View File
@@ -1,16 +1,17 @@
#include "scriptengine.h"
#include <QDir>
#include <QFileInfo>
#include <QDebug>
#include <QInputDialog>
#include <QJsonValue>
#include <QJSValueIterator>
#include "batchprocessing.h"
#include "fitswrapper.h"
#include "libxisf.h"
#include "loadimage.h"
#include "loadrunable.h"
#include "rawimage.h"
#include "loadimage.h"
#include "batchprocessing.h"
#include <QDebug>
#include <QDir>
#include <QFileInfo>
#include <QInputDialog>
#include <QJSValueIterator>
#include <QJsonValue>
#include <fitsio2.h>
#include "libxisf.h"
#ifdef PLATESOLVER
#include "solver.h"
#endif // PLATESOLVER
@@ -672,10 +673,11 @@ bool File::modifyFITSRecords(const FITSRecordModify *modify)
if(isFITS(suffix()))
{
fitsfile *file;
int status = 0;
QString path = makeUNCPath(_path);
fits_open_diskfile(&file, path.toLocal8Bit().data(), READWRITE, &status);
FITSWrapper file(path, READWRITE, true);
status = file.status();
int num = 0;
fits_get_num_hdus(file, &num, &status);
if(status)
@@ -795,8 +797,7 @@ bool File::modifyFITSRecords(const FITSRecordModify *modify)
return false;
}
}
fits_close_file(file, &status);
if(status)qWarning() << "Failed to modify FITS header" << status;
return status == 0;
}
else if(isXISF(suffix()))
@@ -804,9 +805,9 @@ bool File::modifyFITSRecords(const FITSRecordModify *modify)
try
{
LibXISF::XISFModify modifyXISF;
QString in = makeUNCPath(absoluteFilePath());
QString out = in + "~";
modifyXISF.open(in.toLocal8Bit().data());
QFileInfo in(makeUNCPath(absoluteFilePath()));
QFileInfo out(in.absoluteFilePath() + "~");
modifyXISF.open(in.filesystemFilePath());
qDebug() << "modify" << in << out;
for(auto &remove : modify->_remove)
@@ -821,9 +822,9 @@ bool File::modifyFITSRecords(const FITSRecordModify *modify)
for(auto &property : modify->_property)
modifyXISF.updateProperty(modify->_imageIdx, property);
modifyXISF.save(out.toLocal8Bit().toStdString());
modifyXISF.save(out.filesystemFilePath());
modifyXISF.close();
std::filesystem::rename(out.toLocal8Bit().toStdString(), in.toLocal8Bit().toStdString());
std::filesystem::rename(out.filesystemFilePath(), in.filesystemFilePath());
return true;
}
catch(std::filesystem::filesystem_error &err)
+6
View File
@@ -147,6 +147,10 @@ SettingsDialog::SettingsDialog(QWidget *parent) : QDialog(parent)
else if(lang == "sk")m_lang->setCurrentIndex(2);
else if(lang == "pt_BR")m_lang->setCurrentIndex(3);
m_checkUpdate = new QCheckBox(tr("Check for new version"), this);
m_checkUpdate->setToolTip(tr("Check for new version every week"));
m_checkUpdate->setChecked(settings.value("settings/checkupdate", false).toBool());
layout->addRow(tr("Image preload count"), m_preloadImages);
layout->addRow(tr("Thumbnails size"), m_thumSize);
layout->addRow(tr("Saturation"), m_saturation);
@@ -156,6 +160,7 @@ SettingsDialog::SettingsDialog(QWidget *parent) : QDialog(parent)
layout->addRow(m_qualityThumbnail);
layout->addRow(m_useNativeDialog);
layout->addRow(m_bestFit);
layout->addRow(m_checkUpdate);
layout->addRow(new QLabel(tr("FITS header highlight"), this));
layout->addRow(m_headerHighlight);
layout->addRow(m_keyword, color);
@@ -265,4 +270,5 @@ void SettingsDialog::saveSettings()
case 3: lang = "pt_BR"; break;
}
settings.setValue("settings/lang", lang);
settings.setValue("settings/checkupdate", m_checkUpdate->isChecked());
}
+1
View File
@@ -33,6 +33,7 @@ private:
QColor m_color = Qt::yellow;
QLineEdit *m_keyword;
QComboBox *m_lang;
QCheckBox *m_checkUpdate;
};
#endif // SETTINGSDIALOG_H
+1
View File
@@ -1,6 +1,7 @@
#include <vector>
#include <string>
#include <iostream>
#include <fstream>
#include "../src/rawimage.h"
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"
Binary file not shown.
+33 -14
View File
@@ -174,17 +174,17 @@
<context>
<name>DataBaseView</name>
<message>
<location filename="../src/databaseview.cpp" line="327"/>
<location filename="../src/databaseview.cpp" line="328"/>
<source>Select columns</source>
<translation>Select columns</translation>
</message>
<message>
<location filename="../src/databaseview.cpp" line="378"/>
<location filename="../src/databaseview.cpp" line="379"/>
<source>Text to search, you can % as wildcard</source>
<translation>Text to search, you can % as wildcard</translation>
</message>
<message>
<location filename="../src/databaseview.cpp" line="394"/>
<location filename="../src/databaseview.cpp" line="395"/>
<source>Filter</source>
<translation>Filter</translation>
</message>
@@ -192,27 +192,27 @@
<context>
<name>DatabaseTableView</name>
<message>
<location filename="../src/databaseview.cpp" line="265"/>
<location filename="../src/databaseview.cpp" line="266"/>
<source>Mark</source>
<translation>Mark</translation>
</message>
<message>
<location filename="../src/databaseview.cpp" line="266"/>
<location filename="../src/databaseview.cpp" line="267"/>
<source>Unmark</source>
<translation>Unmark</translation>
</message>
<message>
<location filename="../src/databaseview.cpp" line="267"/>
<location filename="../src/databaseview.cpp" line="268"/>
<source>Open</source>
<translation>Open</translation>
</message>
<message>
<location filename="../src/databaseview.cpp" line="268"/>
<location filename="../src/databaseview.cpp" line="269"/>
<source>Open file location</source>
<translation>Open file location</translation>
</message>
<message>
<location filename="../src/databaseview.cpp" line="269"/>
<location filename="../src/databaseview.cpp" line="270"/>
<source>Copy files</source>
<translation>Copy files</translation>
</message>
@@ -220,23 +220,42 @@
<context>
<name>DatabaseTreeSettings</name>
<message>
<location filename="../src/databasetree.cpp" line="13"/>
<source>Add tree filter</source>
<translation>Add tree filter</translation>
<translation type="vanished">Add tree filter</translation>
</message>
<message>
<location filename="../src/databasetree.cpp" line="16"/>
<source>Add tree grouping</source>
<translation>Add tree grouping</translation>
</message>
<message>
<location filename="../src/databasetree.cpp" line="41"/>
<source>Aggregate function</source>
<translation>Aggregate function</translation>
</message>
<message>
<location filename="../src/databasetree.cpp" line="45"/>
<source>This aggregate function will be applied to last level of grouping</source>
<translation>This aggregate function will be applied to last level of grouping</translation>
</message>
</context>
<context>
<name>DatabaseTreeView</name>
<message>
<location filename="../src/databasetree.cpp" line="358"/>
<location filename="../src/databasetree.cpp" line="480"/>
<source>Add</source>
<translation>Add</translation>
</message>
<message>
<location filename="../src/databasetree.cpp" line="359"/>
<location filename="../src/databasetree.cpp" line="481"/>
<source>Remove</source>
<translation>Remove</translation>
</message>
<message>
<location filename="../src/databasetree.cpp" line="482"/>
<source>Tree/Table</source>
<translation>Tree/Table</translation>
</message>
</context>
<context>
<name>Dialog</name>
@@ -249,7 +268,7 @@
<context>
<name>FITSFileModel</name>
<message>
<location filename="../src/databaseview.cpp" line="244"/>
<location filename="../src/databaseview.cpp" line="245"/>
<source>File name</source>
<translation>File name</translation>
</message>
@@ -431,7 +450,7 @@
<context>
<name>HelpDialog</name>
<message>
<location filename="../src/about.cpp" line="33"/>
<location filename="../src/about.cpp" line="35"/>
<source>Help</source>
<translation>Help</translation>
</message>
Binary file not shown.
+29 -14
View File
@@ -174,17 +174,17 @@
<context>
<name>DataBaseView</name>
<message>
<location filename="../src/databaseview.cpp" line="327"/>
<location filename="../src/databaseview.cpp" line="328"/>
<source>Select columns</source>
<translation>Choix des colonnes</translation>
</message>
<message>
<location filename="../src/databaseview.cpp" line="378"/>
<location filename="../src/databaseview.cpp" line="379"/>
<source>Text to search, you can % as wildcard</source>
<translation>Texte à chercher, utilisez % comme caractère générique</translation>
</message>
<message>
<location filename="../src/databaseview.cpp" line="394"/>
<location filename="../src/databaseview.cpp" line="395"/>
<source>Filter</source>
<translation>Filtre</translation>
</message>
@@ -192,27 +192,27 @@
<context>
<name>DatabaseTableView</name>
<message>
<location filename="../src/databaseview.cpp" line="265"/>
<location filename="../src/databaseview.cpp" line="266"/>
<source>Mark</source>
<translation>Marquer</translation>
</message>
<message>
<location filename="../src/databaseview.cpp" line="266"/>
<location filename="../src/databaseview.cpp" line="267"/>
<source>Unmark</source>
<translation>Décocher</translation>
</message>
<message>
<location filename="../src/databaseview.cpp" line="267"/>
<location filename="../src/databaseview.cpp" line="268"/>
<source>Open</source>
<translation type="unfinished">Ouvrir</translation>
</message>
<message>
<location filename="../src/databaseview.cpp" line="268"/>
<location filename="../src/databaseview.cpp" line="269"/>
<source>Open file location</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/databaseview.cpp" line="269"/>
<location filename="../src/databaseview.cpp" line="270"/>
<source>Copy files</source>
<translation type="unfinished"></translation>
</message>
@@ -220,23 +220,38 @@
<context>
<name>DatabaseTreeSettings</name>
<message>
<location filename="../src/databasetree.cpp" line="13"/>
<source>Add tree filter</source>
<location filename="../src/databasetree.cpp" line="16"/>
<source>Add tree grouping</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/databasetree.cpp" line="41"/>
<source>Aggregate function</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/databasetree.cpp" line="45"/>
<source>This aggregate function will be applied to last level of grouping</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>DatabaseTreeView</name>
<message>
<location filename="../src/databasetree.cpp" line="358"/>
<location filename="../src/databasetree.cpp" line="480"/>
<source>Add</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/databasetree.cpp" line="359"/>
<location filename="../src/databasetree.cpp" line="481"/>
<source>Remove</source>
<translation type="unfinished">Supprimer</translation>
</message>
<message>
<location filename="../src/databasetree.cpp" line="482"/>
<source>Tree/Table</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>Dialog</name>
@@ -249,7 +264,7 @@
<context>
<name>FITSFileModel</name>
<message>
<location filename="../src/databaseview.cpp" line="244"/>
<location filename="../src/databaseview.cpp" line="245"/>
<source>File name</source>
<translation>Nom de fichier</translation>
</message>
@@ -431,7 +446,7 @@
<context>
<name>HelpDialog</name>
<message>
<location filename="../src/about.cpp" line="33"/>
<location filename="../src/about.cpp" line="35"/>
<source>Help</source>
<translation>Aide</translation>
</message>
Binary file not shown.
+30 -15
View File
@@ -175,17 +175,17 @@
<context>
<name>DataBaseView</name>
<message>
<location filename="../src/databaseview.cpp" line="327"/>
<location filename="../src/databaseview.cpp" line="328"/>
<source>Select columns</source>
<translation>Selecionar colunas</translation>
</message>
<message>
<location filename="../src/databaseview.cpp" line="378"/>
<location filename="../src/databaseview.cpp" line="379"/>
<source>Text to search, you can % as wildcard</source>
<translation>Texto a ser pesquisado, você pode % como curinga</translation>
</message>
<message>
<location filename="../src/databaseview.cpp" line="394"/>
<location filename="../src/databaseview.cpp" line="395"/>
<source>Filter</source>
<translation>Filtro</translation>
</message>
@@ -193,27 +193,27 @@
<context>
<name>DatabaseTableView</name>
<message>
<location filename="../src/databaseview.cpp" line="265"/>
<location filename="../src/databaseview.cpp" line="266"/>
<source>Mark</source>
<translation>Assinalar</translation>
</message>
<message>
<location filename="../src/databaseview.cpp" line="266"/>
<location filename="../src/databaseview.cpp" line="267"/>
<source>Unmark</source>
<translation>Desmarcar</translation>
</message>
<message>
<location filename="../src/databaseview.cpp" line="267"/>
<location filename="../src/databaseview.cpp" line="268"/>
<source>Open</source>
<translation type="unfinished">Abrir</translation>
</message>
<message>
<location filename="../src/databaseview.cpp" line="268"/>
<location filename="../src/databaseview.cpp" line="269"/>
<source>Open file location</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/databaseview.cpp" line="269"/>
<location filename="../src/databaseview.cpp" line="270"/>
<source>Copy files</source>
<translation type="unfinished"></translation>
</message>
@@ -221,23 +221,38 @@
<context>
<name>DatabaseTreeSettings</name>
<message>
<location filename="../src/databasetree.cpp" line="13"/>
<source>Add tree filter</source>
<location filename="../src/databasetree.cpp" line="16"/>
<source>Add tree grouping</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/databasetree.cpp" line="41"/>
<source>Aggregate function</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/databasetree.cpp" line="45"/>
<source>This aggregate function will be applied to last level of grouping</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>DatabaseTreeView</name>
<message>
<location filename="../src/databasetree.cpp" line="358"/>
<location filename="../src/databasetree.cpp" line="480"/>
<source>Add</source>
<translation type="unfinished">Add</translation>
</message>
<message>
<location filename="../src/databasetree.cpp" line="359"/>
<location filename="../src/databasetree.cpp" line="481"/>
<source>Remove</source>
<translation type="unfinished">Retirar</translation>
</message>
<message>
<location filename="../src/databasetree.cpp" line="482"/>
<source>Tree/Table</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>Dialog</name>
@@ -250,7 +265,7 @@
<context>
<name>FITSFileModel</name>
<message>
<location filename="../src/databaseview.cpp" line="244"/>
<location filename="../src/databaseview.cpp" line="245"/>
<source>File name</source>
<translation>Nome do arquivo</translation>
</message>
@@ -265,7 +280,7 @@
<message>
<location filename="../src/fitskeyword.ui" line="41"/>
<source>Add</source>
<translation type="unfinished">Add</translation>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/fitskeyword.ui" line="48"/>
@@ -432,7 +447,7 @@
<context>
<name>HelpDialog</name>
<message>
<location filename="../src/about.cpp" line="33"/>
<location filename="../src/about.cpp" line="35"/>
<source>Help</source>
<translation>Ajuda</translation>
</message>
Binary file not shown.
+33 -14
View File
@@ -174,17 +174,17 @@
<context>
<name>DataBaseView</name>
<message>
<location filename="../src/databaseview.cpp" line="327"/>
<location filename="../src/databaseview.cpp" line="328"/>
<source>Select columns</source>
<translation>Vyber stĺpce</translation>
</message>
<message>
<location filename="../src/databaseview.cpp" line="378"/>
<location filename="../src/databaseview.cpp" line="379"/>
<source>Text to search, you can % as wildcard</source>
<translation>Text na vyhľadanie, môžete použit % ako zástupný znak</translation>
</message>
<message>
<location filename="../src/databaseview.cpp" line="394"/>
<location filename="../src/databaseview.cpp" line="395"/>
<source>Filter</source>
<translatorcomment>Meno súboru</translatorcomment>
<translation>Filter</translation>
@@ -193,27 +193,27 @@
<context>
<name>DatabaseTableView</name>
<message>
<location filename="../src/databaseview.cpp" line="265"/>
<location filename="../src/databaseview.cpp" line="266"/>
<source>Mark</source>
<translation>Označiť</translation>
</message>
<message>
<location filename="../src/databaseview.cpp" line="266"/>
<location filename="../src/databaseview.cpp" line="267"/>
<source>Unmark</source>
<translation>Odznačiť</translation>
</message>
<message>
<location filename="../src/databaseview.cpp" line="267"/>
<location filename="../src/databaseview.cpp" line="268"/>
<source>Open</source>
<translation>Otvoriť</translation>
</message>
<message>
<location filename="../src/databaseview.cpp" line="268"/>
<location filename="../src/databaseview.cpp" line="269"/>
<source>Open file location</source>
<translation>Otvoriť umiestnenie súboru</translation>
</message>
<message>
<location filename="../src/databaseview.cpp" line="269"/>
<location filename="../src/databaseview.cpp" line="270"/>
<source>Copy files</source>
<translation>Skopírovať súbory</translation>
</message>
@@ -221,23 +221,42 @@
<context>
<name>DatabaseTreeSettings</name>
<message>
<location filename="../src/databasetree.cpp" line="13"/>
<source>Add tree filter</source>
<translation>Pridať stromový filter</translation>
<translation type="vanished">Pridať stromový filter</translation>
</message>
<message>
<location filename="../src/databasetree.cpp" line="16"/>
<source>Add tree grouping</source>
<translation>Pridaj stromové zoskupovanie</translation>
</message>
<message>
<location filename="../src/databasetree.cpp" line="41"/>
<source>Aggregate function</source>
<translation>Agregačná funkcia</translation>
</message>
<message>
<location filename="../src/databasetree.cpp" line="45"/>
<source>This aggregate function will be applied to last level of grouping</source>
<translation>Táto agregáčná funkcia bude použitá na poslednú úroveň zoskupovania</translation>
</message>
</context>
<context>
<name>DatabaseTreeView</name>
<message>
<location filename="../src/databasetree.cpp" line="358"/>
<location filename="../src/databasetree.cpp" line="480"/>
<source>Add</source>
<translation>Pridať</translation>
</message>
<message>
<location filename="../src/databasetree.cpp" line="359"/>
<location filename="../src/databasetree.cpp" line="481"/>
<source>Remove</source>
<translation>Odstrániť</translation>
</message>
<message>
<location filename="../src/databasetree.cpp" line="482"/>
<source>Tree/Table</source>
<translation>Strom/Tabuľka</translation>
</message>
</context>
<context>
<name>Dialog</name>
@@ -250,7 +269,7 @@
<context>
<name>FITSFileModel</name>
<message>
<location filename="../src/databaseview.cpp" line="244"/>
<location filename="../src/databaseview.cpp" line="245"/>
<source>File name</source>
<translation>Meno súboru</translation>
</message>
@@ -432,7 +451,7 @@
<context>
<name>HelpDialog</name>
<message>
<location filename="../src/about.cpp" line="33"/>
<location filename="../src/about.cpp" line="35"/>
<source>Help</source>
<translation>Pomoc</translation>
</message>