Compare commits

...

55 Commits

Author SHA1 Message Date
Dušan Poizl 39775b5e98 Scale float images to 0,1 range on load 4 weeks ago
Dušan Poizl 93b56e2966 Workaround for QTBUG-87332 2 months ago
Dušan Poizl 2e41464ff4 Update build instructions 2 months ago
Dušan Poizl b6ae7d4cdb Update french help, fix few typo. Thanks to Patrick Chevalley 2 months ago
Dušan Poizl 1bd48e8fb4 Refres database table when indexing is done 2 months ago
Dušan Poizl 1d65eda490 Search with CRVAL# 2 months ago
Dušan Poizl 97346df596 Update help 2 months ago
Dušan Poizl a157b274a2 Save to database CRVALi 2 months ago
Dušan Poizl 6466702819 Set correct type to vertex attribute 2 months ago
Dušan Poizl b4ea65b42a Upload sizes to OpenGL only once per draw 2 months ago
Dušan Poizl 1682de4e1b Update translations 2 months ago
Dušan Poizl 00872b31df Add icon for MacOS 2 months ago
Dušan Poizl 2884787916 Changes to build on MacOS 2 months ago
Dušan Poizl 86ea9fc137 Add MacOS PCL libs 2 months ago
Dušan Poizl 67199a033d Better status bar 2 months ago
Dušan Poizl 0f182900c2 Fix build on older gcc 2 months ago
Dušan Poizl 19ed5ae1a4 Better handling of FITS records 2 months ago
Dušan Poizl 46215c7a7d Workaround for incorect handling of PV1_2 2 months ago
Dušan Poizl c346487504 Add parsing WCS info from XISF 2 months ago
Dušan Poizl 5b6fead6f1 Fix issue with numeric values in XISF FITS header 2 months ago
Dušan Poizl 3e94aa0eda Add search by RA and DEC point 2 months ago
Dušan Poizl 08e70cdb52 Calculate bounds on indexing 2 months ago
Dušan Poizl 04e587b51c Add WCSData::calculateBounds 2 months ago
Dušan Poizl 42dd55244a Add wcslib as lib name 2 months ago
Dušan Poizl 6b9ea5e4b9 Add support for WCS 2 months ago
Dušan Poizl 701a425cc7 Add support for 16 bit PNG images 2 months ago
Dušan Poizl 9cd2ae14b3 Add status bar with color value 2 months ago
Dušan Poizl f7e4e1874f Proper filter setting 2 months ago
Dušan Poizl e6749fc487 Move shaders to subdirectory 2 months ago
Dušan Poizl dc6aa6baa8 Fix bug with wayland backend 2 months ago
Dušan Poizl c8a70d22f8 Show marked files in file list bold 2 months ago
Dušan Poizl dbb533176c Remove unecessary call 3 months ago
Dušan Poizl 032f5b0577 Improve file selection in file system widget 3 months ago
Dušan Poizl eb417010c3 Set max value in autostretch 3 months ago
Dušan Poizl 5b44d2ac69 Add support for NEF, DNG 3 months ago
Dušan Poizl d1df789691 French help and updated translations 3 months ago
Dušan Poizl ab7d04b625 Workaround for unsupported QSqlQuery::size() 3 months ago
Dušan Poizl a4cfc65d4b Wrap reindex into transaction 3 months ago
Dušan Poizl b4746be190 Update help about marking images 3 months ago
Dušan Poizl 9ceb7556f9 Add marking and unmark from thumbnails view 3 months ago
Dušan Poizl 41b29f0701 Show marked files in database view 3 months ago
Dušan Poizl 67ae2d4b62 Mark unmark files from database view 3 months ago
Dušan Poizl b6b6863331 Add override keyword 3 months ago
Dušan Poizl 571fa57af2 Update translations 3 months ago
Dušan Poizl abb3d631bf Double click in file tree open file 3 months ago
Dušan Poizl b65911943e Fix in context menu 3 months ago
Dušan Poizl 9d9f8db499 Fix crash 3 months ago
Dušan Poizl 3060b17c0c Translations update 3 months ago
Dušan Poizl fcf336d63a Second call to QTranslator::load() seem to clear translation 3 months ago
Dušan Poizl 54ef8e990c Selecting thumbnails 3 months ago
Dušan Poizl 94466a6b9b Draw file name under thumbnail 3 months ago
Dušan Poizl b84d8127ad Add copy, move and index action to File tree 3 months ago
Dušan Poizl c971a919ec Add Filetree dock 3 months ago
Dušan Poizl 8c248b7cfc Add French tranlation, credit Patrick Chevalley 3 months ago
Dušan Poizl 43b510a78c Try load translation from application dir 3 months ago
  1. BIN
      3rdparty/lib/MacOS/libPCL-pxi.a
  2. BIN
      3rdparty/lib/MacOS/libRFC6234-pxi.a
  3. BIN
      3rdparty/lib/MacOS/liblcms-pxi.a
  4. BIN
      3rdparty/lib/MacOS/liblz4-pxi.a
  5. BIN
      3rdparty/lib/MacOS/libzlib-pxi.a
  6. 26
      CMakeLists.txt
  7. 19
      README
  8. 25
      about/help_en
  9. 106
      about/help_fr
  10. 5
      about/help_sk
  11. 110
      database.cpp
  12. 6
      database.h
  13. 171
      databaseview.cpp
  14. 31
      databaseview.h
  15. 106
      filesystemwidget.cpp
  16. 20
      filesystemwidget.h
  17. 229
      imageinfo.cpp
  18. 43
      imageinfo.h
  19. 83
      imageringlist.cpp
  20. 13
      imageringlist.h
  21. 12
      imagescrollarea.h
  22. 261
      imagescrollareagl.cpp
  23. 61
      imagescrollareagl.h
  24. 38
      loadrunable.cpp
  25. 4
      loadrunable.h
  26. 8
      main.cpp
  27. 74
      mainwindow.cpp
  28. 11
      mainwindow.h
  29. 98
      rawimage.cpp
  30. 5
      rawimage.h
  31. 16
      resources.qrc
  32. 4
      shaders/image.frag
  33. 0
      shaders/image.vert
  34. 1
      shaders/thumb.frag
  35. 2
      shaders/thumb.vert
  36. 23
      statusbar.cpp
  37. 19
      statusbar.h
  38. 10
      stretchtoolbar.cpp
  39. BIN
      tenmon.icns
  40. BIN
      translations/tenmon_en.qm
  41. 68
      translations/tenmon_en.ts
  42. BIN
      translations/tenmon_fr.qm
  43. 450
      translations/tenmon_fr.ts
  44. BIN
      translations/tenmon_sk.qm
  45. 68
      translations/tenmon_sk.ts

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

@ -19,6 +19,7 @@ find_library(GSLCBLAS_LIB gslcblas 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 PATHS REQUIRED)
set(TENMON_SRC
about.cpp
@ -35,6 +36,7 @@ set(TENMON_SRC
markedfiles.cpp
rawimage.cpp
starfit.cpp
statusbar.cpp
stfslider.cpp
stretchtoolbar.cpp
)
@ -43,31 +45,43 @@ qt5_add_resources(TENMON_SRC resources.qrc)
if(WIN32)
list(APPEND TENMON_SRC icon.rc)
add_compile_definitions("__PCL_WINDOWS")
set(tenmon_ICON "")
elseif(APPLE)
add_compile_definitions("__PCL_MACOS")
set(tenmon_ICON ${CMAKE_CURRENT_SOURCE_DIR}/tenmon.icns)
set_source_files_properties(${tenmon_ICON} PROPERTIES MACOSX_PACKAGE_LOCATION "Resources")
else()
add_compile_definitions("__PCL_LINUX")
set(tenmon_ICON "")
endif()
add_executable(tenmon WIN32 ${TENMON_SRC})
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 ${OpenCV_INCLUDE_DIRS} ${FITS_INCLUDE} 3rdparty/include ${CMAKE_BINARY_DIR})
if(WIN32)
target_link_directories(tenmon PRIVATE 3rdparty/lib/Windows)
elseif(APPLE)
target_link_directories(tenmon PRIVATE 3rdparty/lib/MacOS)
else()
target_link_directories(tenmon PRIVATE 3rdparty/lib/Linux)
endif()
target_link_libraries(tenmon Qt5::Widgets Qt5::Sql ${OpenCV_LIBS} ${GSL_LIB} ${GSLCBLAS_LIB} ${EXIF_LIB} ${FITS_LIB} ${RAW_LIB})
target_link_libraries(tenmon PCL lcms lz4 RFC6234 zlib)
target_link_libraries(tenmon Qt5::Widgets Qt5::Sql ${OpenCV_LIBS} ${GSL_LIB} ${GSLCBLAS_LIB} ${EXIF_LIB} ${FITS_LIB} ${RAW_LIB} ${WCS_LIB})
if(APPLE)
target_link_libraries(tenmon PCL-pxi lcms-pxi lz4-pxi RFC6234-pxi zlib-pxi "-framework CoreFoundation")
else()
target_link_libraries(tenmon PCL lcms lz4 RFC6234 zlib)
endif(APPLE)
if(LIBRAW_STATIC)
add_compile_definitions("LIBRAW_NODLL")
target_link_libraries(tenmon jasper)
endif()
install(TARGETS tenmon)
if(UNIX)
install(TARGETS tenmon BUNDLE DESTINATION .)
if(UNIX AND NOT APPLE)
find_program(XDG-DESKTOP-MENU_EXECUTABLE xdg-desktop-menu)
if(XDG-DESKTOP-MENU_EXECUTABLE)
install(SCRIPT install.cmake)
@ -80,7 +94,7 @@ if(UNIX)
install(FILES org.nou.tenmon.png DESTINATION "/usr/share/icons/hicolor/32x32/apps")
endif()
endif()
endif(UNIX)
endif(UNIX AND NOT APPLE)
option(RELEASE_BUILD "Release build" OFF)
if(RELEASE_BUILD)

@ -2,10 +2,23 @@ FITS/XISF image viewer with multithreaded image loading
To get all dependencies install these packages
sudo apt install qtbase5-dev libraw-dev libexif-dev libcfitsio-dev libgsl-dev cmake
sudo apt install qtbase5-dev libraw-dev libexif-dev libcfitsio-dev libgsl-dev wcslib-dev libopencv-dev cmake
on OpenSUSE
sudo zypper install opencv-devel gsl-devel exif-devel libraw-devel wcslib-devel libqt5-qtbase-devel
MacOS X
To compile on MacOS install XCode first. Then install homebrew in x86_64 mode
with "arch -i x86_64". Building on native ARM is not supported.
homebrew install qt5 libraw cfitsio libexif libgsl wcslib opencv
You may need to set CMAKE_PREFIX_PATH for Qt5 and OpenCV so CMake can find them.
Then to build run standard cmake
cmake -B build -S .
make
./tenmon
cmake --build build
./build/tenmon

@ -14,6 +14,7 @@ img { margin: 5px; }
<li>FITS 8, 16 bit integer and 32 bit float</li>
<li>XISF 8, 16 bit integer and 32 bit float</li>
<li>JPEG and PNG images</li>
<li>CR2, NEF, DNG raw images</li>
</ul>
</p>
@ -23,6 +24,8 @@ The <i>File system</i> panel shows other images in the same directory as the loa
<i>Stretch panel</i> containing various options for auto-stretching linear image data.</p>
<p>All panels in the interface can be moved around and/or closed. Any closed or non-visible panel can be
re-opened through the <i>Docks</i> menu at the top.</p>
<p>At bottom there is status bar that show current lightness or red, green, blue pixel value under mouse cursor then X and Y coordinates and
if image contain World Coordinate System metadata it show celestial coordinates.</p>
<h3>Opening and saving images</h3>
<p>To load an image select <i>File->Open</i> and choose the file. After a file is loaded, it becomes visible in the main image panel, and the
@ -65,6 +68,20 @@ Following the slider are 5 buttons for automatic stretching:
This dialog can be useful to clear marks from images. Marked images show a <b>*</b> character in the title bar of the main window.
Marked images can be copied or moved to a selected directory with <i>File->Copy/Move marked files</i>.
After copying or moving, the list of marked files is cleared. The list of marked files will be remembered after quitting the program.</p>
<p>Another way to mark images is in database view where you can select rows and then select mark or unmark action in context menu. Marked
files will be shown with bold text. Third way to mark files is from thumbnails view where you can press press <i>Shift</i> and click with left
mouse button and drag across thumbnails to mark them. Holding <i>Ctrl</i> will unmark files.</p>
<h3>File system and tree</h3>
<p>File system panel contain list of images in current opened directory. You can select file from this list and it will be displayed. It is also possible to
use arrow keys to go back (left and up) and forth (right and down) between images.</p>
<p>File tree show file system structure. You can right click to show context menu to perform various actions from <i>File</i> menu. There are also few others
<ul>
<li><i>Set as root directory</i> show only this directory and subdirectories</li>
<li><i>Reset root directory</i> show whole file system</li>
<li><i>Go up</i> show directory that is one level above current root directory</li>
</ul>
</p>
<h3>Database of FITS/XISF files</h3>
<p>Tenmon can scan a directory of FITS/XISF files and index metadata from FITS headers into it's internal database. This allows searching and sorting images based on that metadata.</p>
@ -78,6 +95,14 @@ Below the table is button to select which columns/properties are displayed.</p>
<p>Also at the bottom of the database panel are three combo boxes and text inputs used for filtering.
Select the property to filter on with the combo box and in the adjacent text box enter a string to search for in that property.
These three combo box contain list of all properties that are found during indexing except first five. First one set searching in file name.
Next two "RA pos" and "DEC pos" allow to filter out indexed images that contain point with entered RA/DEC coordinate. Expected format is three
number separated by space. In case of "DEC pos" it also accept +- sign. Omitting one or two last number is also valid. Some examples "02 12 32" "-12 43 12" "+45 32" "13".
So for RA it means hour, minutes and seconds while for DEC it is degrees, minutes and seconds.
Setting both "RA pos" and "DEC pos" can return images that doesn't contain entered point as it search against minimum and maximum RA/DEC coordinates that images contain.
"RA range" and "DEC range" filter out images which center coordinate is within entered range.
Pressing Enter or clicking on <i>Filter</i> button will filter out database record according to search parameter.
<p>Wildcards:
<ul>
<li><b>%</b> (percent) is a wildcard representing zero or more of any characters.</li>

@ -0,0 +1,106 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
<head>
<style type="text/css">
h1, h2, h3, h4 { padding:0px; margin:10px; }
p { padding:0px; margin:5px; }
img { margin: 5px; }
</style>
</head>
<body>
<h2>Aide de Tenmon</h2>
<p>Tenmon est destiné principalement à la visualisation de photos et d'images astronomique. Il prend en charge les formats suivants :
<ul>
<li>FITS 8, 16 bit entier et 32 bit point flottant</li>
<li>XISF 8, 16 bit entier et 32 bit point flottant</li>
<li>images JPEG et PNG</li>
<li>images RAW CR2, NEF, DNG</li>
</ul>
</p>
<h3>Fenêtre principale</h3>
<p>La fenêtre principale affiche l'image actuellement chargée. Sur la gauche se trouve le panneau <i>Informations de l'image</i> qui affiche des détails sur l'image chargée.
Le panneau <i>Système de fichier</i> affiche d'autres images dans le même répertoire que l'image chargée.
En haut se trouve le menu principal et en dessous se trouve le panneau <i>Réglage de la luminosité</i> contenant diverses options pour l'étirement automatique des images linéaires.</p>
<p>Tous les panneaux de l'interface peuvent être déplacés et/ou fermés. Tout panneau fermé ou non visible peut être rouvert via le menu <i>Fenêtres encrables</i> en haut.</p>
<p>En bas, il y a une barre d'état qui affiche l'intensité ou la valeur du pixel rouge, vert, bleu sous le curseur de la souris, puis les coordonnées X et Y et,
si l'image contient des métadonnées du World Coordinate System, elle affiche les coordonnées célestes.</p>
<h3>Ouvrir et enregistrer des images</h3>
<p>Pour charger une image, sélectionnez <i>Fichier->Ouvrir</i> et choisissez le fichier. Une fois qu'un fichier est chargé,
il devient visible dans le panneau d'image et le panneau du <i>système de fichiers</i> affiche les autres images dans le même répertoire.</p>
<p>L'image chargée peut être exportée dans un format différent avec <i>Fichier->Enregistrer sous</i>. Tous les formats JPEG, PNG FITS et XISF
peuvent être sélectionnés. Dans le cas d'un enregistrement JPEG ou PNG, la fonction d'étirement de la luminosité est appliquée à l'image enregistrée.
FITS et XISF sont enregistrés/convertis sans appliquer l'étirement.
Pour ouvrir une image, vous pouvez également la faire glisser et la déposer dans la fenêtre principale.</p>
<h3>Voir</h3>
<p>Le menu <i>Voir</i> propose des options pour contrôler la taille et l'échelle des images affichées :
<ul>
<li><i>Zoom avant</i> et <i>Zoom arrière</i> agrandissent et rétrécissent l'image. La molette de la souris peut également être utilisée pour zoomer librement.</li>
<li><i>Meilleur ajustement</i>, zoom automatiquement l'image pour l'adapter à la taille actuelle de la fenêtre.</li>
<li><i>100 %</i>, zoom à l'échelle 1:1.</li>
<li><i>Plein écran</i> agrandit la fenêtre principale sur tout l'écran.</li>
<li><i>Vignettes</i>, affiche de petites vignettes pour toutes les images du répertoire actuel.</li>
</ul>
<p>
<h3>Barre d'outils du réglage de la luminosité</h3>
<p>Ces outils modifient la luminosité des images affichées.
<br><img src=":/about/stretch-panel.png"></p>
<p>À partir de la gauche, il y a une échelle de luminosité avec trois points réglables pour contrôler manuellement l'étirement.
<ul>
<li>point noir - tous les pixels avec une valeur inférieure (plus sombre) que ce paramètre seront écrêtés en noir</li>
<li>point médian - définit la valeur à étirer à 50 % d'intensité</li>
<li>point blanc - tous les pixels avec une valeur supérieure (plus lumineuse) que celle-ci seront écrêtés en blanc</li>
</ul>
Après le curseur se trouvent 5 boutons pour la luminosité automatique :
<ul>
<li><i>Luminosité automatique</i>, applique automatiquement les points noirs et moyens pour rendre l'image avec une luminosité optimale.</li>
<li><i>Réinitialiser</i>, réinitialise les trois valeurs pour le point noir, moyen et blanc par défaut.</li>
<li><i>Inverser</i>, inverse les couleurs pour afficher l'image en négatif.</li>
<li><i>Super pixel CFA </i>, moyenne 2x2 pixels en un (adapté aux images d'une caméra couleur).</li>
<li><i>Appliquer la luminosité automatique au chargement</i>, applique la luminosité automatique pour chaque image lors du chargement.</p>
</ul>
<h3>Marquer les images</h3>
<p>Les images peuvent être marquées dans le menu <i>Sélectionner</i>. Pour afficher une liste des seules images marquées, utilisez <i>Sélectionner->Afficher marqué</i>. Cette boîte de dialogue peut être utile pour effacer les marques des images. Les images marquées affichent un caractère <b>*</b> dans la barre de titre de la fenêtre principale. Les images marquées peuvent être copiées ou déplacées vers un répertoire sélectionné avec <i>Fichier->Copier/Déplacer les fichiers marqués</i>. Après la copie ou le déplacement, la liste des fichiers marqués est effacée. La liste des fichiers marqués sera mémorisée après avoir quitté le programme.</p>
<p>Une autre façon de marquer des images est dans la vue de la base de données où vous pouvez sélectionner des lignes, puis sélectionner l'action marquer ou décocher dans le menu contextuel. Les fichiers marqués seront affichés en texte gras. La troisième façon de marquer les fichiers est de voir les vignettes où vous pouvez appuyer sur <i>Maj</i> et cliquer avec le bouton gauche de la souris et faire glisser sur les vignettes pour les marquer. Maintenir <i>Ctrl</i> décochera les fichiers.</p>
<h3>Système de fichier et arborescence</h3>
<p>Le panneau du système de fichiers contient la liste des images du répertoire ouvert. Vous pouvez sélectionner un fichier dans cette liste et il sera affiché. Il est également possible
d'utiliser les touches fléchées pour revenir en arrière (gauche et haut) et avancer (droite et bas) entre les images.</p>
<p>L'arborescence des fichiers montre la structure du système de fichiers. Vous pouvez cliquer avec le bouton droit de la souris pour afficher un menu contextuel permettant d'effectuer diverses actions du menu <i>Fichier</i>. Il y a aussi les actions suivantes
<ul>
<li><i>Définir comme répertoire racine</i> afficher uniquement ce répertoire et ses sous-répertoires</li>
<li><i>Réinitialiser la racine</i> afficher tout le système de fichiers</li>
<li><i>Monter</i> afficher le répertoire qui est un niveau au-dessus du répertoire racine actuel</li>
</ul>
</p>
<h3>Base de donnée de fichiers FITS/XISF</h3>
<p>Tenmon peut analyser un répertoire de fichiers FITS/XISF et indexer les métadonnées des en-têtes FITS dans sa base de données interne. Cela permet de rechercher et de trier des images en fonction de ces métadonnées.</p>
<p>Pour remplir la base de données, sélectionnez un répertoire de fichiers FITS/XISF avec <i>Fichier->Indexer le répertoire</i>. Une fois le répertoire parcouru, les métadonnées analysées à partir des images seront stockées dans la base de données. Pour actualiser la base de données, exécutez <i>Fichier-> Ré-indexer les fichiers</i>. Cela mettra à jour toutes les métadonnées modifiées et supprimera tout enregistrement de fichiers supprimés. Pour indexer de nouveaux fichiers, exécutez simplement à nouveau <i>Fichier->Indexer le répertoire</i>.</p>
<p>La base de données est visualisée via un panneau qui n'est pas visible dans la mise en page par défaut. Pour ajouter le panneau de base de données à la vue, basculez <i>Fenêtres encrables->Base de données FITS/XISF</i>. Une fois visible, le panneau de la base de données affiche la base de données sous forme de tableau avec une colonne pour chaque propriété. Sous le tableau se trouve un bouton pour sélectionner les colonnes/propriétés à afficher.</p>
<p>Au bas du panneau de la base de données se trouvent également trois boîtes de liste déroulante et des entrées de texte utilisées pour le filtrage. Sélectionnez la propriété à filtrer dans une liste déroulante et dans la zone de texte adjacente, entrez un texte à rechercher pour cette propriété.
Ces trois boîtes contiennent la liste de toutes les propriétés qui sont trouvées pendant l'indexation, sauf les trois premières. La première définit la recherche dans le nom du fichier.
Les deux suivantes "RA pos" et "DEC pos" permettent de filtrer les images indexées qui contiennent un point avec les coordonnées RA/DEC entrée. Le format attendu est trois nombres séparés par un espace.
Dans le cas de "DEC pos", il accepte également le signe +-. L'omission d'un ou deux derniers chiffres est également valable. Quelques exemples "02 12 32" "-12 43 12" "+45 32" "13".
Le fait de définir à la fois "RA pos" et "DEC pos" peut renvoyer des images qui ne contiennent pas le point saisi, car la recherche est faite sur les coordonnées RA/DEC minimum et maximum que les images contiennent.
"RA range" et "DEC range" filtrent les images dont les coordonnées centrale se trouvent dans la plage saisie.
En appuyant sur la touche Enter ou en cliquant sur le bouton <i>Filtre</i>, les enregistrements de la base de données seront filtrés en fonction des paramètres de recherche.
<p>Caractères génériques :
<ul>
<li><b>%</b> (pourcentage) est un caractère générique représentant zéro ou plusieurs caractères.</li>
<li><b>_</b> (trait de soulignement) est un caractère générique pour exactement un caractère quelconque.</li>
<li>En l'absence de caractères génériques, le texte exacte doit correspondre.</li>
</ul>
</p>
<br><img src=":/about/filter.png"><br>
Cet exemple filtre les fichiers où : "Bias" figure dans le nom de fichier, la propriété OBJECT est "M_42" (où le trait de soulignement peut être n'importe quel caractère) et la propriété DATE commence par "2022".
</p>
<p><small>PS: Le Kanji de icône (tenmon) signifie astronomie en japonais</small></p>
</body>
</html>

@ -13,6 +13,7 @@ p { padding:0px; margin:5px 5px 10px 5px; }
<li>FITS 8, 16 bitové celočíselné a 32 bitové s plávajúcou čiarkou</li>
<li>XISF 8, 16 bitové celočíselné a 32 bitové s plávajúcou čiarkou</li>
<li>JPEG a PNG obrázky</li>
<li>CR2, NEF, DNG raw obrázky</li>
</ul>
</p>
@ -59,6 +60,10 @@ Posledné tlačidlo zapína a vypína nastavovanie optimálnych hodnôt úrovní
obrázkoch zobrazuje znak * v záhlaví hlavného okna. Takto označené obrázky je potom možné skopírovať alebo
presunúť do vybraného adresára pomocou <i>Súbor->Skopírovať/Presunúť označené súbory</i>. Po skopírovaní alebo
presunutú sa zoznam označených obrázkov vymaže. Program si tento zoznam pamätá aj po svojom ukončení.</p>
<p>Ďalší spôsob ako označiť obrázky je cez databázu FITS/XISF kde je možné vybrať jednotlivé riadky. Potom stačí
vybrať označit alebo odznačiť v kontextovom menu. Označené súbory budú zobrazené tučným textom. Tretí spôsob na označenie
obrázkov je možné cez náhľady. Držaním <i>Shift</i> a následne kliknutím ľavým tlačítkom myši sa daný obrázok označí.
Pre odznačenie je treba držať <i>Ctrl</i></p>
<h3>Databáza FITS/XISF súborov</h3>
<p>Program vie prehľadať adresár a indexovať meta údaje z FIST a XISF obrázkov do internej databázy v ktorej sa dá

@ -22,16 +22,32 @@ bool Database::init()
if(m_database.isValid())
{
m_database.setDatabaseName(dir.absoluteFilePath("database.db"));
m_database.setDatabaseName(dir.absoluteFilePath("database2.db"));
if(m_database.open())
{
m_database.exec("PRAGMA foreign_keys = ON");
m_database.exec("CREATE TABLE IF NOT EXISTS files (id INTEGER PRIMARY KEY AUTOINCREMENT, file VARCHAR(255) UNIQUE)");
m_database.exec("CREATE TABLE IF NOT EXISTS fits_files (id INTEGER PRIMARY KEY AUTOINCREMENT, file VARCHAR(255) UNIQUE, mtime DATETIME)");
m_database.exec("CREATE TABLE IF NOT EXISTS fits_headers (id INTEGER PRIMARY KEY AUTOINCREMENT, id_file INTEGER,"
"key VARCHAR(81), value VARCHAR(81), comment VARCHAR(81), FOREIGN KEY(id_file) REFERENCES fits_files(id) ON DELETE CASCADE)");
m_database.exec("CREATE INDEX IF NOT EXISTS key_value ON fits_headers(key, value)");
m_database.exec("CREATE INDEX IF NOT EXISTS id_file ON fits_headers(id_file)");
int version = checkVersion();
if(version == 0)
{
m_database.exec("PRAGMA user_version = 1");
m_database.exec("CREATE TABLE IF NOT EXISTS files (id INTEGER PRIMARY KEY AUTOINCREMENT, file VARCHAR(255) UNIQUE)");
m_database.exec("CREATE TABLE IF NOT EXISTS fits_files (id INTEGER PRIMARY KEY AUTOINCREMENT, file VARCHAR(255) UNIQUE, mtime DATETIME,"
" minRa REAL, maxRa REAL, minDec REAL, maxDec REAL, crVal1 REAL, crVal2 REAL)");
m_database.exec("CREATE TABLE IF NOT EXISTS fits_headers (id INTEGER PRIMARY KEY AUTOINCREMENT, id_file INTEGER,"
"key VARCHAR(81), value VARCHAR(81), comment VARCHAR(81), FOREIGN KEY(id_file) REFERENCES fits_files(id) ON DELETE CASCADE)");
m_database.exec("CREATE INDEX IF NOT EXISTS key_value ON fits_headers(key, value)");
m_database.exec("CREATE INDEX IF NOT EXISTS id_file ON fits_headers(id_file)");
m_database.exec("CREATE INDEX IF NOT EXISTS minRa_idx ON fits_files(minRa)");
m_database.exec("CREATE INDEX IF NOT EXISTS maxRa_idx ON fits_files(maxRa)");
m_database.exec("CREATE INDEX IF NOT EXISTS minDec_idx ON fits_files(minDec)");
m_database.exec("CREATE INDEX IF NOT EXISTS maxDec_idx ON fits_files(maxDec)");
}
else if(version > 1)
{
qDebug() << "Database version is too new";
return false;
}
QSqlError error = m_database.lastError();
if(error.type() == QSqlError::NoError)
@ -45,6 +61,8 @@ bool Database::init()
m_insertFile = QSqlQuery(m_database);
m_insertFile.prepare("INSERT INTO fits_files (file, mtime) VALUES (?, ?)");
m_insertFileWcs = QSqlQuery(m_database);
m_insertFileWcs.prepare("INSERT INTO fits_files (file, mtime, minRa, maxRa, minDec, maxDec, crVal1, crVal2) VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
m_insertFitsHeader = QSqlQuery(m_database);
m_insertFitsHeader.prepare("INSERT INTO fits_headers (id_file, key, value, comment) VALUES (?, ?, ?, ?)");
m_checkFile = QSqlQuery(m_database);
@ -55,7 +73,6 @@ bool Database::init()
m_deleteFile.prepare("DELETE FROM fits_files WHERE id=?");
return true;
}
qDebug() << error.text();
}
}
@ -76,6 +93,20 @@ bool Database::unmark(const QString &filename)
return checkError(m_unmarkQuery);
}
bool Database::mark(const QStringList &filenames)
{
m_markQuery.bindValue(0, filenames);
m_markQuery.execBatch();
return checkError(m_markQuery);
}
bool Database::unmark(const QStringList &filenames)
{
m_unmarkQuery.bindValue(0, filenames);
m_unmarkQuery.execBatch();
return checkError(m_unmarkQuery);
}
bool Database::isMarked(const QString &filename)
{
m_isMarkedQuery.bindValue(":name", filename);
@ -115,6 +146,15 @@ bool Database::checkError(QSqlQuery &query)
}
}
int Database::checkVersion()
{
QSqlDatabase db = QSqlDatabase::database();
QSqlQuery query = db.exec("PRAGMA user_version");
if(query.next())
return query.value(0).toInt();
return -1;
}
static QStringList nameFilters = {"*.fit", "*.fits", "*.xisf"};
static int countFiles(const QDir &dir, int count = 0)
@ -134,17 +174,25 @@ void Database::indexDir(const QDir &dir, QProgressDialog *progress)
QSqlDatabase database = QSqlDatabase::database();
database.transaction();
if(indexDir2(dir, progress))
{
database.commit();
emit databaseChanged();
}
else
{
database.rollback();
}
}
void Database::reindex(QProgressDialog *progress)
{
QVariantList deleteids;
QSqlDatabase database = QSqlDatabase::database();
database.transaction();
QSqlQuery size = database.exec("SELECT COUNT(*) FROM fits_files");
size.next();
progress->setMaximum(size.value(0).toInt());
QSqlQuery files = database.exec("SELECT id,file,mtime FROM fits_files");
progress->setMaximum(files.size());
int i = 0;
while(files.next())
{
@ -155,10 +203,16 @@ void Database::reindex(QProgressDialog *progress)
if(!file.exists())
deleteids.append(files.value(0));
progress->setValue(i++);
if(progress->wasCanceled())
{
database.rollback();
return;
}
}
QSqlQuery deleteFiles("DELETE FROM fits_files WHERE id = ?", database);
deleteFiles.bindValue(0, deleteids);
deleteFiles.execBatch();
database.commit();
}
QStringList Database::getFitsKeywords()
@ -219,16 +273,40 @@ bool Database::indexFile(const QFileInfo &file)
qlonglong last_id = -1;
if(ok)
{
m_insertFile.bindValue(0, filePath);
m_insertFile.bindValue(1, mtime);
if(!m_insertFile.exec())
if(info.wcs)
{
qDebug() << m_insertFile.lastError();
return false;
double minRa, maxRa, minDec, maxDec, crVal1, crVal2;
info.wcs->calculateBounds(minRa, maxRa, minDec, maxDec, crVal1, crVal2);
qDebug() << "bounds" << minRa << maxRa << minDec << maxDec;
m_insertFileWcs.bindValue(0, filePath);
m_insertFileWcs.bindValue(1, mtime);
m_insertFileWcs.bindValue(2, minRa);
m_insertFileWcs.bindValue(3, maxRa);
m_insertFileWcs.bindValue(4, minDec);
m_insertFileWcs.bindValue(5, maxDec);
m_insertFileWcs.bindValue(6, crVal1);
m_insertFileWcs.bindValue(7, crVal2);
if(!m_insertFileWcs.exec())
{
qDebug() << m_insertFileWcs.lastError();
return false;
}
last_id = m_insertFileWcs.lastInsertId().toLongLong();
}
else
{
m_insertFile.bindValue(0, filePath);
m_insertFile.bindValue(1, mtime);
if(!m_insertFile.exec())
{
qDebug() << m_insertFile.lastError();
return false;
}
last_id = m_insertFile.lastInsertId().toLongLong();
}
last_id = m_insertFile.lastInsertId().toLongLong();
QVariantList file_id, keys, values, comments;
for(auto &record : info.fitsHeader)
for(const auto &record : info.fitsHeader)
{
file_id << last_id;
keys << QString(record.key);

@ -15,6 +15,7 @@ class Database : public QObject
QSqlQuery m_isMarkedQuery;
QSqlQuery m_insertFile;
QSqlQuery m_insertFileWcs;
QSqlQuery m_insertFitsHeader;
QSqlQuery m_checkFile;
QSqlQuery m_headerKeywords;
@ -26,6 +27,8 @@ public:
bool init();
bool mark(const QString &filename);
bool unmark(const QString &filename);
bool mark(const QStringList &filenames);
bool unmark(const QStringList &filenames);
bool isMarked(const QString &filename);
QStringList getMarkedFiles();
void clearMarkedFiles();
@ -37,6 +40,9 @@ protected:
bool indexDir2(const QDir &dir, QProgressDialog *progress);
bool indexFile(const QFileInfo &file);
bool checkError(QSqlQuery &query);
int checkVersion();
signals:
void databaseChanged();
};
#endif // DATABASE_H

@ -6,10 +6,36 @@
#include <QHeaderView>
#include <QSqlError>
#include <QDebug>
#include <QMenu>
#include <QContextMenuEvent>
#include <QRegExp>
#include <iostream>
const QStringList DEFAULT_COLUMNS = {"EXPTIME", "OBJECT", "RA", "DEC"};
double RA(const QString &ra)
{
QRegularExpression reg("(\\d+)\\s*(\\d+)?\\s*(\\d+)?");
QRegularExpressionMatch match = reg.match(ra);
double h = match.captured(1).toDouble();
double m = match.captured(2).toDouble();
double s = match.captured(3).toDouble();
qDebug() << match.capturedTexts() << h << m << s;
return h*15 + m*0.25 + s*15/3600;
}
double DEC(const QString &dec)
{
QRegularExpression reg("([\\+\\-])?(\\d+)\\s*(\\d+)?\\s*(\\d+)?");
QRegularExpressionMatch match = reg.match(dec);
double sign = match.captured(1) == "-" ? -1 : 1;
double d = match.captured(2).toDouble();
double m = match.captured(3).toDouble();
double s = match.captured(4).toDouble();
qDebug() << match.capturedTexts() << sign << d << m << s;
return sign * (d + m/60 + s/3600);
}
SelectColumnsDialog::SelectColumnsDialog(QWidget *parent) : QDialog(parent)
{
m_listWidget = new QListWidget(this);
@ -47,9 +73,9 @@ QStringList SelectColumnsDialog::selectedColumns()
return ret;
}
FITSFileModel::FITSFileModel(QObject *parent) : QSqlQueryModel(parent)
FITSFileModel::FITSFileModel(Database *database, QObject *parent) : QSqlQueryModel(parent)
, m_database(database)
{
}
void FITSFileModel::sort(int column, Qt::SortOrder order)
@ -71,31 +97,79 @@ void FITSFileModel::setColumns(const QStringList &columns)
prepareQuery();
}
void FITSFileModel::setFilter(const QStringList &key, const QStringList &value)
void FITSFileModel::setFilter(const QStringList &key, const QStringList &value, const QStringList &limit)
{
if(value.isEmpty())
{
m_key.clear();
m_value.clear();
m_limit.clear();
}
else
{
m_key = key;
m_value = value;
m_limit = limit;
}
prepareQuery();
}
QVariant FITSFileModel::data(const QModelIndex &index, int role) const
{
if(role == Qt::FontRole && index.column() == 0)
{
QFont font;
QString file = index.data().toString();
font.setBold(m_markedFiles.contains(file));
return font;
}
return QSqlQueryModel::data(index, role);
}
void FITSFileModel::filesMarked(const QModelIndexList &indexes)
{
for(auto &index : indexes)
{
QString file = index.data().toString();
if(!m_markedFiles.contains(file))
{
m_markedFiles.insert(file);
emit dataChanged(index, index, {Qt::FontRole});
}
}
}
void FITSFileModel::filesUnmarked(const QModelIndexList &indexes)
{
for(auto &index : indexes)
{
QString file = index.data().toString();
if(m_markedFiles.contains(file))
{
m_markedFiles.remove(file);
emit dataChanged(index, index, {Qt::FontRole});
}
}
}
void FITSFileModel::prepareQuery()
{
QString cols;
QString join;
QString where;
QStringList where;
QString sql = "SELECT f.file,";
for(int i=0; i<m_value.size(); i++)
{
if(m_key[i] == "file")
where = QString(" WHERE f.file LIKE '%1'").arg(m_value[i]);
where.append(QString(" f.file LIKE '%1' ").arg(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])));
else if(m_key[i] == "DEC pos")
where.append(QString(" %1 BETWEEN f.minDec AND f.maxDec ").arg(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])));
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])));
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]);
}
@ -110,7 +184,7 @@ void FITSFileModel::prepareQuery()
sql += cols;
sql += " FROM fits_files AS f";
sql += join;
sql += where;
if(!where.isEmpty())sql += " WHERE " + where.join("AND");
sql += " GROUP BY f.id" + m_sort;
setQuery(sql);
setHeaderData(0, Qt::Horizontal, tr("File name"));
@ -122,6 +196,31 @@ void FITSFileModel::prepareQuery()
std::cout << sql.toStdString() << std::endl;
if(lastError().type() != QSqlError::NoError)
qDebug() << lastError();
m_markedFiles = m_database->getMarkedFiles().toSet();
}
DatabaseTableView::DatabaseTableView(QWidget *parent) : QTableView(parent)
{
}
void DatabaseTableView::contextMenuEvent(QContextMenuEvent *event)
{
QMenu menu;
QAction *mark = menu.addAction(tr("Mark"));
QAction *unmark = menu.addAction(tr("Unmark"));
QAction *a = menu.exec(event->globalPos());
if(a == nullptr)
return;
QModelIndexList indexes = selectionModel()->selectedRows();
if(a == mark)
emit filesMarked(indexes);
else if(a == unmark)
emit filesUnmarked(indexes);
}
DataBaseView::DataBaseView(Database *database, QWidget *parent) : QWidget(parent)
@ -130,14 +229,15 @@ DataBaseView::DataBaseView(Database *database, QWidget *parent) : QWidget(parent
QVBoxLayout *layout = new QVBoxLayout(this);
setLayout(layout);
m_tableView = new QTableView(this);
m_tableView = new DatabaseTableView(this);
m_tableView->verticalHeader()->setDefaultSectionSize(1);
m_tableView->setSortingEnabled(true);
m_tableView->horizontalHeader()->setSortIndicatorShown(true);
m_tableView->setSelectionBehavior(QAbstractItemView::SelectRows);
layout->addWidget(m_tableView);
connect(m_tableView, &QTableView::activated, this, &DataBaseView::itemActivated);
m_model = new FITSFileModel(this);
m_model = new FITSFileModel(m_database, this);
QSettings settings;
m_tableView->setModel(m_model);
@ -151,21 +251,67 @@ DataBaseView::DataBaseView(Database *database, QWidget *parent) : QWidget(parent
hlayout->addWidget(selectColumnsButton);
connect(selectColumnsButton, &QPushButton::pressed, this, &DataBaseView::selectColumns);
connect(m_tableView, &DatabaseTableView::filesMarked, [this](QModelIndexList indexes){
QStringList files;
for(auto &index : indexes)
files.append(index.data().toString());
m_database->mark(files);
m_model->filesMarked(indexes);
});
connect(m_tableView, &DatabaseTableView::filesUnmarked, [this](QModelIndexList indexes){
QStringList files;
for(auto &index : indexes)
files.append(index.data().toString());
m_database->unmark(files);
m_model->filesUnmarked(indexes);
});
auto addFilterItems = [](QComboBox *combobox, const QStringList &fitsKeywords)
{
combobox->clear();
combobox->addItem("file");
combobox->addItem("RA pos");
combobox->addItem("DEC pos");
combobox->addItem("RA range");
combobox->addItem("DEC range");
combobox->addItems(fitsKeywords);
};
QStringList fitsKeywords = m_database->getFitsKeywords();
for(int i=0; i<3; i++)
{
m_filterKeyword[i] = new QComboBox(this);
m_filterKeyword[i]->addItem("file");
m_filterKeyword[i]->addItems(m_database->getFitsKeywords());
addFilterItems(m_filterKeyword[i], fitsKeywords);
m_search[i] = new QLineEdit(this);
m_search[i]->setPlaceholderText(tr("Text to search, you can % as wildcard"));
m_limit[i] = new QLineEdit(this);
m_limit[i]->hide();
connect(m_filterKeyword[i], static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), [this, i](int index){
if(index == 3 || index == 4)m_limit[i]->show();
else m_limit[i]->hide();
});
connect(m_search[i], &QLineEdit::returnPressed, this, &DataBaseView::applyFilter);
connect(m_limit[i], &QLineEdit::returnPressed, this, &DataBaseView::applyFilter);
hlayout->addWidget(m_filterKeyword[i]);
hlayout->addWidget(m_search[i]);
hlayout->addWidget(m_limit[i]);
}
QPushButton *filterButton = new QPushButton(tr("Filter"), this);
connect(filterButton, SIGNAL(pressed()), this, SLOT(applyFilter()));
hlayout->addWidget(filterButton);
connect(m_database, &Database::databaseChanged, [this, &addFilterItems](){
QStringList fitsKeywords = m_database->getFitsKeywords();
for(int i=0; i<3; i++)
addFilterItems(m_filterKeyword[i], fitsKeywords);
applyFilter();
});
}
DataBaseView::~DataBaseView()
@ -203,6 +349,7 @@ void DataBaseView::applyFilter()
{
QStringList keys;
QStringList values;
QStringList limits;
for(int i=0; i<3; i++)
{
QString key = m_filterKeyword[i]->currentText();
@ -210,8 +357,8 @@ void DataBaseView::applyFilter()
{
keys.append(key);
values.append(m_search[i]->text());
limits.append(m_limit[i]->text());
}
}
m_model->setFilter(keys, values);
m_model->setFilter(keys, values, limits);
}

@ -15,7 +15,7 @@ class SelectColumnsDialog : public QDialog
Q_OBJECT
QListWidget *m_listWidget;
public:
SelectColumnsDialog(QWidget *parent = nullptr);
explicit SelectColumnsDialog(QWidget *parent = nullptr);
void setColumns(QStringList columns);
QStringList selectedColumns();
};
@ -27,26 +27,45 @@ class FITSFileModel : public QSqlQueryModel
QString m_sort;
QStringList m_key;
QStringList m_value;
QStringList m_limit;
QSet<QString> m_markedFiles;
Database *m_database;
public:
FITSFileModel(QObject *parent = nullptr);
void sort(int column, Qt::SortOrder order);
explicit FITSFileModel(Database *database, QObject *parent = nullptr);
void sort(int column, Qt::SortOrder order) override;
void setColumns(const QStringList &columns);
void setFilter(const QStringList &key, const QStringList &value);
void setFilter(const QStringList &key, const QStringList &value, const QStringList &limit);
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
void filesMarked(const QModelIndexList &indexes);
void filesUnmarked(const QModelIndexList &indexes);
protected:
void prepareQuery();
};
class DatabaseTableView : public QTableView
{
Q_OBJECT
public:
explicit DatabaseTableView(QWidget *parent = nullptr);
protected:
void contextMenuEvent(QContextMenuEvent *event) override;
signals:
void filesMarked(QModelIndexList indexes);
void filesUnmarked(QModelIndexList indexes);
};
class DataBaseView : public QWidget
{
Q_OBJECT
Database *m_database;
QTableView *m_tableView;
DatabaseTableView *m_tableView;
FITSFileModel *m_model;
QComboBox *m_filterKeyword[3];
QLineEdit *m_search[3];
QLineEdit *m_limit[3];
public:
explicit DataBaseView(Database *database, QWidget *parent = nullptr);
~DataBaseView();
~DataBaseView() override;
public slots:
void selectColumns();
void loadDatabase();

@ -1,6 +1,10 @@
#include "filesystemwidget.h"
#include <QSettings>
#include <QVBoxLayout>
#include <QContextMenuEvent>
#include <QMenu>
#include <QSettings>
#include <QHeaderView>
FilesystemWidget::FilesystemWidget(QAbstractItemModel *model, QWidget *parent) : QWidget(parent)
, m_model(model)
@ -13,7 +17,7 @@ FilesystemWidget::FilesystemWidget(QAbstractItemModel *model, QWidget *parent) :
setLayout(layout);
connect(m_listView, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(fileClicked(QModelIndex)));
connect(m_listView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FilesystemWidget::fileClicked);
}
void FilesystemWidget::setDir(const QString &dir)
@ -25,12 +29,106 @@ void FilesystemWidget::setDir(const QString &dir)
void FilesystemWidget::selectFile(int row)
{
QModelIndex index = m_model->index(row, 0);
m_listView->selectionModel()->clearSelection();
m_listView->selectionModel()->select(index, QItemSelectionModel::SelectCurrent);
m_listView->scrollTo(index);
}
void FilesystemWidget::fileClicked(const QModelIndex &index)
void FilesystemWidget::fileClicked(const QModelIndex &index, const QModelIndex &)
{
if(index.isValid())
emit fileSelected(index.row());
}
Filetree::Filetree(QWidget *parent) : QTreeView(parent)
{
QSettings settings;
m_rootDir = settings.value("filetree/rootDir", QDir::homePath()).toString();
m_fileSystemModel = new QFileSystemModel(this);
m_fileSystemModel->setRootPath(m_rootDir);
m_fileSystemModel->setNameFilters({"*.fits", "*.fit", "*.xisf", "*.jpg", "*.jpeg", "*.png", "*.cr2", "*.nef", "*.dng"});
m_fileSystemModel->setNameFilterDisables(false);
setModel(m_fileSystemModel);
setRootIndex(m_fileSystemModel->index(m_rootDir));
header()->restoreState(settings.value("filetree/header").toByteArray());
}
Filetree::~Filetree()
{
QSettings settings;
settings.setValue("filetree/rootDir", m_rootDir);
settings.setValue("filetree/header", header()->saveState());
}
void Filetree::contextMenuEvent(QContextMenuEvent *event)
{
QModelIndex index = indexAt(event->pos());
QFileInfo info = m_fileSystemModel->fileInfo(index);
QMenu menu;
QAction *open = nullptr;
QAction *setRoot = nullptr;
QAction *copy = nullptr;
QAction *move = nullptr;
QAction *indexDir = nullptr;
if(info.isFile())
open = menu.addAction(tr("Open"));
if(info.isDir())
{
open = menu.addAction(tr("Open"));
setRoot = menu.addAction(tr("Set as root"));
copy = menu.addAction(tr("Copy marked files"));
move = menu.addAction(tr("Move marked files"));
indexDir = menu.addAction(tr("Index directory"));
}
menu.addSeparator();
QAction *resetRoot = menu.addAction(tr("Reset root"));
QAction *goUp = menu.addAction(tr("Go up"));
QAction *a = menu.exec(event->globalPos());
if(a == nullptr)
return;
if(a == open)
{
emit fileSelected(m_fileSystemModel->filePath(index));
}
else if(a == setRoot && index.isValid())
{
setRootIndex(index);
m_rootDir = m_fileSystemModel->filePath(index);
}
else if(a == resetRoot)
{
setRootIndex(QModelIndex());
m_rootDir = QDir::rootPath();
}
else if(a == goUp)
{
setRootIndex(rootIndex().parent());
m_rootDir = m_fileSystemModel->filePath(rootIndex().parent());
}
else if(a == copy)
{
emit copyFiles(m_fileSystemModel->filePath(index));
}
else if(a == move)
{
emit moveFiles(m_fileSystemModel->filePath(index));
}
else if(a == indexDir)
{
emit indexDirectory(m_fileSystemModel->filePath(index));
}
}
void Filetree::mouseDoubleClickEvent(QMouseEvent *event)
{
emit fileSelected(index.row());
QModelIndex index = indexAt(event->pos());
QFileInfo info = m_fileSystemModel->fileInfo(index);
if(info.isFile())
emit fileSelected(info.filePath());
else
QTreeView::mouseDoubleClickEvent(event);
}

@ -4,6 +4,7 @@
#include <QWidget>
#include <QFileSystemModel>
#include <QListView>
#include <QTreeView>
class FilesystemWidget : public QWidget
{
@ -15,9 +16,26 @@ public:
void setDir(const QString &dir);
private slots:
void selectFile(int row);
void fileClicked(const QModelIndex &index);
void fileClicked(const QModelIndex &index, const QModelIndex &);
signals:
void fileSelected(int row);
};
class Filetree : public QTreeView
{
Q_OBJECT
QFileSystemModel *m_fileSystemModel;
QString m_rootDir;
public:
explicit Filetree(QWidget *parent = nullptr);
~Filetree() override;
void contextMenuEvent(QContextMenuEvent *event) override;
void mouseDoubleClickEvent(QMouseEvent *event) override;
signals:
void fileSelected(const QString &path);
void copyFiles(const QString &path);
void moveFiles(const QString &path);
void indexDirectory(const QString &path);
};
#endif // FILESYSTEMWIDGET_H

@ -1,6 +1,10 @@
#include "imageinfo.h"
#include <QSettings>
#include <QTime>
#include <QHeaderView>
#include <wcslib/wcshdr.h>
#include <wcslib/wcsfix.h>
#include "pcl/FITSHeaderKeyword.h"
static const QVector<QByteArray> noEditableKey = {"SIMPLE", "BITPIX", "NAXIS", "NAXIS1", "NAXIS2", "NAXIS3", "EXTEND", "BZERO", "BSCALE"};
@ -9,6 +13,44 @@ bool FITSRecord::editable() const
return noEditableKey.count(key);
}
FITSRecord::FITSRecord(const QByteArray &key, const QVariant &value, const QByteArray &comment) :
key(key), value(value), comment(comment)
{
}
FITSRecord::FITSRecord(const pcl::FITSHeaderKeyword &record)
{
key = record.name.c_str();
comment = record.comment.c_str();
QString string = record.value.c_str();
if(string.startsWith('\'') && string.endsWith('\''))
{
string.chop(1);
string.remove(0, 1);
}
bool isint;
bool isdouble;
double vald = string.toDouble(&isdouble);
long long vall = string.toLongLong(&isint);
if(isint)
value = vall;
else if(isdouble)
value = vald;
else if(string == "T" || string == "F")
value = string == "T";
else
value = string;
}
QByteArray FITSRecord::valueToByteArray() const
{
if(value.type() == QVariant::Bool)
return value.toBool() ? "T" : "F";
else
return value.toString().toLatin1();
}
ImageInfo::ImageInfo(QWidget *parent) : QTreeWidget(parent)
{
setColumnCount(3);
@ -47,3 +89,190 @@ void ImageInfo::setInfo(const ImageInfoData &info)
}
expandAll();
}
void WCSData::freeWCS()
{
wcsvfree(&nwcs, &wcs);
nwcs = 0;
wcs = nullptr;
}
WCSData::WCSData(int width, int height, char *header, int nrec) :
width(width),
height(height)
{
int nreject = 0;
int status = wcspih(header, nrec, 1, 0, &nreject, &nwcs, &wcs);
if(status != 0)
{
freeWCS();
return;
}
status = cdfix(wcs);
if(status > 0 || wcs->crpix[0] == 0)
freeWCS();
}
WCSData::WCSData(int width, int height, const QVector<FITSRecord> &header) :
width(width),
height(height)
{
int status = 0;
QByteArray str;
int nrec = 1;