from PyQt5 import QtWidgets, QtSql, QtCore, QtGui from ..database import Clip, Database, Type from pathlib import Path from zipfile import ZipFile, ZIP_DEFLATED import shutil import os from .. import utils as utils from .. import config, logger from .source import EditSource, ShowSource from .add import AddClip, AddSource from .connect import Connect from . import _Window import json from .QCustomObject import QTagEdit, QTableSortFilterProxyModel import re from . import resources # the database, global available _database = None class Window(_Window): """The main window class""" default_driver = "QSQLITE" default_name = str(Path().absolute().joinpath('athnos.sqlite3')) default_user = "root" default_password = "" default_ip = "127.0.0.1" default_port = 3306 def __init__(self, application: QtWidgets.QApplication, main_window: QtWidgets.QMainWindow = None, driver=default_driver, name=default_name, user=default_user, password=default_password, ip=default_ip, port=default_port): """ Setup the main window and database connection Args: main_window: main window driver: The sql driver. See `connect.Connect.drivers` for all supported drivers name: Name of the database, or path to the database if it's stored in a file user: SQL username password: SQL password ip: IP of the database port: Port of the database """ global _database self.application = application if not main_window: main_window = QtWidgets.QMainWindow() # setup all internal variables self._id = 0 self._clip: Clip = None self._row_number = -1 self._all_ids = [] self._columns = {} # passing all given arguments to class wide arguments self.db = QtSql.QSqlDatabase(driver) if name: self.db.setDatabaseName(name) if user: self.db.setUserName(user) if password: self.db.setPassword(password) if ip: self.db.setHostName(ip) if port: self.db.setPort(port) # tries to open a database connection if not self.db.open(): raise IOError(self.db.lastError().text()) # setup all tables _database = Database(self.db) self.database = _database # the default search filter rules (Tag, Name, Description, Type) self.filter_rules = [True, True, False, True] # all filter items self.filter_items_lower = ('tag', 'name', 'description', 'type') # the model where all the data will be stored in self.model: QTableSortFilterProxyModel = None super().__init__(main_window) # on startup self.load_model() self.on_filter_box_change(None) def setupUi(self, MainWindow): MainWindow.setObjectName("MainWindow") MainWindow.resize(923, 684) MainWindow.setTabShape(QtWidgets.QTabWidget.Rounded) self.centralwidget = QtWidgets.QWidget(MainWindow) self.centralwidget.setObjectName("centralwidget") self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget) self.verticalLayout.setObjectName("verticalLayout") self.main_splitter = QtWidgets.QSplitter(self.centralwidget) self.main_splitter.setEnabled(True) self.main_splitter.setOrientation(QtCore.Qt.Horizontal) self.main_splitter.setOpaqueResize(True) self.main_splitter.setObjectName("main_splitter") self.verticalLayoutWidget = QtWidgets.QWidget(self.main_splitter) self.verticalLayoutWidget.setObjectName("verticalLayoutWidget") self.search_vlayout = QtWidgets.QVBoxLayout(self.verticalLayoutWidget) self.search_vlayout.setContentsMargins(0, 0, 0, 0) self.search_vlayout.setObjectName("search_vlayout") self.clip_hlayout = QtWidgets.QGroupBox(self.verticalLayoutWidget) self.clip_hlayout.setStyleSheet("QGroupBox {border: 1px solid grey; margin-top: 0.5em; font: 12px consolas;} QGroupBox::title {top: -6px; left: 10px}") self.clip_hlayout.setObjectName("clip_hlayout") self.verticalLayout_6 = QtWidgets.QVBoxLayout(self.clip_hlayout) self.verticalLayout_6.setObjectName("verticalLayout_6") spacerItem = QtWidgets.QSpacerItem(20, 5, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) self.verticalLayout_6.addItem(spacerItem) self.search_box_hlayout = QtWidgets.QHBoxLayout() self.search_box_hlayout.setObjectName("search_box_hlayout") self.search_input = QtWidgets.QLineEdit(self.clip_hlayout) self.search_input.setPlaceholderText("") self.search_input.setClearButtonEnabled(True) self.search_input.setObjectName("search_input") self.search_box_hlayout.addWidget(self.search_input) self.filter_box = QtWidgets.QComboBox(self.clip_hlayout) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.filter_box.sizePolicy().hasHeightForWidth()) self.filter_box.setSizePolicy(sizePolicy) self.filter_box.setCurrentText("") self.filter_box.setObjectName("filter_box") self.search_box_hlayout.addWidget(self.filter_box) self.verticalLayout_6.addLayout(self.search_box_hlayout) self.clip_view = QtWidgets.QTableView(self.clip_hlayout) self.clip_view.setContextMenuPolicy(QtCore.Qt.DefaultContextMenu) self.clip_view.setFrameShape(QtWidgets.QFrame.StyledPanel) self.clip_view.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents) self.clip_view.setAutoScroll(True) self.clip_view.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers) self.clip_view.setDragEnabled(False) self.clip_view.setAlternatingRowColors(True) self.clip_view.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection) self.clip_view.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) self.clip_view.setSortingEnabled(True) self.clip_view.setObjectName("clip_view") self.clip_view.horizontalHeader().setSortIndicatorShown(True) self.verticalLayout_6.addWidget(self.clip_view) self.search_vlayout.addWidget(self.clip_hlayout) self.clip_source_splitter = QtWidgets.QSplitter(self.main_splitter) self.clip_source_splitter.setEnabled(True) self.clip_source_splitter.setOrientation(QtCore.Qt.Vertical) self.clip_source_splitter.setObjectName("clip_source_splitter") self.clip_group_box = QtWidgets.QGroupBox(self.clip_source_splitter) self.clip_group_box.setEnabled(True) self.clip_group_box.setStyleSheet("QGroupBox {border: 1px solid grey; margin-top: 0.5em; font: 12px consolas;} QGroupBox::title {top: -6px; left: 10px}") self.clip_group_box.setObjectName("clip_group_box") self.verticalLayout_3 = QtWidgets.QVBoxLayout(self.clip_group_box) self.verticalLayout_3.setObjectName("verticalLayout_3") spacerItem1 = QtWidgets.QSpacerItem(20, 5, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) self.verticalLayout_3.addItem(spacerItem1) self.file_url_hlayout = QtWidgets.QHBoxLayout() self.file_url_hlayout.setObjectName("file_url_hlayout") self.file_url_label = QtWidgets.QLabel(self.clip_group_box) self.file_url_label.setMinimumSize(QtCore.QSize(70, 0)) self.file_url_label.setObjectName("file_url_label") self.file_url_hlayout.addWidget(self.file_url_label) self.file_url_edit = QtWidgets.QLineEdit(self.clip_group_box) self.file_url_edit.setObjectName("file_url_edit") self.file_url_hlayout.addWidget(self.file_url_edit) self.verticalLayout_3.addLayout(self.file_url_hlayout) self.extended_view_clip_line = QtWidgets.QFrame(self.clip_group_box) self.extended_view_clip_line.setFrameShape(QtWidgets.QFrame.HLine) self.extended_view_clip_line.setFrameShadow(QtWidgets.QFrame.Sunken) self.extended_view_clip_line.setObjectName("extended_view_clip_line") self.verticalLayout_3.addWidget(self.extended_view_clip_line) self.name_hlayout = QtWidgets.QHBoxLayout() self.name_hlayout.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint) self.name_hlayout.setObjectName("name_hlayout") self.name_label = QtWidgets.QLabel(self.clip_group_box) self.name_label.setEnabled(True) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.name_label.sizePolicy().hasHeightForWidth()) self.name_label.setSizePolicy(sizePolicy) self.name_label.setMinimumSize(QtCore.QSize(70, 0)) self.name_label.setObjectName("name_label") self.name_hlayout.addWidget(self.name_label) self.name_edit = QtWidgets.QLineEdit(self.clip_group_box) self.name_edit.setEnabled(True) self.name_edit.setReadOnly(False) self.name_edit.setObjectName("name_edit") self.name_hlayout.addWidget(self.name_edit) self.verticalLayout_3.addLayout(self.name_hlayout) self.description_hlayout = QtWidgets.QHBoxLayout() self.description_hlayout.setObjectName("description_hlayout") self.description_label = QtWidgets.QLabel(self.clip_group_box) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.description_label.sizePolicy().hasHeightForWidth()) self.description_label.setSizePolicy(sizePolicy) self.description_label.setMinimumSize(QtCore.QSize(70, 0)) self.description_label.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop) self.description_label.setWordWrap(False) self.description_label.setOpenExternalLinks(False) self.description_label.setObjectName("description_label") self.description_hlayout.addWidget(self.description_label) self.description_edit = QtWidgets.QPlainTextEdit(self.clip_group_box) self.description_edit.setEnabled(True) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.description_edit.sizePolicy().hasHeightForWidth()) self.description_edit.setSizePolicy(sizePolicy) self.description_edit.setReadOnly(False) self.description_edit.setObjectName("description_edit") self.description_hlayout.addWidget(self.description_edit) self.verticalLayout_3.addLayout(self.description_hlayout) self.line = QtWidgets.QFrame(self.clip_group_box) self.line.setFrameShape(QtWidgets.QFrame.HLine) self.line.setFrameShadow(QtWidgets.QFrame.Sunken) self.line.setObjectName("line") self.verticalLayout_3.addWidget(self.line) self.type_hlayout = QtWidgets.QHBoxLayout() self.type_hlayout.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint) self.type_hlayout.setObjectName("type_hlayout") self.type_label = QtWidgets.QLabel(self.clip_group_box) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.type_label.sizePolicy().hasHeightForWidth()) self.type_label.setSizePolicy(sizePolicy) self.type_label.setMinimumSize(QtCore.QSize(70, 0)) self.type_label.setObjectName("type_label") self.type_hlayout.addWidget(self.type_label) self.type_box = QtWidgets.QComboBox(self.clip_group_box) self.type_box.setEnabled(True) self.type_box.setEditable(False) self.type_box.setDuplicatesEnabled(False) self.type_box.setProperty("placeholderText", "") self.type_box.setObjectName("type_box") self.type_box.addItem("") self.type_box.addItem("") self.type_box.addItem("") self.type_box.addItem("") self.type_hlayout.addWidget(self.type_box) self.verticalLayout_3.addLayout(self.type_hlayout) self.tags_hlayout = QtWidgets.QHBoxLayout() self.tags_hlayout.setObjectName("tags_hlayout") self.tags_label = QtWidgets.QLabel(self.clip_group_box) self.tags_label.setMinimumSize(QtCore.QSize(70, 0)) self.tags_label.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop) self.tags_label.setObjectName("tags_label") self.tags_hlayout.addWidget(self.tags_label) self.tags_edit = QtWidgets.QScrollArea(self.clip_group_box) self.tags_edit.setEnabled(True) self.tags_edit.setWidgetResizable(True) self.tags_edit.setObjectName("tags_edit") self.scrollAreaWidgetContents = QtWidgets.QWidget() self.scrollAreaWidgetContents.setGeometry(QtCore.QRect(0, 0, 483, 83)) self.scrollAreaWidgetContents.setObjectName("scrollAreaWidgetContents") self.tags_edit.setWidget(self.scrollAreaWidgetContents) self.tags_hlayout.addWidget(self.tags_edit) self.verticalLayout_3.addLayout(self.tags_hlayout) self.editing_hlayout = QtWidgets.QHBoxLayout() self.editing_hlayout.setObjectName("editing_hlayout") self.save_button = QtWidgets.QPushButton(self.clip_group_box) self.save_button.setEnabled(True) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.save_button.sizePolicy().hasHeightForWidth()) self.save_button.setSizePolicy(sizePolicy) self.save_button.setObjectName("save_button") self.editing_hlayout.addWidget(self.save_button) spacerItem2 = QtWidgets.QSpacerItem(15, 20, QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Minimum) self.editing_hlayout.addItem(spacerItem2) self.enable_editing_check_box = QtWidgets.QCheckBox(self.clip_group_box) self.enable_editing_check_box.setEnabled(True) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.enable_editing_check_box.sizePolicy().hasHeightForWidth()) self.enable_editing_check_box.setSizePolicy(sizePolicy) self.enable_editing_check_box.setObjectName("enable_editing_check_box") self.editing_hlayout.addWidget(self.enable_editing_check_box) spacerItem3 = QtWidgets.QSpacerItem(15, 20, QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Minimum) self.editing_hlayout.addItem(spacerItem3) self.delete_button = QtWidgets.QPushButton(self.clip_group_box) self.delete_button.setEnabled(True) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.delete_button.sizePolicy().hasHeightForWidth()) self.delete_button.setSizePolicy(sizePolicy) self.delete_button.setCheckable(False) self.delete_button.setChecked(False) self.delete_button.setObjectName("delete_button") self.editing_hlayout.addWidget(self.delete_button) self.verticalLayout_3.addLayout(self.editing_hlayout) self.source_group_box = QtWidgets.QGroupBox(self.clip_source_splitter) self.source_group_box.setStyleSheet("QGroupBox {border: 1px solid grey; margin-top: 0.5em; font: 12px consolas;} QGroupBox::title {top: -6px; left: 10px}") self.source_group_box.setObjectName("source_group_box") self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.source_group_box) self.verticalLayout_2.setObjectName("verticalLayout_2") spacerItem4 = QtWidgets.QSpacerItem(20, 5, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) self.verticalLayout_2.addItem(spacerItem4) self.from_hlayout = QtWidgets.QHBoxLayout() self.from_hlayout.setObjectName("from_hlayout") self.from_label = QtWidgets.QLabel(self.source_group_box) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.from_label.sizePolicy().hasHeightForWidth()) self.from_label.setSizePolicy(sizePolicy) self.from_label.setObjectName("from_label") self.from_hlayout.addWidget(self.from_label) self.source_name = QtWidgets.QLineEdit(self.source_group_box) self.source_name.setEnabled(True) self.source_name.setText("") self.source_name.setReadOnly(False) self.source_name.setObjectName("source_name") self.from_hlayout.addWidget(self.source_name) spacerItem5 = QtWidgets.QSpacerItem(7, 20, QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Minimum) self.from_hlayout.addItem(spacerItem5) self.season_label = QtWidgets.QLabel(self.source_group_box) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Maximum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.season_label.sizePolicy().hasHeightForWidth()) self.season_label.setSizePolicy(sizePolicy) self.season_label.setObjectName("season_label") self.from_hlayout.addWidget(self.season_label) self.source_season = QtWidgets.QLineEdit(self.source_group_box) self.source_season.setEnabled(True) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.source_season.sizePolicy().hasHeightForWidth()) self.source_season.setSizePolicy(sizePolicy) self.source_season.setMaximumSize(QtCore.QSize(50, 16777215)) self.source_season.setBaseSize(QtCore.QSize(0, 0)) self.source_season.setReadOnly(False) self.source_season.setObjectName("source_season") self.from_hlayout.addWidget(self.source_season) spacerItem6 = QtWidgets.QSpacerItem(7, 20, QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Minimum) self.from_hlayout.addItem(spacerItem6) self.episode_label = QtWidgets.QLabel(self.source_group_box) self.episode_label.setObjectName("episode_label") self.from_hlayout.addWidget(self.episode_label) self.source_episode = QtWidgets.QLineEdit(self.source_group_box) self.source_episode.setEnabled(True) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.source_episode.sizePolicy().hasHeightForWidth()) self.source_episode.setSizePolicy(sizePolicy) self.source_episode.setMaximumSize(QtCore.QSize(50, 16777215)) self.source_episode.setBaseSize(QtCore.QSize(0, 0)) self.source_episode.setReadOnly(False) self.source_episode.setObjectName("source_episode") self.from_hlayout.addWidget(self.source_episode) self.verticalLayout_2.addLayout(self.from_hlayout) self.source_descriptio_hlayout = QtWidgets.QHBoxLayout() self.source_descriptio_hlayout.setObjectName("source_descriptio_hlayout") self.source_description_label = QtWidgets.QLabel(self.source_group_box) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.source_description_label.sizePolicy().hasHeightForWidth()) self.source_description_label.setSizePolicy(sizePolicy) self.source_description_label.setMinimumSize(QtCore.QSize(70, 0)) self.source_description_label.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop) self.source_description_label.setObjectName("source_description_label") self.source_descriptio_hlayout.addWidget(self.source_description_label) self.source_description_edit = QtWidgets.QPlainTextEdit(self.source_group_box) self.source_description_edit.setEnabled(True) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.source_description_edit.sizePolicy().hasHeightForWidth()) self.source_description_edit.setSizePolicy(sizePolicy) self.source_description_edit.setObjectName("source_description_edit") self.source_descriptio_hlayout.addWidget(self.source_description_edit) self.verticalLayout_2.addLayout(self.source_descriptio_hlayout) self.source_type_hlayout = QtWidgets.QHBoxLayout() self.source_type_hlayout.setObjectName("source_type_hlayout") self.source_type_label = QtWidgets.QLabel(self.source_group_box) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.source_type_label.sizePolicy().hasHeightForWidth()) self.source_type_label.setSizePolicy(sizePolicy) self.source_type_label.setMinimumSize(QtCore.QSize(70, 0)) self.source_type_label.setObjectName("source_type_label") self.source_type_hlayout.addWidget(self.source_type_label) self.source_type_box = QtWidgets.QComboBox(self.source_group_box) self.source_type_box.setObjectName("source_type_box") self.source_type_box.addItem("") self.source_type_box.addItem("") self.source_type_box.addItem("") self.source_type_box.addItem("") self.source_type_hlayout.addWidget(self.source_type_box) self.verticalLayout_2.addLayout(self.source_type_hlayout) self.edit_change_hlayout = QtWidgets.QHBoxLayout() self.edit_change_hlayout.setObjectName("edit_change_hlayout") self.edit_source_button = QtWidgets.QPushButton(self.source_group_box) self.edit_source_button.setEnabled(True) self.edit_source_button.setObjectName("edit_source_button") self.edit_change_hlayout.addWidget(self.edit_source_button) self.change_source_button = QtWidgets.QPushButton(self.source_group_box) self.change_source_button.setEnabled(True) self.change_source_button.setObjectName("change_source_button") self.edit_change_hlayout.addWidget(self.change_source_button) self.verticalLayout_2.addLayout(self.edit_change_hlayout) self.verticalLayout.addWidget(self.main_splitter) MainWindow.setCentralWidget(self.centralwidget) self.menubar = QtWidgets.QMenuBar(MainWindow) self.menubar.setGeometry(QtCore.QRect(0, 0, 923, 30)) self.menubar.setObjectName("menubar") self.settings_menu = QtWidgets.QMenu(self.menubar) self.settings_menu.setObjectName("settings_menu") self.style_menu = QtWidgets.QMenu(self.settings_menu) self.style_menu.setObjectName("style_menu") self.update_menu = QtWidgets.QMenu(self.settings_menu) self.update_menu.setObjectName("update_menu") self.update_message_menu = QtWidgets.QMenu(self.update_menu) self.update_message_menu.setObjectName("update_message_menu") self.menuHelp = QtWidgets.QMenu(self.menubar) self.menuHelp.setObjectName("menuHelp") self.file_menu = QtWidgets.QMenu(self.menubar) self.file_menu.setObjectName("file_menu") MainWindow.setMenuBar(self.menubar) self.connect_to_database = QtWidgets.QAction(MainWindow) self.connect_to_database.setObjectName("connect_to_database") self.check_for_updates_on_startup = QtWidgets.QAction(MainWindow) self.check_for_updates_on_startup.setCheckable(True) self.check_for_updates_on_startup.setChecked(True) self.check_for_updates_on_startup.setObjectName("check_for_updates_on_startup") self.check_for_updates = QtWidgets.QAction(MainWindow) self.check_for_updates.setObjectName("check_for_updates") self.on_major_update = QtWidgets.QAction(MainWindow) self.on_major_update.setCheckable(True) self.on_major_update.setObjectName("on_major_update") self.on_minor_update = QtWidgets.QAction(MainWindow) self.on_minor_update.setCheckable(True) self.on_minor_update.setChecked(True) self.on_minor_update.setObjectName("on_minor_update") self.on_patch = QtWidgets.QAction(MainWindow) self.on_patch.setCheckable(True) self.on_patch.setObjectName("on_patch") self.about = QtWidgets.QAction(MainWindow) self.about.setObjectName("about") self.file_import = QtWidgets.QAction(MainWindow) self.file_import.setObjectName("file_import") self.file_export = QtWidgets.QAction(MainWindow) self.file_export.setObjectName("file_export") self.close_window = QtWidgets.QAction(MainWindow) self.close_window.setObjectName("close_window") self.add_clip = QtWidgets.QAction(MainWindow) self.add_clip.setObjectName("add_clip") self.add_source = QtWidgets.QAction(MainWindow) self.add_source.setObjectName("add_source") self.update_message_menu.addAction(self.on_major_update) self.update_message_menu.addAction(self.on_minor_update) self.update_message_menu.addAction(self.on_patch) self.update_menu.addAction(self.check_for_updates_on_startup) self.update_menu.addSeparator() self.update_menu.addAction(self.update_message_menu.menuAction()) self.update_menu.addAction(self.check_for_updates) self.settings_menu.addAction(self.connect_to_database) self.settings_menu.addSeparator() self.settings_menu.addAction(self.style_menu.menuAction()) self.settings_menu.addAction(self.update_menu.menuAction()) self.menuHelp.addAction(self.about) self.file_menu.addAction(self.file_import) self.file_menu.addAction(self.file_export) self.file_menu.addSeparator() self.file_menu.addAction(self.add_clip) self.file_menu.addAction(self.add_source) self.file_menu.addSeparator() self.file_menu.addAction(self.close_window) self.menubar.addAction(self.file_menu.menuAction()) self.menubar.addAction(self.settings_menu.menuAction()) self.menubar.addAction(self.menuHelp.menuAction()) self.retranslateUi(MainWindow) self.type_box.setCurrentIndex(3) self.source_type_box.setCurrentIndex(3) QtCore.QMetaObject.connectSlotsByName(MainWindow) def retranslateUi(self, MainWindow): _translate = QtCore.QCoreApplication.translate MainWindow.setWindowTitle(_translate("MainWindow", "Athnos")) self.clip_hlayout.setTitle(_translate("MainWindow", "Search")) self.clip_group_box.setTitle(_translate("MainWindow", "Clip")) self.file_url_label.setText(_translate("MainWindow", "File / URL")) self.name_label.setText(_translate("MainWindow", "Name")) self.description_label.setText(_translate("MainWindow", "Description")) self.type_label.setText(_translate("MainWindow", "Type")) self.type_box.setCurrentText(_translate("MainWindow", "Other")) self.type_box.setItemText(0, _translate("MainWindow", "Audio")) self.type_box.setItemText(1, _translate("MainWindow", "Image")) self.type_box.setItemText(2, _translate("MainWindow", "Video")) self.type_box.setItemText(3, _translate("MainWindow", "Other")) self.tags_label.setText(_translate("MainWindow", "Tags")) self.save_button.setText(_translate("MainWindow", "Save")) self.enable_editing_check_box.setText(_translate("MainWindow", "Enable editing")) self.delete_button.setText(_translate("MainWindow", "Delete")) self.source_group_box.setTitle(_translate("MainWindow", "Source")) self.from_label.setText(_translate("MainWindow", "From")) self.season_label.setText(_translate("MainWindow", "Season")) self.episode_label.setText(_translate("MainWindow", "Episode")) self.source_description_label.setText(_translate("MainWindow", "Description")) self.source_type_label.setText(_translate("MainWindow", "Type")) self.source_type_box.setItemText(0, _translate("MainWindow", "Audio")) self.source_type_box.setItemText(1, _translate("MainWindow", "Movie")) self.source_type_box.setItemText(2, _translate("MainWindow", "Series")) self.source_type_box.setItemText(3, _translate("MainWindow", "Other")) self.edit_source_button.setText(_translate("MainWindow", "Edit source")) self.change_source_button.setText(_translate("MainWindow", "Change source")) self.settings_menu.setTitle(_translate("MainWindow", "Settings")) self.style_menu.setTitle(_translate("MainWindow", "Style")) self.update_menu.setTitle(_translate("MainWindow", "Updates")) self.update_message_menu.setTitle(_translate("MainWindow", "Update message")) self.menuHelp.setTitle(_translate("MainWindow", "Help")) self.file_menu.setTitle(_translate("MainWindow", "File")) self.connect_to_database.setText(_translate("MainWindow", "Connect to database...")) self.check_for_updates_on_startup.setText(_translate("MainWindow", "Check for updates on startup")) self.check_for_updates.setText(_translate("MainWindow", "Check for updates...")) self.on_major_update.setText(_translate("MainWindow", "On major update")) self.on_minor_update.setText(_translate("MainWindow", "On minor update")) self.on_patch.setText(_translate("MainWindow", "On patch")) self.about.setText(_translate("MainWindow", "About")) self.file_import.setText(_translate("MainWindow", "Import...")) self.file_export.setText(_translate("MainWindow", "Export...")) self.close_window.setText(_translate("MainWindow", "Close")) self.add_clip.setText(_translate("MainWindow", "Add clip...")) self.add_source.setText(_translate("MainWindow", "Add source...")) def after_setup_ui(self): # --- add menu --- # » add clip self.add_clip.triggered.connect(lambda: AddClip(QtWidgets.QDialog(self.window, QtCore.Qt.WindowSystemMenuHint)).show()) # » add source self.add_source.triggered.connect(lambda: AddSource(QtWidgets.QDialog(self.window, QtCore.Qt.WindowSystemMenuHint)).show()) # » import from .importexport import Import self.file_import.triggered.connect(lambda: Import(self)) # » close self.close_window.triggered.connect(self.close) # --- settings menu --- # » connect to database self.connect_to_database.triggered.connect(lambda: self.set_database(Connect(QtWidgets.QDialog(self.window, QtCore.Qt.WindowSystemMenuHint)).show())) # » set style for i, style in enumerate(QtWidgets.QStyleFactory.keys()): style_item = QtWidgets.QAction(style, self.window) style_item.setCheckable(True) if style.lower() == QtWidgets.QApplication.style().objectName().lower(): style_item.setChecked(True) style_item.triggered.connect(lambda checked, style=style_item: self.on_new_style(checked, style)) self.style_menu.addAction(style_item) # --- search input --- # » connects `self.on_search_input_change` if the text changes self.search_input.textChanged.connect(self.on_search_input_change) # --- filter box --- # » create the filter box model self.filter_box_model = QtGui.QStandardItemModel() # » loop through the `filter_box_items` and add them to the model for i, item_name in enumerate(self.filter_items_lower): item_name = item_name[0].upper() + item_name[1:] item = QtGui.QStandardItem(item_name) # »» make the item checkable item.setFlags(QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsEnabled) item.setData(QtCore.Qt.Unchecked, QtCore.Qt.CheckStateRole) if self.filter_rules[i]: item.setCheckState(QtCore.Qt.Checked) else: item.setCheckState(QtCore.Qt.Unchecked) # »» add the item to the model self.filter_box_model.setItem(i, 0, item) # » apply the filter box model to the box and do some style stuff self.filter_box.setModel(self.filter_box_model) self.filter_box.setStyleSheet('QAbstractItemView {min-width: 120px;}') # » everytime a item is selected or unselected `self.on_filter_box_change` gets called self.filter_box_model.dataChanged.connect(self.on_filter_box_change) # » set the line edit (text) of the filter box. # » normally it's the current selected item text, but i want it to show 'Filter...' filter_box_edit = QtWidgets.QLineEdit('Filter...') filter_box_edit.setReadOnly(True) # » if the 'Apply changes' button is pressed, the filter box text will be still 'Filter...' filter_box_edit.textChanged.connect(lambda a0: filter_box_edit.setText('Filter...')) # » if the text edit and not the dropdown arrow is pressed, the dropdown menu will get show anyway filter_box_edit.mouseReleaseEvent = lambda a0: self.filter_box.showPopup() self.filter_box.setLineEdit(filter_box_edit) # --- item / clip view --- self.clip_view.clicked.connect(self.on_item_clicked) # --- clip / source --- action = QtWidgets.QAction() action.eventFilter = self.open_file_or_link_event_filter action.setIcon(QtGui.QIcon(':/file_url/internet')) self.file_url_edit.addAction(action, QtWidgets.QLineEdit.TrailingPosition) self.enable_editing_check_box.clicked.connect(lambda value: self.set_extended_clip_view(value)) self.save_button.clicked.connect(self.on_save_edited_clip) self.type_box.setCurrentIndex(-1) # deletes the old tags edit scroll area self.tags_edit.deleteLater() # overwrites `self.tags_edit` and use a `QCustomObject.QTagEdit` instead self.tags_edit = QTagEdit() # configures the tage edit self.tags_edit.setEnabled(False) self.tags_edit.enableTagSuggestions(True) self.tags_hlayout.addWidget(self.tags_edit) self.edit_source_button.clicked.connect(self.on_edit_source) self.change_source_button.clicked.connect(self.on_change_source) # search input self.search_input.textChanged.connect(self.on_search_input_change) # before startup self.set_extended_clip_view() self.set_extended_source_view() self.clip_source_splitter.setEnabled(False) def load_model(self) -> None: """Creates / loads the `self.model` for the clip view""" # all currently available clips, tags, clip names, clip descriptions and clip types self.all_clips = self.database.Clip.get_all() self.all_tag_names = [tag.name for tag in self.database.Tag.get_all()] self.all_clip_names = [] self.all_description_texts = [] for clip in self.all_clips: self.all_clip_names.append(clip.name) self.all_description_texts.append(clip.description) self.all_type_names = [type.name for type in Type.Clip] self.all_names = [self.all_tag_names, self.all_clip_names, self.all_description_texts, self.all_type_names] self.tags_edit.setTagSuggestions(self.all_tag_names) # creates the sql model and configures it sql_model = QtSql.QSqlTableModel() sql_model.setQuery(self.all_clips.query) # sets the models query sql_model.setEditStrategy(QtSql.QSqlTableModel.OnManualSubmit) # creates a `QCustomObject.QTableSortFilterProxyModel` out of the `sql_model` to filter via the `self.line_input` model = QTableSortFilterProxyModel(True) model.setSourceModel(sql_model) # sets the clip view model and configures the headers self.clip_view.setModel(model) self.clip_view.verticalHeader().setHidden(True) self.clip_view.horizontalHeader().resizeSections(QtWidgets.QHeaderView.Stretch) self.clip_view.horizontalHeader().setStretchLastSection(True) type_column = -1 # this sets the header (first letter uppercase, remove id, etc.) for i in range(model.columnCount()): header = str(model.headerData(i, QtCore.Qt.Horizontal)).lower() self._columns[header] = i # hides the column if its in source_id, id or path if header in ['source_id', 'id', 'path']: if header == 'id': for row in range(model.rowCount()): self._all_ids.append(int(model.index(row, i).data())) self.clip_view.hideColumn(i) continue elif header == 'type': type_column = i # makes the first letter of the header text uppercase model.setHeaderData(i, QtCore.Qt.Horizontal, header[0].upper() + header[1:]) # creates a new column for the tags tags_column = model.columnCount() model.insertColumn(tags_column) model.setHeaderData(tags_column, QtCore.Qt.Horizontal, 'tags') # hides the column self.clip_view.hideColumn(tags_column) for row in range(model.rowCount()): # convert the type numbers (type is stored as a number in the sql table) to text type_index = model.index(row, type_column) type_name = Type.Clip(model.data(type_index)).name.lower() model.setData(type_index, type_name[0].upper() + type_name[1:]) # adds the corresponding tags to the index tags_index = model.index(row, tags_column) model.setData(tags_index, ', '.join(tag.name for tag in self.all_clips[row].get_tags())) # makes the model available for the entire class self.model = model # self.clip_view.setShowGrid(False) def set_database(self, database: Database) -> None: """Sets the local and global database""" if database is not None: # global database _database = database # local database self.database = database self.load_model() # --- add menu --- def on_zip_add(self) -> None: file = QtWidgets.QFileDialog.getOpenFileName(self.window, directory=str(Path.home()), filter='zip packed clips(*.zip);;All files(*.*)')[0] if file: with ZipFile(file, 'r', compression=ZIP_DEFLATED) as archive: found = False for member in archive.namelist(): if member.startswith('clips/'): found = True break if found: free_size = shutil.disk_usage(os.path.abspath(os.sep))[2] uncompressed_size = sum([info.file_size for info in archive.filelist]) if uncompressed_size > free_size: zip_too_big_error = QtWidgets.QErrorMessage(self.window) zip_too_big_error.showMessage( f'Cannot add clips from \'{file}\': The uncompressed size ({uncompressed_size * 6000000}MB) ' f'is bigger than the available space on the disk ({free_size * 6000000}MB)') else: clips_path = utils.AthnosPath.athnos_path() for file in archive.namelist(): if file.startswith('clips/'): final_file = os.path.join(clips_path, file) if os.path.exists(final_file): logger.warning(f'Skipping file {final_file}: The file already exists') else: archive.extract(file, clips_path) def on_json_add(self) -> None: file = QtWidgets.QFileDialog.getOpenFileName(self.window, directory=str(Path.home()), filter='json(*.json);;All files(*.*)')[0] if file: try: clips = json.load(open(file, 'r')) except json.decoder.JSONDecodeError: logger.warning(f'{file} is not a json file or has a bad syntax') json_error = QtWidgets.QErrorMessage(self.window) json_error.showMessage(f'{file} is not a json file or is corrupt / has a bad syntax') return try: to_add = [] duplicated_files = 0 files_not_found = 0 source_not_found = 0 root_dir = clips['root_dir'] for clip_info in clips['clips']: path = clip_info['path'] source = clip_info['source'] type_ = clip_info['type'] name = clip_info['name'] description = clip_info['description'] if not os.path.isfile(path): files_not_found += 1 # logger.warning(f'Skipping file {file}: The file does not exist') elif utils.AthnosPath.clips_path().joinpath(utils.remove_prefix(path, root_dir)).is_file(): duplicated_files += 1 # logger.warning(f'Skipping file {file}: The file already exist in the destination directory') else: if isinstance(source, int): if not self.database.Source.has_id(source): clip_info['source'] = 0 source_not_found += 1 elif ''.join(str(value) for value in source.values()) == '': clip_info['source'] = 0 else: source_id = self.database.Source.get_by(path=source['path'], type=source['type'], name=source['name'], description=source['description'], season=source['season'], episode=source['episode']) if source_id: clip_info['source'] = source_id to_add.append(clip_info) except TypeError as e: error_message = QtWidgets.QMessageBox(self.window) error_message.warning(self.window, 'Syntax error', f'Syntax error in {file}\n{str(e)}') # logger.warning(f'Syntax error in {file}') return warning_string = '' if duplicated_files: warning_string += f'Duplicated files: {duplicated_files}\n' if files_not_found: warning_string += f'Files not found: {files_not_found}\n' if source_not_found: warning_string += f'Source not found: {source_not_found}\n' if warning_string: warning_string += '\nDo you want to proceed?' warning_message = QtWidgets.QMessageBox() reply = warning_message.warning(self.window, f'{duplicated_files + files_not_found + source_not_found} warnings', warning_string, buttons=QtWidgets.QMessageBox.No | QtWidgets.QMessageBox.Yes, defaultButton=QtWidgets.QMessageBox.Yes) if reply != QtWidgets.QMessageBox.Yes: return # iterate two times through the (nearly) same list is pretty inefficient,but for the warning message above it's necessary for clip_info in to_add: if isinstance(clip_info['source'], int): source_id = clip_info['source'] else: source = clip_info['source'] source_id = self.database.Source.add_source(source['path'], source['type'], source['name'], source['description'], source['season'], source['episode']) self.database.Clip.add_clip(clip_info['path'], source_id, clip_info['name'], clip_info['type'], clip_info['description']) logger.debug(f'Added {len(to_add)} clips from json to database') # --- settings menu --- def on_new_style(self, checked: bool, action: QtWidgets.QAction) -> None: """Gets called if a new style is chosen""" if checked: for menu_action in self.style_menu.actions(): if menu_action.isChecked() and menu_action != action: menu_action.setChecked(False) break # changes the style in the config config['style'] = action.text() # changes the style on the ui self.application.setStyle(action.text()) else: action.setChecked(True) # --- table view specific --- def on_search_input_change(self, text: str) -> None: """This gets called if the search input text has changed""" if not self.model: self.load_model() for i in range(self.model.columnCount()): # get the column header header = str(self.model.headerData(i, QtCore.Qt.Horizontal)).lower() # checks if the header is in the filter items (which are shown in the filter box) # and if so, it will check if it's marked / checked if header in self.filter_items_lower and self.filter_rules[self.filter_items_lower.index(header)]: # »» sets a new regex filter self.model.setRegExpFilter(self._columns[header], QtCore.QRegExp(f'^{text}', QtCore.Qt.CaseInsensitive)) # refresh the model self.model.invalidate() def on_filter_box_change(self, index: QtCore.QModelIndex) -> None: # the texts for the search input completer completer_texts = [] placeholder_text = [] # applies the check states of every item in the filter box to `self.filter_rules` for i in range(self.filter_box_model.rowCount()): item = self.filter_box_model.item(i, 0) checked = item.checkState() == QtCore.Qt.Checked self.filter_rules[i] = checked if item.flags() == QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsEnabled and checked: placeholder_text.append(item.data(QtCore.Qt.DisplayRole)) completer_texts.extend(self.all_names[i]) if completer_texts: # sets the input edit completer search_input_completer = QtWidgets.QCompleter(completer_texts) search_input_completer.setCaseSensitivity(QtCore.Qt.CaseInsensitive) self.search_input.setCompleter(search_input_completer) else: self.search_input.setCompleter(None) # sets the placeholder text, matching to the filter items if placeholder_text: self.search_input.setPlaceholderText(', '.join(placeholder_text) + ', ...') # re-sets the clip view to apply the filter box changes self.on_search_input_change(self.search_input.text()) def on_item_clicked(self, index: QtCore.QModelIndex) -> None: """ Gets called if a clip / item is from the clip view was clicked Args: index: The clicked item """ if not self.clip_source_splitter.isEnabled(): self.clip_source_splitter.setEnabled(True) self.set_extended_clip_view() self.set_extended_source_view() self._row_number = index.row() self.model = index.model() # gets the clip (and sets it for global usage), source and tags of the clicked item self._clip = self.database.Clip.get(self._all_ids[self._row_number]) source = self._clip.get_source() tags = self._clip.get_tags() # sets the tag edit stuff self.file_url_edit.setText(self._clip.path) self.name_edit.setText(self._clip.name) self.description_edit.setPlainText(self._clip.description) if self._clip.type == Type.Clip.AUDIO: self.type_box.setCurrentIndex(0) elif self._clip.type == Type.Clip.IMAGE: self.type_box.setCurrentIndex(1) elif self._clip.type == Type.Clip.VIDEO: self.type_box.setCurrentIndex(2) else: self.type_box.setCurrentIndex(3) self.tags_edit.setTags([tag.name for tag in tags]) # source edit self.source_name.setText(source.name) self.source_season.setText(source.season) self.source_episode.setText(source.episode) self.source_description_edit.setPlainText(source.description) # --- clip / source view --- def open_file_or_link_event_filter(self, a0: QtWidgets.QWidget, a1: QtCore.QEvent): if a1.type() == QtCore.QEvent.MouseButtonRelease and a1.button() == QtCore.Qt.LeftButton: regex = re.compile(r'^www\.|' # www. r'(?:http|ftp)s?://' # http:// or https:// r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' # domain... r'localhost|' # localhost... r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # ...or ip r'(?::\d+)?' # optional port r'(?:/?|[/?]\S+)$', re.IGNORECASE) if re.match(regex, self.file_url_edit.text()): url = QtCore.QUrl(self.file_url_edit.text()) else: url = QtCore.QUrl.fromLocalFile(self.file_url_edit.text()) QtGui.QDesktopServices.openUrl(url) def set_extended_clip_view(self, enable=False, clear=False) -> None: """ Sets the extended clip view to default Args: enable: If True, it enables the extended clip view, False if not clear: If True, all current texts, clips etc. in the edits will be cleared """ # clears everything if clear: self.file_url_edit.clear() self.name_edit.clear() self.description_edit.clear() self.tags_edit.clear() self.type_box.setCurrentIndex(3) # enable / disable everything self.file_url_edit.setReadOnly(not enable) self.name_edit.setReadOnly(not enable) self.description_edit.setReadOnly(not enable) self.type_box.setEnabled(enable) self.tags_edit.setEnabled(enable) self.tags_edit.setEnabled(enable) self.save_button.setEnabled(enable) self.delete_button.setEnabled(enable) self.enable_editing_check_box.setChecked(enable) def on_save_edited_clip(self) -> None: """Saves an edited clip""" # the clip type type = Type.Clip.from_name(self.type_box.currentText()) # the tags (names) from the not edited clip old_tags = self._clip.get_tags() old_tag_names = [tag.name for tag in old_tags] new_tags = [] for tag in self.tags_edit.tags(): # if a tag doesn't exist, it will be new created if tag not in self.all_tag_names: # creates the new tag and connect it via an item with the clip self.database.Item.add_item(self._clip.id, self.database.Tag.add_tag(tag).id) new_tags.append(tag) elif tag not in old_tag_names: # adds a new clip-tag connection via an item self.database.Item.add_item(self._clip.id, self.database.Tag.get_by(name=tag).id) else: # removes the clip from the old (not edited) clip tags del old_tags[old_tag_names.index(tag)] if new_tags: self.all_tag_names.extend(new_tags) self.tags_edit.setTagSuggestions(self.all_tag_names) for tag in old_tags: self._clip.remove_tag(tag.id) # edits the clip itself self.database.Clip.edit(self._clip.id, name=self.name_edit.text(), description=self.description_edit.toPlainText(), type=type.value) logger.debug(f'Edited clip - file / url: `{self.file_url_edit.text()}`, type: `{type.name}`, name: `{self.name_edit.text()}`, ' f'description: `{self.description_edit.toPlainText()}`, tags: `{", ".join(self.tags_edit.tags())}`') # update the clip view model with the new clip data self.model.setData(self.model.index(self._row_number, self._columns['name']), self.name_edit.text()) self.model.setData(self.model.index(self._row_number, self._columns['description']), self.description_edit.toPlainText()) self.model.submit() def set_extended_source_view(self, enable=False, clear=False) -> None: """ Sets the extended source view to default Args: enable: If True, it enables the extended source view, False if not clear: If True, all current texts etc. in the edits will be cleared """ # clears everything if clear: self.source_name.clear() self.source_season.clear() self.source_episode.clear() self.source_type_box.setCurrentIndex(3) # enable / disable everything self.source_name.setReadOnly(not enable) self.source_season.setReadOnly(not enable) self.source_episode.setReadOnly(not enable) self.source_description_edit.setReadOnly(not enable) self.source_type_box.setEnabled(enable) def on_edit_source(self) -> None: """Gets called if the 'Edit source' button is clicked""" # opens the edit source dialog EditSource(QtWidgets.QDialog(self.window, QtCore.Qt.WindowSystemMenuHint)).show(self._clip.source_id) # re-sets the source data for the item self.on_item_clicked(self.clip_view.selectedIndexes()[0]) def on_change_source(self) -> None: """Gets called if the 'Change source' button is clicked""" # opens the change source dialog source_id = ShowSource(QtWidgets.QDialog(self.window, QtCore.Qt.WindowSystemMenuHint)).select() if source_id is not None: # changes the old to the new source id self.database.Clip.edit(self._clip.id, source_id=source_id) # re-sets the source data for the item self.on_item_clicked(self.clip_view.selectedIndexes()[0]) def close(self) -> None: # closes the database and the window self.database.close() super().close()