setiastrosuitepro 1.6.2.post1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of setiastrosuitepro might be problematic. Click here for more details.
- setiastro/__init__.py +2 -0
- setiastro/data/SASP_data.fits +0 -0
- setiastro/data/catalogs/List_of_Galaxies_with_Distances_Gly.csv +488 -0
- setiastro/data/catalogs/astrobin_filters.csv +890 -0
- setiastro/data/catalogs/astrobin_filters_page1_local.csv +51 -0
- setiastro/data/catalogs/cali2.csv +63 -0
- setiastro/data/catalogs/cali2color.csv +65 -0
- setiastro/data/catalogs/celestial_catalog - original.csv +16471 -0
- setiastro/data/catalogs/celestial_catalog.csv +24031 -0
- setiastro/data/catalogs/detected_stars.csv +24784 -0
- setiastro/data/catalogs/fits_header_data.csv +46 -0
- setiastro/data/catalogs/test.csv +8 -0
- setiastro/data/catalogs/updated_celestial_catalog.csv +16471 -0
- setiastro/images/Astro_Spikes.png +0 -0
- setiastro/images/Background_startup.jpg +0 -0
- setiastro/images/HRDiagram.png +0 -0
- setiastro/images/LExtract.png +0 -0
- setiastro/images/LInsert.png +0 -0
- setiastro/images/Oxygenation-atm-2.svg.png +0 -0
- setiastro/images/RGB080604.png +0 -0
- setiastro/images/abeicon.png +0 -0
- setiastro/images/aberration.png +0 -0
- setiastro/images/andromedatry.png +0 -0
- setiastro/images/andromedatry_satellited.png +0 -0
- setiastro/images/annotated.png +0 -0
- setiastro/images/aperture.png +0 -0
- setiastro/images/astrosuite.ico +0 -0
- setiastro/images/astrosuite.png +0 -0
- setiastro/images/astrosuitepro.icns +0 -0
- setiastro/images/astrosuitepro.ico +0 -0
- setiastro/images/astrosuitepro.png +0 -0
- setiastro/images/background.png +0 -0
- setiastro/images/background2.png +0 -0
- setiastro/images/benchmark.png +0 -0
- setiastro/images/big_moon_stabilizer_timeline.png +0 -0
- setiastro/images/big_moon_stabilizer_timeline_clean.png +0 -0
- setiastro/images/blaster.png +0 -0
- setiastro/images/blink.png +0 -0
- setiastro/images/clahe.png +0 -0
- setiastro/images/collage.png +0 -0
- setiastro/images/colorwheel.png +0 -0
- setiastro/images/contsub.png +0 -0
- setiastro/images/convo.png +0 -0
- setiastro/images/copyslot.png +0 -0
- setiastro/images/cosmic.png +0 -0
- setiastro/images/cosmicsat.png +0 -0
- setiastro/images/crop1.png +0 -0
- setiastro/images/cropicon.png +0 -0
- setiastro/images/curves.png +0 -0
- setiastro/images/cvs.png +0 -0
- setiastro/images/debayer.png +0 -0
- setiastro/images/denoise_cnn_custom.png +0 -0
- setiastro/images/denoise_cnn_graph.png +0 -0
- setiastro/images/disk.png +0 -0
- setiastro/images/dse.png +0 -0
- setiastro/images/exoicon.png +0 -0
- setiastro/images/eye.png +0 -0
- setiastro/images/fliphorizontal.png +0 -0
- setiastro/images/flipvertical.png +0 -0
- setiastro/images/font.png +0 -0
- setiastro/images/freqsep.png +0 -0
- setiastro/images/functionbundle.png +0 -0
- setiastro/images/graxpert.png +0 -0
- setiastro/images/green.png +0 -0
- setiastro/images/gridicon.png +0 -0
- setiastro/images/halo.png +0 -0
- setiastro/images/hdr.png +0 -0
- setiastro/images/histogram.png +0 -0
- setiastro/images/hubble.png +0 -0
- setiastro/images/imagecombine.png +0 -0
- setiastro/images/invert.png +0 -0
- setiastro/images/isophote.png +0 -0
- setiastro/images/isophote_demo_figure.png +0 -0
- setiastro/images/isophote_demo_image.png +0 -0
- setiastro/images/isophote_demo_model.png +0 -0
- setiastro/images/isophote_demo_residual.png +0 -0
- setiastro/images/jwstpupil.png +0 -0
- setiastro/images/linearfit.png +0 -0
- setiastro/images/livestacking.png +0 -0
- setiastro/images/mask.png +0 -0
- setiastro/images/maskapply.png +0 -0
- setiastro/images/maskcreate.png +0 -0
- setiastro/images/maskremove.png +0 -0
- setiastro/images/morpho.png +0 -0
- setiastro/images/mosaic.png +0 -0
- setiastro/images/multiscale_decomp.png +0 -0
- setiastro/images/nbtorgb.png +0 -0
- setiastro/images/neutral.png +0 -0
- setiastro/images/nuke.png +0 -0
- setiastro/images/openfile.png +0 -0
- setiastro/images/pedestal.png +0 -0
- setiastro/images/pen.png +0 -0
- setiastro/images/pixelmath.png +0 -0
- setiastro/images/platesolve.png +0 -0
- setiastro/images/ppp.png +0 -0
- setiastro/images/pro.png +0 -0
- setiastro/images/project.png +0 -0
- setiastro/images/psf.png +0 -0
- setiastro/images/redo.png +0 -0
- setiastro/images/redoicon.png +0 -0
- setiastro/images/rescale.png +0 -0
- setiastro/images/rgbalign.png +0 -0
- setiastro/images/rgbcombo.png +0 -0
- setiastro/images/rgbextract.png +0 -0
- setiastro/images/rotate180.png +0 -0
- setiastro/images/rotateclockwise.png +0 -0
- setiastro/images/rotatecounterclockwise.png +0 -0
- setiastro/images/satellite.png +0 -0
- setiastro/images/script.png +0 -0
- setiastro/images/selectivecolor.png +0 -0
- setiastro/images/simbad.png +0 -0
- setiastro/images/slot0.png +0 -0
- setiastro/images/slot1.png +0 -0
- setiastro/images/slot2.png +0 -0
- setiastro/images/slot3.png +0 -0
- setiastro/images/slot4.png +0 -0
- setiastro/images/slot5.png +0 -0
- setiastro/images/slot6.png +0 -0
- setiastro/images/slot7.png +0 -0
- setiastro/images/slot8.png +0 -0
- setiastro/images/slot9.png +0 -0
- setiastro/images/spcc.png +0 -0
- setiastro/images/spin_precession_vs_lunar_distance.png +0 -0
- setiastro/images/spinner.gif +0 -0
- setiastro/images/stacking.png +0 -0
- setiastro/images/staradd.png +0 -0
- setiastro/images/staralign.png +0 -0
- setiastro/images/starnet.png +0 -0
- setiastro/images/starregistration.png +0 -0
- setiastro/images/starspike.png +0 -0
- setiastro/images/starstretch.png +0 -0
- setiastro/images/statstretch.png +0 -0
- setiastro/images/supernova.png +0 -0
- setiastro/images/uhs.png +0 -0
- setiastro/images/undoicon.png +0 -0
- setiastro/images/upscale.png +0 -0
- setiastro/images/viewbundle.png +0 -0
- setiastro/images/whitebalance.png +0 -0
- setiastro/images/wimi_icon_256x256.png +0 -0
- setiastro/images/wimilogo.png +0 -0
- setiastro/images/wims.png +0 -0
- setiastro/images/wrench_icon.png +0 -0
- setiastro/images/xisfliberator.png +0 -0
- setiastro/qml/ResourceMonitor.qml +126 -0
- setiastro/saspro/__init__.py +20 -0
- setiastro/saspro/__main__.py +945 -0
- setiastro/saspro/_generated/__init__.py +7 -0
- setiastro/saspro/_generated/build_info.py +3 -0
- setiastro/saspro/abe.py +1346 -0
- setiastro/saspro/abe_preset.py +196 -0
- setiastro/saspro/aberration_ai.py +694 -0
- setiastro/saspro/aberration_ai_preset.py +224 -0
- setiastro/saspro/accel_installer.py +218 -0
- setiastro/saspro/accel_workers.py +30 -0
- setiastro/saspro/add_stars.py +624 -0
- setiastro/saspro/astrobin_exporter.py +1010 -0
- setiastro/saspro/astrospike.py +153 -0
- setiastro/saspro/astrospike_python.py +1841 -0
- setiastro/saspro/autostretch.py +198 -0
- setiastro/saspro/backgroundneutral.py +602 -0
- setiastro/saspro/batch_convert.py +328 -0
- setiastro/saspro/batch_renamer.py +522 -0
- setiastro/saspro/blemish_blaster.py +491 -0
- setiastro/saspro/blink_comparator_pro.py +2926 -0
- setiastro/saspro/bundles.py +61 -0
- setiastro/saspro/bundles_dock.py +114 -0
- setiastro/saspro/cheat_sheet.py +213 -0
- setiastro/saspro/clahe.py +368 -0
- setiastro/saspro/comet_stacking.py +1442 -0
- setiastro/saspro/common_tr.py +107 -0
- setiastro/saspro/config.py +38 -0
- setiastro/saspro/config_bootstrap.py +40 -0
- setiastro/saspro/config_manager.py +316 -0
- setiastro/saspro/continuum_subtract.py +1617 -0
- setiastro/saspro/convo.py +1400 -0
- setiastro/saspro/convo_preset.py +414 -0
- setiastro/saspro/copyastro.py +190 -0
- setiastro/saspro/cosmicclarity.py +1589 -0
- setiastro/saspro/cosmicclarity_preset.py +407 -0
- setiastro/saspro/crop_dialog_pro.py +973 -0
- setiastro/saspro/crop_preset.py +189 -0
- setiastro/saspro/curve_editor_pro.py +2562 -0
- setiastro/saspro/curves_preset.py +375 -0
- setiastro/saspro/debayer.py +673 -0
- setiastro/saspro/debug_utils.py +29 -0
- setiastro/saspro/dnd_mime.py +35 -0
- setiastro/saspro/doc_manager.py +2664 -0
- setiastro/saspro/exoplanet_detector.py +2166 -0
- setiastro/saspro/file_utils.py +284 -0
- setiastro/saspro/fitsmodifier.py +748 -0
- setiastro/saspro/fix_bom.py +32 -0
- setiastro/saspro/free_torch_memory.py +48 -0
- setiastro/saspro/frequency_separation.py +1349 -0
- setiastro/saspro/function_bundle.py +1596 -0
- setiastro/saspro/generate_translations.py +3092 -0
- setiastro/saspro/ghs_dialog_pro.py +663 -0
- setiastro/saspro/ghs_preset.py +284 -0
- setiastro/saspro/graxpert.py +637 -0
- setiastro/saspro/graxpert_preset.py +287 -0
- setiastro/saspro/gui/__init__.py +0 -0
- setiastro/saspro/gui/main_window.py +8810 -0
- setiastro/saspro/gui/mixins/__init__.py +33 -0
- setiastro/saspro/gui/mixins/dock_mixin.py +362 -0
- setiastro/saspro/gui/mixins/file_mixin.py +450 -0
- setiastro/saspro/gui/mixins/geometry_mixin.py +403 -0
- setiastro/saspro/gui/mixins/header_mixin.py +441 -0
- setiastro/saspro/gui/mixins/mask_mixin.py +421 -0
- setiastro/saspro/gui/mixins/menu_mixin.py +389 -0
- setiastro/saspro/gui/mixins/theme_mixin.py +367 -0
- setiastro/saspro/gui/mixins/toolbar_mixin.py +1457 -0
- setiastro/saspro/gui/mixins/update_mixin.py +309 -0
- setiastro/saspro/gui/mixins/view_mixin.py +435 -0
- setiastro/saspro/gui/statistics_dialog.py +47 -0
- setiastro/saspro/halobgon.py +488 -0
- setiastro/saspro/header_viewer.py +448 -0
- setiastro/saspro/headless_utils.py +88 -0
- setiastro/saspro/histogram.py +756 -0
- setiastro/saspro/history_explorer.py +941 -0
- setiastro/saspro/i18n.py +168 -0
- setiastro/saspro/image_combine.py +417 -0
- setiastro/saspro/image_peeker_pro.py +1604 -0
- setiastro/saspro/imageops/__init__.py +37 -0
- setiastro/saspro/imageops/mdi_snap.py +292 -0
- setiastro/saspro/imageops/scnr.py +36 -0
- setiastro/saspro/imageops/starbasedwhitebalance.py +210 -0
- setiastro/saspro/imageops/stretch.py +236 -0
- setiastro/saspro/isophote.py +1182 -0
- setiastro/saspro/layers.py +208 -0
- setiastro/saspro/layers_dock.py +714 -0
- setiastro/saspro/lazy_imports.py +193 -0
- setiastro/saspro/legacy/__init__.py +2 -0
- setiastro/saspro/legacy/image_manager.py +2226 -0
- setiastro/saspro/legacy/numba_utils.py +3676 -0
- setiastro/saspro/legacy/xisf.py +1071 -0
- setiastro/saspro/linear_fit.py +537 -0
- setiastro/saspro/live_stacking.py +1841 -0
- setiastro/saspro/log_bus.py +5 -0
- setiastro/saspro/logging_config.py +460 -0
- setiastro/saspro/luminancerecombine.py +309 -0
- setiastro/saspro/main_helpers.py +201 -0
- setiastro/saspro/mask_creation.py +931 -0
- setiastro/saspro/masks_core.py +56 -0
- setiastro/saspro/mdi_widgets.py +353 -0
- setiastro/saspro/memory_utils.py +666 -0
- setiastro/saspro/metadata_patcher.py +75 -0
- setiastro/saspro/mfdeconv.py +3831 -0
- setiastro/saspro/mfdeconv_earlystop.py +71 -0
- setiastro/saspro/mfdeconvcudnn.py +3263 -0
- setiastro/saspro/mfdeconvsport.py +2382 -0
- setiastro/saspro/minorbodycatalog.py +567 -0
- setiastro/saspro/morphology.py +407 -0
- setiastro/saspro/multiscale_decomp.py +1293 -0
- setiastro/saspro/nbtorgb_stars.py +541 -0
- setiastro/saspro/numba_utils.py +3145 -0
- setiastro/saspro/numba_warmup.py +141 -0
- setiastro/saspro/ops/__init__.py +9 -0
- setiastro/saspro/ops/command_help_dialog.py +623 -0
- setiastro/saspro/ops/command_runner.py +217 -0
- setiastro/saspro/ops/commands.py +1594 -0
- setiastro/saspro/ops/script_editor.py +1102 -0
- setiastro/saspro/ops/scripts.py +1473 -0
- setiastro/saspro/ops/settings.py +637 -0
- setiastro/saspro/parallel_utils.py +554 -0
- setiastro/saspro/pedestal.py +121 -0
- setiastro/saspro/perfect_palette_picker.py +1071 -0
- setiastro/saspro/pipeline.py +110 -0
- setiastro/saspro/pixelmath.py +1604 -0
- setiastro/saspro/plate_solver.py +2445 -0
- setiastro/saspro/project_io.py +797 -0
- setiastro/saspro/psf_utils.py +136 -0
- setiastro/saspro/psf_viewer.py +549 -0
- setiastro/saspro/pyi_rthook_astroquery.py +95 -0
- setiastro/saspro/remove_green.py +331 -0
- setiastro/saspro/remove_stars.py +1599 -0
- setiastro/saspro/remove_stars_preset.py +404 -0
- setiastro/saspro/resources.py +501 -0
- setiastro/saspro/rgb_combination.py +208 -0
- setiastro/saspro/rgb_extract.py +19 -0
- setiastro/saspro/rgbalign.py +723 -0
- setiastro/saspro/runtime_imports.py +7 -0
- setiastro/saspro/runtime_torch.py +754 -0
- setiastro/saspro/save_options.py +73 -0
- setiastro/saspro/selective_color.py +1552 -0
- setiastro/saspro/sfcc.py +1472 -0
- setiastro/saspro/shortcuts.py +3043 -0
- setiastro/saspro/signature_insert.py +1102 -0
- setiastro/saspro/stacking_suite.py +18470 -0
- setiastro/saspro/star_alignment.py +7435 -0
- setiastro/saspro/star_alignment_preset.py +329 -0
- setiastro/saspro/star_metrics.py +49 -0
- setiastro/saspro/star_spikes.py +765 -0
- setiastro/saspro/star_stretch.py +507 -0
- setiastro/saspro/stat_stretch.py +538 -0
- setiastro/saspro/status_log_dock.py +78 -0
- setiastro/saspro/subwindow.py +3328 -0
- setiastro/saspro/supernovaasteroidhunter.py +1719 -0
- setiastro/saspro/swap_manager.py +99 -0
- setiastro/saspro/torch_backend.py +89 -0
- setiastro/saspro/torch_rejection.py +434 -0
- setiastro/saspro/translations/all_source_strings.json +3654 -0
- setiastro/saspro/translations/ar_translations.py +3865 -0
- setiastro/saspro/translations/de_translations.py +3749 -0
- setiastro/saspro/translations/es_translations.py +3939 -0
- setiastro/saspro/translations/fr_translations.py +3858 -0
- setiastro/saspro/translations/hi_translations.py +3571 -0
- setiastro/saspro/translations/integrate_translations.py +270 -0
- setiastro/saspro/translations/it_translations.py +3678 -0
- setiastro/saspro/translations/ja_translations.py +3601 -0
- setiastro/saspro/translations/pt_translations.py +3869 -0
- setiastro/saspro/translations/ru_translations.py +2848 -0
- setiastro/saspro/translations/saspro_ar.qm +0 -0
- setiastro/saspro/translations/saspro_ar.ts +255 -0
- setiastro/saspro/translations/saspro_de.qm +0 -0
- setiastro/saspro/translations/saspro_de.ts +253 -0
- setiastro/saspro/translations/saspro_es.qm +0 -0
- setiastro/saspro/translations/saspro_es.ts +12520 -0
- setiastro/saspro/translations/saspro_fr.qm +0 -0
- setiastro/saspro/translations/saspro_fr.ts +12514 -0
- setiastro/saspro/translations/saspro_hi.qm +0 -0
- setiastro/saspro/translations/saspro_hi.ts +257 -0
- setiastro/saspro/translations/saspro_it.qm +0 -0
- setiastro/saspro/translations/saspro_it.ts +12520 -0
- setiastro/saspro/translations/saspro_ja.qm +0 -0
- setiastro/saspro/translations/saspro_ja.ts +257 -0
- setiastro/saspro/translations/saspro_pt.qm +0 -0
- setiastro/saspro/translations/saspro_pt.ts +257 -0
- setiastro/saspro/translations/saspro_ru.qm +0 -0
- setiastro/saspro/translations/saspro_ru.ts +237 -0
- setiastro/saspro/translations/saspro_sw.qm +0 -0
- setiastro/saspro/translations/saspro_sw.ts +257 -0
- setiastro/saspro/translations/saspro_uk.qm +0 -0
- setiastro/saspro/translations/saspro_uk.ts +10771 -0
- setiastro/saspro/translations/saspro_zh.qm +0 -0
- setiastro/saspro/translations/saspro_zh.ts +12520 -0
- setiastro/saspro/translations/sw_translations.py +3671 -0
- setiastro/saspro/translations/uk_translations.py +3700 -0
- setiastro/saspro/translations/zh_translations.py +3675 -0
- setiastro/saspro/versioning.py +77 -0
- setiastro/saspro/view_bundle.py +1558 -0
- setiastro/saspro/wavescale_hdr.py +645 -0
- setiastro/saspro/wavescale_hdr_preset.py +101 -0
- setiastro/saspro/wavescalede.py +680 -0
- setiastro/saspro/wavescalede_preset.py +230 -0
- setiastro/saspro/wcs_update.py +374 -0
- setiastro/saspro/whitebalance.py +492 -0
- setiastro/saspro/widgets/__init__.py +48 -0
- setiastro/saspro/widgets/common_utilities.py +306 -0
- setiastro/saspro/widgets/graphics_views.py +122 -0
- setiastro/saspro/widgets/image_utils.py +518 -0
- setiastro/saspro/widgets/minigame/game.js +986 -0
- setiastro/saspro/widgets/minigame/index.html +53 -0
- setiastro/saspro/widgets/minigame/style.css +241 -0
- setiastro/saspro/widgets/preview_dialogs.py +280 -0
- setiastro/saspro/widgets/resource_monitor.py +237 -0
- setiastro/saspro/widgets/spinboxes.py +275 -0
- setiastro/saspro/widgets/themed_buttons.py +13 -0
- setiastro/saspro/widgets/wavelet_utils.py +331 -0
- setiastro/saspro/wimi.py +7996 -0
- setiastro/saspro/wims.py +578 -0
- setiastro/saspro/window_shelf.py +185 -0
- setiastro/saspro/xisf.py +1123 -0
- setiastrosuitepro-1.6.2.post1.dist-info/METADATA +278 -0
- setiastrosuitepro-1.6.2.post1.dist-info/RECORD +367 -0
- setiastrosuitepro-1.6.2.post1.dist-info/WHEEL +4 -0
- setiastrosuitepro-1.6.2.post1.dist-info/entry_points.txt +6 -0
- setiastrosuitepro-1.6.2.post1.dist-info/licenses/LICENSE +674 -0
- setiastrosuitepro-1.6.2.post1.dist-info/licenses/license.txt +2580 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# src/setiastro/saspro/widgets/resource_monitor.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
import os
|
|
4
|
+
import psutil
|
|
5
|
+
from PyQt6.QtCore import Qt, QUrl, QTimer, QObject, pyqtProperty, pyqtSignal, QThread
|
|
6
|
+
from PyQt6.QtWidgets import QWidget, QVBoxLayout, QFrame
|
|
7
|
+
from PyQt6.QtQuickWidgets import QQuickWidget
|
|
8
|
+
|
|
9
|
+
from setiastro.saspro.memory_utils import get_memory_usage_mb
|
|
10
|
+
from setiastro.saspro.resources import _get_base_path
|
|
11
|
+
|
|
12
|
+
class GPUWorker(QThread):
|
|
13
|
+
"""Background worker to monitor GPU without blocking the UI."""
|
|
14
|
+
resultReady = pyqtSignal(float)
|
|
15
|
+
|
|
16
|
+
def __init__(self, has_nvidia: bool, parent=None):
|
|
17
|
+
super().__init__(parent)
|
|
18
|
+
self._has_nvidia = has_nvidia
|
|
19
|
+
self._last_val = 0.0
|
|
20
|
+
|
|
21
|
+
def _get_windows_gpu_load(self) -> float:
|
|
22
|
+
if os.name != 'nt':
|
|
23
|
+
return 0.0
|
|
24
|
+
try:
|
|
25
|
+
import subprocess
|
|
26
|
+
# Aggregation logic to match Task Manager:
|
|
27
|
+
# 1. Group by unique engine (Adapter LUID + Engine Type/Index).
|
|
28
|
+
# 2. Sum utilization of all processes sharing that engine.
|
|
29
|
+
# 3. Take the Maximum of these sums as the overall GPU load.
|
|
30
|
+
# Aggregation logic to match Task Manager:
|
|
31
|
+
# 1. Group by unique engine (Adapter LUID + Engine Type/Index).
|
|
32
|
+
# 2. Sum utilization of all processes sharing that engine.
|
|
33
|
+
# 3. Take the Maximum of these sums as the overall GPU load.
|
|
34
|
+
cmd = (
|
|
35
|
+
"powershell -NoProfile -ExecutionPolicy Bypass -Command \""
|
|
36
|
+
"$groups = Get-CimInstance Win32_PerfFormattedData_GPUPerformanceCounters_GPUEngine -ErrorAction SilentlyContinue | "
|
|
37
|
+
"Group-Object -Property { $_.Name -replace '^pid_\\d+_', '' }; "
|
|
38
|
+
"$res_list = $groups | ForEach-Object { ($_.Group | Measure-Object -Property UtilizationPercentage -Sum).Sum }; "
|
|
39
|
+
"$max_val = ($res_list | Measure-Object -Maximum).Maximum; "
|
|
40
|
+
"if ($max_val) { [math]::Round($max_val, 1) } else { 0 }\""
|
|
41
|
+
)
|
|
42
|
+
startupinfo = subprocess.STARTUPINFO()
|
|
43
|
+
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
|
44
|
+
startupinfo.wShowWindow = 0
|
|
45
|
+
|
|
46
|
+
out = subprocess.check_output(cmd, startupinfo=startupinfo, timeout=5.0)
|
|
47
|
+
val_str = out.decode("utf-8").strip()
|
|
48
|
+
|
|
49
|
+
if not val_str: return 0.0
|
|
50
|
+
return float(val_str.replace(",", "."))
|
|
51
|
+
except Exception:
|
|
52
|
+
return 0.0
|
|
53
|
+
|
|
54
|
+
def _get_gpu_load(self) -> float:
|
|
55
|
+
nv_val = 0.0
|
|
56
|
+
win_val = 0.0
|
|
57
|
+
|
|
58
|
+
# 1. Check NVIDIA (Discrete)
|
|
59
|
+
if self._has_nvidia:
|
|
60
|
+
try:
|
|
61
|
+
import subprocess
|
|
62
|
+
startupinfo = None
|
|
63
|
+
if os.name == 'nt':
|
|
64
|
+
startupinfo = subprocess.STARTUPINFO()
|
|
65
|
+
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
|
66
|
+
startupinfo.wShowWindow = 0
|
|
67
|
+
|
|
68
|
+
out = subprocess.check_output(
|
|
69
|
+
["nvidia-smi", "--query-gpu=utilization.gpu", "--format=csv,noheader,nounits"],
|
|
70
|
+
startupinfo=startupinfo,
|
|
71
|
+
timeout=0.6
|
|
72
|
+
)
|
|
73
|
+
line = out.decode("utf-8").strip().split('\n')[0]
|
|
74
|
+
nv_val = float(line)
|
|
75
|
+
except Exception:
|
|
76
|
+
pass
|
|
77
|
+
|
|
78
|
+
# 2. Check Universal (Integrated)
|
|
79
|
+
if os.name == 'nt':
|
|
80
|
+
win_val = self._get_windows_gpu_load()
|
|
81
|
+
|
|
82
|
+
return max(nv_val, win_val)
|
|
83
|
+
|
|
84
|
+
def run(self):
|
|
85
|
+
while not self.isInterruptionRequested():
|
|
86
|
+
try:
|
|
87
|
+
val = self._get_gpu_load()
|
|
88
|
+
self.resultReady.emit(val)
|
|
89
|
+
# Sleep between measurements. 250ms as requested.
|
|
90
|
+
# Note: PowerShell queries might take longer than 250ms,
|
|
91
|
+
# but this loop will run as fast as the hardware allows without blocking UI.
|
|
92
|
+
self.msleep(250)
|
|
93
|
+
except Exception:
|
|
94
|
+
self.msleep(1000) # Error backoff on failure
|
|
95
|
+
|
|
96
|
+
class ResourceBackend(QObject):
|
|
97
|
+
"""Backend logic for the QML Resource Monitor."""
|
|
98
|
+
|
|
99
|
+
cpuChanged = pyqtSignal()
|
|
100
|
+
ramChanged = pyqtSignal()
|
|
101
|
+
gpuChanged = pyqtSignal()
|
|
102
|
+
appRamChanged = pyqtSignal()
|
|
103
|
+
|
|
104
|
+
def __init__(self, parent=None):
|
|
105
|
+
super().__init__(parent)
|
|
106
|
+
self._cpu = 0.0
|
|
107
|
+
self._ram = 0.0
|
|
108
|
+
self._gpu = 0.0
|
|
109
|
+
self._app_ram_val = 0.0
|
|
110
|
+
self._app_ram_str = "0 MB"
|
|
111
|
+
|
|
112
|
+
# Check if nvidia-smi is reachable once
|
|
113
|
+
has_nvidia = False
|
|
114
|
+
try:
|
|
115
|
+
import shutil
|
|
116
|
+
if shutil.which("nvidia-smi"):
|
|
117
|
+
has_nvidia = True
|
|
118
|
+
except Exception:
|
|
119
|
+
pass
|
|
120
|
+
|
|
121
|
+
# Start Background GPU Worker
|
|
122
|
+
self._gpu_worker = GPUWorker(has_nvidia, self)
|
|
123
|
+
self._gpu_worker.resultReady.connect(self._on_gpu_measured)
|
|
124
|
+
self._gpu_worker.start()
|
|
125
|
+
|
|
126
|
+
# Timer for CPU/RAM updates (250ms as requested)
|
|
127
|
+
self._timer = QTimer(self)
|
|
128
|
+
self._timer.setInterval(250)
|
|
129
|
+
self._timer.timeout.connect(self._update_stats)
|
|
130
|
+
self._timer.start()
|
|
131
|
+
|
|
132
|
+
def _on_gpu_measured(self, val: float):
|
|
133
|
+
self._gpu = val
|
|
134
|
+
self.gpuChanged.emit()
|
|
135
|
+
|
|
136
|
+
@pyqtProperty(float, notify=cpuChanged)
|
|
137
|
+
def cpuUsage(self):
|
|
138
|
+
return self._cpu
|
|
139
|
+
|
|
140
|
+
@pyqtProperty(float, notify=ramChanged)
|
|
141
|
+
def ramUsage(self):
|
|
142
|
+
return self._ram
|
|
143
|
+
|
|
144
|
+
@pyqtProperty(float, notify=gpuChanged)
|
|
145
|
+
def gpuUsage(self):
|
|
146
|
+
return self._gpu
|
|
147
|
+
|
|
148
|
+
@pyqtProperty(str, notify=appRamChanged)
|
|
149
|
+
def appRamString(self):
|
|
150
|
+
return self._app_ram_str
|
|
151
|
+
|
|
152
|
+
def _update_stats(self):
|
|
153
|
+
# 1. CPU
|
|
154
|
+
try:
|
|
155
|
+
self._cpu = psutil.cpu_percent(interval=None)
|
|
156
|
+
except Exception:
|
|
157
|
+
self._cpu = 0.0
|
|
158
|
+
|
|
159
|
+
# 2. System RAM
|
|
160
|
+
try:
|
|
161
|
+
vm = psutil.virtual_memory()
|
|
162
|
+
self._ram = vm.percent
|
|
163
|
+
except Exception:
|
|
164
|
+
self._ram = 0.0
|
|
165
|
+
|
|
166
|
+
# 3. App RAM
|
|
167
|
+
try:
|
|
168
|
+
mb = get_memory_usage_mb()
|
|
169
|
+
self._app_ram_val = mb
|
|
170
|
+
self._app_ram_str = f"{int(mb)} MB"
|
|
171
|
+
except Exception:
|
|
172
|
+
self._app_ram_str = "? MB"
|
|
173
|
+
|
|
174
|
+
self.cpuChanged.emit()
|
|
175
|
+
self.ramChanged.emit()
|
|
176
|
+
self.appRamChanged.emit()
|
|
177
|
+
|
|
178
|
+
def stop(self):
|
|
179
|
+
"""Explicitly stop background threads."""
|
|
180
|
+
if hasattr(self, "_gpu_worker") and self._gpu_worker.isRunning():
|
|
181
|
+
self._gpu_worker.requestInterruption()
|
|
182
|
+
self._gpu_worker.quit()
|
|
183
|
+
self._gpu_worker.wait(1000)
|
|
184
|
+
|
|
185
|
+
def __del__(self):
|
|
186
|
+
self.stop()
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
class SystemMonitorWidget(QQuickWidget):
|
|
190
|
+
"""
|
|
191
|
+
The QQuickWidget hosting the QML content.
|
|
192
|
+
"""
|
|
193
|
+
def __init__(self, parent=None):
|
|
194
|
+
super().__init__(parent)
|
|
195
|
+
|
|
196
|
+
self.setResizeMode(QQuickWidget.ResizeMode.SizeRootObjectToView)
|
|
197
|
+
self.setAttribute(Qt.WidgetAttribute.WA_AlwaysStackOnTop, False)
|
|
198
|
+
self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground, True)
|
|
199
|
+
self.setClearColor(Qt.GlobalColor.transparent)
|
|
200
|
+
|
|
201
|
+
# Connect Backend
|
|
202
|
+
self.backend = ResourceBackend(self)
|
|
203
|
+
self.rootContext().setContextProperty("backend", self.backend)
|
|
204
|
+
|
|
205
|
+
# We need to manually wire property updates because we are binding to root properties in QML
|
|
206
|
+
# Actually, simpler pattern: QML file reads from an object we inject.
|
|
207
|
+
# Let's adjust QML slightly to bind to `backend.cpuUsage` etc. if we can,
|
|
208
|
+
# OR we leave QML as having properties and we set them from Python.
|
|
209
|
+
#
|
|
210
|
+
# Better approach for Py+QML:
|
|
211
|
+
# Inject `backend` into context, modify QML to use `backend.cpuUsage`.
|
|
212
|
+
# But since I already wrote QML with root properties, I will just set them directly
|
|
213
|
+
# or update the QML file. Updating QML is cleaner.
|
|
214
|
+
#
|
|
215
|
+
# For now, let's keep QML independent and binding via setProperty?
|
|
216
|
+
# No, properly: context property is best.
|
|
217
|
+
#
|
|
218
|
+
# Let's re-write the QML loading part to use a safer 'initialProperties' approach or just signal/slots.
|
|
219
|
+
#
|
|
220
|
+
# EASIEST: QML binds to `root.cpuUsage`. Python sets `root.cpuUsage`.
|
|
221
|
+
|
|
222
|
+
self.backend.cpuChanged.connect(self._push_data_to_qml)
|
|
223
|
+
self.backend.ramChanged.connect(self._push_data_to_qml)
|
|
224
|
+
self.backend.gpuChanged.connect(self._push_data_to_qml)
|
|
225
|
+
self.backend.appRamChanged.connect(self._push_data_to_qml)
|
|
226
|
+
|
|
227
|
+
# Load QML
|
|
228
|
+
qml_path = os.path.join(_get_base_path(), "qml", "ResourceMonitor.qml")
|
|
229
|
+
self.setSource(QUrl.fromLocalFile(qml_path))
|
|
230
|
+
|
|
231
|
+
def _push_data_to_qml(self):
|
|
232
|
+
root = self.rootObject()
|
|
233
|
+
if root:
|
|
234
|
+
root.setProperty("cpuUsage", self.backend.cpuUsage)
|
|
235
|
+
root.setProperty("ramUsage", self.backend.ramUsage)
|
|
236
|
+
root.setProperty("gpuUsage", self.backend.gpuUsage)
|
|
237
|
+
root.setProperty("appRamString", self.backend.appRamString)
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
# pro/widgets/spinboxes.py
|
|
2
|
+
"""
|
|
3
|
+
Custom spinbox widgets for Seti Astro Suite Pro.
|
|
4
|
+
|
|
5
|
+
Provides enhanced spinbox widgets with consistent styling and behavior.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from PyQt6.QtCore import Qt, pyqtSignal
|
|
10
|
+
from PyQt6.QtWidgets import (
|
|
11
|
+
QWidget, QHBoxLayout, QVBoxLayout, QLineEdit, QToolButton
|
|
12
|
+
)
|
|
13
|
+
from PyQt6.QtGui import QIntValidator, QDoubleValidator
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class CustomSpinBox(QWidget):
|
|
17
|
+
"""
|
|
18
|
+
A custom integer spin box widget with up/down buttons.
|
|
19
|
+
|
|
20
|
+
Emits valueChanged(int) when the value changes.
|
|
21
|
+
|
|
22
|
+
Usage:
|
|
23
|
+
spin = CustomSpinBox(minimum=0, maximum=100, initial=50, step=1)
|
|
24
|
+
spin.valueChanged.connect(my_handler)
|
|
25
|
+
"""
|
|
26
|
+
valueChanged = pyqtSignal(int)
|
|
27
|
+
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
minimum: int = 0,
|
|
31
|
+
maximum: int = 100,
|
|
32
|
+
initial: int = 0,
|
|
33
|
+
step: int = 1,
|
|
34
|
+
parent: QWidget | None = None
|
|
35
|
+
):
|
|
36
|
+
super().__init__(parent)
|
|
37
|
+
self.minimum = minimum
|
|
38
|
+
self.maximum = maximum
|
|
39
|
+
self.step = step
|
|
40
|
+
self._value = initial
|
|
41
|
+
|
|
42
|
+
# Line edit for value display/entry
|
|
43
|
+
self.lineEdit = QLineEdit(str(initial))
|
|
44
|
+
self.lineEdit.setAlignment(Qt.AlignmentFlag.AlignRight)
|
|
45
|
+
self.lineEdit.setValidator(QIntValidator(self.minimum, self.maximum, self))
|
|
46
|
+
self.lineEdit.editingFinished.connect(self._on_editing_finished)
|
|
47
|
+
|
|
48
|
+
# Up/down buttons
|
|
49
|
+
self.upButton = QToolButton()
|
|
50
|
+
self.upButton.setText("▲")
|
|
51
|
+
self.upButton.setAutoRepeat(True)
|
|
52
|
+
self.upButton.setAutoRepeatInterval(50)
|
|
53
|
+
self.upButton.setAutoRepeatDelay(300)
|
|
54
|
+
self.upButton.clicked.connect(self._increase_value)
|
|
55
|
+
|
|
56
|
+
self.downButton = QToolButton()
|
|
57
|
+
self.downButton.setText("▼")
|
|
58
|
+
self.downButton.setAutoRepeat(True)
|
|
59
|
+
self.downButton.setAutoRepeatInterval(50)
|
|
60
|
+
self.downButton.setAutoRepeatDelay(300)
|
|
61
|
+
self.downButton.clicked.connect(self._decrease_value)
|
|
62
|
+
|
|
63
|
+
# Layout buttons vertically
|
|
64
|
+
button_layout = QVBoxLayout()
|
|
65
|
+
button_layout.addWidget(self.upButton)
|
|
66
|
+
button_layout.addWidget(self.downButton)
|
|
67
|
+
button_layout.setSpacing(0)
|
|
68
|
+
button_layout.setContentsMargins(0, 0, 0, 0)
|
|
69
|
+
|
|
70
|
+
# Main horizontal layout
|
|
71
|
+
main_layout = QHBoxLayout()
|
|
72
|
+
main_layout.addWidget(self.lineEdit)
|
|
73
|
+
main_layout.addLayout(button_layout)
|
|
74
|
+
main_layout.setSpacing(0)
|
|
75
|
+
main_layout.setContentsMargins(0, 0, 0, 0)
|
|
76
|
+
self.setLayout(main_layout)
|
|
77
|
+
|
|
78
|
+
self._update_button_states()
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def value(self) -> int:
|
|
82
|
+
"""Get the current value."""
|
|
83
|
+
return self._value
|
|
84
|
+
|
|
85
|
+
def setValue(self, val: int) -> None:
|
|
86
|
+
"""Set the value, clamping to min/max."""
|
|
87
|
+
val = max(self.minimum, min(self.maximum, val))
|
|
88
|
+
if val != self._value:
|
|
89
|
+
self._value = val
|
|
90
|
+
self.lineEdit.setText(str(val))
|
|
91
|
+
self.valueChanged.emit(val)
|
|
92
|
+
self._update_button_states()
|
|
93
|
+
|
|
94
|
+
def setMinimum(self, minimum: int) -> None:
|
|
95
|
+
"""Set the minimum value."""
|
|
96
|
+
self.minimum = minimum
|
|
97
|
+
self.lineEdit.setValidator(QIntValidator(self.minimum, self.maximum, self))
|
|
98
|
+
if self._value < minimum:
|
|
99
|
+
self.setValue(minimum)
|
|
100
|
+
self._update_button_states()
|
|
101
|
+
|
|
102
|
+
def setMaximum(self, maximum: int) -> None:
|
|
103
|
+
"""Set the maximum value."""
|
|
104
|
+
self.maximum = maximum
|
|
105
|
+
self.lineEdit.setValidator(QIntValidator(self.minimum, self.maximum, self))
|
|
106
|
+
if self._value > maximum:
|
|
107
|
+
self.setValue(maximum)
|
|
108
|
+
self._update_button_states()
|
|
109
|
+
|
|
110
|
+
def setRange(self, minimum: int, maximum: int) -> None:
|
|
111
|
+
"""Set both minimum and maximum values."""
|
|
112
|
+
self.minimum = minimum
|
|
113
|
+
self.maximum = maximum
|
|
114
|
+
self.lineEdit.setValidator(QIntValidator(self.minimum, self.maximum, self))
|
|
115
|
+
self.setValue(max(minimum, min(maximum, self._value)))
|
|
116
|
+
|
|
117
|
+
def setSingleStep(self, step: int) -> None:
|
|
118
|
+
"""Set the step value for up/down buttons."""
|
|
119
|
+
self.step = step
|
|
120
|
+
|
|
121
|
+
def _on_editing_finished(self) -> None:
|
|
122
|
+
"""Handle manual text entry."""
|
|
123
|
+
try:
|
|
124
|
+
val = int(self.lineEdit.text())
|
|
125
|
+
self.setValue(val)
|
|
126
|
+
except ValueError:
|
|
127
|
+
self.lineEdit.setText(str(self._value))
|
|
128
|
+
|
|
129
|
+
def _increase_value(self) -> None:
|
|
130
|
+
"""Increase value by step."""
|
|
131
|
+
self.setValue(self._value + self.step)
|
|
132
|
+
|
|
133
|
+
def _decrease_value(self) -> None:
|
|
134
|
+
"""Decrease value by step."""
|
|
135
|
+
self.setValue(self._value - self.step)
|
|
136
|
+
|
|
137
|
+
def _update_button_states(self) -> None:
|
|
138
|
+
"""Enable/disable buttons at limits."""
|
|
139
|
+
self.upButton.setEnabled(self._value < self.maximum)
|
|
140
|
+
self.downButton.setEnabled(self._value > self.minimum)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class CustomDoubleSpinBox(QWidget):
|
|
144
|
+
"""
|
|
145
|
+
A custom double (float) spin box widget with up/down buttons.
|
|
146
|
+
|
|
147
|
+
Emits valueChanged(float) when the value changes.
|
|
148
|
+
|
|
149
|
+
Usage:
|
|
150
|
+
spin = CustomDoubleSpinBox(minimum=0.0, maximum=1.0, initial=0.5, step=0.1, decimals=2)
|
|
151
|
+
spin.valueChanged.connect(my_handler)
|
|
152
|
+
"""
|
|
153
|
+
valueChanged = pyqtSignal(float)
|
|
154
|
+
|
|
155
|
+
def __init__(
|
|
156
|
+
self,
|
|
157
|
+
minimum: float = 0.0,
|
|
158
|
+
maximum: float = 100.0,
|
|
159
|
+
initial: float = 0.0,
|
|
160
|
+
step: float = 1.0,
|
|
161
|
+
decimals: int = 2,
|
|
162
|
+
parent: QWidget | None = None
|
|
163
|
+
):
|
|
164
|
+
super().__init__(parent)
|
|
165
|
+
self.minimum = minimum
|
|
166
|
+
self.maximum = maximum
|
|
167
|
+
self.step = step
|
|
168
|
+
self.decimals = decimals
|
|
169
|
+
self._value = initial
|
|
170
|
+
|
|
171
|
+
# Line edit for value display/entry
|
|
172
|
+
self.lineEdit = QLineEdit(f"{initial:.{decimals}f}")
|
|
173
|
+
self.lineEdit.setAlignment(Qt.AlignmentFlag.AlignRight)
|
|
174
|
+
self.lineEdit.setValidator(QDoubleValidator(self.minimum, self.maximum, decimals, self))
|
|
175
|
+
self.lineEdit.editingFinished.connect(self._on_editing_finished)
|
|
176
|
+
|
|
177
|
+
# Up/down buttons
|
|
178
|
+
self.upButton = QToolButton()
|
|
179
|
+
self.upButton.setText("▲")
|
|
180
|
+
self.upButton.setAutoRepeat(True)
|
|
181
|
+
self.upButton.setAutoRepeatInterval(50)
|
|
182
|
+
self.upButton.setAutoRepeatDelay(300)
|
|
183
|
+
self.upButton.clicked.connect(self._increase_value)
|
|
184
|
+
|
|
185
|
+
self.downButton = QToolButton()
|
|
186
|
+
self.downButton.setText("▼")
|
|
187
|
+
self.downButton.setAutoRepeat(True)
|
|
188
|
+
self.downButton.setAutoRepeatInterval(50)
|
|
189
|
+
self.downButton.setAutoRepeatDelay(300)
|
|
190
|
+
self.downButton.clicked.connect(self._decrease_value)
|
|
191
|
+
|
|
192
|
+
# Layout buttons vertically
|
|
193
|
+
button_layout = QVBoxLayout()
|
|
194
|
+
button_layout.addWidget(self.upButton)
|
|
195
|
+
button_layout.addWidget(self.downButton)
|
|
196
|
+
button_layout.setSpacing(0)
|
|
197
|
+
button_layout.setContentsMargins(0, 0, 0, 0)
|
|
198
|
+
|
|
199
|
+
# Main horizontal layout
|
|
200
|
+
main_layout = QHBoxLayout()
|
|
201
|
+
main_layout.addWidget(self.lineEdit)
|
|
202
|
+
main_layout.addLayout(button_layout)
|
|
203
|
+
main_layout.setSpacing(0)
|
|
204
|
+
main_layout.setContentsMargins(0, 0, 0, 0)
|
|
205
|
+
self.setLayout(main_layout)
|
|
206
|
+
|
|
207
|
+
self._update_button_states()
|
|
208
|
+
|
|
209
|
+
@property
|
|
210
|
+
def value(self) -> float:
|
|
211
|
+
"""Get the current value."""
|
|
212
|
+
return self._value
|
|
213
|
+
|
|
214
|
+
def setValue(self, val: float) -> None:
|
|
215
|
+
"""Set the value, clamping to min/max."""
|
|
216
|
+
val = max(self.minimum, min(self.maximum, val))
|
|
217
|
+
if abs(val - self._value) > 1e-10:
|
|
218
|
+
self._value = val
|
|
219
|
+
self.lineEdit.setText(f"{val:.{self.decimals}f}")
|
|
220
|
+
self.valueChanged.emit(val)
|
|
221
|
+
self._update_button_states()
|
|
222
|
+
|
|
223
|
+
def setMinimum(self, minimum: float) -> None:
|
|
224
|
+
"""Set the minimum value."""
|
|
225
|
+
self.minimum = minimum
|
|
226
|
+
self.lineEdit.setValidator(QDoubleValidator(self.minimum, self.maximum, self.decimals, self))
|
|
227
|
+
if self._value < minimum:
|
|
228
|
+
self.setValue(minimum)
|
|
229
|
+
self._update_button_states()
|
|
230
|
+
|
|
231
|
+
def setMaximum(self, maximum: float) -> None:
|
|
232
|
+
"""Set the maximum value."""
|
|
233
|
+
self.maximum = maximum
|
|
234
|
+
self.lineEdit.setValidator(QDoubleValidator(self.minimum, self.maximum, self.decimals, self))
|
|
235
|
+
if self._value > maximum:
|
|
236
|
+
self.setValue(maximum)
|
|
237
|
+
self._update_button_states()
|
|
238
|
+
|
|
239
|
+
def setRange(self, minimum: float, maximum: float) -> None:
|
|
240
|
+
"""Set both minimum and maximum values."""
|
|
241
|
+
self.minimum = minimum
|
|
242
|
+
self.maximum = maximum
|
|
243
|
+
self.lineEdit.setValidator(QDoubleValidator(self.minimum, self.maximum, self.decimals, self))
|
|
244
|
+
self.setValue(max(minimum, min(maximum, self._value)))
|
|
245
|
+
|
|
246
|
+
def setSingleStep(self, step: float) -> None:
|
|
247
|
+
"""Set the step value for up/down buttons."""
|
|
248
|
+
self.step = step
|
|
249
|
+
|
|
250
|
+
def setDecimals(self, decimals: int) -> None:
|
|
251
|
+
"""Set the number of decimal places."""
|
|
252
|
+
self.decimals = decimals
|
|
253
|
+
self.lineEdit.setText(f"{self._value:.{decimals}f}")
|
|
254
|
+
self.lineEdit.setValidator(QDoubleValidator(self.minimum, self.maximum, decimals, self))
|
|
255
|
+
|
|
256
|
+
def _on_editing_finished(self) -> None:
|
|
257
|
+
"""Handle manual text entry."""
|
|
258
|
+
try:
|
|
259
|
+
val = float(self.lineEdit.text())
|
|
260
|
+
self.setValue(val)
|
|
261
|
+
except ValueError:
|
|
262
|
+
self.lineEdit.setText(f"{self._value:.{self.decimals}f}")
|
|
263
|
+
|
|
264
|
+
def _increase_value(self) -> None:
|
|
265
|
+
"""Increase value by step."""
|
|
266
|
+
self.setValue(self._value + self.step)
|
|
267
|
+
|
|
268
|
+
def _decrease_value(self) -> None:
|
|
269
|
+
"""Decrease value by step."""
|
|
270
|
+
self.setValue(self._value - self.step)
|
|
271
|
+
|
|
272
|
+
def _update_button_states(self) -> None:
|
|
273
|
+
"""Enable/disable buttons at limits."""
|
|
274
|
+
self.upButton.setEnabled(self._value < self.maximum - 1e-10)
|
|
275
|
+
self.downButton.setEnabled(self._value > self.minimum + 1e-10)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# pro/widgets/themed_buttons.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
from PyQt6.QtGui import QIcon
|
|
4
|
+
from PyQt6.QtWidgets import QToolButton
|
|
5
|
+
|
|
6
|
+
def themed_toolbtn(icon_name: str, tip: str, *, on_click=None) -> QToolButton:
|
|
7
|
+
b = QToolButton()
|
|
8
|
+
b.setIcon(QIcon.fromTheme(icon_name))
|
|
9
|
+
b.setToolTip(tip)
|
|
10
|
+
b.setAutoRaise(True) # nice flat toolbar look
|
|
11
|
+
if on_click is not None:
|
|
12
|
+
b.clicked.connect(on_click)
|
|
13
|
+
return b
|