Compare commits

...

54 Commits

Author SHA1 Message Date
nou b7f1a0abc9 Add id_file_key index to database 2026-03-22 09:55:19 +01:00
nou 33c976d3c9 Remember a selected filter keyword in database view 2026-03-22 09:54:57 +01:00
nou a17001cdf9 Trim whitespace string from XISF 2026-03-21 22:21:35 +01:00
nou 305c1d1f55 Deffered SQL query when database is visible 2026-03-21 20:33:47 +01:00
nou 95808b094d Fix buidling query 2026-03-21 20:31:44 +01:00
nou 2b56af27fe Add explicit link to Svg module to solve some issues with SVG icon 2026-03-15 17:47:51 +01:00
nou 8edf746827 Use bindvalue in DatabaseTableView 2026-03-15 17:47:24 +01:00
nou 729a330e6c Add backspace as move to trash shortcut for MacOS 2026-03-15 17:45:01 +01:00
nou 1ac5a4e42a Update metainfo 2026-02-16 22:52:28 +01:00
nou 83d212aa91 Enable sorting of FITS header 2026-02-16 22:29:25 +01:00
nou bd24fba407 Update README 2026-02-11 21:26:33 +01:00
nou 3448f62f31 Try to fix crash in ImageRingList 2026-01-19 20:57:13 +01:00
nou 567e66acb5 Update libXISF 2025-11-02 23:17:10 +01:00
nou 9e79133464 Fix compile error 2025-11-01 12:11:53 +01:00
nou e08107aa13 Improve Save as 2025-11-01 12:06:24 +01:00
nou 6eda2c4e48 Remove some code 2025-10-20 23:48:47 +02:00
nou b16ae3a9ee Add language setting 2025-10-20 21:29:59 +02:00
nou 56bba27ae3 Give save filter only formats that are supported 2025-10-20 00:23:11 +02:00
nou 1070dc32c1 Update metainfo 2025-09-15 15:55:00 +02:00
nou f61cf12f0a Update help files 2025-09-15 15:49:30 +02:00
nou 530b0c62c3 Open parent directory if it doesn't exist 2025-09-15 15:49:16 +02:00
nou 7e95440dd6 Update translations 2025-09-15 10:58:29 +02:00
nou 03492972cb Include all types in completion 2025-09-14 20:44:36 +02:00
nou 9cca183677 Working copy/move operation 2025-09-14 20:44:07 +02:00
nou afd059b36b Add some margins when retriving objects 2025-09-14 13:43:20 +02:00
nou 1b9f218acb Console line with simple auto completion 2025-08-17 17:39:01 +02:00
nou 32f91d7b2f Add PCL:AstrometricSolution to XISF solved files 2025-08-03 20:58:01 +02:00
nou 69fbad34b6 Add image index to FITSRecordModify 2025-08-03 20:57:33 +02:00
nou e026042604 Use ifFITS isXISF functions 2025-08-03 20:37:40 +02:00
nou bb7e5182af Fix metainfo linter 2025-07-27 17:45:23 +02:00
nou f0152e2496 Fix compilation issue 2025-07-27 17:33:14 +02:00
nou eccf928032 Make path in file manager editable 2025-07-27 16:29:44 +02:00
nou 897306d1c3 Add fitskeyword.ui 2025-07-27 15:40:06 +02:00
nou cbc779090f Prioritize M number then IC for object name 2025-07-27 15:36:19 +02:00
nou d826744f26 Add tabs to file manager 2025-07-27 15:35:25 +02:00
nou 3bdfb12d4f Disable use of QFileSystemModel on ARM platform 2025-07-27 10:06:38 +02:00
nou c416ae9941 Fix suffix handling, do not index PCL: properties 2025-07-26 18:19:46 +02:00
nou abbba2890f Add context menu to hide columns in file manager 2025-07-26 17:50:54 +02:00
nou 9c6847d334 Do not pass XISF properies into WCS 2025-07-26 15:49:21 +02:00
nou af1c26a9fe Use PCL:AstrometricSolution properties to construct WCS 2025-07-26 15:48:58 +02:00
nou e0441a6494 Change type to uint64_t 2025-07-24 20:22:06 +02:00
nou a88f05a9fe Add filemanager 2025-07-21 20:16:34 +02:00
nou b58559a18a Update modify FITS header script 2025-07-13 10:43:15 +02:00
nou 2ac14a6c04 Fix thumbnailer compilation 2025-07-13 10:42:59 +02:00
nou b84256625c Add stellarsolver6 as name 2025-06-12 16:48:09 +02:00
nou 202a2b11b7 Add marked files in batch processing 2025-06-09 19:09:50 +02:00
nou 32f192ed7e Add draw grid button 2025-05-31 00:19:15 +02:00
nou a0422683bd Move source files to src directory 2025-05-30 16:49:33 +02:00
nou ce67b35bfa Mention OpenNGC 2025-05-29 23:44:55 +02:00
nou f016500f12 Include ngc db 2025-05-29 17:39:13 +02:00
nou 6069ebbbac Refractor drawing grid 2025-05-27 16:26:03 +02:00
nou e587d84e05 Remove that empty action 2025-05-26 17:05:19 +02:00
nou c01f2e328a Add sky grid painting 2025-05-26 15:50:37 +02:00
nou 8b498bbe73 Prefer writing keyword as integer 2025-05-24 23:13:14 +02:00
87 changed files with 4279 additions and 1986 deletions
+37 -35
View File
@@ -17,45 +17,47 @@ 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)
find_library(WCS_LIB wcs wcslib REQUIRED)
find_library(LCMS2_LIB lcms2 REQUIRED)
find_library(STELLARSOLVER_LIB stellarsolver)
find_library(STELLARSOLVER_LIB NAMES stellarsolver stellarsolver6)
add_subdirectory(libXISF)
set(TENMON_SRC
about.cpp about.h
batchprocessing.cpp batchprocessing.h batchprocessing.ui
chartgraph.h chartgraph.cpp
database.cpp database.h
databaseview.cpp databaseview.h
delete.cpp
filesystemwidget.cpp filesystemwidget.h
histogram.cpp histogram.h
httpdownloader.h httpdownloader.cpp
imageinfo.cpp imageinfo.h
imageinfodata.cpp imageinfodata.h
imageringlist.cpp imageringlist.h
imagescrollarea.cpp imagescrollarea.h
imagewidget.h imagewidget.cpp
loadimage.h loadimage.cpp
loadrunable.cpp loadrunable.h
main.cpp
mainwindow.cpp mainwindow.h
markedfiles.cpp markedfiles.h
mtfparam.h
rawimage.cpp rawimage.h
rawimage_sse.cpp
scriptengine.cpp scriptengine.h
settingsdialog.cpp settingsdialog.h
statusbar.cpp statusbar.h
stfslider.cpp stfslider.h
stretchtoolbar.cpp stretchtoolbar.h
tfloat16.h
src/about.cpp src/about.h
src/batchprocessing.cpp src/batchprocessing.h src/batchprocessing.ui
src/chartgraph.h src/chartgraph.cpp
src/database.cpp src/database.h
src/databaseview.cpp src/databaseview.h
src/delete.cpp
src/filemanager.h src/filemanager.cpp src/filemanager.ui
src/filesystemwidget.cpp src/filesystemwidget.h
src/fitskeyword.ui
src/histogram.cpp src/histogram.h
src/httpdownloader.h src/httpdownloader.cpp
src/imageinfo.cpp src/imageinfo.h
src/imageinfodata.cpp src/imageinfodata.h
src/imageringlist.cpp src/imageringlist.h
src/imagescrollarea.cpp src/imagescrollarea.h
src/imagewidget.h src/imagewidget.cpp
src/loadimage.h src/loadimage.cpp
src/loadrunable.cpp src/loadrunable.h
src/main.cpp
src/mainwindow.cpp src/mainwindow.h
src/markedfiles.cpp src/markedfiles.h
src/mtfparam.h
src/rawimage.cpp src/rawimage.h
src/rawimage_sse.cpp
src/scriptengine.cpp src/scriptengine.h
src/settingsdialog.cpp src/settingsdialog.h
src/statusbar.cpp src/statusbar.h
src/stfslider.cpp src/stfslider.h
src/stretchtoolbar.cpp src/stretchtoolbar.h
src/tfloat16.h
thumbnailer/genthumbnail.cpp thumbnailer/genthumbnail.h
)
@@ -77,7 +79,7 @@ endif()
qt_add_executable(tenmon WIN32 MACOSX_BUNDLE ${tenmon_ICON} ${TENMON_SRC})
find_path(FITS_INCLUDE fitsio2.h PATH_SUFFIXES cfitsio REQUIRED)
target_include_directories(tenmon PRIVATE ${FITS_INCLUDE} ${CMAKE_BINARY_DIR} ${libXISF_SOURCE_DIR})
target_include_directories(tenmon PRIVATE ${FITS_INCLUDE} ${CMAKE_BINARY_DIR} ${libXISF_SOURCE_DIR} "src")
option(COLOR_MANAGMENT "Enable sRGB framebuffer support for gamma correct images and color profiles support" ON)
if(COLOR_MANAGMENT)
@@ -96,14 +98,14 @@ if(STELLARSOLVER_INCLUDE AND STELLARSOLVER_LIB)
endif(MXE)
target_compile_definitions(tenmon PRIVATE "PLATESOLVER")
target_sources(tenmon PRIVATE
solver.cpp solver.h
platesolving.cpp platesolving.h platesolving.ui
platesolvingsettings.cpp platesolvingsettings.h platesolvingsettings.ui
src/solver.cpp src/solver.h
src/platesolving.cpp src/platesolving.h src/platesolving.ui
src/platesolvingsettings.cpp src/platesolvingsettings.h src/platesolvingsettings.ui
)
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)
+7 -1
View File
@@ -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,12 @@ 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.
Using OpenNGC database https://github.com/mattiaverga/OpenNGC under CC-BY-SA-4.0 https://creativecommons.org/licenses/by-sa/4.0/
+17 -3
View File
@@ -61,7 +61,7 @@ This image should be 256 pixel wide. Each row of image will be used as separate
<li>mid point - defines the value to be stretched to 50% intensity</li>
<li>white point - all pixels with higher value (brighter) than this will be clipped white</li>
</ul>
<p>Following the slider are 7 buttons for automatic stretching:</p>
<p>Following the slider are 8 buttons to control image display:</p>
<ul>
<li><i>Linked channels</i> toggle stretching each RGB channel individually.</li>
<li><i>Auto Stretch</i> automatically apply black and mid points to render the image with optimal brightness.</li>
@@ -70,6 +70,7 @@ This image should be 256 pixel wide. Each row of image will be used as separate
<li><i>False colors</i> show black and white in false colour palette.</li>
<li><i>Debayer CFA</i> Demosaicing black and white image from CFA sensor to color one.</li>
<li><i>Apply Auto stretch on load</i> toggle automatically applying Autostretch for each image when loaded.</li>
<li><i>Draw equatorial grid</i> toggle drawing equatorial coordinate grid and catalogue objects. Needs that file have WCS data.</li>
</ul>
<h3>Marking images</h3>
@@ -134,12 +135,24 @@ solver.
<p>Then finally there are various action button. Settings button show dialog where you can set path to existing index files or auto download some.
Extract button will just extract stars from image and it will show their count, HFR and eccentricity. This action doesn't need index files.
Solve button will try to find coordinates of images. Abort button will stop extraction or solving. Update FITS header will update FITS fits keywords
with found solution.
with found solution.</p>
<p>In settings dialog you can set path to index files which is by default custom internal one. It also try to locate commonly used path from other
programs like KStars for astrometry.net index files.
</p>
<h3>File Manager</h3>
<p>
This is simple double panel file manager. It can show columns with selected FITS keywords. Each panel have tabs where it easily switch between
multiple directories. You can copy or move files and directories either inside one panel or between two panels by selecting and then dragging.
By default files are copies. To move then press Shift key before start dragging. Double click on file will open it in main window if it is image
or other file it will open default program that is associated with it this file type. You can also drag file to other programs like from default
file explorer programs that are in system or from file explorer to this program.
</p>
<p>
In menu you can select which FITS keywords will be showed. Temporarilly disable loading FITS header or copy file paths of selected files as text.
</p>
<h3>Batch processing</h3>
<p>This module allow to write scripts in JavaScript that process image files. Batch Processing window consist from three main parts. On top is list of input files and directories.
You can add directories or individual files to this list. Directories are scanned recursively to find all files even non image files. This list of files is then passed to script in array named <b>files</b>.
@@ -159,7 +172,8 @@ this output directory is ignored.</p>
<p>Bellow that is list of scripts. These scripts are located in application data directory. Select script which you want to run by clicking on it. Clicking on <i>Open scripts</i> will open directory with these scripts where you create new and edit them.
Location of this directory is on Windows: "C:/Users/&lt;USER&gt;/AppData/Roaming/nou/Tenmon/scripts" Linux: "~/.local/share/nou/Tenmon/scripts" MacOS: "~/Library/Application Support/nou/Tenmon/scripts"</p>
<p>Next is Log windows that contain any messages that come from scripts. Mainly calls to <code>core.log()</code> At bottom there buttons that can start or stop execution of selected scripts.</p>
<p>Next is Log windows that contain any messages that come from scripts. Mainly calls to <code>core.log()</code> At bottom there is console that enable run simple script commands and
buttons that can start or stop execution of selected scripts.</p>
<h4>core</h4>
<p>There is global object called <b>core</b> that have these methods.</p>
+45
View File
@@ -51,6 +51,17 @@ na ktorej sa dajú nastaviť tri body.
<li>stredný bod - pixeli s touto hodnotou budú zobrazené ako 50% šedá</li>
<li>biely bod - pixeli nad touto hodnotou budú zobrazené ako biele</li>
</ul>
<p>Nasleduje 8 tlačidiel pre nastavenie zobrazenie obrázka:</p>
<ul>
<li><i>Prepojené kanály</i> prepína medzi nataihnutím jasových urovní pre každý RGB kanál zvlášť alebo jednotné pre všetky kanály.</li>
<li><i>Automatické natiahnutie</i> automaticky nastavý čierny, šedý a biely bod pre optimálne zobrazenie..</li>
<li><i>Resetuj funkciu prevodu na obrazovku</i> nastavý hodnoty čierneho, šedého a bieleho bodu na východzie hodnoty.</li>
<li><i>Invertuj farby</i> invertuje zobrazené farby a zobrazý obrázok ako negatív.</li>
<li><i>Falošné farby</i> pre zobrazenie čiernobielych obrázkov sa použije farebná paleta.</li>
<li><i>Preved CFA na farbu</i> Demosaicing black and white image from CFA sensor to color one.</li>
<li><i>Aplikuj automatické natiahnutie pri načítaní</i> pri zapnutí </li>
<li><i>Vykresli equatoriálnu mriežku</i> zapína zobrazenie equatoriálnej mriežky. Je poitrebné aby súbor obsahoval WCS dáta.</li>
</ul>
<p>Prvé tlačidlo prepína prepojenie nastavenia čierneho, stretdného a bieleho bodu. Po prepnutí sa dá každý farebný kanál nastaviť
samostatne.
Nasleduje tlačidlo ktoré nastaví hodnoty čierneho a stredného bodu tak aby bol obrázok zobrazený optimálnym jasom.
@@ -94,6 +105,34 @@ V nasledovnom príklade sa vyhľadajú súbory ktoré majú v mene súboru "Bias
zástupný znak za hocijaký reťazec znakov aj žiadny. Znak _ je tiež zástupný znak zastupujúci práve jeden znak.
Bez použitia zástupných znakov sa vyhľadá iba presný výskyt.</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.
<b>Profil</b> toto nastavuje rôzne parametre ovplivňujúce extrahovanie hviezd a hľadanie koordinátov.
<b>Štartovný bod</b> program sa pokúsi automaticky určiť bod začiatku hľadania koordinátov pre zrýchlenie hľadania.
Možete nechať jednu alebo obydve voľbi nezaškrtnuté kedy sa bude hľadať riešenie naslepo. Ak je pozícia alebo škála nesprávna hľadanie môže zlyhať.
<b>Riešenie</b> táto časť obsahuje výsledné riešenie hľadania ako sú RA,DEC koordináty stredu obrázku, veľkosť zorného poľa, uhol natočenia v stupňoch,
škála obrázku v arcsekundách na pixel, počet nájdených hviezd a veľkosť hviezd HFR a excentricita. Potom je tu ešte okno so záznamom z riešenia.
</p>
<p>Pod tým je tlačidlo pre nastavenie cesty k indexovým súborom a ich stiahnutie. Je možné buď stiahnuť indexové súbory alebo použiť už existujúce súbory.
Tlačidlo Extrahovať nájde hviezdy a zobrazý ich počet veľkosť HFR a excentricitu. Na toto nie sú potrebné indexové súbory.
Tlačidlo Vyriešiť sa pokúsi nájsť koordináty obrázka. Zrušiť preruší hľadanie riešenia. Aktualizovať FITS hlavičku zapíše najdené riešenie do súboru.</p>
<p>In settings dialog you can set path to index files which is by default custom internal one. It also try to locate commonly used path from other
programs like KStars for astrometry.net index files.
</p>
<h3>File Manager</h3>
<p>
This is simple double panel file manager. It can show columns with selected FITS keywords. Each panel have tabs where it easily switch between
multiple directories. You can copy or move files and directories either inside one panel or between two panels by selecting and then dragging.
By default files are copies. To move then press Shift key before start dragging. Double click on file will open it in main window if it is image
or other file it will open default program that is associated with it this file type. You can also drag file to other programs like from default
file explorer programs that are in system or from file explorer to this program.
</p>
<p>
In menu you can select which FITS keywords will be showed. Temporarilly disable loading FITS header or copy file paths of selected files as text.
</p>
<h3>Hromadné spracovanie</h3>
Tento modul umožnuje písanie skriptov v JavaScripte ktoré spracujú súbory obrázkov. Okno Hromadného spracovanie pozostáva z troch častí. Navrchu je zoznam vstupných súborov a adresárov.
@@ -109,6 +148,12 @@ V skripte potom cez toto pole iteruje nasledovne.
}
</pre>
<p>Pod týmto zoznamom je výstupný adresár. Všetky relatívne cesty predané do metod ako sú copy(), move() alebo convert() budú relatívne voči tomuto adresáru.
Ak je ako argument použitá absolútna cesta tak je tento vystupný adresár ignorovaný.</p>
<p>Nasleduje logovacie okno kde sú zapisováné všetký výpisy z behu scriptu. Hlavne volania z <code>core.log()</code> Na spodu je konzola kde je možné vkladať jednoduché príkazy a nakoniec ešte tlačítka
ktoré spúštať alebo zastavovať vybraný skript.</p>
<h4>core</h4>
V skripte je dostupný globálny objekt nazvaný <b>core</b> ktorý má nasledovné metódy.
<ul>
+1 -1
Submodule libXISF updated: 9a32138f6a...7b70b6a081
+141
View File
@@ -0,0 +1,141 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="48"
height="48"
viewBox="0 0 12.699999 12.699999"
version="1.1"
id="svg5"
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
sodipodi:docname="grid.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
showgrid="false"
inkscape:zoom="9.5144352"
inkscape:cx="39.361243"
inkscape:cy="25.067174"
inkscape:window-width="2510"
inkscape:window-height="1371"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" />
<defs
id="defs2">
<inkscape:path-effect
effect="skeletal"
id="path-effect636"
is_visible="true"
lpeversion="1"
pattern="M 0,4.992138 C 0,2.2364778 2.2364778,0 4.992138,0 c 2.7556601,0 4.9921379,2.2364778 4.9921379,4.992138 0,2.7556601 -2.2364778,4.9921379 -4.9921379,4.9921379 C 2.2364778,9.9842759 0,7.7477981 0,4.992138 Z"
copytype="single_stretched"
prop_scale="1"
scale_y_rel="false"
spacing="0"
normal_offset="0"
tang_offset="0"
prop_units="false"
vertical_pattern="false"
hide_knot="false"
fuse_tolerance="0" />
<inkscape:path-effect
effect="skeletal"
id="path-effect632"
is_visible="true"
lpeversion="1"
pattern="M 0,4.992138 C 0,2.2364778 2.2364778,0 4.992138,0 c 2.7556601,0 4.9921379,2.2364778 4.9921379,4.992138 0,2.7556601 -2.2364778,4.9921379 -4.9921379,4.9921379 C 2.2364778,9.9842759 0,7.7477981 0,4.992138 Z"
copytype="single_stretched"
prop_scale="1"
scale_y_rel="false"
spacing="0"
normal_offset="0"
tang_offset="0"
prop_units="false"
vertical_pattern="false"
hide_knot="false"
fuse_tolerance="0" />
<inkscape:path-effect
effect="skeletal"
id="path-effect628"
is_visible="true"
lpeversion="1"
pattern="M 0,4.992138 C 0,2.2364778 2.2364778,0 4.992138,0 c 2.7556601,0 4.9921379,2.2364778 4.9921379,4.992138 0,2.7556601 -2.2364778,4.9921379 -4.9921379,4.9921379 C 2.2364778,9.9842759 0,7.7477981 0,4.992138 Z"
copytype="single_stretched"
prop_scale="1"
scale_y_rel="false"
spacing="0"
normal_offset="0"
tang_offset="0"
prop_units="false"
vertical_pattern="false"
hide_knot="false"
fuse_tolerance="0" />
<inkscape:path-effect
effect="bspline"
id="path-effect624"
is_visible="true"
lpeversion="1"
weight="33.333333"
steps="2"
helper_size="0"
apply_no_weight="true"
apply_with_weight="true"
only_selected="false" />
<inkscape:path-effect
effect="spiro"
id="path-effect620"
is_visible="true"
lpeversion="1" />
</defs>
<g
inkscape:label="Vrstva 1"
inkscape:groupmode="layer"
id="layer1">
<path
style="fill:none;stroke:#000000;stroke-width:0.503;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-dasharray:none"
d="M -5,-5 13,13"
id="path616"
sodipodi:nodetypes="cc" />
<circle
style="fill:none;stroke:#000000;stroke-width:0.503;stroke-linejoin:round;stroke-opacity:1;stroke-dasharray:none"
id="path643"
cx="-4.9824347"
cy="-4.9865055"
r="12.973718" />
<circle
style="fill:none;stroke:#000000;stroke-width:0.503;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="path1665"
cx="-4.9600825"
cy="-4.9741392"
r="17.086035" />
<circle
style="fill:none;stroke:#000000;stroke-width:0.503;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="path1667"
cx="-5.0079365"
cy="-5.0034046"
r="21.147657" />
<path
style="fill:none;stroke:#000000;stroke-width:0.467;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-dasharray:none"
d="M 14.371451,3.5622727 -4.9904999,-5.0054782 4.2432806,13.903978"
id="path1734" />
<circle
style="fill:none;stroke:#000000;stroke-width:0.503;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="path1736"
cx="-5.155458"
cy="-5.1256938"
r="9.6808758" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.7 KiB

BIN
View File
Binary file not shown.
+5 -3
View File
@@ -19,14 +19,16 @@
<file>../translations/tenmon_pt_BR.qm</file>
<file alias="help">../about/help_en</file>
<file>colormap.png</file>
<file>ngc.db</file>
<file>grid.svg</file>
</qresource>
<qresource prefix="/" lang="en">
<qresource lang="en" prefix="/">
<file alias="help">../about/help_en</file>
</qresource>
<qresource prefix="/" lang="sk">
<qresource lang="sk" prefix="/">
<file alias="help">../about/help_sk</file>
</qresource>
<qresource prefix="/" lang="fr">
<qresource lang="fr" prefix="/">
<file alias="help">../about/help_fr</file>
</qresource>
</RCC>
+45 -12
View File
@@ -12,10 +12,12 @@ if(files.length == 0)
throw "";
}
let action = core.getItem(["UPDATE", "ADD", "REMOVE"], "Do you want update, add or remove record?");
let action = core.getItem(["UPDATE", "UPDATE_ADD", "ADD", "REMOVE"], "Do you want update, add or remove record?");
let modify = new FITSRecordModify();
let proceed = false;
if(action == "UPDATE")
{
let keywords = files[0].fitsKeywords().filter(checkFITS);
@@ -25,28 +27,59 @@ if(action == "UPDATE")
value = core.getString("Enter new value", value);
else
value = core.getFloat("Enter new value", value);
modify.updateKeyword(keyword, value);
if(keyword && value)
{
proceed = true;
modify.updateKeyword(keyword, value);
}
}
else if(action == "UPDATE_ADD")
{
let keyword = core.getString("Enter keyword to update");
let value = core.getString("Enter new value");
if(keyword && value)
{
proceed = true;
keyword = keyword.toUpperCase();
modify.updateKeyword(keyword, value);
}
}
else if(action == "ADD")
{
let keyword = core.getString("Enter keyword to add");
let value = core.getString("Enter new value");
keyword = keyword.toUpperCase();
modify.addKeyword(keyword, value);
if(keyword && value)
{
proceed = true;
keyword = keyword.toUpperCase();
modify.addKeyword(keyword, value);
}
}
else if(action == "REMOVE")
{
let keywords = files[0].fitsKeywords().filter(checkFITS);
let keyword = core.getItem(keywords, "Select keyword to remove");
modify.removeKeyword(keyword);
}
for(file of files)
{
if(file.suffix() == "fits" || file.suffix() == "fit" || file.suffix() == "xisf")
if(keyword)
{
core.log("Modifing " + file.fileName());
file.modifyFITSRecords(modify);
proceed = true;
modify.removeKeyword(keyword);
}
}
if(proceed)
{
for(file of files)
{
if(file.suffix() == "fits" || file.suffix() == "fit" || file.suffix() == "xisf")
{
core.log("Modifing " + file.fileName());
file.modifyFITSRecords(modify);
}
}
}
else
{
core.log("Canceled");
}
+30 -1
View File
@@ -33,6 +33,7 @@
<li>Color space aware</li>
<li>Histogram</li>
<li>Scripting</li>
<li>Plate solving</li>
</ul>
</description>
<categories>
@@ -46,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>
@@ -58,6 +60,33 @@
</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>
<li>Draw equatorial grid and objects overlay</li>
<li>File Manager</li>
<li>Support for PCL:AstrometricSolution</li>
<li>Script console</li>
</ul>
</description>
</release>
<release version="20250429" date="2025-04-29">
<description>
<ul>
@@ -118,7 +147,7 @@
</release>
<release version="20240816" date="2024-08-16">
<description>
Fix saving image
<p>Fix saving image</p>
</description>
</release>
<release version="20240616" date="2024-06-16">
View File
View File
@@ -13,6 +13,7 @@
#include <QChart>
#include <QChartView>
#include <QLineSeries>
#include <QCompleter>
#include "scriptengine.h"
#include "chartgraph.h"
@@ -100,6 +101,7 @@ BatchProcessing::BatchProcessing(Database *database, QWidget *parent) : QDialog(
connect(_ui->addFilesButton, &QPushButton::released, this, &BatchProcessing::addFiles);
connect(_ui->addDirButton, &QPushButton::released, this, &BatchProcessing::addDir);
connect(_ui->addMarkedButton, &QPushButton::released, this, &BatchProcessing::addMarked);
connect(_ui->removeButton, &QPushButton::released, this, &BatchProcessing::removePath);
connect(_ui->removeAllButton, &QPushButton::released, this, &BatchProcessing::removeAllPaths);
connect(_ui->startButton, &QPushButton::released, this, &BatchProcessing::runScript);
@@ -109,6 +111,31 @@ BatchProcessing::BatchProcessing(Database *database, QWidget *parent) : QDialog(
_textColor = _ui->log->palette().text().color();
_engine = new Script::ScriptEngine(_database, this);
connect(_engine, &Script::ScriptEngine::newMessage, this, &BatchProcessing::newMessage);
_completerModel = new QStringListModel(this);
_completer = new QCompleter(_completerModel, this);
_ui->consoleLineEdit->setCompleter(_completer);
connect(_ui->executeButton, &QPushButton::clicked, _ui->consoleLineEdit, &QLineEdit::returnPressed);
connect(_ui->consoleLineEdit, &QLineEdit::returnPressed, [this](){
if(!_completer->popup()->isVisible())
{
QString program = _ui->consoleLineEdit->text();
QJSValue val = _engine->eval(program);
_ui->consoleLineEdit->addLine();
//qDebug() << val.toString();
}
});
connect(_ui->consoleLineEdit, &QLineEdit::textEdited, [this](const QString &text){
QStringList comp = _engine->complete(text);
//qDebug() << comp;
_completerModel->setStringList(comp);
});
_ui->addFilesButton->setAutoDefault(false);
QSettings settings;
_ui->outputPath->setText(settings.value("batchprocessing/outputpath", QStandardPaths::standardLocations(QStandardPaths::PicturesLocation).first()).toString());
}
@@ -142,6 +169,15 @@ void BatchProcessing::closeEvent(QCloseEvent *event)
}
}
void BatchProcessing::refreshPaths()
{
QStringList paths;
for(int i=0; i<_ui->pathsList->count(); i++)
paths.append(_ui->pathsList->item(i)->text());
_paths = scanDirectories(paths);
_engine->setParams("", _paths, _ui->outputPath->text());
}
void BatchProcessing::addFiles()
{
QSettings settings;
@@ -151,6 +187,7 @@ void BatchProcessing::addFiles()
_ui->pathsList->addItems(files);
settings.setValue("batchprocessing/inputpath", QFileInfo(files.first()).absolutePath());
}
refreshPaths();
}
void BatchProcessing::addDir()
@@ -162,17 +199,32 @@ void BatchProcessing::addDir()
_ui->pathsList->addItem(dir);
settings.setValue("batchprocessing/inputpath", dir);
}
refreshPaths();
}
void BatchProcessing::addMarked()
{
QStringList files = _database->getMarkedFiles();
for(const QString &file : files)
{
QFileInfo info(file);
if(info.exists() && info.isReadable())
_ui->pathsList->addItem(file);
};
refreshPaths();
}
void BatchProcessing::removePath()
{
for(auto &item : _ui->pathsList->selectedItems())
delete item;
refreshPaths();
}
void BatchProcessing::removeAllPaths()
{
_ui->pathsList->clear();
refreshPaths();
}
void BatchProcessing::browse()
@@ -196,9 +248,6 @@ void BatchProcessing::runScript()
_engineThread = new Script::ScriptEngineThread(_database, this);
connect(_engineThread, &Script::ScriptEngineThread::newMessage, this, &BatchProcessing::newMessage);
connect(_engineThread, &Script::ScriptEngineThread::finished, this, &BatchProcessing::scriptFinished);
QStringList paths;
for(int i=0; i<_ui->pathsList->count(); i++)
paths.append(_ui->pathsList->item(i)->text());
QFileInfo outDir(_ui->outputPath->text());
if(outDir.exists() && outDir.isWritable())
@@ -209,7 +258,7 @@ void BatchProcessing::runScript()
else
script = ":/scripts/" + script;
_engineThread->setParams(script, scanDirectories(paths), _ui->outputPath->text());
_engineThread->setParams(script, _paths, _ui->outputPath->text());
_engineThread->start();
_ui->startButton->setEnabled(false);
_ui->stopButton->setEnabled(true);
@@ -317,6 +366,48 @@ void BatchProcessing::plot(const QVariant &graph)
chart->plot(graph);
}
ConsoleLine::ConsoleLine(QWidget *parent) : QLineEdit(parent)
{
}
void ConsoleLine::addLine()
{
QString line = text();
clear();
if(_history.size() && _history.last() == line)return;
_history.append(line);
if(_history.size() > 100)_history.removeFirst();
_currentLine = _history.size();
}
void ConsoleLine::keyReleaseEvent(QKeyEvent *event)
{
if(event->key() == Qt::Key_Up)
{
_currentLine--;
if(_currentLine < 0)
{
_currentLine = -1;
clear();
return;
}
setText(_history.at(_currentLine));
}
else if(event->key() == Qt::Key_Down)
{
_currentLine++;
if(_currentLine >= _history.size())
{
_currentLine = _history.size();
clear();
return;
}
setText(_history.at(_currentLine));
}
else QLineEdit::keyReleaseEvent(event);
}
void openDir(const QString &path)
{
#ifdef Q_OS_LINUX
@@ -3,6 +3,9 @@
#include <QDialog>
#include <QFileSystemWatcher>
#include <QStringListModel>
#include <QCompleter>
#include <QLineEdit>
#include "scriptengine.h"
namespace Ui { class BatchProcessing; }
@@ -16,8 +19,12 @@ class BatchProcessing : public QDialog
QString _scriptBasePath;
QFileSystemWatcher _fileWatcher;
Script::ScriptEngineThread *_engineThread = nullptr;
Script::ScriptEngine *_engine = nullptr;
QColor _textColor;
Database *_database;
QStringListModel *_completerModel = nullptr;
QCompleter *_completer = nullptr;
QList<QPair<QString, QString>> _paths;
private slots:
void scanScriptDir();
public:
@@ -25,9 +32,11 @@ public:
~BatchProcessing();
protected:
void closeEvent(QCloseEvent *event);
void refreshPaths();
public slots:
void addFiles();
void addDir();
void addMarked();
void removePath();
void removeAllPaths();
void browse();
@@ -46,6 +55,18 @@ public slots:
void plot(const QVariant &graph);
};
class ConsoleLine : public QLineEdit
{
Q_OBJECT
public:
explicit ConsoleLine(QWidget *parent = nullptr);
void addLine();
void keyReleaseEvent(QKeyEvent *event) override;
private:
int _currentLine = 0;
QStringList _history;
};
void openDir(const QString &path);
#endif // BATCHPROCESSING_H
+70 -33
View File
@@ -43,6 +43,9 @@
<property name="text">
<string>Add files</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
<item>
@@ -50,6 +53,19 @@
<property name="text">
<string>Add directories</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="addMarkedButton">
<property name="text">
<string>Add marked</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
<item>
@@ -57,6 +73,9 @@
<property name="text">
<string>Remove</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
<item>
@@ -64,6 +83,9 @@
<property name="text">
<string>Remove all</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
@@ -91,6 +113,9 @@
<property name="text">
<string>Browse</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
@@ -122,6 +147,9 @@
<property name="text">
<string>Open scripts</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
@@ -164,23 +192,30 @@
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
<widget class="ConsoleLine" name="consoleLineEdit">
<property name="placeholderText">
<string>Console</string>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</widget>
</item>
<item>
<widget class="QPushButton" name="executeButton">
<property name="text">
<string>Execute</string>
</property>
</spacer>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="startButton">
<property name="text">
<string>Start script</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
<item>
@@ -191,12 +226,8 @@
<property name="text">
<string>Stop script</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="closeButton">
<property name="text">
<string>Close</string>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
@@ -204,23 +235,29 @@
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>ConsoleLine</class>
<extends>QLineEdit</extends>
<header>batchprocessing.h</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>pathsList</tabstop>
<tabstop>addFilesButton</tabstop>
<tabstop>addDirButton</tabstop>
<tabstop>addMarkedButton</tabstop>
<tabstop>removeButton</tabstop>
<tabstop>removeAllButton</tabstop>
<tabstop>browseButton</tabstop>
<tabstop>openScriptsButton</tabstop>
<tabstop>scriptsList</tabstop>
<tabstop>consoleLineEdit</tabstop>
<tabstop>startButton</tabstop>
<tabstop>stopButton</tabstop>
<tabstop>outputPath</tabstop>
<tabstop>log</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
<sender>closeButton</sender>
<signal>released()</signal>
<receiver>BatchProcessing</receiver>
<slot>close()</slot>
<hints>
<hint type="sourcelabel">
<x>973</x>
<y>745</y>
</hint>
<hint type="destinationlabel">
<x>511</x>
<y>383</y>
</hint>
</hints>
</connection>
</connections>
<connections/>
</ui>
View File
+88 -7
View File
@@ -16,10 +16,29 @@ bool Database::init(const QLatin1String &connectionName)
QDir dir(path);
database = QSqlDatabase::addDatabase("QSQLITE", connectionName);
ngc = QSqlDatabase::addDatabase("QSQLITE", connectionName + "ngc");
if(!dir.mkpath("."))
return false;
if(ngc.isValid())
{
QString ngcDb = dir.absoluteFilePath("ngc.db");
if(!QFile::exists(ngcDb))
QFile::copy(":/ngc.db", ngcDb);
ngc.setDatabaseName(ngcDb);
if(ngc.open())
{
m_getNgc = QSqlQuery(ngc);
m_getNgc.prepare("SELECT *,IIF(V_Mag IS NULL, B_Mag, V_Mag) AS mag FROM ngc WHERE RA_deg BETWEEN ? AND ? AND DEC_deg BETWEEN ? AND ?");
}
else
{
qDebug() << "Could not open NGC database";
}
}
if(database.isValid())
{
database.setDatabaseName(dir.absoluteFilePath("database2.db"));
@@ -42,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;
@@ -162,7 +188,7 @@ int Database::checkVersion(QSqlDatabase &db)
return -1;
}
static QStringList nameFilters = {"*.fit", "*.fits", "*.fz", "*.xisf"};
static QStringList nameFilters = {"*.fit", "*.fits", "*.fz", "*.fts", "*.xisf"};
static int countFiles(const QDir &dir, QStringList &scannedDirs)
{
@@ -182,7 +208,6 @@ void Database::indexDir(const QDir &dir, QProgressDialog *progress)
QStringList scannedDirs;
int count = countFiles(dir, scannedDirs);
progress->setMaximum(count);
QSqlDatabase database = QSqlDatabase::database();
database.transaction();
scannedDirs.clear();
@@ -200,7 +225,6 @@ void Database::indexDir(const QDir &dir, QProgressDialog *progress)
void Database::reindex(QProgressDialog *progress)
{
QVariantList deleteids;
QSqlDatabase database = QSqlDatabase::database();
database.transaction();
QSqlQuery size("SELECT COUNT(*) FROM fits_files", database);
size.next();
@@ -239,6 +263,62 @@ QStringList Database::getFitsKeywords()
return keywords;
}
QVector<SkyObject> Database::getObjects(double minRa, double maxRa, double minDec, double maxDec)
{
QVector<SkyObject> objects;
if(!ngc.isOpen())return objects;
m_getNgc.bindValue(0, minRa);
m_getNgc.bindValue(1, maxRa);
m_getNgc.bindValue(2, minDec);
m_getNgc.bindValue(3, maxDec);
if(m_getNgc.exec())
{
while(m_getNgc.next())
{
QString name;
QString name2;
QString m = m_getNgc.value("M").toString();
QString ic = m_getNgc.value("IC").toString();
if(!m.isEmpty())
{
name = "M" + m;
m.clear();
}
else if(!ic.isEmpty())
{
name = "IC" + ic;
ic.clear();
}
else
{
name = m_getNgc.value("Name").toString();
}
if(!ic.isEmpty())name2 += "IC" + ic + " ";
name2 += m_getNgc.value("Common names").toString();
objects.append({
name,
name2,
{m_getNgc.value("RA_deg").toDouble(), m_getNgc.value("DEC_deg").toDouble()},
m_getNgc.value("MajAx").toDouble(),
m_getNgc.value("MinAx").toDouble(),
m_getNgc.value("PosAng").toDouble(),
m_getNgc.value("mag").isNull() ? NAN : m_getNgc.value("mag").toDouble(),
{0, 0},
});
}
}
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;
@@ -279,10 +359,10 @@ bool Database::indexFile(const QFileInfo &file)
}
}
bool ok;
if(filePath.endsWith(".xisf", Qt::CaseInsensitive))
bool ok = false;
if(isXISF(file.suffix()))
ok = readXISFHeader(filePath, info);
else
else if(isFITS(file.suffix()))
ok = readFITSHeader(filePath, info);
qlonglong last_id = -1;
@@ -323,6 +403,7 @@ bool Database::indexFile(const QFileInfo &file)
QVariantList file_id, keys, values, comments;
for(const auto &record : info.fitsHeader)
{
if(record.xisf && record.key.startsWith("PCL:"))continue;
file_id << last_id;
keys << QString(record.key);
values << record.value.toString();
+6
View File
@@ -6,11 +6,13 @@
#include <QSqlQuery>
#include <QDir>
#include <QProgressDialog>
#include "imageinfodata.h"
class Database : public QObject
{
Q_OBJECT
QSqlDatabase database;
QSqlDatabase ngc;
QSqlQuery m_markQuery;
QSqlQuery m_unmarkQuery;
QSqlQuery m_isMarkedQuery;
@@ -22,6 +24,8 @@ class Database : public QObject
QSqlQuery m_headerKeywords;
QSqlQuery m_deleteFile;
QSqlQuery m_getNgc;
int m_progress;
public:
explicit Database(QObject *parent = 0);
@@ -37,6 +41,8 @@ public:
void indexDir(const QDir &dir, QProgressDialog *progress);
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);
+67 -9
View File
@@ -157,32 +157,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 +226,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)
@@ -300,12 +346,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 +386,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 +444,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 +473,8 @@ bool DataBaseView::exportCSV(const QString &path)
}
return true;
}
void DataBaseView::visible(bool visible)
{
if(visible)m_model->load();
}
+3
View File
@@ -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);
};
View File
+766
View File
@@ -0,0 +1,766 @@
#include "filemanager.h"
#include "ui_filemanager.h"
#include "ui_fitskeyword.h"
#include <QSettings>
#include <QStandardPaths>
#include <QDesktopServices>
#include <QMimeData>
#include <QClipboard>
#include <QThread>
#include <QDirIterator>
#include "loadimage.h"
class FileTimes
{
public:
explicit FileTimes(const QString &path)
{
QFile file(path);
#ifndef Q_OS_WIN
birthTime = file.fileTime(QFileDevice::FileBirthTime);
#endif
modificationTime = file.fileTime(QFileDevice::FileModificationTime);
accessTime = file.fileTime(QFileDevice::FileAccessTime);
}
void apply(const QString &path)
{
QFile file(path);
if(file.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::ExistingOnly))
{
#ifndef Q_OS_WIN // Only windows allow changing birth time
file.setFileTime(birthTime, QFileDevice::FileBirthTime);
#endif
file.setFileTime(accessTime, QFileDevice::FileAccessTime);
file.setFileTime(modificationTime, QFileDevice::FileModificationTime);
}
}
private:
QDateTime birthTime;
QDateTime modificationTime;
QDateTime accessTime;
};
FileTransfer::FileTransfer(FileManager *fm) :
_fm(fm)
{
}
FileTransfer::~FileTransfer()
{
_run = false;
}
void FileTransfer::copy(const QStringList &src, const QString &dst)
{
_run = true;
perform(src, dst, true);
emit finished();
}
void FileTransfer::move(const QStringList &src, const QString &dst)
{
_run = true;
perform(src, dst, false);
emit finished();
}
void FileTransfer::cancel()
{
_run = false;
}
void FileTransfer::perform(const QStringList &src, const QString &dst, bool copy)
{
QDir dstDir(dst);
if(!dstDir.exists())
{
emit error(tr("Error"), tr("Destination directory %1 doesn't exists").arg(dstDir.absolutePath()));
return;
}
QList<Action> actions;
QStringList dirs;
emit progress(0);
for(const QString &i : src)
{
QFileInfo srcInfo(i);
if(srcInfo.absolutePath() == dst || dst.startsWith(srcInfo.absoluteFilePath()))
return;
if(srcInfo.isDir())
{
QDir srcDir(i);
//qDebug() << "dir" << srcInfo.absoluteFilePath() << srcInfo.fileName();
if(!copy && !dstDir.exists(srcInfo.fileName()))
{
if(QFile::rename(srcInfo.absoluteFilePath(), dstDir.absoluteFilePath(srcInfo.fileName())))
continue;
}
actions.append({srcInfo.absoluteFilePath(), srcInfo.fileName(), true});
if(!copy)dirs.prepend(srcInfo.absoluteFilePath());
QDirIterator it(i, QDirIterator::Subdirectories | QDirIterator::FollowSymlinks);
while(it.hasNext())
{
QFileInfo info = it.nextFileInfo();
if(info.fileName() == "." || info.fileName() == "..")
continue;
QString relativePath = srcDir.dirName() + "/" + srcDir.relativeFilePath(info.absoluteFilePath());
if(info.isDir())
{
actions.append({"", relativePath, true});
if(!copy)dirs.prepend(info.absoluteFilePath());
//qDebug() << "dir" << info.absoluteFilePath() << relativePath;
}
else
{
actions.append({info.absoluteFilePath(), dstDir.absoluteFilePath(relativePath), false});
//qDebug() << "file" << info.absoluteFilePath() << dstDir.absoluteFilePath(relativePath);
}
}
}
else
{
actions.append({srcInfo.absoluteFilePath(), dstDir.absoluteFilePath(srcInfo.fileName())});
//qDebug() << "file" << srcInfo.absoluteFilePath() << dstDir.absoluteFilePath(srcInfo.fileName());
}
}
bool overwriteAll = false;
bool skipAll = false;
int total = actions.size();
int i = 0;
for(auto &a : actions)
{
if(!_run)
return;
if(a.dir)
{
dstDir.mkpath(a.dst);
}
else
{
QFileInfo dstInfo(a.dst);
if(dstInfo.exists())
{
if(overwriteAll)
{
QFile::remove(dstInfo.absoluteFilePath());
}
else if(skipAll)
{
emit progress(i++ * 100 / total);
continue;
}
else
{
QMessageBox::StandardButton ret;
QMetaObject::invokeMethod(_fm, "overwrite", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QMessageBox::StandardButton, ret), Q_ARG(QString, dstInfo.fileName()));
switch(ret)
{
case QMessageBox::YesToAll:
overwriteAll = true;//break; is intentionally missing
case QMessageBox::Yes:
QFile::remove(dstInfo.absoluteFilePath());
break;
case QMessageBox::NoToAll:
skipAll = true;//break; is intentionally missing
case QMessageBox::No:
emit progress(i++ * 100 / total);
continue;
break;
case QMessageBox::Cancel:
return;
default:
break;
}
}
}
FileTimes t(a.src);
if(copy)
{
if(!QFile::copy(a.src, a.dst))
{
emit error(tr("Copy failed"), tr("Failed to copy file %1 to %2").arg(a.src).arg(a.dst));
return;
}
}
else
{
if(!QFile::rename(a.src, a.dst))
{
emit error(tr("Move failed"), tr("Failed to move file %1 to %2").arg(a.src).arg(a.dst));
return;
}
}
t.apply(a.dst);
}
emit progress(i++ * 100 / total);
}
if(!copy)
{
for(const QString &d : dirs)
{
QDir dir(d);
if(dir.isEmpty())
dir.removeRecursively();
}
}
}
PathTabBar::PathTabBar(const QStringList &tabs) :
_tabs(tabs)
{
setTabsClosable(true);
setExpanding(false);
for(auto &t : _tabs)
{
QDir dir(t);
int i = addTab(tabName(t));
setTabToolTip(i, t);
}
connect(this, &QTabBar::currentChanged, [this](int index){
QString path = _tabs.at(index);
emit pathChanged(path);
});
connect(this, &QTabBar::tabCloseRequested, [this](int index){
if(_tabs.size() >= 2)
{
_tabs.remove(index);
removeTab(index);
}
});
connect(this, &QTabBar::currentChanged, [this](int index){
emit tabChanged(_tabs[index]);
});
}
QHBoxLayout *PathTabBar::createLayout()
{
QHBoxLayout *hlayout = new QHBoxLayout();
hlayout->addWidget(this);
hlayout->addStretch(2);
QPushButton *addButton = new QPushButton("+");
connect(addButton, &QPushButton::clicked, [this](){
QString path = _tabs[currentIndex()];
_tabs.append(path);
int i = addTab(tabName(path));
setTabToolTip(i, path);
});
hlayout->addWidget(addButton);
return hlayout;
}
const QStringList &PathTabBar::tabPaths() const
{
return _tabs;
}
QString PathTabBar::currentTabPath() const
{
int index = std::clamp(currentIndex(), 0, (int)_tabs.size());
return _tabs[index];
}
void PathTabBar::pathChanged(const QString &path)
{
QDir dir(path);
int index = currentIndex();
setTabText(index, tabName(path));
setTabToolTip(index, path);
_tabs[index] = path;
}
QString PathTabBar::tabName(const QString &path)
{
QDir dir(path);
if(dir.dirName().isEmpty())
return path;
else
return dir.dirName();
}
FITSSelection::FITSSelection(const QStringList &keywords, QWidget *parent) : QDialog(parent)
,ui(new Ui::FITSKeyword)
{
ui->setupUi(this);
connect(ui->addButton, &QPushButton::clicked, [this](){
auto item = ui->keywordList->findItems(ui->keyword->text(), Qt::MatchFixedString | Qt::MatchCaseSensitive);
if(item.size())return;
ui->keywordList->addItem(ui->keyword->text());
});
connect(ui->removeButton, &QPushButton::clicked, [this](){
auto items = ui->keywordList->selectedItems();
for(auto item : items)
delete item;
});
ui->keywordList->addItems(keywords);
}
FITSSelection::~FITSSelection()
{
delete ui;
}
QStringList FITSSelection::FITSKeywords() const
{
QStringList keywords;
for(int i = 0; i < ui->keywordList->count(); i++)
keywords.append(ui->keywordList->item(i)->text());
return keywords;
}
FileManager::FileManager(const QSet<QString> &openFilter, QWidget *parent) : QMainWindow(parent)
,ui(new Ui::FileManager)
{
ui->setupUi(this);
QStringList standardLocations = QStandardPaths::standardLocations(QStandardPaths::PicturesLocation);
QString picturesPath;
if(standardLocations.size())
picturesPath = standardLocations.first();
QSettings settings;
QStringList leftTabs = settings.value("filemanager/leftTabPaths", picturesPath).toStringList();
QStringList rightTabs = settings.value("filemanager/rightTabPaths", picturesPath).toStringList();
if(leftTabs.empty())leftTabs.append(picturesPath);
if(rightTabs.empty())rightTabs.append(picturesPath);
ui->leftTab->setOpenFilter(openFilter);
ui->rightTab->setOpenFilter(openFilter);
_rightTabBar = new PathTabBar(rightTabs);
ui->rightLayout->insertLayout(0, _rightTabBar->createLayout());
connect(_rightTabBar, &PathTabBar::tabChanged, ui->rightTab, &DirView::setDir);
_leftTabBar = new PathTabBar(leftTabs);
ui->leftLayout->insertLayout(0, _leftTabBar->createLayout());
connect(_leftTabBar, &PathTabBar::tabChanged, ui->leftTab, &DirView::setDir);
connect(ui->leftTab, &DirView::dirChanged, ui->leftPath, &QLineEdit::setText);
connect(ui->leftTab, &DirView::dirChanged, _leftTabBar, &PathTabBar::pathChanged);
connect(ui->rightTab, &DirView::dirChanged, ui->rightPath, &QLineEdit::setText);
connect(ui->rightTab, &DirView::dirChanged, _rightTabBar, &PathTabBar::pathChanged);
connect(ui->leftTab, &DirView::openFile, this, &FileManager::openFile);
connect(ui->rightTab, &DirView::openFile, this, &FileManager::openFile);
connect(ui->leftTab, &DirView::filesAction, this, &FileManager::copyMoveFiles, Qt::QueuedConnection);
connect(ui->rightTab, &DirView::filesAction, this, &FileManager::copyMoveFiles, Qt::QueuedConnection);
connect(ui->actionLoad_FITS_keywordsLeft, &QAction::toggled, ui->leftTab, &DirView::loadFitsKeywords);
connect(ui->actionLoad_FITS_keywordsRight, &QAction::toggled, ui->rightTab, &DirView::loadFitsKeywords);
ui->leftTab->setDir(_leftTabBar->currentTabPath());
ui->leftTab->setFITSKeywords(settings.value("filemanager/leftFitsKeywords", QStringList("OBJECT")).toStringList());
ui->leftTab->header()->restoreState(settings.value("filemanager/leftTabHeader").toByteArray());
ui->rightTab->setDir(_rightTabBar->currentTabPath());
ui->rightTab->setFITSKeywords(settings.value("filemanager/rightFitsKeywords", QStringList("OBJECT")).toStringList());
ui->rightTab->header()->restoreState(settings.value("filemanager/rightTabHeader").toByteArray());
ui->actionLoad_FITS_keywordsLeft->setChecked(settings.value("filemanager/leftLoadFitsKeywords", true).toBool());
ui->actionLoad_FITS_keywordsRight->setChecked(settings.value("filemanager/rightLoadFitsKeywords", true).toBool());
restoreGeometry(settings.value("filemanager/geometry").toByteArray());
setAttribute(Qt::WA_DeleteOnClose);
connect(ui->actionSelect_columnsLeft, &QAction::triggered, this, &FileManager::selectFITSKeywords);
connect(ui->actionSelect_columnsRight, &QAction::triggered, this, &FileManager::selectFITSKeywords);
connect(ui->actionCopySelectedFilesPathsLeft, &QAction::triggered, this, &FileManager::copySelectedFilesPaths);
connect(ui->actionCopySelectedFilesPathsRight, &QAction::triggered, this, &FileManager::copySelectedFilesPaths);
connect(ui->leftPath, &QLineEdit::returnPressed, this, &FileManager::pathEdited);
connect(ui->rightPath, &QLineEdit::returnPressed, this, &FileManager::pathEdited);
QFileInfoList drives = QDir::drives();
for(auto &drive : drives)
{
QString path = drive.absoluteFilePath();
ui->menuLeft_Tab->addAction(drive.absoluteFilePath(), [path, this](){ ui->leftTab->setDir(path); });
ui->menuRight_Tab->addAction(drive.absoluteFilePath(), [path, this](){ ui->rightTab->setDir(path); });
}
ui->progressBar->hide();
ui->cancelButton->hide();
_thread = new QThread(this);
_thread->start();
_fileTransfer = new FileTransfer(this);
_fileTransfer->moveToThread(_thread);
connect(_fileTransfer, &FileTransfer::progress, ui->progressBar, &QProgressBar::setValue);
connect(_fileTransfer, &FileTransfer::error, this, &FileManager::errorMessage);
connect(_fileTransfer, &FileTransfer::finished, [this](){
ui->leftTab->setDragEnabled(true);
ui->rightTab->setDragEnabled(true);
ui->progressBar->hide();
ui->cancelButton->hide();
});
connect(this, &FileManager::copy, _fileTransfer, &FileTransfer::copy);
connect(this, &FileManager::move, _fileTransfer, &FileTransfer::move);
connect(ui->cancelButton, &QPushButton::clicked, [this](){ _fileTransfer->cancel(); });
}
FileManager::~FileManager()
{
QSettings settings;
settings.setValue("filemanager/leftFitsKeywords", ui->leftTab->FITSKeywords());
settings.setValue("filemanager/leftTabPaths", _leftTabBar->tabPaths());
settings.setValue("filemanager/leftTabHeader", ui->leftTab->header()->saveState());
settings.setValue("filemanager/rightFitsKeywords", ui->rightTab->FITSKeywords());
settings.setValue("filemanager/rightTabPaths", _rightTabBar->tabPaths());
settings.setValue("filemanager/rightTabHeader", ui->rightTab->header()->saveState());
settings.setValue("filemanager/leftLoadFitsKeywords", ui->actionLoad_FITS_keywordsLeft->isChecked());
settings.setValue("filemanager/rightLoadFitsKeywords", ui->actionLoad_FITS_keywordsRight->isChecked());
settings.setValue("filemanager/geometry", saveGeometry());
delete ui;
_fileTransfer->cancel();
_thread->quit();
_thread->wait();
delete _fileTransfer;
}
void FileManager::selectFITSKeywords()
{
QStringList columns;
if(sender() == ui->actionSelect_columnsLeft)
columns = ui->leftTab->FITSKeywords();
if(sender() == ui->actionSelect_columnsRight)
columns = ui->rightTab->FITSKeywords();
FITSSelection selection(columns, this);
int ret = selection.exec();
if(ret == QDialog::Accepted)
{
if(sender() == ui->actionSelect_columnsLeft)
ui->leftTab->setFITSKeywords(selection.FITSKeywords());
if(sender() == ui->actionSelect_columnsRight)
ui->rightTab->setFITSKeywords(selection.FITSKeywords());
}
}
void FileManager::copySelectedFilesPaths()
{
if(sender() == ui->actionCopySelectedFilesPathsLeft)
ui->leftTab->copySelectedFilesPathsToClipboard();
if(sender() == ui->actionCopySelectedFilesPathsRight)
ui->rightTab->copySelectedFilesPathsToClipboard();
}
void FileManager::pathEdited()
{
if(sender() == ui->leftPath)
{
QDir dir(ui->leftPath->text());
if(dir.exists())
ui->leftTab->setDir(dir.absolutePath());
}
if(sender() == ui->rightPath)
{
QDir dir(ui->rightPath->text());
if(dir.exists())
ui->rightTab->setDir(dir.absolutePath());
}
}
void FileManager::copyMoveFiles(Qt::DropAction action, const QStringList &src, const QString &dst)
{
ui->leftTab->setDragEnabled(false);
ui->rightTab->setDragEnabled(false);
ui->progressBar->show();
ui->cancelButton->show();
switch(action)
{
case Qt::CopyAction:
emit copy(src, dst);
break;
case Qt::MoveAction:
emit move(src, dst);
break;
case Qt::LinkAction:
default:
break;
}
}
QMessageBox::StandardButton FileManager::overwrite(const QString &dst)
{
QMessageBox::StandardButton button = QMessageBox::question(this, tr("Overwrite file?"), tr("Destination file %1 already exists. Overwrite?").arg(dst),
QMessageBox::Yes | QMessageBox::YesToAll | QMessageBox::No | QMessageBox::NoToAll | QMessageBox::Cancel);
return button;
}
void FileManager::errorMessage(const QString &title, const QString &text)
{
QMessageBox::critical(this, title, text);
}
QCache<QString, ImageInfoData>* DirFileSystemModel::getCacheInstance()
{
static bool init = true;
static QCache<QString, ImageInfoData> cache;
if(!init)
{
cache.setMaxCost(10000);
init = false;
}
return &cache;
}
DirFileSystemModel::DirFileSystemModel(QWidget *parentWidget) : QFileSystemModel(parentWidget)
,_parentWidget(parentWidget)
{
_cache = getCacheInstance();
setFilter(QDir::AllEntries | QDir::NoDot);
_fitsKeywords = {"OBJECT"};
}
void DirFileSystemModel::setDir(const QString &path)
{
_dir = index(path);
}
QString DirFileSystemModel::dir() const
{
return fileInfo(_dir).canonicalFilePath();
}
void DirFileSystemModel::setFITSKeywords(const QStringList &keywords)
{
beginResetModel();
_fitsKeywords = keywords;
endResetModel();
}
const QStringList &DirFileSystemModel::FITSKeywords() const
{
return _fitsKeywords;
}
Qt::ItemFlags DirFileSystemModel::flags(const QModelIndex &index) const
{
Qt::ItemFlags ret = QFileSystemModel::flags(index) & ~Qt::ItemIsEditable;
if(index.row() == 0)ret &= ~Qt::ItemIsDragEnabled;
return ret;
}
int DirFileSystemModel::columnCount(const QModelIndex &parent) const
{
return QFileSystemModel::columnCount(parent) + _fitsKeywords.size();
}
QVariant DirFileSystemModel::data(const QModelIndex &index, int role) const
{
if(index.column() >= QFileSystemModel::columnCount() && role == Qt::DisplayRole)
{
QFileInfo info = fileInfo(index);
QString path = info.canonicalFilePath();
QString suffix = info.suffix();
ImageInfoData *infoData = nullptr;
if(_cache->contains(path))
{
infoData = _cache->object(path);
}
else
{
if(_loadFitsKeywords)
{
infoData = new ImageInfoData;
if(isFITS(suffix))
readFITSHeader(path, *infoData);
else if(isXISF(suffix))
readXISFHeader(path, *infoData);
_cache->insert(path, infoData);
}
}
if(infoData)
{
int column = index.column() - QFileSystemModel::columnCount();
if(column < _fitsKeywords.size())
{
const QString &key = _fitsKeywords.at(column);
for(auto &record : infoData->fitsHeader)
if(record.key == key)
return record.value;
}
}
return "";
}
return QFileSystemModel::data(index, role);
}
QVariant DirFileSystemModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if(orientation == Qt::Horizontal && role == Qt::DisplayRole && section >= QFileSystemModel::columnCount())
return _fitsKeywords.at(section - QFileSystemModel::columnCount());
return QFileSystemModel::headerData(section, orientation, role);
}
bool DirFileSystemModel::hasChildren(const QModelIndex &parent) const
{
if(parent.parent() == _dir)return false;
return QFileSystemModel::hasChildren(parent);
}
bool DirFileSystemModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
{
Q_UNUSED(row);
Q_UNUSED(column);
if(data->hasUrls())
{
QStringList srcPaths;
for(auto &url : data->urls())
srcPaths.append(url.toLocalFile());
emit filesAction(action, srcPaths, filePath(parent));
}
return false;
}
void DirFileSystemModel::loadFitsKeywords(bool enable)
{
_loadFitsKeywords = enable;
}
DirView::DirView(QWidget *parent) : QTreeView(parent)
{
_dirFileSystemModel = new DirFileSystemModel(this);
_dirFileSystemModel->setRootPath(QDir::drives().first().path());
_dirFileSystemModel->setReadOnly(false);
setDragEnabled(true);
setAcceptDrops(true);
connect(_dirFileSystemModel, &DirFileSystemModel::filesAction, this, &DirView::filesAction);
setModel(_dirFileSystemModel);
setSelectionMode(QAbstractItemView::ExtendedSelection);
connect(this, &QTreeView::doubleClicked, [this](const QModelIndex &index){
QFileInfo info = _dirFileSystemModel->fileInfo(index);
if(_dirFileSystemModel->isDir(index))
{
setDir(info.canonicalFilePath());
}
else if(info.isFile())
{
if(_openFilter.contains(info.suffix().toLower()))
emit openFile(info.absoluteFilePath());
else
QDesktopServices::openUrl(QUrl::fromLocalFile(info.absoluteFilePath()));
}
});
header()->setContextMenuPolicy(Qt::CustomContextMenu);
connect(header(), &QHeaderView::customContextMenuRequested, this, &DirView::headerContextMenu);
}
void DirView::setDir(const QString &path)
{
QString oldPath = _dirFileSystemModel->dir();
#ifdef Q_OS_WINDOWS
const int ROOT_LEN = 3;
#else
const int ROOT_LEN = 1;
#endif
if(oldPath.left(ROOT_LEN) != path.left(ROOT_LEN))
_dirFileSystemModel->setRootPath(path.left(ROOT_LEN));
QString newPath = path;
if(!QFileInfo::exists(path))
{
QDir dir(path);
do
{
dir.setPath(QDir::cleanPath(dir.filePath("..")));
}while(!dir.exists() && !dir.isRoot());
newPath = dir.path();
}
_dirFileSystemModel->setDir(newPath);
setRootIndex(_dirFileSystemModel->index(newPath, 0));
clearSelection();
if(oldPath != newPath)emit dirChanged(newPath);
}
QString DirView::dir() const
{
return _dirFileSystemModel->dir();
}
void DirView::setOpenFilter(const QSet<QString> &openFilter)
{
_openFilter = openFilter;
}
void DirView::setFITSKeywords(const QStringList &keywords)
{
QString d = dir();
_dirFileSystemModel->setFITSKeywords(keywords);
setDir(d);
}
const QStringList &DirView::FITSKeywords() const
{
return _dirFileSystemModel->FITSKeywords();
}
void DirView::headerContextMenu(const QPoint &pos)
{
QHeaderView *head = header();
QMenu menu;
int count = head->count();
for(int i = 0; i < count; i++)
{
QAction *a = menu.addAction(head->model()->headerData(i, Qt::Horizontal).toString());
a->setCheckable(true);
a->setChecked(!head->isSectionHidden(i));
a->setData(i);
}
QAction *a = menu.exec(mapToGlobal(pos));
if(a)
{
if(a->isChecked())head->showSection(a->data().toInt());
else head->hideSection(a->data().toInt());
}
}
void DirView::loadFitsKeywords(bool enable)
{
_dirFileSystemModel->loadFitsKeywords(enable);
}
void DirView::copySelectedFilesPathsToClipboard() const
{
QList<QUrl> urls;
QString text;
auto selected = selectionModel()->selectedRows();
for(auto &item : selected)
{
if(item.column() == 0)
{
QString path = _dirFileSystemModel->filePath(item);
text.append(path); text.append('\n');
urls.append(QUrl::fromLocalFile(path));
}
}
QClipboard *clipboard = QApplication::clipboard();
QMimeData *mimeData = new QMimeData();
mimeData->setUrls(urls);
mimeData->setText(text);
clipboard->setMimeData(mimeData);
}
+150
View File
@@ -0,0 +1,150 @@
#ifndef FILEMANAGER_H
#define FILEMANAGER_H
#include <QMainWindow>
#include <QCache>
#include <QFileSystemModel>
#include <QTreeView>
#include <QDialog>
#include <QTabBar>
#include <QHBoxLayout>
#include <QMessageBox>
#include "imageinfodata.h"
namespace Ui {
class FileManager;
class FITSKeyword;
}
class FileManager;
class FileTransfer: public QObject
{
Q_OBJECT
public:
explicit FileTransfer(FileManager *fm);
~FileTransfer();
public slots:
void copy(const QStringList &src, const QString &dst);
void move(const QStringList &src, const QString &dst);
void cancel();
signals:
void progress(int percent);
void finished();
void error(const QString &title, const QString &text);
private:
void perform(const QStringList &src, const QString &dst, bool copy);
struct Action
{
QString src;
QString dst;
bool dir = false;
};
FileManager *_fm;
bool _run = true;
};
class PathTabBar : public QTabBar
{
Q_OBJECT
public:
explicit PathTabBar(const QStringList &tabs);
QHBoxLayout* createLayout();
const QStringList& tabPaths() const;
QString currentTabPath() const;
public slots:
void pathChanged(const QString &path);
signals:
void tabChanged(const QString &path);
private:
QStringList _tabs;
QString tabName(const QString &path);
};
class FITSSelection : public QDialog
{
Q_OBJECT
public:
FITSSelection(const QStringList &keywords, QWidget *parent = nullptr);
~FITSSelection();
QStringList FITSKeywords() const;
private:
Ui::FITSKeyword *ui;
};
class FileManager : public QMainWindow
{
Q_OBJECT
public:
explicit FileManager(const QSet<QString> &openFilter, QWidget *parent = nullptr);
~FileManager();
public slots:
void selectFITSKeywords();
void copySelectedFilesPaths();
void pathEdited();
void copyMoveFiles(Qt::DropAction action, const QStringList &src, const QString &dst);
QMessageBox::StandardButton overwrite(const QString &dst);
void errorMessage(const QString &title, const QString &text);
signals:
void openFile(const QString &path);
void copy(const QStringList &src, const QString &dst);
void move(const QStringList &src, const QString &dst);
private:
Ui::FileManager *ui;
PathTabBar *_leftTabBar;
PathTabBar *_rightTabBar;
QThread *_thread;
FileTransfer *_fileTransfer;
};
class DirFileSystemModel : public QFileSystemModel
{
Q_OBJECT
public:
explicit DirFileSystemModel(QWidget *parentWidget);
void setDir(const QString &path);
QString dir() const;
void setFITSKeywords(const QStringList &keywords);
const QStringList& FITSKeywords() const;
Qt::ItemFlags flags(const QModelIndex &index) const override;
int columnCount(const QModelIndex &parent) const override;
QVariant data(const QModelIndex &index, int role) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
bool hasChildren(const QModelIndex &parent) const override;
bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override;
public slots:
void loadFitsKeywords(bool enable);
signals:
void filesAction(Qt::DropAction action, const QStringList &src, const QString &dst);
private:
mutable QCache<QString, ImageInfoData> *_cache = nullptr;
static QCache<QString, ImageInfoData>* getCacheInstance();
QModelIndex _dir;
QStringList _fitsKeywords;
bool _loadFitsKeywords = true;
QWidget *_parentWidget = nullptr;
};
class DirView : public QTreeView
{
Q_OBJECT
DirFileSystemModel *_dirFileSystemModel = nullptr;
QSet<QString> _openFilter;
public:
explicit DirView(QWidget *parent = nullptr);
void setDir(const QString &path);
QString dir() const;
void setOpenFilter(const QSet<QString> &openFilter);
void setFITSKeywords(const QStringList &keywords);
const QStringList& FITSKeywords() const;
public slots:
void headerContextMenu(const QPoint &pos);
void loadFitsKeywords(bool enable);
void copySelectedFilesPathsToClipboard() const;
signals:
void dirChanged(const QString &path);
void openFile(const QString &path);
void filesAction(Qt::DropAction action, const QStringList &src, const QString &dst);
};
#endif // FILEMANAGER_H
+150
View File
@@ -0,0 +1,150 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>FileManager</class>
<widget class="QMainWindow" name="FileManager">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1282</width>
<height>858</height>
</rect>
</property>
<property name="windowTitle">
<string>File Manager</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<layout class="QVBoxLayout" name="leftLayout">
<item>
<widget class="QLineEdit" name="leftPath"/>
</item>
<item>
<widget class="DirView" name="leftTab"/>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="rightLayout">
<item>
<widget class="QLineEdit" name="rightPath"/>
</item>
<item>
<widget class="DirView" name="rightTab"/>
</item>
</layout>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="progressLayout">
<item>
<widget class="QProgressBar" name="progressBar">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="cancelButton">
<property name="text">
<string>Cancel</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1282</width>
<height>23</height>
</rect>
</property>
<widget class="QMenu" name="menuLeft_Tab">
<property name="title">
<string>Left Tab</string>
</property>
<addaction name="actionLoad_FITS_keywordsLeft"/>
<addaction name="actionSelect_columnsLeft"/>
<addaction name="actionCopySelectedFilesPathsLeft"/>
<addaction name="separator"/>
</widget>
<widget class="QMenu" name="menuRight_Tab">
<property name="title">
<string>Right Tab</string>
</property>
<addaction name="actionLoad_FITS_keywordsRight"/>
<addaction name="actionSelect_columnsRight"/>
<addaction name="actionCopySelectedFilesPathsRight"/>
<addaction name="separator"/>
</widget>
<addaction name="menuLeft_Tab"/>
<addaction name="menuRight_Tab"/>
</widget>
<action name="actionLoad_FITS_keywordsLeft">
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="text">
<string>Load FITS keywords</string>
</property>
</action>
<action name="actionLoad_FITS_keywordsRight">
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="text">
<string>Load FITS keywords</string>
</property>
</action>
<action name="actionSelect_columnsLeft">
<property name="text">
<string>Select columns</string>
</property>
</action>
<action name="actionSelect_columnsRight">
<property name="text">
<string>Select columns</string>
</property>
</action>
<action name="actionCopySelectedFilesPathsLeft">
<property name="text">
<string>Copy selected files paths</string>
</property>
</action>
<action name="actionCopySelectedFilesPathsRight">
<property name="text">
<string>Copy selected files paths</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>
<class>DirView</class>
<extends>QTreeView</extends>
<header>filemanager.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
@@ -5,6 +5,7 @@
#include <QMenu>
#include <QSettings>
#include <QHeaderView>
#include <QMimeDatabase>
FilesystemWidget::FilesystemWidget(QAbstractItemModel *model, QWidget *parent) : QWidget(parent)
, m_model(model)
@@ -117,6 +118,7 @@ void Filetree::contextMenuEvent(QContextMenuEvent *event)
{
setRootIndex(index);
m_rootDir = m_fileSystemModel->filePath(index);
m_fileSystemModel->setRootPath(m_rootDir);
}
else if(a == resetRoot)
{
@@ -127,6 +129,7 @@ void Filetree::contextMenuEvent(QContextMenuEvent *event)
{
setRootIndex(rootIndex().parent());
m_rootDir = m_fileSystemModel->filePath(rootIndex().parent());
m_fileSystemModel->setRootPath(m_rootDir);
}
else if(a == copy)
{
@@ -3,6 +3,7 @@
#include <QWidget>
#include <QFileSystemModel>
#include <QIdentityProxyModel>
#include <QListView>
#include <QTreeView>
+98
View File
@@ -0,0 +1,98 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>FITSKeyword</class>
<widget class="QDialog" name="FITSKeyword">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>432</width>
<height>443</height>
</rect>
</property>
<property name="windowTitle">
<string>FITS Columns</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QListWidget" name="keywordList">
<property name="dragEnabled">
<bool>true</bool>
</property>
<property name="dragDropMode">
<enum>QAbstractItemView::InternalMove</enum>
</property>
<property name="defaultDropAction">
<enum>Qt::MoveAction</enum>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="keyword"/>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="addButton">
<property name="text">
<string>Add</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="removeButton">
<property name="text">
<string>Remove</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>FITSKeyword</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>215</x>
<y>420</y>
</hint>
<hint type="destinationlabel">
<x>215</x>
<y>221</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>FITSKeyword</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>215</x>
<y>420</y>
</hint>
<hint type="destinationlabel">
<x>215</x>
<y>221</y>
</hint>
</hints>
</connection>
</connections>
</ui>
View File
View File
+2
View 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()
View File
+173 -3
View File
@@ -4,6 +4,7 @@
#include <QRegularExpression>
#include <wcslib/wcshdr.h>
#include <wcslib/wcsfix.h>
#include "database.h"
#include "libxisf.h"
static const QVector<QByteArray> noEditableKey = {"SIMPLE", "BITPIX", "NAXIS", "NAXIS1", "NAXIS2", "NAXIS3", "EXTEND", "BZERO", "BSCALE"};
@@ -29,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);
@@ -85,6 +87,7 @@ WCSDataT::WCSDataT(int width, int height, const QVector<FITSRecord> &header) :
for(const FITSRecord &record : header)
{
if(record.key.startsWith("PV"))continue;
if(record.xisf)continue;
QByteArray rec;
rec.append(record.key.leftJustified(8, ' '));
@@ -153,9 +156,9 @@ bool WCSDataT::worldToPixel(const SkyPoint &point, QPointF &pixel) const
return false;
}
void WCSDataT::calculateBounds(double &minRa, double &maxRa, double &minDec, double &maxDec, double &crVal1, double &crVal2) const
bool WCSDataT::calculateBounds(double &minRa, double &maxRa, double &minDec, double &maxDec, double &crVal1, double &crVal2) const
{
if(wcs == nullptr)return;
if(wcs == nullptr)return false;
minRa = 1000;
maxRa = -1000;
@@ -208,6 +211,7 @@ void WCSDataT::calculateBounds(double &minRa, double &maxRa, double &minDec, dou
if(s.contains(scp))
minDec = -90;
}
return true;
}
double hav(double x)
@@ -266,6 +270,16 @@ QString SkyPoint::toString() const
return QString("RA: %1 DEC: %2° %3' %4\"").arg(t.toString("HH'h' mm'm' ss's'")).arg(deg, 2, 'f', 0, '0').arg(min, 2, 'f', 0, '0').arg(sec, 2, 'f', 0, '0');
}
QString SkyPoint::RAString() const
{
return toHMS(ra / 15);
}
QString SkyPoint::DECString() const
{
return toDMS(dec);
}
double SkyPoint::fromHMS(const QString &hms)
{
double deg = fromDMS(hms);
@@ -308,11 +322,21 @@ QString SkyPoint::toHMS(double decHour)
QString SkyPoint::toDMS(double deg)
{
int sign = deg < 0.0 ? -1 : 1;
deg *= sign;
double d,m,s,md;
md = std::modf(deg, &d) * 60.0;
s = std::modf(md, &m) * 60.0;
return QString("%1˚ %2' %3\"").arg((int)d, 2, 10, QChar('0')).arg((int)m, 2, 10, QChar('0')).arg((int)s, 2, 10, QChar('0'));
return QString("%1˚ %2' %3\"").arg((int)d * sign, 2, 10, QChar('0')).arg((int)m, 2, 10, QChar('0')).arg((int)s, 2, 10, QChar('0'));
}
SkyPoint SkyPoint::operator+(const SkyPoint &p)
{
SkyPoint ret;
ret.ra = ra + p.ra;
ret.dec = dec + p.dec;
return ret;
}
SkyPointScale ImageInfoData::getCenterRaDec() const
@@ -403,3 +427,149 @@ SkyPointScale ImageInfoData::getCenterRaDec() const
}
return ret;
}
SkyPoint greatCircle(SkyPoint &p, double dist, double azm)
{
dist = dist * M_PI / 180;
azm = azm * M_PI / 180;
double dec0 = p.DEC() * M_PI / 180;
double ra0 = p.RA() * M_PI / 180;
double dec1 = std::asin(std::sin(dec0) * std::cos(dist) + std::cos(dec0) * std::sin(dist) * std::cos(azm));
double ra1 = ra0 + std::atan2(std::sin(azm) * std::sin(dist) * std::cos(dec0), std::cos(dist) - std::sin(dec0) * std::sin(dec1));
return SkyPoint(ra1 * 180 / M_PI, dec1 * 180 / M_PI);
}
SkyGrid WCSDataT::prepareGrid(uint32_t w, uint32_t h, Database *database)
{
SkyGrid skyGrid;
if(!wcs)return skyGrid;
double minRa, maxRa, minDec, maxDec, crVal1, crVal2;
calculateBounds(minRa, maxRa, minDec, maxDec, crVal1, crVal2);
QPointF a,b;
worldToPixel(SkyPoint(crVal1, crVal2), a);
worldToPixel(SkyPoint(crVal1 + 0.01, crVal2), b);
skyGrid.rot_ang = std::atan2(b.y() - a.y(), b.x() - a.x()) / M_PI * -180.0;
if(database)
{
double size = std::max(maxRa - minRa, maxDec - minDec);
skyGrid.objects = database->getObjects(minRa - size, maxRa + size, minDec - size, maxDec + size);
for(auto &object : skyGrid.objects)
{
QPointF p;
if(worldToPixel(object.skyPoint, p))
object.pixel = p;
QPointF majax;
worldToPixel(greatCircle(object.skyPoint, (object.min_ax + object.maj_ax) / 120.0, object.pos_ang), majax);
majax -= p;
object.maj_ax = std::sqrt(QPointF::dotProduct(majax, majax));
}
}
double raStep = 15;
double decStep = 15;
double raRange = maxRa - minRa;
double decRange = maxDec - minDec;
const QVector<double> raSteps = {15, 5, 2.5, 1.25, 0.25, 20/240.0, 10/240.0, 5/240.0, 1/240.0};
const QVector<double> decSteps = {20, 10, 5, 2, 1, 20/60.0, 10/60.0, 5/60.0, 2/60.0, 1/60.0, 20/3600.0, 10/3600.0, 5/3600.0, 2/3600.0, 1/3600.0};
for(double ra : raSteps)
{
if(ra * 5 <= raRange)
{
raStep = ra;
break;
}
}
for(double dec : decSteps)
{
if(dec * 5 <= decRange)
{
decStep = dec;
break;
}
}
minRa -= std::fmod(minRa, raStep);
minDec -= std::fmod(minDec, decStep);
if(minRa < 0)minRa -= raStep;
if(minDec < 0)minDec -= decStep;
QRectF clip(0, 0, w, h);
const double step = 0.2;
maxRa += raStep;
maxDec += decStep;
for(double ra = minRa; ra <= maxRa; ra += raStep)
{
QPointF p;
worldToPixel(SkyPoint(ra, minDec), p);
skyGrid.grid.moveTo(p);
for(double dec = minDec + decStep * step; dec <= maxDec; dec += decStep * step)
{
worldToPixel(SkyPoint(ra, dec), p);
skyGrid.grid.lineTo(p);
}
}
for(double dec = minDec; dec <= maxDec; dec += decStep)
{
QPointF p;
worldToPixel(SkyPoint(minRa, dec), p);
skyGrid.grid.moveTo(p);
for(double ra = minRa + raStep * step; ra <= maxRa; ra += raStep * step)
{
worldToPixel(SkyPoint(ra, dec), p);
skyGrid.grid.lineTo(p);
}
}
SkyPoint sp1, sp2,orig;
pixelToWorld(QPointF(-1, -1), orig);
sp1 = orig;
for(uint32_t x = 0; x < w; x++)
{
QPointF p(x, 0);
if(!pixelToWorld(p, sp2))
break;
if(static_cast<int>(sp1.RA() / raStep) != static_cast<int>(sp2.RA() / raStep))
skyGrid.text.append({p, std::abs(sp1.RA()) > std::abs(sp2.RA()) ? sp1.RAString() : sp2.RAString()});
if(static_cast<int>(sp1.DEC() / decStep) != static_cast<int>(sp2.DEC() / decStep))
skyGrid.text.append({p, std::abs(sp1.DEC()) > std::abs(sp2.DEC()) ? sp1.DECString() : sp2.DECString()});
sp1 = sp2;
}
sp1 = orig;
for(uint32_t y = 0; y < h; y++)
{
QPointF p(0, y);
if(!pixelToWorld(p, sp2))
break;
if(static_cast<int>(sp1.RA() / raStep) != static_cast<int>(sp2.RA() / raStep))
skyGrid.text.append({p, std::abs(sp1.RA()) > std::abs(sp2.RA()) ? sp1.RAString() : sp2.RAString()});
if(static_cast<int>(sp1.DEC() / decStep) != static_cast<int>(sp2.DEC() / decStep))
skyGrid.text.append({p, std::abs(sp1.DEC()) > std::abs(sp2.DEC()) ? sp1.DECString() : sp2.DECString()});
sp1 = sp2;
}
skyGrid.empty = false;
return skyGrid;
}
void SkyGrid::clear()
{
empty = true;
grid.clear();
text.clear();
objects.clear();
}
+30 -1
View File
@@ -5,12 +5,15 @@
#include <QPointF>
#include <QVector>
#include <QVariant>
#include <QPainterPath>
#include <wcslib/wcs.h>
#include <cmath>
#include <memory>
namespace LibXISF { struct FITSKeyword; struct Property; }
class Database;
struct FITSRecord
{
QByteArray key;
@@ -36,10 +39,13 @@ public:
double RAHour() const { return ra / 15.0; }
double DEC() const { return dec; }
QString toString() const;
QString RAString() const;
QString DECString() const;
static double fromHMS(const QString &hms);
static double fromDMS(const QString &dms);
static QString toHMS(double decHour);
static QString toDMS(double deg);
SkyPoint operator+(const SkyPoint &p);
};
struct SkyPointScale
@@ -51,6 +57,28 @@ struct SkyPointScale
double scaleHigh = 10000.0;
};
struct SkyObject
{
QString name;
QString name2;
SkyPoint skyPoint;
double maj_ax;
double min_ax;
double pos_ang;
double mag;
QPointF pixel;
};
struct SkyGrid
{
bool empty = true;
QPainterPath grid;
QVector<QPair<QPointF, QString>> text;
QVector<SkyObject> objects;
double rot_ang = 0;
void clear();
};
class WCSDataT
{
int nwcs = 0;
@@ -65,9 +93,10 @@ public:
~WCSDataT();
bool pixelToWorld(const QPointF &pixel, SkyPoint &point) const;
bool worldToPixel(const SkyPoint &point, QPointF &pixel) const;
void calculateBounds(double &minRa, double &maxRa, double &minDec, double &maxDec, double &crVal1, double &crVal2) const;
bool calculateBounds(double &minRa, double &maxRa, double &minDec, double &maxDec, double &crVal1, double &crVal2) const;
bool valid() const { return wcs; };
SkyPointScale getRaDecScale() const;
SkyGrid prepareGrid(uint32_t w, uint32_t h, Database *database);
};
struct ImageInfoData
+24 -20
View File
@@ -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
View File
@@ -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:
@@ -142,6 +142,11 @@ void ImageScrollArea::falseColor(bool enable)
m_imageWidget->falseColor(enable);
}
void ImageScrollArea::drawGrid(bool enable)
{
m_imageWidget->drawGrid(enable);
}
QImage ImageScrollArea::renderToImage()
{
return m_imageWidget->renderToImage();
@@ -31,6 +31,7 @@ public slots:
void invert(bool enable);
void superPixel(bool enable);
void falseColor(bool enable);
void drawGrid(bool enable);
QImage renderToImage();
protected slots:
void scrollEvent();
+51 -1
View File
@@ -180,6 +180,10 @@ void ImageWidgetGL::setImage(std::shared_ptr<RawImage> image, int index)
void ImageWidgetGL::setWCS(std::shared_ptr<WCSDataT> wcs)
{
m_wcs = wcs;
m_grid.clear();
if(m_drawGrid && m_wcs)
m_grid = m_wcs->prepareGrid(m_imgWidth, m_imgHeight, m_database);
}
void ImageWidgetGL::zoom(int zoom, const QPointF &mousePos)
@@ -496,6 +500,18 @@ void swPaint(std::shared_ptr<RawImage> &rawImage, float dx, float dy, float scal
painter.drawImage(0, 0, img);
}
void ImageWidgetGL::drawGrid(bool enable)
{
if(m_grid.empty && m_wcs)
m_grid = m_wcs->prepareGrid(m_imgWidth, m_imgHeight, m_database);
if(enable != m_drawGrid)
{
m_drawGrid = enable;
update();
}
}
void ImageWidgetGL::paintGL()
{
float dx = m_dx;
@@ -613,9 +629,43 @@ void ImageWidgetGL::paintGL()
m_program->setUniformValue("colormapIdx", m_colormapIdx);
f->glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
m_vao->release();
if(m_drawGrid && !m_grid.empty)
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setRenderHint(QPainter::TextAntialiasing);
painter.setPen(QPen(Qt::yellow, 1.0 / m_scale));
QTransform tran;
tran.translate(-std::floor(dx), -std::floor(dy));
tran.scale(m_scale, m_scale);
painter.setTransform(tran);
painter.setClipRect(0, 0, m_imgWidth, m_imgHeight);
painter.drawPath(m_grid.grid);
painter.setPen(Qt::yellow);
QFont font({"Arial", "serif-sans"});
font.setPointSizeF(12 / m_scale);
painter.setFont(font);
for(auto &text : m_grid.text)
painter.drawText(QRectF(text.first, QSizeF(4000, 4000)), text.second);
painter.setPen(QPen(Qt::green, 1.0 / m_scale));
QFontMetricsF fontMetric = QFontMetricsF(font);
for(auto &object : m_grid.objects)
{
QRectF rect = fontMetric.boundingRect(object.name);
rect.moveCenter(object.pixel);
painter.setTransform(tran);
painter.drawText(rect, Qt::TextDontClip, object.name);
painter.translate(object.pixel);
painter.rotate(object.pos_ang);
painter.drawEllipse(QPointF(0, 0), object.maj_ax, object.maj_ax);
}
}
}
}
}
void ImageWidgetGL::resizeGL(int w, int h)
+5
View File
@@ -8,6 +8,7 @@
#include <QOpenGLTexture>
#include <QOpenGLVertexArrayObject>
#include <QOpenGLFunctions>
#include <QPainterPath>
#include "database.h"
#include "rawimage.h"
#include "imageinfodata.h"
@@ -37,6 +38,7 @@ public:
virtual QImage renderToImage() = 0;
virtual void thumbnailLoaded(const Image *image) = 0;
virtual void showThumbnail(bool enable) = 0;
virtual void drawGrid(bool enable) = 0;
static QImage loadColormap();
};
@@ -70,6 +72,7 @@ class ImageWidgetGL : public QOpenGLWidget, public ImageWidget
GLuint m_debayerTex = 0;
std::shared_ptr<RawImage> m_rawImage;
std::shared_ptr<WCSDataT> m_wcs;
SkyGrid m_grid;
int m_width, m_height;
int m_imgWidth = -1, m_imgHeight = -1;
int m_currentImg = 0;
@@ -87,6 +90,7 @@ class ImageWidgetGL : public QOpenGLWidget, public ImageWidget
bool m_selecting = false;
bool m_sizesDirty = false;
bool m_srgb = false;
bool m_drawGrid = false;
int m_thumbnailCount = 0;
int m_maxTextureSize = 0;
int m_maxArrayLayers = 0;
@@ -116,6 +120,7 @@ public:
QImage renderToImage() override;
void thumbnailLoaded(const Image *image) override;
void showThumbnail(bool enable) override;
void drawGrid(bool enable) override;
protected:
void paintGL() override;
void resizeGL(int w, int h) override;
+56 -4
View File
@@ -241,18 +241,59 @@ bool loadXISF(const QString &path, ImageInfoData &info, std::shared_ptr<RawImage
{
info.fitsHeader.append(fits);
}
QVector<FITSRecord> xisfWCS;
auto imageproperties = xisfImage.imageProperties();
for(auto prop : imageproperties)
{
info.fitsHeader.append(prop);
if(prop.id == "PCL:AstrometricSolution:ReferenceCelestialCoordinates" && prop.value.type() == LibXISF::Variant::Type::F64Vector)
{
auto val = prop.value.value<LibXISF::F64Vector>();
if(val.size() >= 2)
{
xisfWCS.append({"CRVAL1", val[0], "value from PCL:AstrometricSolution"});
xisfWCS.append({"CRVAL2", val[1], "value from PCL:AstrometricSolution"});
}
}
else if(prop.id == "PCL:AstrometricSolution:ReferenceImageCoordinates" && prop.value.type() == LibXISF::Variant::Type::F64Vector)
{
auto val = prop.value.value<LibXISF::F64Vector>();
if(val.size() >= 2)
{
xisfWCS.append({"CRPIX1", val[0], "value from PCL:AstrometricSolution"});
xisfWCS.append({"CRPIX2", val[1], "value from PCL:AstrometricSolution"});
}
}
else if(prop.id == "PCL:AstrometricSolution:LinearTransformationMatrix" && prop.value.type() == LibXISF::Variant::Type::F64Matrix)
{
auto val = prop.value.value<LibXISF::F64Matrix>();
if(val.cols() >= 2 && val.rows() >= 2)
{
xisfWCS.append({"CD1_1", val(0, 0), "value from PCL:AstrometricSolution"});
xisfWCS.append({"CD1_2", val(0, 1), "value from PCL:AstrometricSolution"});
xisfWCS.append({"CD2_1", val(1, 0), "value from PCL:AstrometricSolution"});
xisfWCS.append({"CD2_2", val(1, 1), "value from PCL:AstrometricSolution"});
}
}
else if(prop.id == "PCL:AstrometricSolution:ProjectionSystem")
{
if(prop.value.toString() == "Gnomonic")
{
xisfWCS.append({"CTYPE1", "RA---TAN", "value from PCL:AstrometricSolution"});
xisfWCS.append({"CTYPE", "DEC--TAN", "value from PCL:AstrometricSolution"});
}
}
}
info.num = xisf.imagesCount();
info.index = index;
info.wcs = std::make_shared<WCSDataT>(xisfImage.width(), xisfImage.height(), info.fitsHeader);
info.info.append({QObject::tr("Width"), QString::number(xisfImage.width())});
info.info.append({QObject::tr("Height"), QString::number(xisfImage.height())});
if(!info.wcs->valid())info.wcs.reset();
auto wcs = std::make_shared<WCSDataT>(xisfImage.width(), xisfImage.height(), info.fitsHeader);
if(!wcs->valid() && xisfWCS.size())wcs = std::make_shared<WCSDataT>(xisfImage.width(), xisfImage.height(), xisfWCS);
if(wcs->valid())info.wcs = wcs;
RawImage::DataType type;
switch(xisfImage.sampleFormat())
@@ -409,18 +450,19 @@ bool loadImage(const QString &path, ImageInfoData &info, std::shared_ptr<RawImag
{
bool ret = false;
QElapsedTimer timer;
QFileInfo fileInfo(path);
timer.start();
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();
}
else if(path.endsWith(".FIT", Qt::CaseInsensitive) || path.endsWith(".FITS", Qt::CaseInsensitive) || path.endsWith(".FZ", Qt::CaseInsensitive) || path.endsWith(".FTS", Qt::CaseInsensitive))
else if(isFITS(fileInfo.suffix()))
{
ret = loadFITS(path, info, rawImage, planar, index);
qDebug() << "LoadFITS" << timer.elapsed();
}
else if(path.endsWith(".XISF", Qt::CaseInsensitive))
else if(isXISF(fileInfo.suffix()))
{
ret = loadXISF(path, info, rawImage, planar, index);
qDebug() << "LoadXISF" << timer.elapsed();
@@ -444,3 +486,13 @@ bool loadImage(const QString &path, ImageInfoData &info, std::shared_ptr<RawImag
}
return ret;
}
bool isFITS(const QString &suffix)
{
return suffix.compare("fits", Qt::CaseInsensitive) == 0 || suffix.compare("fit", Qt::CaseInsensitive) == 0 || suffix.compare("fts", Qt::CaseInsensitive) == 0 || suffix.compare("fz", Qt::CaseInsensitive) == 0;
}
bool isXISF(const QString &suffix)
{
return suffix.compare("xisf", Qt::CaseInsensitive) == 0;
}
+2
View File
@@ -10,5 +10,7 @@ QString makeUNCPath(const QString &path);
bool readFITSHeader(const QString &path, ImageInfoData &info);
bool readXISFHeader(const QString &path, ImageInfoData &info);
bool loadImage(const QString &path, ImageInfoData &info, std::shared_ptr<RawImage> &rawImage, int index, bool planar = false);
bool isFITS(const QString &suffix);
bool isXISF(const QString &suffix);
#endif // LOADIMAGE_H
+9 -4
View File
@@ -165,11 +165,12 @@ void writeFITSImage(fitsfile *fw, std::shared_ptr<RawImage> rawimage, ImageInfoD
double vald = record.value.toDouble(&isdouble);
int valb = record.value.toString() == "T";
long long vall = record.value.toLongLong(&isint);
if(isint)isint = vall == vald;
QByteArray str = record.value.toString().toLatin1();
if(isdouble)
fits_write_key(fw, TDOUBLE, record.key.data(), &vald, record.comment.isEmpty() ? nullptr : record.comment.data(), &status);
else if(isint)
if(isint)
fits_write_key(fw, TLONGLONG, record.key.data(), &vall, record.comment.isEmpty() ? nullptr : record.comment.data(), &status);
else if(isdouble)
fits_write_key(fw, TDOUBLE, record.key.data(), &vald, record.comment.isEmpty() ? nullptr : record.comment.data(), &status);
else if(isbool)
fits_write_key(fw, TLOGICAL, record.key.data(), &valb, record.comment.isEmpty() ? nullptr : record.comment.data(), &status);
else if(record.key == "COMMENT")
@@ -192,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();
+3
View File
@@ -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);
};
+15 -3
View File
@@ -3,8 +3,9 @@
#include <QSurfaceFormat>
#include <QTranslator>
#include <QCommandLineParser>
#include <QSettings>
#include <stdlib.h>
#include "thumbnailer/genthumbnail.h"
#include "../thumbnailer/genthumbnail.h"
int main(int argc, char *argv[])
{
@@ -76,8 +77,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);
+62 -21
View File
@@ -18,6 +18,7 @@
#include <QThreadPool>
#include <QStatusBar>
#include <QImageReader>
#include <QImageWriter>
#include <QMimeDatabase>
#include <QDesktopServices>
#include <QJsonDocument>
@@ -29,6 +30,7 @@
#include "settingsdialog.h"
#include "histogram.h"
#include "batchprocessing.h"
#include "filemanager.h"
#ifdef __linux__
#include <sys/ioctl.h>
@@ -56,16 +58,23 @@ 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"});
QImageReader::setAllocationLimit(0);
_openSuffix = {nameFilter.constBegin(), nameFilter.constEnd()};
m_info = new ImageInfo(this);
QDockWidget *infoDock = new QDockWidget(tr("Image info"), this);
@@ -92,6 +101,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
connect(m_stretchPanel, &StretchToolbar::invert, m_image, &ImageScrollArea::invert);
connect(m_stretchPanel, &StretchToolbar::superPixel, m_image, &ImageScrollArea::superPixel);
connect(m_stretchPanel, &StretchToolbar::falseColor, m_image, &ImageScrollArea::falseColor);
connect(m_stretchPanel, &StretchToolbar::drawGrid, m_image, &ImageScrollArea::drawGrid);
m_ringList = new ImageRingList(m_database, nameFilter, this);
m_filesystem = new FilesystemWidget(m_ringList, this);
@@ -99,11 +109,14 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
connect(m_filesystem, &FilesystemWidget::sortChanged, m_ringList, &ImageRingList::setSort);
connect(m_filesystem, &FilesystemWidget::reverseSort, m_ringList, &ImageRingList::reverseSort);
m_filetree = nullptr;
#if !defined(FLATPAK) || !defined(__aarch64__)//bug with QTreeView and QFileSystemModel on ARM64 under flatpak
m_filetree = new Filetree(this);
connect(m_filetree, &Filetree::fileSelected, this, static_cast<void (MainWindow::*)(const QString &)>(&MainWindow::loadFile));
connect(m_filetree, &Filetree::copyFiles, [this](const QString &path){ copyOrMove(true, path); });
connect(m_filetree, &Filetree::moveFiles, [this](const QString &path){ copyOrMove(false, path); });
connect(m_filetree, &Filetree::indexDirectory, this, static_cast<void (MainWindow::*)(const QString &)>(&MainWindow::indexDir));
#endif
m_databaseView = new DataBaseView(m_database, this);
connect(m_databaseView, &DataBaseView::loadFile, this, static_cast<void (MainWindow::*)(const QString &)>(&MainWindow::loadFile));
@@ -142,11 +155,14 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
databaseViewDock->hide();
addDockWidget(Qt::BottomDockWidgetArea, databaseViewDock);
QDockWidget *filetreeDock = new QDockWidget(tr("File tree"), this);
QDockWidget *filetreeDock = nullptr;
#if !defined(FLATPAK) || !defined(__aarch64__)
filetreeDock = new QDockWidget(tr("File tree"), this);
filetreeDock->setWidget(m_filetree);
filetreeDock->setObjectName("filetreeDock");
databaseViewDock->hide();
addDockWidget(Qt::LeftDockWidgetArea, filetreeDock);
#endif
Histogram *histogram = new Histogram(this);
QDockWidget *histogramDock = new QDockWidget(tr("Histogram"), this);
@@ -175,9 +191,17 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
fileMenu->addAction(tr("Open directory recursively"), this, &MainWindow::loadDir);
QAction *saveAs = fileMenu->addAction(tr("Save as"), QKeySequence::Save, this, &MainWindow::saveAs);
fileMenu->addSeparator();
#if !defined(FLATPAK) || !defined(__aarch64__)
fileMenu->addAction(tr("File manager"), this, &MainWindow::openFileManager);
#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);
@@ -190,7 +214,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
fileMenu->addSeparator();
QAction *liveModeAction = fileMenu->addAction(tr("Live mode"), this, &MainWindow::liveMode);
liveModeAction->setCheckable(true);
QAction *exitAction = fileMenu->addAction(tr("Exit"), this, &MainWindow::close);
QAction *exitAction = fileMenu->addAction(tr("Exit"), QCoreApplication::instance(), &QCoreApplication::quit, Qt::QueuedConnection);
exitAction->setShortcut(QKeySequence::Quit);
menuBar()->addMenu(fileMenu);
@@ -263,7 +287,9 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
QAction *slideshowAction = viewMenu->addAction(tr("Slideshow"), Qt::Key_F3, m_ringList, &ImageRingList::toggleSlideshow);
slideshowAction->setCheckable(true);
viewMenu->addSeparator();
viewMenu->addActions(m_stretchPanel->actions());
auto actionList = m_stretchPanel->actions();
actionList.removeFirst();
viewMenu->addActions(actionList);
menuBar()->addMenu(viewMenu);
QMenu *selectMenu = new QMenu(tr("Select"), this);
@@ -309,7 +335,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
dockMenu->addAction(navigationToolbar->toggleViewAction());
dockMenu->addAction(filesystemDock->toggleViewAction());
dockMenu->addAction(databaseViewDock->toggleViewAction());
dockMenu->addAction(filetreeDock->toggleViewAction());
if(filetreeDock)dockMenu->addAction(filetreeDock->toggleViewAction());
dockMenu->addAction(histogramDock->toggleViewAction());
#ifdef PLATESOLVER
dockMenu->addAction(_plateSolving->toggleViewAction());
@@ -360,7 +386,8 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
infoDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable);
filesystemDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable);
databaseViewDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable);
filetreeDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable);
if(filetreeDock)filetreeDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable);
if(_plateSolving)_plateSolving->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable);
m_stretchPanel->setFloatable(false);
}
}
@@ -598,12 +625,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";
@@ -613,30 +644,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()
@@ -805,6 +837,15 @@ void MainWindow::checkNewVersion()
});
}
void MainWindow::openFileManager()
{
#if !defined(FLATPAK) || !defined(__aarch64__)
FileManager *filemanager = new FileManager(_openSuffix);
connect(filemanager, &FileManager::openFile, this, static_cast<void (MainWindow::*)(const QString&)>(&MainWindow::loadFile));
filemanager->show();
#endif
}
void MainWindow::updateWindowTitle()
{
ImagePtr ptr = m_ringList->currentImage();
+4 -2
View File
@@ -23,13 +23,14 @@ class MainWindow : public QMainWindow
FilesystemWidget *m_filesystem;
Filetree *m_filetree;
DataBaseView *m_databaseView;
PlateSolving *_plateSolving;
PlateSolving *_plateSolving = nullptr;
static int socketPair[2];
QSocketNotifier *socketNotifier;
QString _lastDir;
bool _maximized;
QString _openFilter;
QString _saveFilter;
QSet<QString> _openSuffix;
public:
MainWindow(QWidget *parent = 0);
~MainWindow() override;
@@ -51,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();
@@ -67,6 +68,7 @@ public slots:
void showSettingsDialog();
void exportCSV();
void checkNewVersion();
void openFileManager();
};
#endif // MAINWINDOW_H
View File
View File
+1 -1
View File
@@ -1,7 +1,7 @@
#ifndef PLATESOLVING_H
#define PLATESOLVING_H
#include "qelapsedtimer.h"
#include <QElapsedTimer>
#include <QDockWidget>
class Solver;
+24 -8
View File
@@ -403,9 +403,9 @@ uint32_t RawImage::norm() const
}
}
uint32_t RawImage::widthBytes() const
uint64_t RawImage::widthBytes() const
{
return m_ch * m_width * typeSize(m_type);
return (uint64_t)m_ch * m_width * typeSize(m_type);
}
uint32_t RawImage::widthSamples() const
@@ -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;
}
}
};
+1 -1
View File
@@ -97,7 +97,7 @@ public:
uint64_t size() const;
DataType type() const;
uint32_t norm() const;
uint32_t widthBytes() const;
uint64_t widthBytes() const;
uint32_t widthSamples() const;
void* data();
const void* data() const;
+170 -16
View File
@@ -4,6 +4,7 @@
#include <QDebug>
#include <QInputDialog>
#include <QJsonValue>
#include <QJSValueIterator>
#include "loadrunable.h"
#include "rawimage.h"
#include "loadimage.h"
@@ -40,7 +41,7 @@ ScriptEngine::ScriptEngine(Database *database, BatchProcessing *parent)
void ScriptEngine::setParams(const QString &scriptPath, const QList<QPair<QString, QString>> &paths, const QString &outputDir)
{
_scriptPath = scriptPath;
_paths = paths;
setPaths(paths);
_outputDir = outputDir + "/";
}
@@ -92,6 +93,55 @@ bool ScriptEngine::isMarked(const File *file)
return ret;
}
QJSValue ScriptEngine::getObjects(double ra, double dec, double distance)
{
QVector<SkyObject> objects;
QMetaObject::invokeMethod(_database, [this, ra, dec, distance](){
return _database->getObjects(ra - distance, ra + distance, dec - distance, dec + distance); }, Qt::BlockingQueuedConnection, &objects);
QJSValue ret = newArray(objects.size());
qint32 i = 0;
for(auto &object : objects)
{
QJSValue jsObj = newObject();
jsObj.setProperty("name", object.name);
jsObj.setProperty("name2", object.name2);
jsObj.setProperty("ra", object.skyPoint.RA());
jsObj.setProperty("dec", object.skyPoint.DEC());
jsObj.setProperty("mag", object.mag);
ret.setProperty(i++, jsObj);
}
return ret;
}
QJSValue ScriptEngine::getObjects(const QJSValue &bounds)
{
QVector<SkyObject> objects;
double minRa = bounds.property("minRA").toNumber();
double maxRa = bounds.property("maxRA").toNumber();
double minDec = bounds.property("minDEC").toNumber();
double maxDec = bounds.property("maxDEC").toNumber();
QMetaObject::invokeMethod(_database, [this, minRa, maxRa, minDec, maxDec](){
return _database->getObjects(minRa, maxRa, minDec, maxDec); }, Qt::BlockingQueuedConnection, &objects);
QJSValue ret = newArray(objects.size());
qint32 i = 0;
for(auto &object : objects)
{
QJSValue jsObj = newObject();
jsObj.setProperty("name", object.name);
jsObj.setProperty("name2", object.name2);
jsObj.setProperty("ra", object.skyPoint.RA());
jsObj.setProperty("dec", object.skyPoint.DEC());
jsObj.setProperty("mag", object.mag);
ret.setProperty(i++, jsObj);
}
return ret;
}
void ScriptEngine::setMaxThread(int maxthread)
{
int newval = std::max(std::min(QThread::idealThreadCount(), maxthread), 1);
@@ -368,14 +418,73 @@ QJSValue ScriptEngine::newArray(uint size)
return _jsEngine->newArray(size);
}
void ScriptEngine::run()
QJSValue ScriptEngine::eval(const QString &program)
{
QJSValue jsPaths = _jsEngine->newArray(_paths.size());
for(qsizetype i=0; i<_paths.size(); i++)
jsPaths.setProperty(i, _jsEngine->newQObject(new File(_paths[i].first, _paths[i].second, this)));
QStringList stackTrace;
QJSValue result = _jsEngine->evaluate(program, QString(), 1, &stackTrace);
if(result.isError())
{
QString error = result.property("name").toString() + " on line " + result.property("lineNumber").toString() + " : " + result.toString();
error += "\n" + result.property("stack").toString();
emit newMessage(error, true);
}
else if(!result.isUndefined())
{
emit newMessage(result.toString(), false);
}
return result;
}
QStringList ScriptEngine::complete(const QString &line)
{
QStringList complete;
QJSValue globObj = _jsEngine->globalObject();
QRegularExpression reg("[a-zA-Z_][a-zA-Z0-9_]*");
auto match = reg.match(line);
if(match.hasMatch())
{
QString var = match.captured();
if(globObj.hasProperty(var))
{
complete.clear();
QJSValueIterator it(globObj.property(var));
while(it.hasNext())
{
it.next();
if(it.name() != "constructor" && it.name() != "objectNameChanged")
complete.append(var + "." + it.name());
}
}
}
else
{
QJSValueIterator it(globObj);
while(it.hasNext())
{
it.next();
complete.append(it.name());
}
}
return complete;
}
void ScriptEngine::setPaths(const QList<QPair<QString, QString> > &paths)
{
_paths = paths;
QJSValue jsPaths = _jsEngine->newArray(paths.size());
for(qsizetype i=0; i<paths.size(); i++)
jsPaths.setProperty(i, _jsEngine->newQObject(new File(paths[i].first, paths[i].second, this)));
_jsEngine->globalObject().setProperty("files", jsPaths);
}
void ScriptEngine::run()
{
QFile scriptFile(_scriptPath);
if(!scriptFile.open(QIODevice::ReadOnly))
{
@@ -406,16 +515,18 @@ void File::loadFitsKeywords()
{
_fitsKeywordsLoaded = true;
ImageInfoData info;
if(suffix().toLower() == "xisf")
if(isXISF(suffix()))
{
readXISFHeader(_path, info);
}
else if(suffix().toLower() == "fits" || suffix().toLower() == "fit")
else if(isFITS(suffix()))
{
readFITSHeader(_path, info);
}
else return;
_wcs = info.wcs;
for(auto &record : info.fitsHeader)
{
_fitsKeywords.append(record.key);
@@ -557,7 +668,7 @@ bool File::modifyFITSRecords(const FITSRecordModify *modify)
_fitsKeywordsLoaded = false;
_fitsKeywords.clear();
if(QRegularExpression("(fits?|fz|fts)", QRegularExpression::CaseInsensitiveOption).match(suffix()).hasMatch())
if(isFITS(suffix()))
{
fitsfile *file;
int status = 0;
@@ -574,16 +685,22 @@ bool File::modifyFITSRecords(const FITSRecordModify *modify)
int naxis;
long naxes[3] = {0};
int type = -1;
std::vector<int> imageIdxs;
for(int i=1; i <= num; i++)
{
fits_movabs_hdu(file, i, IMAGE_HDU, &status);
fits_get_hdu_type(file, &type, &status);
fits_get_img_param(file, 3, &imgtype, &naxis, naxes, &status);
if(type == IMAGE_HDU && naxis >= 2 && naxis <= 3 && status == 0)
break;
if(i == num)return false;
if(type == IMAGE_HDU)
{
fits_get_img_param(file, 3, &imgtype, &naxis, naxes, &status);
if(naxis >= 2 && naxis <= 3)imageIdxs.push_back(i);
}
}
if(modify->_imageIdx >= imageIdxs.size())return false;
fits_movabs_hdu(file, imageIdxs[modify->_imageIdx], &type, &status);
fits_get_img_param(file, 3, &imgtype, &naxis, naxes, &status);
for(auto &remove : modify->_remove)
{
int status = 0;//we ignore errors from here
@@ -680,7 +797,7 @@ bool File::modifyFITSRecords(const FITSRecordModify *modify)
return status == 0;
}
else if(suffix().toLower() == "xisf")
else if(isXISF(suffix()))
{
try
{
@@ -691,13 +808,16 @@ bool File::modifyFITSRecords(const FITSRecordModify *modify)
qDebug() << "modify" << in << out;
for(auto &remove : modify->_remove)
modifyXISF.removeFITSKeyword(0, remove.toStdString());
modifyXISF.removeFITSKeyword(modify->_imageIdx, remove.toStdString());
for(auto &record : modify->_update)
modifyXISF.updateFITSKeyword(0, {record.key.toStdString(), record.value.toString().toStdString(), record.comment.toStdString()}, true);
modifyXISF.updateFITSKeyword(modify->_imageIdx, {record.key.toStdString(), record.value.toString().toStdString(), record.comment.toStdString()}, true);
for(auto &record : modify->_add)
modifyXISF.addFITSKeyword(0, {record.key.toStdString(), record.value.toString().toStdString(), record.comment.toStdString()});
modifyXISF.addFITSKeyword(modify->_imageIdx, {record.key.toStdString(), record.value.toString().toStdString(), record.comment.toStdString()});
for(auto &property : modify->_property)
modifyXISF.updateProperty(modify->_imageIdx, property);
modifyXISF.save(out.toLocal8Bit().toStdString());
modifyXISF.close();
@@ -706,6 +826,7 @@ bool File::modifyFITSRecords(const FITSRecordModify *modify)
}
catch(std::filesystem::filesystem_error &err)
{
if(_engine)_engine->newMessage("Failed to modify file " + _path + " " + err.what(), true);
return false;
}
catch(LibXISF::Error &err)
@@ -788,6 +909,24 @@ QJSValue File::stats()
return _stats;
}
QJSValue File::calculatedBounds()
{
QJSValue ret = _engine->newObject();
loadFitsKeywords();
if(_wcs)
{
double minRa, maxRa, minDec, maxDec, crVal1, crVal2;
_wcs->calculateBounds(minRa, maxRa, minDec, maxDec, crVal1, crVal2);
ret.setProperty("minRA", minRa);
ret.setProperty("maxRA", maxRa);
ret.setProperty("minDEC", minDec);
ret.setProperty("maxDEC", maxDec);
ret.setProperty("crVal1", crVal1);
ret.setProperty("crVal2", crVal2);
}
return ret;
}
#ifdef PLATESOLVER
QJSValue File::solve(bool updateHeader)
{
@@ -857,6 +996,21 @@ void FITSRecordModify::addKeyword(const QString &key, const QVariant &value, con
_update.append({key.toLatin1(), value, comment.toLatin1()});
}
void FITSRecordModify::updateProperty(const QString &id, const LibXISF::Variant &value)
{
_property.append(LibXISF::Property(id.toStdString(), value));
}
uint32_t FITSRecordModify::imageIndex() const
{
return _imageIdx;
}
void FITSRecordModify::setImageIndex(uint32_t idx)
{
_imageIdx = idx;
}
bool TextFile::open(const QString &path, const QString &mode)
{
_fr.setFileName(path);
+14
View File
@@ -9,6 +9,7 @@
#include <QSemaphore>
#include "database.h"
#include "imageinfodata.h"
#include "libxisf.h"
class BatchProcessing;
class Solver;
@@ -41,6 +42,8 @@ public:
Q_INVOKABLE void mark(File *file);
Q_INVOKABLE void unmark(File *file);
Q_INVOKABLE bool isMarked(const File *file);
Q_INVOKABLE QJSValue getObjects(double ra, double dec, double distance);
Q_INVOKABLE QJSValue getObjects(const QJSValue &bounds);
Q_INVOKABLE void setMaxThread(int maxthread);
Q_INVOKABLE void sync();
Q_INVOKABLE QJSValue getString(const QString &label = QString(), const QString &text = QString()) const;
@@ -61,6 +64,9 @@ public:
#endif // PLATESOLVER
QJSValue newObject();
QJSValue newArray(uint size);
QJSValue eval(const QString &program);
QStringList complete(const QString &line);
void setPaths(const QList<QPair<QString, QString>> &paths);
public slots:
void run();
signals:
@@ -96,6 +102,7 @@ class File : public QObject
bool _fitsKeywordsLoaded = false;
QStringList _fitsKeywords;
QMultiHash<QString, FITSRecord> _fitsRecords;
std::shared_ptr<WCSDataT> _wcs;
void loadFitsKeywords();
bool mkpath(const QString &path) const;
QJSValue _stats;
@@ -124,6 +131,7 @@ public:
Q_INVOKABLE File* convert(const QString &outpath, const QString &format, const QVariantMap &params = QVariantMap());
Q_INVOKABLE File* convertAsync(const QString &outpath, const QString &format, const QVariantMap &params = QVariantMap());
Q_INVOKABLE QJSValue stats();
Q_INVOKABLE QJSValue calculatedBounds();
#ifdef PLATESOLVER
Q_INVOKABLE QJSValue solve(bool updateHeader = false);
Q_INVOKABLE QJSValue extractStars(bool hfr);
@@ -136,6 +144,8 @@ class FITSRecordModify : public QObject
QStringList _remove;
QVector<FITSRecord> _update;
QVector<FITSRecord> _add;
QVector<LibXISF::Property> _property;
uint32_t _imageIdx = 0;
friend class File;
public:
@@ -143,6 +153,10 @@ public:
Q_INVOKABLE void removeKeyword(const QString &key);
Q_INVOKABLE void updateKeyword(const QString &key, const QVariant &value, const QString &comment = QString());
Q_INVOKABLE void addKeyword(const QString &key, const QVariant &value, const QString &comment = QString());
Q_PROPERTY(uint32_t imageIndex READ imageIndex WRITE setImageIndex);
void updateProperty(const QString &id, const LibXISF::Variant &value);
uint32_t imageIndex() const;
void setImageIndex(uint32_t idx);
};
class TextFile : public QObject
+29 -1
View File
@@ -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
+13
View File
@@ -186,6 +186,19 @@ bool Solver::updateHeader(QString &error)
modify.updateKeyword("CTYPE2", "DEC--TAN", QByteArray("first parameter DEC, projection TANgential"));
modify.updateKeyword("RADESYS", "ICRS", QByteArray("International Celestial Reference System"));
modify.updateKeyword("EQUINOX", 2000, QByteArray("Equinox of coordinates"));
LibXISF::F64Matrix matrix(2, 2);
matrix(0, 0) = std::cos(rotationRad) * cdeltx;
matrix(0, 1) =-std::sin(rotationRad) * cdelty;
matrix(1, 0) = std::sin(rotationRad) * cdeltx;
matrix(1, 1) = std::cos(rotationRad) * cdelty;
modify.updateProperty("PCL:AstrometricSolution:ReferenceCelestialCoordinates", LibXISF::F64Vector({solution.ra, solution.dec}));
modify.updateProperty("PCL:AstrometricSolution:ReferenceImageCoordinates", LibXISF::F64Vector({_stats.width / 2.0, _stats.height / 2.0}));
modify.updateProperty("PCL:AstrometricSolution:LinearTransformationMatrix", LibXISF::F64Matrix(matrix));
modify.updateProperty("PCL:AstrometricSolution:ProjectionSystem", LibXISF::String("Gnomonic"));
modify.updateProperty("PCL:AstrometricSolution:ReferenceNativeCoordinates", LibXISF::F64Vector({0, 90}));
bool ret = file.modifyFITSRecords(&modify);
if(!ret)error = tr("Failed to update file header");
else emit headerUpdated(_path);
View File
View File
View File
View File
View File
@@ -89,6 +89,11 @@ StretchToolbar::StretchToolbar(QWidget *parent) : QToolBar(tr("Stretch toolbar")
m_autoStretchOnLoad = addAction(QIcon(":/nuke_a.png"), tr("Apply auto stretch on load"));
m_autoStretchOnLoad->setCheckable(true);
QAction *showGridButton = addAction(QIcon(":/grid.svg"), tr("Draw equatorial grid"));
showGridButton->setCheckable(true);
connect(showGridButton, &QAction::toggled, this, &StretchToolbar::drawGrid);
QSettings settings;
m_autoStretchOnLoad->setChecked(settings.value("stretchtoolbar/autostretch", false).toBool());
}
@@ -99,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();
@@ -33,6 +34,7 @@ signals:
void invert(bool enable);
void superPixel(bool enable);
void falseColor(bool enable);
void drawGrid(bool enable);
};
#endif // STRETCHTOOLBAR_H
View File
+5 -5
View File
@@ -6,9 +6,9 @@ if(BUILD_THUMBNAILER)
Dll.cpp
loadimage.cpp
TenmonThumbnailProvider.cpp
../rawimage.h
../rawimage.cpp
../rawimage_sse.cpp)
../src/rawimage.h
../src/rawimage.cpp
../src/rawimage_sse.cpp)
set_target_properties(tenmonthumbnailer PROPERTIES PREFIX "")
target_compile_definitions(tenmonthumbnailer PRIVATE NO_QT)
@@ -19,8 +19,8 @@ if(BUILD_THUMBNAILER)
qt_add_executable(tenmonthumbnailer
main.cpp
loadimage.cpp
../rawimage.cpp
../rawimage_sse.cpp)
../src/rawimage.cpp
../src/rawimage_sse.cpp)
target_link_libraries(tenmonthumbnailer PRIVATE ${FITS_LIB} XISF)
+1 -1
View File
@@ -2,7 +2,7 @@
#include <thumbcache.h> // For IThumbnailProvider.
#include <new>
#include "libxisf.h"
#include "../rawimage.h"
#include "../src/rawimage.h"
bool loadXISF(const LibXISF::ByteArray &data, std::shared_ptr<RawImage> &rawImage);
bool loadFITS(const LibXISF::ByteArray &data, std::shared_ptr<RawImage> &rawImage);
+2 -3
View File
@@ -1,7 +1,6 @@
#include "genthumbnail.h"
#include "../rawimage.h"
#include "../loadimage.h"
#include "../src/rawimage.h"
#include "../src/loadimage.h"
int generateThumbnail(const QString &input, const QString &output, uint32_t size)
{
+1 -1
View File
@@ -1,5 +1,5 @@
#include "libxisf.h"
#include "../rawimage.h"
#include "../src/rawimage.h"
#ifdef WIN32
#include <windows.h>
#endif
+1 -1
View File
@@ -1,7 +1,7 @@
#include <vector>
#include <string>
#include <iostream>
#include "../rawimage.h"
#include "../src/rawimage.h"
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"
Binary file not shown.
+428 -444
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
+428 -444
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.
+428 -444
View File
File diff suppressed because it is too large Load Diff