Artemis 4 RC1
This commit is contained in:
354
artemis/ui/artemis.py
Normal file
354
artemis/ui/artemis.py
Normal file
@@ -0,0 +1,354 @@
|
||||
import uuid
|
||||
|
||||
from PySide6.QtQml import QQmlApplicationEngine
|
||||
from PySide6.QtCore import QObject, Slot, Signal
|
||||
|
||||
from artemis.utils.constants import Constants, Messages
|
||||
from artemis.utils.sys_utils import open_directory, pack_db, unpack_db
|
||||
from artemis.utils.sql_utils import ArtemisDatabase, ArtemisSignal
|
||||
from artemis.utils.path_utils import check_data_dir
|
||||
from artemis.utils.network_utils import NetworkManager
|
||||
from artemis.utils.generic_utils import generate_filter_query
|
||||
from artemis.utils.path_utils import normalize_dialog_path
|
||||
|
||||
from artemis.ui.preferences import UIPreferences
|
||||
from artemis.ui.dbmanager import UIdbmanager
|
||||
from artemis.ui.signaleditor import UIsignaleditor
|
||||
from artemis.ui.downloader import UIDownloader
|
||||
from artemis.ui.spaceweather import UIspaceweather
|
||||
from artemis.ui.documentsmanager import UIdocumentsmanager
|
||||
from artemis.ui.categoryeditor import UIcategoryeditor
|
||||
|
||||
import artemis.resources
|
||||
|
||||
|
||||
class UIArtemis(QObject):
|
||||
# Python > QML Signals
|
||||
populate_sig_list = Signal(list)
|
||||
populate_sig_details = Signal(list)
|
||||
populate_filter_modulation = Signal(list)
|
||||
|
||||
clear_list = Signal()
|
||||
clear_signal_page = Signal()
|
||||
clear_filter_page = Signal()
|
||||
lock_audio_player = Signal()
|
||||
lock_menu = Signal(bool)
|
||||
|
||||
show_dialog_popup = Signal(str, str, str)
|
||||
show_dialog_download_db = Signal(str, str, str)
|
||||
show_dialog_download_art = Signal(str, str, str)
|
||||
update_info_bar = Signal(str, str)
|
||||
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
# Main UI initialization
|
||||
self._engine = QQmlApplicationEngine()
|
||||
self._engine.rootContext().setContextProperty('APPLICATION_VERSION', Constants.APPLICATION_VERSION)
|
||||
self._engine.rootContext().setContextProperty('PYTHON_VERSION', Constants.PYTHON_VERSION)
|
||||
self._engine.rootContext().setContextProperty('QT_VERSION', Constants.QT_VERSION)
|
||||
self._engine.load('qrc:/ui/Artemis.qml')
|
||||
self._window = self._engine.rootObjects()[0]
|
||||
|
||||
self._window_filter = self._window.findChild(QObject, "filterPageObj")
|
||||
self._window_signal = self._window.findChild(QObject, "signalPageObj")
|
||||
|
||||
self.loaded_db = None
|
||||
|
||||
self._connect()
|
||||
|
||||
# Creating istances for other windows
|
||||
self.preferences = UIPreferences(self)
|
||||
self.dbmanager = UIdbmanager(self)
|
||||
self.downloader = UIDownloader(self)
|
||||
self.spaceweather = UIspaceweather(self)
|
||||
self.docmanager = UIdocumentsmanager(self)
|
||||
self.sigeditor = UIsignaleditor(self)
|
||||
self.cateditor = UIcategoryeditor(self)
|
||||
|
||||
self.network_manager = NetworkManager(self)
|
||||
|
||||
check_data_dir()
|
||||
|
||||
|
||||
def _connect(self):
|
||||
# QML > Python connections
|
||||
self._window.showDBmanager.connect(self.show_dbmanager_ui)
|
||||
self._window.loadSignal.connect(self.load_sig)
|
||||
self._window.showPref.connect(self.show_pref_ui)
|
||||
self._window.openSigEditor.connect(self.open_sig_editor)
|
||||
self._window.startDownloader.connect(self.start_download_db)
|
||||
self._window.checkDbUpdates.connect(self.check_update_db)
|
||||
self._window.showSpaceWeather.connect(self.show_space_weather_ui)
|
||||
self._window.openDbDirectory.connect(self.open_db_directory)
|
||||
self._window.showCatManager.connect(self.open_cat_manager)
|
||||
|
||||
self._window.newDb.connect(self.new_db)
|
||||
self._window.exportDb.connect(self.export_db)
|
||||
self._window.importDb.connect(self.import_db)
|
||||
|
||||
self._window_filter.applyFilter.connect(self.apply_filter)
|
||||
self._window_filter.sendBottomAlert.connect(self.bottom_info_bar)
|
||||
|
||||
self._window_signal.openDocManager.connect(self.show_documentsmanager_ui)
|
||||
self._window_signal.openSigEditor.connect(self.open_sig_editor)
|
||||
self._window_signal.deleteCatTag.connect(self.delete_cat_tag)
|
||||
self._window_signal.addCatTag.connect(self.add_cat_tag)
|
||||
|
||||
# Python > QML connections
|
||||
self.populate_sig_list.connect(self._window.populateList)
|
||||
self.clear_list.connect(self._window.clearList)
|
||||
self.update_info_bar.connect(self._window.bottomInfoBar)
|
||||
self.show_dialog_popup.connect(self._window.openGeneralDialog)
|
||||
self.show_dialog_download_db.connect(self._window.openDialogDownloadDb)
|
||||
self.show_dialog_download_art.connect(self._window.openDialogDownloadArtemis)
|
||||
self.lock_menu.connect(self._window.lockMenu)
|
||||
|
||||
self.populate_sig_details.connect(self._window_signal.populateSignalParam)
|
||||
self.lock_audio_player.connect(self._window_signal.lockPlayer)
|
||||
self.clear_signal_page.connect(self._window_signal.resetAll)
|
||||
|
||||
self.clear_filter_page.connect(self._window_filter.resetAll)
|
||||
self.populate_filter_modulation.connect(self._window_filter.loadLists)
|
||||
|
||||
|
||||
def load_db(self, db_dir_name):
|
||||
""" Load the DB and populate the signals list
|
||||
|
||||
Args:
|
||||
db_dir_name (str): folder name in the data folder
|
||||
"""
|
||||
# Loading DB
|
||||
self.loaded_db = ArtemisDatabase(db_dir_name)
|
||||
self.loaded_db.load()
|
||||
# Clearing UI
|
||||
self.lock_menu.emit(False)
|
||||
self.clear_signal_page.emit()
|
||||
self.clear_filter_page.emit()
|
||||
# Populating UI
|
||||
self.load_filter_lists()
|
||||
self.populate_sig_list.emit(self.loaded_db.all_signals)
|
||||
# Updating status bar
|
||||
total_signals = len(self.loaded_db.all_signals)
|
||||
self.bottom_info_bar("Database loaded with {} signals".format(total_signals), "info")
|
||||
|
||||
|
||||
@Slot(int)
|
||||
def load_sig(self, sig_id):
|
||||
""" Load the selected signal and populate the SignalPage
|
||||
|
||||
Args:
|
||||
sig_id (int): SIG_ID of the signal to be loaded
|
||||
"""
|
||||
self.loaded_sig = ArtemisSignal(self.loaded_db)
|
||||
self.loaded_sig.load(sig_id)
|
||||
sig_dic = self.loaded_sig.generate_dic()
|
||||
|
||||
self.populate_sig_details.emit([sig_dic])
|
||||
|
||||
|
||||
def load_filter_lists(self):
|
||||
""" Populates the 3 listviews in the FilterPage
|
||||
"""
|
||||
self.populate_filter_modulation.emit([{
|
||||
'modulation': self.loaded_db.all_modulation,
|
||||
'location': self.loaded_db.all_location,
|
||||
'category': self.loaded_db.all_category_labels
|
||||
}])
|
||||
|
||||
|
||||
@Slot(dict)
|
||||
def apply_filter(self, filter_status):
|
||||
""" Update the signal list according to the selected filters in the FilterPage.
|
||||
|
||||
Args:
|
||||
filter_status (dic): dictionary containing the active filters with all
|
||||
the details to generate a search query
|
||||
"""
|
||||
filter_status = filter_status.toVariant()
|
||||
if filter_status != {}:
|
||||
filter_query = generate_filter_query(filter_status)
|
||||
self.loaded_db.select_by_filter(filter_query)
|
||||
|
||||
self.clear_signal_page.emit()
|
||||
self.populate_sig_list.emit(self.loaded_db.all_signals)
|
||||
|
||||
total_signals = len(self.loaded_db.all_signals)
|
||||
self.bottom_info_bar("FILTERS ACTIVE: {} signals found".format(total_signals), "warning")
|
||||
else:
|
||||
self.load_db(self.loaded_db.db_dir_name)
|
||||
|
||||
|
||||
def show_pref_ui(self):
|
||||
""" Load the preference windows
|
||||
"""
|
||||
self.preferences.load_preferences_ui()
|
||||
|
||||
|
||||
def show_dbmanager_ui(self):
|
||||
""" Load the DB manager windows
|
||||
"""
|
||||
self.dbmanager.load_dbmanager_ui()
|
||||
|
||||
|
||||
@Slot(str, list, bool)
|
||||
def open_sig_editor(self, type, sig_param, is_new):
|
||||
""" Open the signal editor windows
|
||||
Called when the user want to add, edit or delete the signal or its parametes.
|
||||
"""
|
||||
self.sigeditor.load_signaleditor_ui(type, sig_param, is_new)
|
||||
|
||||
|
||||
def show_space_weather_ui(self):
|
||||
""" Open the space weather windows
|
||||
"""
|
||||
self.spaceweather.load_spaceweather_ui()
|
||||
|
||||
|
||||
def show_documentsmanager_ui(self):
|
||||
""" Open the documents manager windows
|
||||
"""
|
||||
self.docmanager.load_documentsmanager_ui()
|
||||
|
||||
|
||||
def check_update_db(self):
|
||||
""" User manual check for updates db updates
|
||||
"""
|
||||
self.network_manager.show_popup = True
|
||||
self.network_manager.check_updates()
|
||||
|
||||
|
||||
def start_download_db(self):
|
||||
""" Show the downloader and start the download of the sigid db
|
||||
"""
|
||||
self.downloader.show_ui.emit()
|
||||
self.downloader.on_start()
|
||||
|
||||
|
||||
def dialog_download_db(self, message_type, title, message):
|
||||
""" Dialog popup for DB download confirmation
|
||||
"""
|
||||
self.show_dialog_download_db.emit(message_type, title, message)
|
||||
|
||||
|
||||
def dialog_download_artemis(self, message_type, title, message):
|
||||
""" Dialog popup for artemis download confirmation
|
||||
"""
|
||||
self.show_dialog_download_art.emit(message_type, title, message)
|
||||
|
||||
|
||||
def open_db_directory(self):
|
||||
""" Open the local folder of the loaded DB
|
||||
"""
|
||||
open_directory(self.loaded_db.db_dir)
|
||||
|
||||
|
||||
@Slot(str)
|
||||
def new_db(self, name):
|
||||
""" Create a new local DB
|
||||
|
||||
Args:
|
||||
name (str): name of the new DB, hardcoded in sql info table
|
||||
"""
|
||||
try:
|
||||
new_db = ArtemisDatabase(str(uuid.uuid4()))
|
||||
new_db.create(name)
|
||||
self.load_db(new_db.db_dir_name)
|
||||
self.dialog_popup(
|
||||
Messages.DIALOG_TYPE_INFO,
|
||||
Messages.GENERIC_SUCCESS,
|
||||
Messages.DB_CREATION_SUCCESS_MSG
|
||||
)
|
||||
except Exception as e:
|
||||
self.dialog_popup(
|
||||
Messages.DIALOG_TYPE_ERROR,
|
||||
Messages.GENERIC_ERROR,
|
||||
Messages.GENERIC_ERROR_MSG.format(e)
|
||||
)
|
||||
|
||||
|
||||
@Slot(str)
|
||||
def export_db(self, save_path):
|
||||
""" Export the load DB in a tar file. Does not use compression
|
||||
|
||||
Args:
|
||||
save_path (str): destination path of the generated .tar file
|
||||
"""
|
||||
try:
|
||||
dest_path = normalize_dialog_path(save_path)
|
||||
pack_db(dest_path, self.loaded_db.db_dir)
|
||||
self.dialog_popup(
|
||||
Messages.DIALOG_TYPE_INFO,
|
||||
Messages.GENERIC_SUCCESS,
|
||||
Messages.EXPORTING_SUCCESS_MSG
|
||||
)
|
||||
except Exception as e:
|
||||
self.dialog_popup(
|
||||
Messages.DIALOG_TYPE_ERROR,
|
||||
Messages.GENERIC_ERROR,
|
||||
Messages.GENERIC_ERROR_MSG.format(e)
|
||||
)
|
||||
|
||||
|
||||
@Slot(str)
|
||||
def import_db(self, tar_path):
|
||||
""" Import a new DB in the Artemis data folder
|
||||
|
||||
Args:
|
||||
tar_path (str): Path of the archive to be imported
|
||||
"""
|
||||
try:
|
||||
origin_path = normalize_dialog_path(tar_path)
|
||||
unpack_db(origin_path, str(uuid.uuid4()))
|
||||
self.dialog_popup(
|
||||
Messages.DIALOG_TYPE_INFO,
|
||||
Messages.GENERIC_SUCCESS,
|
||||
Messages.IMPORTING_SUCCESS_MSG
|
||||
)
|
||||
except Exception as e:
|
||||
self.dialog_popup(
|
||||
Messages.DIALOG_TYPE_ERROR,
|
||||
Messages.GENERIC_ERROR,
|
||||
Messages.GENERIC_ERROR_MSG.format(e)
|
||||
)
|
||||
|
||||
|
||||
@Slot(int)
|
||||
def add_cat_tag(self, clb_id):
|
||||
self.loaded_sig.insert_category(clb_id)
|
||||
self.load_db(self.loaded_db.db_dir_name)
|
||||
|
||||
|
||||
@Slot(int)
|
||||
def delete_cat_tag(self, cat_id):
|
||||
self.loaded_sig.delete_category(cat_id)
|
||||
self.load_db(self.loaded_db.db_dir_name)
|
||||
|
||||
|
||||
def open_cat_manager(self):
|
||||
""" Open the category manager windows
|
||||
"""
|
||||
self.cateditor.load_cateditor_ui()
|
||||
|
||||
|
||||
def dialog_popup(self, message_type, title, message):
|
||||
""" Opens a general dialog popup
|
||||
|
||||
Args:
|
||||
message_type (str): 'info', 'question', 'warn', 'error'
|
||||
title (str): header of the dialoog
|
||||
message (sstr): description inside the dialog
|
||||
"""
|
||||
self.show_dialog_popup.emit(message_type, title, message)
|
||||
|
||||
|
||||
@Slot(str, str)
|
||||
def bottom_info_bar(self, message, message_type):
|
||||
""" Manage the footer info bar
|
||||
|
||||
Args:
|
||||
message (str): text to be shown in the info bar
|
||||
message_type (str): 'info', 'warning'
|
||||
"""
|
||||
self.update_info_bar.emit(message, message_type)
|
||||
67
artemis/ui/categoryeditor.py
Normal file
67
artemis/ui/categoryeditor.py
Normal file
@@ -0,0 +1,67 @@
|
||||
from PySide6.QtQml import QQmlApplicationEngine
|
||||
from PySide6.QtCore import QObject, Signal, Slot
|
||||
|
||||
from artemis.utils.path_utils import *
|
||||
from artemis.utils.generic_utils import *
|
||||
|
||||
|
||||
class UIcategoryeditor(QObject):
|
||||
# Python > QML Signals
|
||||
show_ui = Signal()
|
||||
load = Signal(list)
|
||||
|
||||
|
||||
def __init__(self, parent):
|
||||
super().__init__()
|
||||
|
||||
self._parent = parent
|
||||
|
||||
self._engine = QQmlApplicationEngine()
|
||||
self._engine.load('qrc:/ui/CategoryEditor.qml')
|
||||
self._window = self._engine.rootObjects()[0]
|
||||
|
||||
self._connect()
|
||||
|
||||
|
||||
def _connect(self):
|
||||
# QML > Python connections
|
||||
self._window.saveParam.connect(self.save)
|
||||
self._window.deleteParam.connect(self.delete)
|
||||
|
||||
# Python > QML connections
|
||||
self.show_ui.connect(self._window.show)
|
||||
self.load.connect(self._window.loadList)
|
||||
|
||||
|
||||
def load_cateditor_ui(self):
|
||||
""" Load the list with existing category tags and show the UI
|
||||
"""
|
||||
all_cat = self._parent.loaded_db.all_category_labels
|
||||
self.load.emit(all_cat)
|
||||
self.show_ui.emit()
|
||||
|
||||
|
||||
@Slot(list, bool)
|
||||
def save(self, data, is_new):
|
||||
""" Save new category tag or update the existing ones.
|
||||
"""
|
||||
data = data.toVariant()
|
||||
|
||||
if is_new:
|
||||
self._parent.loaded_db.insert_category_label(data[0])
|
||||
else:
|
||||
self._parent.loaded_db.update_category_label(data[1], data[0])
|
||||
|
||||
self._parent.load_db(self._parent.loaded_db.db_dir_name)
|
||||
self.load_cateditor_ui()
|
||||
|
||||
|
||||
@Slot(int)
|
||||
def delete(self, clb_id):
|
||||
""" Delete a database category tag.
|
||||
All the entries in the documents table are automatically beign deleted due to
|
||||
foreign-key cascade propagation
|
||||
"""
|
||||
self._parent.loaded_db.delete_category_label(clb_id)
|
||||
self._parent.load_db(self._parent.loaded_db.db_dir_name)
|
||||
self.load_cateditor_ui()
|
||||
105
artemis/ui/dbmanager.py
Normal file
105
artemis/ui/dbmanager.py
Normal file
@@ -0,0 +1,105 @@
|
||||
from PySide6.QtQml import QQmlApplicationEngine
|
||||
from PySide6.QtCore import QObject, Signal, Slot
|
||||
|
||||
from artemis.utils.path_utils import *
|
||||
from artemis.utils.generic_utils import *
|
||||
from artemis.utils.sql_utils import ArtemisDatabase
|
||||
from artemis.utils.constants import Constants
|
||||
|
||||
|
||||
class UIdbmanager(QObject):
|
||||
# Python > QML Signals
|
||||
show_ui = Signal()
|
||||
close_ui = Signal()
|
||||
populate_db_list = Signal(list)
|
||||
|
||||
|
||||
def __init__(self, parent):
|
||||
super().__init__()
|
||||
|
||||
self._parent = parent
|
||||
|
||||
self._engine = QQmlApplicationEngine()
|
||||
self._engine.load('qrc:/ui/DbManager.qml')
|
||||
self._window = self._engine.rootObjects()[0]
|
||||
|
||||
self._connect()
|
||||
|
||||
|
||||
def _connect(self):
|
||||
# QML > Python connections
|
||||
self._window.loadDB.connect(self.load_db)
|
||||
self._window.deleteDB.connect(self.delete_db)
|
||||
self._window.renameDB.connect(self.rename_db)
|
||||
|
||||
# Python > QML connections
|
||||
self.show_ui.connect(self._window.show)
|
||||
self.close_ui.connect(self._window.close)
|
||||
self.populate_db_list.connect(self._window.loadList)
|
||||
|
||||
|
||||
def load_dbmanager_ui(self):
|
||||
self.load_local_db_list()
|
||||
self.show_ui.emit()
|
||||
|
||||
|
||||
def load_local_db_list(self):
|
||||
""" Scan for all the valid DBs in the data folder and show them on the list
|
||||
"""
|
||||
valid_db_list = self.scan_db_dir()
|
||||
self.populate_db_list.emit(valid_db_list)
|
||||
|
||||
|
||||
def load_db(self, db_dir_name):
|
||||
""" Load the selected DB (from the DB Manager list) in the main artemis window
|
||||
"""
|
||||
self._parent.load_db(db_dir_name)
|
||||
self.close_ui.emit()
|
||||
|
||||
|
||||
@Slot(str)
|
||||
def delete_db(self, db_dir_name):
|
||||
""" Delete the DB folder.
|
||||
Clear the main UI if the database to be deleted is the selected one
|
||||
"""
|
||||
if self._parent.loaded_db is not None:
|
||||
if self._parent.loaded_db.db_dir_name == db_dir_name:
|
||||
self._parent.lock_menu.emit(True)
|
||||
self._parent.clear_list.emit()
|
||||
self._parent.clear_signal_page.emit()
|
||||
delete_db_dir(db_dir_name)
|
||||
self.load_local_db_list()
|
||||
|
||||
|
||||
@Slot(str, str)
|
||||
def rename_db(self, db_dir_name, new_name):
|
||||
""" Rename db in the data folder
|
||||
"""
|
||||
database = ArtemisDatabase(db_dir_name)
|
||||
database.rename(new_name)
|
||||
self.load_local_db_list()
|
||||
|
||||
|
||||
def scan_db_dir(self):
|
||||
""" Scans the data directory for valid databases and
|
||||
return a dictionary containing only the valid ones with a summary
|
||||
"""
|
||||
valid_db_list = []
|
||||
db_dirs = next(os.walk(Constants.DB_DIR))[1]
|
||||
|
||||
for db_dir_name in db_dirs:
|
||||
if valid_db(db_dir_name):
|
||||
database = ArtemisDatabase(db_dir_name)
|
||||
database.load()
|
||||
valid_db_list.append(
|
||||
{
|
||||
'name': database.name,
|
||||
'db_dir_name': database.db_dir_name,
|
||||
'documents_n': database.stats['documents'],
|
||||
'signals_n': database.stats['signals'],
|
||||
'images_n': database.stats['images'],
|
||||
'audio_n': database.stats['audio']
|
||||
}
|
||||
)
|
||||
|
||||
return valid_db_list
|
||||
128
artemis/ui/documentsmanager.py
Normal file
128
artemis/ui/documentsmanager.py
Normal file
@@ -0,0 +1,128 @@
|
||||
from PySide6.QtQml import QQmlApplicationEngine
|
||||
from PySide6.QtCore import QObject, Signal, Slot
|
||||
|
||||
from artemis.utils.path_utils import *
|
||||
from artemis.utils.generic_utils import *
|
||||
from artemis.utils.sys_utils import *
|
||||
|
||||
|
||||
class UIdocumentsmanager(QObject):
|
||||
# Python > QML Signals
|
||||
show_ui = Signal()
|
||||
close_ui = Signal()
|
||||
populate_documents_list = Signal(list)
|
||||
|
||||
|
||||
def __init__(self, parent):
|
||||
super().__init__()
|
||||
|
||||
self._parent = parent
|
||||
|
||||
self._engine = QQmlApplicationEngine()
|
||||
self._engine.load('qrc:/ui/DocumentsManager.qml')
|
||||
self._window = self._engine.rootObjects()[0]
|
||||
|
||||
self._connect()
|
||||
|
||||
|
||||
def _connect(self):
|
||||
# QML > Python connections
|
||||
self._window.saveNewDoc.connect(self.save_new_doc)
|
||||
self._window.deleteDoc.connect(self.delete_doc)
|
||||
self._window.updateDoc.connect(self.update_doc)
|
||||
self._window.openDoc.connect(self.open_doc)
|
||||
|
||||
|
||||
# Python > QML connections
|
||||
self.show_ui.connect(self._window.show)
|
||||
self.close_ui.connect(self._window.close)
|
||||
self.populate_documents_list.connect(self._window.loadList)
|
||||
|
||||
|
||||
def load_documentsmanager_ui(self):
|
||||
self.load_documents_list()
|
||||
self.show_ui.emit()
|
||||
|
||||
|
||||
def load_documents_list(self):
|
||||
""" Load the documents of the selected signal and populate the documents list
|
||||
"""
|
||||
self._parent.loaded_sig.select_documents()
|
||||
all_documents = self._parent.loaded_sig.documents
|
||||
|
||||
keys = (
|
||||
'doc_id',
|
||||
'extension',
|
||||
'name',
|
||||
'description',
|
||||
'type',
|
||||
'preview'
|
||||
)
|
||||
|
||||
doc_lst = [dict(zip(keys, values)) for values in all_documents]
|
||||
self.populate_documents_list.emit(doc_lst)
|
||||
|
||||
|
||||
@Slot(list)
|
||||
def save_new_doc(self, doc_lst):
|
||||
""" Save the new document (identified by the DOC_ID = -1) and reload the document list.
|
||||
doc_param contains all the details of the new documents.
|
||||
"""
|
||||
doc_param = doc_lst.toVariant()
|
||||
file_extension = os.path.splitext(doc_param[0])[1][1:]
|
||||
|
||||
doc_id = self._parent.loaded_sig.insert_document([
|
||||
-1,
|
||||
file_extension,
|
||||
doc_param[1],
|
||||
doc_param[2],
|
||||
doc_param[3],
|
||||
0
|
||||
])
|
||||
|
||||
local_file_name = '{}.{}'.format(str(doc_id), file_extension)
|
||||
origin_path = normalize_dialog_path(doc_param[0])
|
||||
copy_file(origin_path, self._parent.loaded_db.media_dir / local_file_name)
|
||||
self.load_documents_list()
|
||||
|
||||
|
||||
@Slot(list)
|
||||
def update_doc(self, doc_lst):
|
||||
""" Update the details of the existent document
|
||||
"""
|
||||
doc_list = doc_lst.toVariant()
|
||||
for doc in doc_list:
|
||||
self._parent.loaded_sig.update_documents(doc[0], doc[1], doc[2], doc[3], doc[4])
|
||||
self.load_documents_list()
|
||||
|
||||
|
||||
@Slot(str, str)
|
||||
def open_doc(self, doc_id, extension):
|
||||
""" Open the selected document with the proper system application (if any)
|
||||
"""
|
||||
try:
|
||||
open_file(self._parent.loaded_db.media_dir / '{}.{}'.format(doc_id, extension))
|
||||
except Exception as e:
|
||||
self.close_ui.emit()
|
||||
self._parent.dialog_popup(
|
||||
Messages.DIALOG_TYPE_ERROR,
|
||||
Messages.GENERIC_ERROR,
|
||||
str(e)
|
||||
)
|
||||
|
||||
|
||||
@Slot(str, str, str, bool)
|
||||
def delete_doc(self, doc_id, doc_extension, doc_type, doc_preview):
|
||||
""" Delete the selected document
|
||||
"""
|
||||
doc_file_name = '{}.{}'.format(doc_id, doc_extension)
|
||||
doc_file_path = self._parent.loaded_db.media_dir / doc_file_name
|
||||
|
||||
self._parent.loaded_sig.delete_document(doc_id)
|
||||
|
||||
if doc_preview:
|
||||
if doc_type == 'Audio':
|
||||
self._parent.lock_audio_player.emit()
|
||||
|
||||
delete_file(doc_file_path)
|
||||
self.load_documents_list()
|
||||
133
artemis/ui/downloader.py
Normal file
133
artemis/ui/downloader.py
Normal file
@@ -0,0 +1,133 @@
|
||||
from PySide6.QtQml import QQmlApplicationEngine
|
||||
from PySide6.QtCore import QObject, Slot, Signal, QUrl, QSaveFile, QDir, QIODevice
|
||||
from PySide6.QtNetwork import QNetworkReply, QNetworkRequest, QNetworkAccessManager
|
||||
|
||||
from artemis.utils.config_utils import *
|
||||
from artemis.utils.sys_utils import delete_file, match_hash, unpack_db
|
||||
from artemis.utils.constants import Messages
|
||||
from artemis.utils.sys_utils import delete_db_dir
|
||||
|
||||
|
||||
class UIDownloader(QObject):
|
||||
# Python > QML Signals
|
||||
show_ui = Signal()
|
||||
close_ui = Signal()
|
||||
|
||||
def __init__(self, parent):
|
||||
super().__init__()
|
||||
|
||||
self._parent = parent
|
||||
|
||||
self._engine = QQmlApplicationEngine()
|
||||
self._engine.load('qrc:/ui/Downloader.qml')
|
||||
self._window = self._engine.rootObjects()[0]
|
||||
self._progress_bar = self._window.findChild(QObject, "progressBar")
|
||||
self._label_progress = self._window.findChild(QObject, "labelProgress")
|
||||
|
||||
self._connect()
|
||||
|
||||
|
||||
def _connect(self):
|
||||
# QML > Python connections
|
||||
self._window.onAbort.connect(self.on_abort)
|
||||
|
||||
# Python > QML connections
|
||||
self.show_ui.connect(self._window.show)
|
||||
self.close_ui.connect(self._window.close)
|
||||
|
||||
|
||||
@Slot()
|
||||
def on_start(self):
|
||||
""" Start the download of the DB taking the needed url and size from
|
||||
the attributes of the UpdatesController class
|
||||
"""
|
||||
url_file = QUrl(self._parent.network_manager.remote_db_url)
|
||||
dest_path = QDir(Constants.DB_DIR)
|
||||
self.dest_file = dest_path.filePath(url_file.fileName())
|
||||
self.file = QSaveFile(self.dest_file)
|
||||
|
||||
if self.file.open(QIODevice.WriteOnly):
|
||||
# Start a GET HTTP request
|
||||
self.manager = QNetworkAccessManager(self)
|
||||
self.reply = self.manager.get(QNetworkRequest(url_file))
|
||||
self.reply.downloadProgress.connect(self.on_progress)
|
||||
self.reply.finished.connect(self.on_finished)
|
||||
self.reply.readyRead.connect(self.on_ready_read)
|
||||
self.reply.errorOccurred.connect(self.on_error)
|
||||
else:
|
||||
self.close_ui.emit()
|
||||
self.show_popup_error(
|
||||
self.file.errorString()
|
||||
)
|
||||
|
||||
|
||||
@Slot()
|
||||
def on_abort(self):
|
||||
""" Stop the download when user press abort button """
|
||||
if self.reply:
|
||||
self.reply.abort()
|
||||
self._progress_bar.setProperty("value", 0)
|
||||
|
||||
if self.file:
|
||||
self.file.cancelWriting()
|
||||
|
||||
self.close_ui.emit()
|
||||
|
||||
|
||||
@Slot()
|
||||
def on_ready_read(self):
|
||||
""" Get available bytes and store them into the file """
|
||||
if self.reply:
|
||||
if self.reply.error() == QNetworkReply.NoError:
|
||||
self.file.write(self.reply.readAll())
|
||||
|
||||
|
||||
@Slot()
|
||||
def on_finished(self):
|
||||
""" Delete reply, close the file, check the hash for integrity,
|
||||
extract the database and delete the downloaded zip
|
||||
"""
|
||||
if self.reply:
|
||||
self.reply.deleteLater()
|
||||
|
||||
if self.file:
|
||||
self.file.commit()
|
||||
|
||||
self._label_progress.setProperty("text", "Checking DB integrity (SHA-256)")
|
||||
|
||||
if match_hash(self.dest_file, self._parent.network_manager.remote_db_hash):
|
||||
self._label_progress.setProperty("text", "Unpacking archive...")
|
||||
delete_db_dir('SigID')
|
||||
unpack_db(self.dest_file, 'SigID')
|
||||
delete_file(self.dest_file)
|
||||
self._parent.load_db('SigID')
|
||||
self.close_ui.emit()
|
||||
|
||||
|
||||
@Slot(int, int)
|
||||
def on_progress(self, bytesReceived: int):
|
||||
""" Update progress bar and label
|
||||
"""
|
||||
total_bytes = self._parent.network_manager.remote_db_size
|
||||
self._label_progress.setProperty("text", "{:.1f} Mb / {:.1f} Mb".format(bytesReceived/10**6, total_bytes/10**6))
|
||||
self._progress_bar.setProperty("to", total_bytes)
|
||||
self._progress_bar.setProperty("value", bytesReceived)
|
||||
|
||||
|
||||
@Slot(QNetworkReply.NetworkError)
|
||||
def on_error(self, code: QNetworkReply.NetworkError):
|
||||
""" Show a message if an error happen during download
|
||||
"""
|
||||
if self.reply:
|
||||
self.close_ui.emit()
|
||||
self.show_popup_error(
|
||||
self.reply.errorString()
|
||||
)
|
||||
|
||||
|
||||
def show_popup_error(self, error_msg):
|
||||
self._parent.dialog_popup(
|
||||
Messages.DIALOG_TYPE_ERROR,
|
||||
Messages.GENERIC_ERROR,
|
||||
Messages.GENERIC_ERROR_MSG.format(error_msg)
|
||||
)
|
||||
56
artemis/ui/preferences.py
Normal file
56
artemis/ui/preferences.py
Normal file
@@ -0,0 +1,56 @@
|
||||
from PySide6.QtQml import QQmlApplicationEngine
|
||||
from PySide6.QtCore import QObject, Slot, Signal
|
||||
|
||||
from artemis.utils.config_utils import *
|
||||
|
||||
|
||||
class UIPreferences(QObject):
|
||||
# Python > QML Signals
|
||||
show_ui = Signal()
|
||||
load_material_accent = Signal(str)
|
||||
load_material_theme = Signal(str)
|
||||
|
||||
|
||||
def __init__(self, parent):
|
||||
super().__init__()
|
||||
|
||||
self._parent = parent
|
||||
|
||||
self._engine = QQmlApplicationEngine()
|
||||
self._engine.load('qrc:/ui/Preferences.qml')
|
||||
self._window = self._engine.rootObjects()[0]
|
||||
|
||||
self._connect()
|
||||
|
||||
|
||||
def _connect(self):
|
||||
# QML > Python connections
|
||||
self._window.saveMaterialAccent.connect(self.save_material_accent)
|
||||
self._window.saveMaterialTheme.connect(self.save_material_theme)
|
||||
|
||||
# Python > QML connections
|
||||
self.show_ui.connect(self._window.show)
|
||||
self.load_material_accent.connect(self._window.loadMaterialAccent)
|
||||
self.load_material_theme.connect(self._window.loadMaterialTheme)
|
||||
|
||||
|
||||
def load_preferences_ui(self):
|
||||
""" Loading all the initial preferences from the conf file to the UI
|
||||
"""
|
||||
self.load_material_accent.emit(CONFIGURE_QT.get_or_default("Material", "Accent", "Green"))
|
||||
self.load_material_theme.emit(CONFIGURE_QT.get_or_default("Material", "Theme", "System"))
|
||||
self.show_ui.emit()
|
||||
|
||||
|
||||
@Slot(str)
|
||||
def save_material_accent(self, material_accent):
|
||||
""" Saving material accent setting
|
||||
"""
|
||||
CONFIGURE_QT.set("Material", "Accent", material_accent)
|
||||
|
||||
|
||||
@Slot(str)
|
||||
def save_material_theme(self, material_theme):
|
||||
""" Saving material theme setting
|
||||
"""
|
||||
CONFIGURE_QT.set("Material", "Theme", material_theme)
|
||||
124
artemis/ui/signaleditor.py
Normal file
124
artemis/ui/signaleditor.py
Normal file
@@ -0,0 +1,124 @@
|
||||
from PySide6.QtQml import QQmlApplicationEngine
|
||||
from PySide6.QtCore import QObject, Signal, Slot
|
||||
|
||||
from artemis.utils.path_utils import *
|
||||
from artemis.utils.generic_utils import *
|
||||
from artemis.utils.sql_utils import ArtemisSignal
|
||||
|
||||
|
||||
class UIsignaleditor(QObject):
|
||||
# Python > QML Signals
|
||||
show_ui = Signal()
|
||||
load = Signal(str, list, bool)
|
||||
|
||||
|
||||
def __init__(self, parent):
|
||||
super().__init__()
|
||||
|
||||
self._parent = parent
|
||||
|
||||
self._engine = QQmlApplicationEngine()
|
||||
self._engine.load('qrc:/ui/SignalEditor.qml')
|
||||
self._window = self._engine.rootObjects()[0]
|
||||
|
||||
self._connect()
|
||||
|
||||
|
||||
def _connect(self):
|
||||
# QML > Python connections
|
||||
self._window.saveParam.connect(self.save)
|
||||
self._window.deleteParam.connect(self.delete)
|
||||
|
||||
# Python > QML connections
|
||||
self.show_ui.connect(self._window.show)
|
||||
self.load.connect(self._window.load)
|
||||
|
||||
|
||||
def load_signaleditor_ui(self, param_type, sig_param, is_new):
|
||||
""" Load all the details of the selected signal
|
||||
|
||||
Args:
|
||||
param_type (str): Signal, Frequency, Bandwidth, Modulation, Mode,
|
||||
ACF, Location
|
||||
sig_param (list): a list formed as [id, value, description]
|
||||
is_new (bool): If true, the windows open in an empty state ready to
|
||||
be compiled by the user. If false, the windows will open all the
|
||||
current parameter for the loaded signal, for editing or deleting purposes.
|
||||
"""
|
||||
if param_type == 'Signal' and not is_new:
|
||||
sig_param = [
|
||||
self._parent.loaded_sig.sig_id,
|
||||
self._parent.loaded_sig.name,
|
||||
self._parent.loaded_sig.description
|
||||
]
|
||||
self.load.emit(param_type, sig_param, is_new)
|
||||
self.show_ui.emit()
|
||||
|
||||
|
||||
@Slot(str, list, bool)
|
||||
def save(self, param_type, data, is_new):
|
||||
""" Save new signal parameters or update the existing ones.
|
||||
"""
|
||||
data = data.toVariant()
|
||||
|
||||
if is_new:
|
||||
if param_type == 'Signal':
|
||||
self._parent.loaded_sig = ArtemisSignal(self._parent.loaded_db)
|
||||
self._parent.loaded_sig.insert_signal(data[1], data[2])
|
||||
elif param_type == 'Frequency':
|
||||
self._parent.loaded_sig.insert_frequency(int(data[1]), data[2])
|
||||
elif param_type == 'Bandwidth':
|
||||
self._parent.loaded_sig.insert_bandwidth(int(data[1]), data[2])
|
||||
elif param_type == 'Modulation':
|
||||
self._parent.loaded_sig.insert_modulation(data[1], data[2])
|
||||
elif param_type == 'Mode':
|
||||
self._parent.loaded_sig.insert_mode(data[1], data[2])
|
||||
elif param_type == 'ACF':
|
||||
self._parent.loaded_sig.insert_acf(data[1], data[2])
|
||||
elif param_type == 'Location':
|
||||
self._parent.loaded_sig.insert_location(data[1], data[2])
|
||||
else:
|
||||
if param_type == 'Signal':
|
||||
self._parent.loaded_sig.update_signal(data[0], data[1], data[2])
|
||||
elif param_type == 'Frequency':
|
||||
self._parent.loaded_sig.update_frequency(data[0], int(data[1]), data[2])
|
||||
elif param_type == 'Bandwidth':
|
||||
self._parent.loaded_sig.update_bandwidth(data[0], int(data[1]), data[2])
|
||||
elif param_type == 'Modulation':
|
||||
self._parent.loaded_sig.update_modulation(data[0], data[1], data[2])
|
||||
elif param_type == 'Mode':
|
||||
self._parent.loaded_sig.update_mode(data[0], data[1], data[2])
|
||||
elif param_type == 'ACF':
|
||||
self._parent.loaded_sig.update_acf(data[0], data[1], data[2])
|
||||
elif param_type == 'Location':
|
||||
self._parent.loaded_sig.update_location(data[0], data[1], data[2])
|
||||
|
||||
self._parent.load_db(self._parent.loaded_db.db_dir_name)
|
||||
|
||||
|
||||
@Slot(str, int)
|
||||
def delete(self, param_type, id):
|
||||
""" Delete a signal parameter or the signal itself (with all the parameters and documents).
|
||||
All the entries in the documents table are automatically beign deleted due to
|
||||
foreign-key cascade propagation
|
||||
"""
|
||||
if param_type == 'Signal':
|
||||
self._parent.loaded_sig.delete_signal()
|
||||
for doc in self._parent.loaded_sig.documents:
|
||||
doc_file_name = '{}.{}'.format(str(doc[0]), doc[1])
|
||||
doc_file_path = self._parent.loaded_db.media_dir / doc_file_name
|
||||
delete_file(doc_file_path)
|
||||
elif param_type == 'Frequency':
|
||||
self._parent.loaded_sig.delete_frequency(id)
|
||||
elif param_type == 'Bandwidth':
|
||||
self._parent.loaded_sig.delete_bandwidth(id)
|
||||
elif param_type == 'Modulation':
|
||||
self._parent.loaded_sig.delete_modulation(id)
|
||||
elif param_type == 'Mode':
|
||||
self._parent.loaded_sig.delete_mode(id)
|
||||
elif param_type == 'ACF':
|
||||
self._parent.loaded_sig.delete_acf(id)
|
||||
elif param_type == 'Location':
|
||||
self._parent.loaded_sig.delete_location(id)
|
||||
|
||||
self._parent.load_db(self._parent.loaded_db.db_dir_name)
|
||||
65
artemis/ui/spaceweather.py
Normal file
65
artemis/ui/spaceweather.py
Normal file
@@ -0,0 +1,65 @@
|
||||
from PySide6.QtQml import QQmlApplicationEngine
|
||||
from PySide6.QtCore import QObject, Signal
|
||||
|
||||
from artemis.utils.path_utils import *
|
||||
from artemis.utils.generic_utils import *
|
||||
|
||||
from artemis.utils.constants import Constants
|
||||
|
||||
|
||||
class UIspaceweather(QObject):
|
||||
# Python > QML Signals
|
||||
show_ui = Signal()
|
||||
load_poseidon_report = Signal(dict)
|
||||
load_poseidon_forecast_report = Signal(dict)
|
||||
update_bottom_bar = Signal(str)
|
||||
|
||||
|
||||
def __init__(self, parent):
|
||||
super().__init__()
|
||||
|
||||
self._parent = parent
|
||||
|
||||
self._engine = QQmlApplicationEngine()
|
||||
self._engine.load('qrc:/ui/SpaceWeather.qml')
|
||||
self._window = self._engine.rootObjects()[0]
|
||||
|
||||
self._window_current = self._window.findChild(QObject, "spaceWeatherCurrentObj")
|
||||
self._window_forecast = self._window.findChild(QObject, "spaceWeatherForecastObj")
|
||||
|
||||
self._connect()
|
||||
|
||||
|
||||
def _connect(self):
|
||||
# QML > Python connections
|
||||
|
||||
# Python > QML connections
|
||||
self.show_ui.connect(self._window.show)
|
||||
self.update_bottom_bar.connect(self._window.updateBottomBar)
|
||||
self.load_poseidon_report.connect(self._window_current.loadReport)
|
||||
self.load_poseidon_forecast_report.connect(self._window_forecast.loadForecastReport)
|
||||
|
||||
|
||||
def load_spaceweather_ui(self):
|
||||
""" Before opening the windows, poseidon report (data.json) is read online
|
||||
"""
|
||||
self.download_poseidon_report()
|
||||
|
||||
|
||||
def download_poseidon_report(self):
|
||||
network_manager = self._parent.network_manager
|
||||
network_manager.show_popup = True
|
||||
poseidon_data = network_manager.fetch_remote_json(
|
||||
Constants.POSEIDON_REPORT
|
||||
)
|
||||
if poseidon_data:
|
||||
self.load_poseidon_report.emit(poseidon_data)
|
||||
self.load_poseidon_forecast_report.emit(poseidon_data)
|
||||
|
||||
self.update_bottom_bar.emit(
|
||||
'Loaded Poseidon report issued on {} at {} UTC'.format(
|
||||
poseidon_data['JSON_INFO']['utc_date'],
|
||||
poseidon_data['JSON_INFO']['utc_time']
|
||||
)
|
||||
)
|
||||
self.show_ui.emit()
|
||||
Reference in New Issue
Block a user