1067 lines
55 KiB
Python
Executable File
1067 lines
55 KiB
Python
Executable File
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()
|