Exemplo n.º 1
0
class EncounterPreviewTable(QTableView):
    def __init__(self, parent):
        super(EncounterPreviewTable, self).__init__(parent)
        self.model = EncounterInfoModel()
        self.sortModel = QSortFilterProxyModel()
        self.model.dataChanged.connect(self.compact)
        self.horizontalHeader().sectionResized.connect(self.calcHeader)
        self.horizontalHeader().sectionClicked.connect(self.model.setSortColumn)

    def setup(self, path):
        self.model.setup(path)
        self.sortModel.setSourceModel(self.model)
        self.setModel(self.sortModel)

        sortPoint = self.model.headers.index(self.model.TXT_DPS)
        order = Qt.DescendingOrder
        self.sortModel.sort(sortPoint, order)
        self.horizontalHeader().setSortIndicator(sortPoint,order)

    def compact(self):
        self.resizeColumnsToContents()
        self.resizeRowsToContents()

    def calcHeader(self):
        v: QHeaderView  = self.horizontalHeader()
        totalheader = 0
        headerEnds = []
        for c in range (0, v.count()):
            totalheader += v.sectionSize(c)
            headerEnds.append(totalheader)

        self.model.totalWidth = totalheader
        self.model.headerWidths = headerEnds
Exemplo n.º 2
0
class SortFilterTableView(QTableView):
    def __init__(self, parent=None):
        super().__init__(parent)
        self._proxy_model = QSortFilterProxyModel(self)
        self._proxy_model.setDynamicSortFilter(True)
        super().setModel(self._proxy_model)
        header = FilterHeader(self)
        header.filter_changed.connect(self.set_filter)
        self.setHorizontalHeader(header)
        self.setSortingEnabled(True)
        self.setSelectionMode(QAbstractItemView.ContiguousSelection)
        self.import_export_manager = ImportExportManager(self)
        self.import_export_manager.connect_custom_context_menu()

    def set_filter(self, section, filter_text):
        log.debug("set_filter(section: %s, filter: %r)", section, filter_text)
        self._proxy_model.setFilterWildcard(filter_text)
        self._proxy_model.setFilterKeyColumn(section)

    def setModel(self, model):
        self.horizontalHeader().set_filter_boxes(model.columnCount())
        self._proxy_model.setSourceModel(model)
        self._proxy_model.sort(0, Qt.AscendingOrder)
        super().setModel(self._proxy_model)
        font = model.data(0, Qt.FontRole)
        if font is None:
            font = self.font()
        metrics = QFontMetrics(font)
        self.verticalHeader().setDefaultSectionSize(metrics.lineSpacing() *
                                                    1.5)
        self.horizontalHeader().setDefaultSectionSize(metrics.maxWidth() * 5)
Exemplo n.º 3
0
class SqlRelationalTableModel(QtSql.QSqlRelationalTableModel):
    def __init__(self, model, parent, db):
        super().__init__(parent, db)

        self.model = model
        self._proxyModel = QSortFilterProxyModel(self)
        self._proxyModel.setSortLocaleAware(True)
        self._proxyModel.setSourceModel(self)

    def relationModel(self, _column):
        return self.model

    def data(self, index, role=Qt.DisplayRole):
        if role == Qt.DecorationRole:
            if index.row() < 0:
                return None
            iconIndex = self.index(index.row(), self.fieldIndex('icon'))
            if not self.data(iconIndex) or self.data(iconIndex).isNull():
                return None
            icon = QPixmap()
            icon.loadFromData(self.data(iconIndex))
            return icon

        return super().data(index, role)

    def proxyModel(self):
        return self._proxyModel

    def sort(self, sort=True):
        if sort:
            self._proxyModel.sort(self.fieldIndex('value'))
        else:
            self._proxyModel.sort(-1)
Exemplo n.º 4
0
class SqlRelationalTableModel(QtSql.QSqlRelationalTableModel):
    def __init__(self, model, parent, db):
        super().__init__(parent, db)

        self.model = model
        self._proxyModel = QSortFilterProxyModel(self)
        self._proxyModel.setSortLocaleAware(True)
        self._proxyModel.setSourceModel(self)

    def relationModel(self, _column):
        return self.model

    def data(self, index, role=Qt.DisplayRole):
        if role == Qt.DecorationRole:
            if index.row() < 0:
                return None
            iconIndex = self.index(index.row(), self.fieldIndex('icon'))
            if not self.data(iconIndex) or self.data(iconIndex).isNull():
                return None
            icon = QPixmap()
            icon.loadFromData(self.data(iconIndex))
            return icon

        return super().data(index, role)

    def proxyModel(self):
        return self._proxyModel

    def sort(self, sort=True):
        if sort:
            self._proxyModel.sort(self.fieldIndex('value'))
        else:
            self._proxyModel.sort(-1)
Exemplo n.º 5
0
class MainInputWidget(QWidget):
    def __init__(self, parent, base_model):
        super(MainInputWidget, self).__init__(parent)
        self.resize(1024, 768)
        self.base_model = base_model
        self.fr_model = QSortFilterProxyModel()
        self.fr_model.setSourceModel(FirstRoundModel(self.base_model))
        self.playoff_model = PlayoffInputModel(self.base_model)
        self.final_round_model = QSortFilterProxyModel()
        self.final_round_model.setSourceModel(FinalRoundModel(self.base_model))
        layout = QHBoxLayout(self)
        self.setLayout(layout)
        layout.addWidget(FirstRoundInputWidget(self, self.fr_model))
        layout.addWidget(PlayoffInputWidget(self, self.playoff_model))
        layout.addWidget(FinalRoundInputWidget(self, self.final_round_model))
        self.setWindowTitle(config.TITLE)
        self.game_state = 0

    def finish_first_round(self):
        self.playoff_model.initiate_pairing()

    def finish_playoff(self):
        self.final_round_model.sort(1, QtCore.Qt.DescendingOrder)

    def finish(self):
        for t, r in self.base_model.get_results():
            r.save()
Exemplo n.º 6
0
 def filterListAlphaUpdate(self):
     '''
     Updates filterListAlpha for sorting values and to send rows to videoTable
     '''
     pmodel = QSortFilterProxyModel()
     pmodel.setSourceModel(self.filterListAlphaModel)
     pmodel.sort(3, Qt.AscendingOrder)
     self.filterListAlpha.setModel(pmodel)
     self.filterListAlpha.setModelColumn(3)
     self.filterListAlpha.show()
Exemplo n.º 7
0
def main():
    """The main entry point, compatible with setuptools entry points."""
    app = QApplication(sys.argv)

    with open(os.path.join(os.path.dirname(__file__), 'testgui.ui')) as fobj:
        window = loadUi(fobj)

    model = GameListModel(get_games())
    model_sorted = QSortFilterProxyModel()
    model_sorted.setDynamicSortFilter(True)
    model_sorted.setSortCaseSensitivity(Qt.CaseInsensitive)
    model_sorted.setSourceModel(model)
    model_sorted.sort(0, Qt.AscendingOrder)

    window.view_games.setModel(model_sorted)
    window.show()

    sys.exit(app.exec_())
Exemplo n.º 8
0
class Example(QWidget):
    def __init__(self):
        super().__init__()

        self.setGeometry(300, 300, 350, 200)
        self.setWindowTitle("Sorting")

        self.initData()
        self.initUI()

    def initData(self):

        names = ["Jack", "Tom", "Lucy", "Bill", "Jane"]
        self.model = QStringListModel(names)

        self.model = QStringListModel(names)
        self.filterModel = QSortFilterProxyModel(self)
        self.filterModel.setSourceModel(self.model)

    def initUI(self):

        sortB = QPushButton("Sort", self)
        sortB.move(250, 20)

        self.sortType = QCheckBox("Ascending", self)
        self.sortType.move(250, 60)

        sortB.clicked.connect(self.sortItems)

        self.lv = QListView(self)
        self.lv.setModel(self.filterModel)
        self.lv.setGeometry(20, 20, 200, 150)

    def sortItems(self):

        checked = self.sortType.isChecked()

        if checked:
            self.filterModel.sort(FIRST_COLUMN, Qt.AscendingOrder)

        else:
            self.filterModel.sort(FIRST_COLUMN, Qt.DescendingOrder)
Exemplo n.º 9
0
class SelectionDialog(CustomStandardDialog, Ui_SelectionDialog):
    def __init__(self,
                 source_table,
                 selection_mode=QAbstractItemView.ExtendedSelection,
                 selection_behavior=QAbstractItemView.SelectItems):
        CustomStandardDialog.__init__(self)
        self.setupUi(self)
        self.source_table = source_table
        self.resize(self.dataView.horizontalHeader().width(), self.height())
        self.proxyModel = QSortFilterProxyModel(self)
        self.proxyModel.setSourceModel(source_table)
        self.proxyModel.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive)
        self.proxyModel.setFilterKeyColumn(-1)
        self.dataView.setModel(self.proxyModel)
        self.dataView.setSelectionBehavior(selection_behavior)
        self.dataView.setSelectionMode(selection_mode)
        self.proxyModel.sort(0)

        self.dataView.selectionModel().selectionChanged.connect(
            self.activate_button)
        self.dataView.doubleClicked.connect(self.accept)

        self.restore_dialog_geometry()

    def selected_items(self):
        return [
            self.source_table.item_from_row(x)
            for x in self.dataView.get_selected_rows()
        ]

    @QtCore.pyqtSlot()
    def activate_button(self):
        self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(
            len(self.dataView.selectedIndexes()))

    @QtCore.pyqtSlot(str)
    def update_filter(self, new):
        self.proxyModel.setFilterFixedString(new)
Exemplo n.º 10
0
class DataFrameDialog(QDialog, Ui_DataFrameDialog):
    def __init__(self, dataframe):
        super(DataFrameDialog, self).__init__()
        self.setupUi(self)

        def dataitem(value):
            item = QStandardItem()
            try:
                item.setData(float(value), 2)
            except ValueError:
                item.setText(str(value))
            return item

        self.datatable = QStandardItemModel(self)
        self.datatable.setColumnCount(dataframe.shape[1])
        for row in dataframe.itertuples(index=True):
            self.datatable.appendRow([dataitem(x) for x in row])
        self.datatable.setHorizontalHeaderLabels(
            ["Index"] + [x.title() for x in dataframe.axes[1]])
        self.proxymodel = QSortFilterProxyModel(self)
        self.proxymodel.setSourceModel(self.datatable)
        self.tableView.setModel(self.proxymodel)
        self.proxymodel.setDynamicSortFilter(True)
        self.proxymodel.sort(0)
Exemplo n.º 11
0
class IPTagsSelectDialog(QDialog):
    def __init__(self, parent=None):
        super().__init__(parent)

        self.app = QApplication.instance()

        self.destTags = []

        self.allTagsModel = UneditableStringListModel(self)
        self.destTagsModel = UneditableStringListModel(self)
        self.allTagsProxyModel = QSortFilterProxyModel(self)
        self.allTagsProxyModel.setSourceModel(self.allTagsModel)

        self.ui = ui_iptagsmanager.Ui_IPTagsDialog()
        self.ui.setupUi(self)

        self.ui.destTagsView.setModel(self.destTagsModel)
        self.ui.destTagsView.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.ui.destTagsView.doubleClicked.connect(self.onTagDoubleClicked)

        self.ui.addTagButton.clicked.connect(lambda: ensure(self.addTag()))
        self.ui.lineEditTag.textChanged.connect(self.onTagEditChanged)
        self.ui.lineEditTag.setValidator(
            QRegExpValidator(QRegExp(r'[A-Za-z0-9-_@#]+')))
        self.ui.lineEditTag.setMaxLength(128)

        self.ui.tagItButton.clicked.connect(self.onTagObject)
        self.ui.untagItButton.clicked.connect(self.untagObject)
        self.ui.okButton.clicked.connect(lambda: ensure(self.validate()))
        self.ui.noTagsButton.clicked.connect(self.reject)

        self.setMinimumSize(self.app.desktopGeometry.width() / 2,
                            (2 * self.app.desktopGeometry.height()) / 3)

    def onTagEditChanged(self, text):
        self.allTagsProxyModel.setFilterRegExp(text)
        self.ui.allTagsView.clearSelection()

    def onTagDoubleClicked(self, idx):
        ensure(self.tagObject([idx]))

    def onTagObject(self):
        ensure(self.tagObject())

    def untagObject(self):
        try:
            for idx in self.ui.destTagsView.selectedIndexes():
                tag = self.destTagsModel.data(idx, Qt.DisplayRole)

                if tag:
                    tagList = self.destTagsModel.stringList()
                    tagList.remove(tag)
                    self.destTagsModel.setStringList(tagList)
        except Exception:
            pass

    async def tagObject(self, indexes=None):
        if indexes is None:
            indexes = self.ui.allTagsView.selectedIndexes()

        for idx in indexes:
            tag = self.allTagsProxyModel.data(idx, Qt.DisplayRole)

            if tag and tag not in self.destTagsModel.stringList():
                self.destTagsModel.setStringList(
                    self.destTagsModel.stringList() + [tag])

    async def initDialog(self):
        await self.updateAllTags()

    async def addTag(self):
        tagname = self.ui.lineEditTag.text()
        if not tagname:
            return

        await database.ipTagAdd(ipTagsFormat(tagname))
        self.ui.lineEditTag.clear()
        await self.updateAllTags()

    async def updateAllTags(self):
        tags = [t.name for t in await database.ipTagsAll()]
        self.allTagsModel.setStringList(tags)
        self.ui.allTagsView.setModel(self.allTagsProxyModel)
        self.allTagsProxyModel.sort(0)

    async def validate(self):
        self.destTags = self.destTagsModel.stringList()
        self.done(1)
Exemplo n.º 12
0
class TileStampsDock(QDockWidget):
    setStamp = pyqtSignal(TileStamp)

    def __init__(self, stampManager, parent=None):
        super().__init__(parent)

        self.mTileStampManager = stampManager
        self.mTileStampModel = stampManager.tileStampModel()
        self.mProxyModel = QSortFilterProxyModel(self.mTileStampModel)
        self.mFilterEdit = QLineEdit(self)
        self.mNewStamp = QAction(self)
        self.mAddVariation = QAction(self)
        self.mDuplicate = QAction(self)
        self.mDelete = QAction(self)
        self.mChooseFolder = QAction(self)

        self.setObjectName("TileStampsDock")
        self.mProxyModel.setSortLocaleAware(True)
        self.mProxyModel.setSortCaseSensitivity(Qt.CaseInsensitive)
        self.mProxyModel.setFilterCaseSensitivity(Qt.CaseInsensitive)
        self.mProxyModel.setSourceModel(self.mTileStampModel)
        self.mProxyModel.sort(0)
        self.mTileStampView = TileStampView(self)
        self.mTileStampView.setModel(self.mProxyModel)
        self.mTileStampView.setVerticalScrollMode(
            QAbstractItemView.ScrollPerPixel)
        self.mTileStampView.header().setStretchLastSection(False)
        self.mTileStampView.header().setSectionResizeMode(
            0, QHeaderView.Stretch)
        self.mTileStampView.header().setSectionResizeMode(
            1, QHeaderView.ResizeToContents)
        self.mTileStampView.setContextMenuPolicy(Qt.CustomContextMenu)
        self.mTileStampView.customContextMenuRequested.connect(
            self.showContextMenu)
        self.mNewStamp.setIcon(QIcon(":images/16x16/document-new.png"))
        self.mAddVariation.setIcon(QIcon(":/images/16x16/add.png"))
        self.mDuplicate.setIcon(QIcon(":/images/16x16/stock-duplicate-16.png"))
        self.mDelete.setIcon(QIcon(":images/16x16/edit-delete.png"))
        self.mChooseFolder.setIcon(QIcon(":images/16x16/document-open.png"))
        Utils.setThemeIcon(self.mNewStamp, "document-new")
        Utils.setThemeIcon(self.mAddVariation, "add")
        Utils.setThemeIcon(self.mDelete, "edit-delete")
        Utils.setThemeIcon(self.mChooseFolder, "document-open")

        self.mFilterEdit.setClearButtonEnabled(True)
        self.mFilterEdit.textChanged.connect(
            self.mProxyModel.setFilterFixedString)
        self.mTileStampModel.stampRenamed.connect(self.ensureStampVisible)
        self.mNewStamp.triggered.connect(self.newStamp)
        self.mAddVariation.triggered.connect(self.addVariation)
        self.mDuplicate.triggered.connect(self.duplicate)
        self.mDelete.triggered.connect(self.delete_)
        self.mChooseFolder.triggered.connect(self.chooseFolder)
        self.mDuplicate.setEnabled(False)
        self.mDelete.setEnabled(False)
        self.mAddVariation.setEnabled(False)
        widget = QWidget(self)
        layout = QVBoxLayout(widget)
        layout.setContentsMargins(5, 5, 5, 5)

        buttonContainer = QToolBar()
        buttonContainer.setFloatable(False)
        buttonContainer.setMovable(False)
        buttonContainer.setIconSize(QSize(16, 16))
        buttonContainer.addAction(self.mNewStamp)
        buttonContainer.addAction(self.mAddVariation)
        buttonContainer.addAction(self.mDuplicate)
        buttonContainer.addAction(self.mDelete)
        stretch = QWidget()
        stretch.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum)
        buttonContainer.addWidget(stretch)
        buttonContainer.addAction(self.mChooseFolder)
        listAndToolBar = QVBoxLayout()
        listAndToolBar.setSpacing(0)
        listAndToolBar.addWidget(self.mFilterEdit)
        listAndToolBar.addWidget(self.mTileStampView)
        listAndToolBar.addWidget(buttonContainer)
        layout.addLayout(listAndToolBar)
        selectionModel = self.mTileStampView.selectionModel()
        selectionModel.currentRowChanged.connect(self.currentRowChanged)
        self.setWidget(widget)
        self.retranslateUi()

    def changeEvent(self, e):
        super().changeEvent(e)
        x = e.type()
        if x == QEvent.LanguageChange:
            self.retranslateUi()
        else:
            pass

    def keyPressEvent(self, event):
        x = event.key()
        if x == Qt.Key_Delete or x == Qt.Key_Backspace:
            self.delete_()
            return

        super().keyPressEvent(event)

    def currentRowChanged(self, index):
        sourceIndex = self.mProxyModel.mapToSource(index)
        isStamp = self.mTileStampModel.isStamp(sourceIndex)
        self.mDuplicate.setEnabled(isStamp)
        self.mDelete.setEnabled(sourceIndex.isValid())
        self.mAddVariation.setEnabled(isStamp)
        if (isStamp):
            self.setStamp.emit(self.mTileStampModel.stampAt(sourceIndex))
        else:
            variation = self.mTileStampModel.variationAt(sourceIndex)
            if variation:
                # single variation clicked, use it specifically
                self.setStamp.emit(TileStamp(Map(variation.map)))

    def showContextMenu(self, pos):
        index = self.mTileStampView.indexAt(pos)
        if (not index.isValid()):
            return
        menu = QMenu()
        sourceIndex = self.mProxyModel.mapToSource(index)
        if (self.mTileStampModel.isStamp(sourceIndex)):
            addStampVariation = QAction(self.mAddVariation.icon(),
                                        self.mAddVariation.text(), menu)
            deleteStamp = QAction(self.mDelete.icon(), self.tr("Delete Stamp"),
                                  menu)
            deleteStamp.triggered.connect(self.delete_)
            addStampVariation.triggered.connect(self.addVariation)
            menu.addAction(addStampVariation)
            menu.addSeparator()
            menu.addAction(deleteStamp)
        else:
            removeVariation = QAction(QIcon(":/images/16x16/remove.png"),
                                      self.tr("Remove Variation"), menu)
            Utils.setThemeIcon(removeVariation, "remove")
            removeVariation.triggered.connect(self.delete_)
            menu.addAction(removeVariation)

        menu.exec(self.mTileStampView.viewport().mapToGlobal(pos))

    def newStamp(self):
        stamp = self.mTileStampManager.createStamp()
        if (self.isVisible() and not stamp.isEmpty()):
            stampIndex = self.mTileStampModel.index(stamp)
            if (stampIndex.isValid()):
                viewIndex = self.mProxyModel.mapFromSource(stampIndex)
                self.mTileStampView.setCurrentIndex(viewIndex)
                self.mTileStampView.edit(viewIndex)

    def delete_(self):
        index = self.mTileStampView.currentIndex()
        if (not index.isValid()):
            return
        sourceIndex = self.mProxyModel.mapToSource(index)
        self.mTileStampModel.removeRow(sourceIndex.row(), sourceIndex.parent())

    def duplicate(self):
        index = self.mTileStampView.currentIndex()
        if (not index.isValid()):
            return
        sourceIndex = self.mProxyModel.mapToSource(index)
        if (not self.mTileStampModel.isStamp(sourceIndex)):
            return
        stamp = self.mTileStampModel.stampAt = TileStamp(sourceIndex)
        self.mTileStampModel.addStamp(stamp.clone())

    def addVariation(self):
        index = self.mTileStampView.currentIndex()
        if (not index.isValid()):
            return
        sourceIndex = self.mProxyModel.mapToSource(index)
        if (not self.mTileStampModel.isStamp(sourceIndex)):
            return
        stamp = self.mTileStampModel.stampAt(sourceIndex)
        self.mTileStampManager.addVariation(stamp)

    def chooseFolder(self):
        prefs = Preferences.instance()
        stampsDirectory = prefs.stampsDirectory()
        stampsDirectory = QFileDialog.getExistingDirectory(
            self.window(), self.tr("Choose the Stamps Folder"),
            stampsDirectory)
        if (not stampsDirectory.isEmpty()):
            prefs.setStampsDirectory(stampsDirectory)

    def ensureStampVisible(self, stamp):
        stampIndex = self.mTileStampModel.index(stamp)
        if (stampIndex.isValid()):
            self.mTileStampView.scrollTo(
                self.mProxyModel.mapFromSource(stampIndex))

    def retranslateUi(self):
        self.setWindowTitle(self.tr("Tile Stamps"))
        self.mNewStamp.setText(self.tr("Add New Stamp"))
        self.mAddVariation.setText(self.tr("Add Variation"))
        self.mDuplicate.setText(self.tr("Duplicate Stamp"))
        self.mDelete.setText(self.tr("Delete Selected"))
        self.mChooseFolder.setText(self.tr("Set Stamps Folder"))
        self.mFilterEdit.setPlaceholderText(self.tr("Filter"))
Exemplo n.º 13
0
 def sorted(self):
     proxy_model = QSortFilterProxyModel(self)
     proxy_model.setSourceModel(self)
     proxy_model.setDynamicSortFilter(True)
     proxy_model.sort(0)
     return proxy_model
Exemplo n.º 14
0
class MainWidget(QWidget):
    def __init__(self, parent):
        ## Initiating MainWidget
        super(MainWidget, self).__init__(
            parent)  ## I have no idea what kind of magic going on here

        self.init_variables()

        ## This signal_count counts how many comics user opened. This is needed to disconnect "self.comic_file_table_model.itemChanged.connect(self.comic_file_table_cell_changed)"
        self.signal_count = 0

        self.init_UI()

    def init_UI(self):
        ## Function that makes UI for the program
        layout = QGridLayout()  # Setting QWidget layout to be grid

        self.choose_comic_file_directory = expanduser(
            "~"
        )  # This initiaded here instead of init_variables, because it will be changed during usage of the program to remember last place user chose a file and it doesn't need to be reset every time user choose another file.

        ## All UI elemnts in order they are added
        ## Variable names are self explanatory
        self.label_filename = QLabel("No comic is selected")
        self.label_filename.setAlignment(Qt.AlignCenter)

        self.button_select_comic = QPushButton("Select Comic")
        self.button_select_comic.clicked.connect(self.choose_comic_file)

        page_filename_groupbox = QGroupBox("Page Filename")
        page_filename_groupbox_layout = QVBoxLayout()
        self.page_filename_remove_checkbox = QCheckBox("Remove")
        self.page_filename_remove_checkbox.stateChanged.connect(
            self.page_filename_remove_checkbox_state_changed)
        self.page_filename_remove_line_edit = QLineEdit()
        self.page_filename_remove_line_edit.setEnabled(False)
        self.page_filename_remove_line_edit.setPlaceholderText(
            "Text you want to remove.")
        self.page_filename_replace_checkbox = QCheckBox("Replace with")
        self.page_filename_replace_checkbox.stateChanged.connect(
            self.page_filename_replace_checkbox_state_changed)
        self.page_filename_replace_checkbox.setEnabled(False)
        self.page_filename_replace_line_edit = QLineEdit()
        self.page_filename_replace_line_edit.setEnabled(False)
        self.page_filename_replace_line_edit.setPlaceholderText(
            "Text you want to replace with")
        page_filename_groupbox_layout.addWidget(
            self.page_filename_remove_checkbox)
        page_filename_groupbox_layout.addWidget(
            self.page_filename_remove_line_edit)
        page_filename_groupbox_layout.addWidget(
            self.page_filename_replace_checkbox)
        page_filename_groupbox_layout.addWidget(
            self.page_filename_replace_line_edit)
        page_filename_groupbox.setLayout(page_filename_groupbox_layout)

        # Setting up table model for table that will contain items inside comic's archive
        self.comic_file_table_model = QStandardItemModel()
        self.comic_file_table_model.setColumnCount(2)

        self.comic_file_table_proxy_model = QSortFilterProxyModel()
        self.comic_file_table_proxy_model.setSourceModel(
            self.comic_file_table_model)
        self.comic_file_table_proxy_model.setSortRole(10)
        self.comic_file_table_proxy_model.sort(0, Qt.AscendingOrder)

        # Setting up view for the table that will display items inside comic's archive
        self.comic_file_table = QTableView()
        self.comic_file_table.setModel(self.comic_file_table_proxy_model)
        self.comic_file_table.horizontalHeader().hide()
        self.comic_file_table.verticalHeader().hide()
        self.comic_file_table.resizeColumnToContents(0)
        self.comic_file_table.horizontalHeader().setStretchLastSection(True)

        self.button_convert_to_cbz = QPushButton("Convert to CBZ")
        self.button_convert_to_cbz.setToolTip(
            "Will not change content of the archive.")
        self.button_convert_to_cbz.setEnabled(False)
        self.button_convert_to_cbz.clicked.connect(self.convert_to_cbz_clicked)

        self.button_remove_subfolder_thumbs = QPushButton("Remove Trash")
        self.button_remove_subfolder_thumbs.setToolTip(
            "Only removes subfolder and all extra files. Will not remove pages."
        )
        self.button_remove_subfolder_thumbs.setEnabled(False)
        self.button_remove_subfolder_thumbs.clicked.connect(
            self.button_remove_subfolder_thumbs_clicked)

        self.button_fix_comic = QPushButton("Fix Comic")
        self.button_fix_comic.setToolTip(
            "Removes selected images, subfolder, thumbs.db and renames pages if chosen."
        )
        self.button_fix_comic.setEnabled(False)
        self.button_fix_comic.clicked.connect(self.button_fix_comic_clicked)

        self.label_message = QLabel()
        self.label_message.setAlignment(Qt.AlignCenter)

        # Adding all UI elements to the layout
        layout.addWidget(self.label_filename, 0, 0, 1, 6)
        layout.addWidget(self.button_select_comic, 0, 6, 1, 1)
        layout.addWidget(page_filename_groupbox, 1, 0, 3, 7)
        layout.addWidget(self.comic_file_table, 5, 0, 8, 6)
        layout.addWidget(self.button_convert_to_cbz, 9, 6, 1, 1)
        layout.addWidget(self.button_remove_subfolder_thumbs, 10, 6, 1, 1)
        layout.addWidget(self.button_fix_comic, 12, 6, 1, 1)
        layout.addWidget(self.label_message, 13, 0, 1, 7)
        self.setLayout(layout)  # Setting layout the QMainWindow.

    def choose_comic_file(self):
        ## Prompts user to select a file and checks selected file. Set's variables used by other functions later.

        chosen_file = QFileDialog.getOpenFileName(
            self, "Choose Comic File", self.choose_comic_file_directory,
            "Comics (*.cbr *.cbz)"
        )[0]  # Prompts user to select comic file and saves result to variable

        if chosen_file != "":  # Checks if user actually selected a file

            self.choose_comic_file_directory = split(chosen_file)[0]

            # Resetting all variables for new file
            self.init_variables()

            # Disabling all buttons for new file
            self.button_convert_to_cbz.setEnabled(False)
            self.button_remove_subfolder_thumbs.setEnabled(False)
            self.button_fix_comic.setEnabled(False)

            # Disabling connection to the table if there is one.
            if self.comic_file_table_model.receivers(
                    self.comic_file_table_model.itemChanged) > 0:
                self.comic_file_table_model.itemChanged.disconnect(
                    self.comic_file_table_cell_changed)

            chosen_file_exte = split(chosen_file)[1][-4:].lower(
            )  # Saves user's chosen's file extention to a variable

            if chosen_file_exte == ".cbz" or chosen_file_exte == ".cbr":
                ## Checks if user's selected file actually ends with.
                ## Set's variables, shows message to user with file name and enables buttons if True
                self.comic_file = chosen_file
                self.comic_file_name = split(self.comic_file)[1][:-4]
                self.comic_file_exte = chosen_file_exte

                # Printin file that is being worked on.
                self.label_filename.setText(self.comic_file_name +
                                            self.comic_file_exte)

                # Removing all # from filename if user passed -s as an argument when launching program.
                if len(argv) > 1:
                    if argv[1] == "-s":
                        self.comic_file_name = self.comic_file_name.replace(
                            "#", "")

                self.label_message.clear(
                )  # Clears message in case user selected a not comic file previously or working with multiple file in a row.

                # Checking if comic arhcive is rar file. If true enabling "Convert to CBZ button"
                if self.comic_file_exte == ".cbr":
                    self.button_convert_to_cbz.setEnabled(True)

                # Getting more variables that will be used to by other functions. For more info check engine.check_comic.
                self.sorted_filename_length_dict, self.sub_folder_toggle, self.thumbs_db = engine.check_comic(
                    self.comic_file, self.comic_file_name,
                    self.comic_file_exte)

                # Enabling button "Remove Trash" if toggles are switched by check_engine function
                if self.sub_folder_toggle == 1 or self.thumbs_db[0] == 1:
                    self.button_remove_subfolder_thumbs.setEnabled(True)

                # Enabling "Fix Comic" button
                self.button_fix_comic.setEnabled(True)

                self.label_message.clear(
                )  # Clears message in case user selected a not comic file previously or working with multiple file in a row.

                self.display_comic_files()

                self.comic_file_table.scrollToTop()

            else:
                ## Prints a message to user if he selected not comic file.
                self.label_message.setText(
                    "You have to select cbr or cbz file.")

    def display_comic_files(self):
        ## Adds all comic archive files to the QtableWidget and checkmarks as suggestion based on sorted_file_length_dict

        ignore_file_exte = [".jpg", ".png", ".xml"
                            ]  # extension that will be not marked for deletion

        self.comic_file_list = engine.archive_file_list(
            self.comic_file, self.comic_file_name,
            self.comic_file_exte)  # Getting archive's file list from engine.
        self.comic_file_table_model.setRowCount(
            len(self.comic_file_list)
        )  # Setting tables row count to the count of files inside archive

        ## Prints a message if a subfolder is detected.
        if self.sub_folder_toggle == 1:
            self.label_message.setText("There is a subfolder!")

        ## This makes two presumptions. First, is that first (shortest) file in dictionary will be the one that needs to be removed. Second, that it will be mention just once. Dictionary is already sorted by key (filename length) and this if statement checks if this length one found just once. Adding it to the delete_files list if true.
        if self.sorted_filename_length_dict[0][1][1] == 1:
            self.delete_files.append(self.sorted_filename_length_dict[0][1][0])

        ## If thumbs_db toggle is switched adding it to the delete file list.
        if self.thumbs_db[0] == 1:
            self.delete_files.append(self.thumbs_db[1])

        for item in range(len(self.comic_file_list)):

            ## Checkicg if file extentions isn't *.jpg or *.xml. If not it goes to delete_list.
            if self.comic_file_list[item][-4:].lower(
            ) not in ignore_file_exte and self.comic_file_list[
                    item] not in self.delete_files:
                self.delete_files.append(self.comic_file_list[item])

            ## Adding every item from archive to the table
            item_checkbox_detele = QStandardItem()
            if self.comic_file_list[item] in self.delete_files:
                item_checkbox_detele.setCheckState(
                    Qt.Unchecked
                )  # Setting checkmark as Unchecked. Files is marked for deletion
            else:
                item_checkbox_detele.setCheckState(Qt.Checked)
            item_checkbox_detele.setFlags(
                Qt.ItemIsUserCheckable | Qt.ItemIsEnabled
            )  # Checkmark's cell not editable, but state still can be changed.
            item_filename = QStandardItem(
                split(self.comic_file_list[item])[1]
            )  # Getting just a filename, without a path to the file in archive.
            item_filename.setFlags(
                Qt.ItemIsEnabled)  # Filename's cell not editable
            self.comic_file_table_model.setItem(item, 0, item_checkbox_detele)
            self.comic_file_table_model.setItem(item, 1, item_filename)

        self.comic_file_table_model.itemChanged.connect(
            self.comic_file_table_cell_changed)

    def convert_to_cbz_clicked(self):
        ## This fuction exists because it is not possible to pass variables using connect
        self.disable_buttons()
        engine.convert_to_cbz(self.comic_file, self.comic_file_name)
        self.enable_buttons()
        self.label_message.setText("Converted " + self.comic_file_name +
                                   " to cbz")

    def button_remove_subfolder_thumbs_clicked(self):
        ## This function removes only subfolder and thumbs.db if they exists in the archive.
        ## Even if any other file will be selected for deletion it won't be deleted.
        self.disable_buttons()

        local_delete_files = [
        ]  # Instead of global delete file, local one will be used.

        ## Adding thumbs.db to the delete list if it exists.
        if self.thumbs_db[0] == 1:
            local_delete_files.append(self.thumbs_db[1])

        engine.write_comic(
            self.comic_file, self.comic_file_name, self.comic_file_exte,
            local_delete_files, []
        )  # Passing empty list for remove_from_filename. This function do not change filenames.

        self.enable_buttons()
        self.label_message.setText("Extra file removed!")

    def comic_file_table_cell_changed(self, clicked_checkbox):
        ## Function triggred when user toggles checkmark in table.

        clicked_checkbox_location = clicked_checkbox.index()

        clicked_item_state = clicked_checkbox.checkState()
        clicked_item_filename = self.comic_file_list[
            clicked_checkbox_location.row(
            )]  # Gets filename for the checkmark from comic file list based on checkmark's row.

        ## Depending of the state of the checkmark checks if filename is marked for deletion. Depending on the that removes or adds filename to delete_list
        if clicked_item_state == Qt.Checked:
            if clicked_item_filename in self.delete_files:
                self.delete_files.remove(clicked_item_filename)
        elif clicked_item_state == Qt.Unchecked:
            if clicked_item_filename not in self.delete_files:
                self.delete_files.append(clicked_item_filename)

    def page_filename_remove_checkbox_state_changed(self):
        ## Enables/disables page_filename_remove_line_edit depending on page_filename_remove_checkbox_state
        if self.page_filename_remove_checkbox.checkState() == Qt.Checked:
            self.page_filename_remove_line_edit.setEnabled(True)
            self.page_filename_replace_checkbox.setEnabled(
                True)  # Enables "Replace With" checkbox
        elif self.page_filename_remove_checkbox.checkState() == Qt.Unchecked:
            self.page_filename_remove_line_edit.setEnabled(False)
            self.page_filename_remove_line_edit.clear()
            if self.page_filename_replace_checkbox.checkState() == Qt.Checked:
                ## Checks status of "Replace with" checkbox. If it's checked - removes the checkbox and disables it.
                self.page_filename_replace_checkbox.setChecked(False)
                self.page_filename_replace_checkbox.setEnabled(False)
            self.remove_from_filename = [
            ]  # Resets remove_from_filename, otherwise there will be BUGS.

    def page_filename_replace_checkbox_state_changed(self):
        ## Enables/disables page_filename_remove_line_edit depending on page_filename_remove_checkbox_state
        if self.page_filename_replace_checkbox.checkState() == Qt.Checked:
            self.page_filename_replace_line_edit.setEnabled(True)
        elif self.page_filename_replace_checkbox.checkState() == Qt.Unchecked:
            if self.page_filename_replace_line_edit.text(
            ) in self.remove_from_filename:
                ## Removes text that user planned to replace with removed text.
                self.remove_from_filename.remove(
                    self.page_filename_replace_line_edit.text())
            self.page_filename_replace_line_edit.setEnabled(False)
            self.page_filename_replace_line_edit.clear()

    def button_fix_comic_clicked(self):
        ## This functions inplements main funcction of this program. To actually remove page from comc archive, rename files if chosen.
        self.disable_buttons()
        self.remove_from_filename = [
        ]  # Resets the list, otherwise it would add the same items in the list to infinity.
        if self.page_filename_remove_checkbox.checkState() == Qt.Checked:
            # If remove checkbox is marked appends text from remove_line_edit to remove_from_filename.
            self.remove_from_filename.append(
                self.page_filename_remove_line_edit.text())
            if self.page_filename_replace_checkbox.checkState() == Qt.Checked:
                # If rename checkbox marked appends what's written in replace_line_edit to remove_from_filename.
                self.remove_from_filename.append(
                    self.page_filename_replace_line_edit.text())
            else:
                # If rename checkbox isn't marked appends empty string to the remove_from_filename list
                self.remove_from_filename.append("")

        engine.write_comic(self.comic_file, self.comic_file_name,
                           self.comic_file_exte, self.delete_files,
                           self.remove_from_filename)
        self.enable_buttons()

        self.label_message.setText(
            "Fixed comic: " + self.comic_file_name +
            ".cbz")  # prints messages to user what file was fixed.

    def disable_buttons(self):
        ## To Disable buttons before program starts working archive.
        self.button_select_comic.setEnabled(False)
        self.button_convert_to_cbz.setEnabled(False)
        self.button_remove_subfolder_thumbs.setEnabled(False)
        self.button_fix_comic.setEnabled(False)

    def enable_buttons(self):
        ## To Enable buttons after program finishes saving archive.
        self.button_select_comic.setEnabled(True)
        self.button_convert_to_cbz.setEnabled(True)
        self.button_remove_subfolder_thumbs.setEnabled(True)
        self.button_fix_comic.setEnabled(True)

    def init_variables(self):
        ## Variables needed for Engine Functions and other GUI elements that need to be reset before loading new file.
        self.comic_file = ""
        self.comic_file_name = ""
        self.comic_file_exte = ""
        self.sorted_filename_length_dict = dict()
        self.sub_folder_toggle = 0
        self.thumbs_db = (0, "")
        self.comic_file_list = []
        self.delete_files = []
        self.remove_from_filename = []
Exemplo n.º 15
0
 def sorted(self):
     proxy_model = QSortFilterProxyModel(self)
     proxy_model.setSourceModel(self)
     proxy_model.setDynamicSortFilter(True)
     proxy_model.sort(0)
     return proxy_model
Exemplo n.º 16
0
class SearchFileWidget(QWidget):

    language_filter_change = pyqtSignal(list)

    def __init__(self):
        QWidget.__init__(self)

        self._refreshing = False

        self.fileModel = None
        self.proxyFileModel = None
        self.videoModel = None

        self._state = None

        self.timeLastSearch = QTime.currentTime()

        self.ui = Ui_SearchFileWidget()
        self.setup_ui()

    def set_state(self, state):
        self._state = state
        self._state.login_status_changed.connect(self.on_login_state_changed)
        self._state.interface_language_changed.connect(
            self.on_interface_language_changed)

    def get_state(self):
        return self._state

    def setup_ui(self):
        self.ui.setupUi(self)
        settings = QSettings()

        self.ui.splitter.setSizes([600, 1000])
        self.ui.splitter.setChildrenCollapsible(False)

        # Set up folder view

        lastDir = settings.value("mainwindow/workingDirectory",
                                 QDir.homePath())
        log.debug('Current directory: {currentDir}'.format(currentDir=lastDir))

        self.fileModel = QFileSystemModel(self)
        self.fileModel.setFilter(QDir.AllDirs | QDir.Dirs | QDir.Drives
                                 | QDir.NoDotAndDotDot | QDir.Readable
                                 | QDir.Executable | QDir.Writable)
        self.fileModel.iconProvider().setOptions(
            QFileIconProvider.DontUseCustomDirectoryIcons)
        self.fileModel.setRootPath(QDir.rootPath())
        self.fileModel.directoryLoaded.connect(self.onFileModelDirectoryLoaded)

        self.proxyFileModel = QSortFilterProxyModel(self)
        self.proxyFileModel.setSortRole(Qt.DisplayRole)
        self.proxyFileModel.setSourceModel(self.fileModel)
        self.proxyFileModel.sort(0, Qt.AscendingOrder)
        self.proxyFileModel.setSortCaseSensitivity(Qt.CaseInsensitive)
        self.ui.folderView.setModel(self.proxyFileModel)

        self.ui.folderView.setHeaderHidden(True)
        self.ui.folderView.hideColumn(3)
        self.ui.folderView.hideColumn(2)
        self.ui.folderView.hideColumn(1)

        index = self.fileModel.index(lastDir)
        proxyIndex = self.proxyFileModel.mapFromSource(index)
        self.ui.folderView.scrollTo(proxyIndex)

        self.ui.folderView.expanded.connect(self.onFolderViewExpanded)
        self.ui.folderView.clicked.connect(self.onFolderTreeClicked)
        self.ui.buttonFind.clicked.connect(self.onButtonFind)
        self.ui.buttonRefresh.clicked.connect(self.onButtonRefresh)

        # Set up introduction
        self.showInstructions()

        # Set up video view
        self.ui.filterLanguageForVideo.set_unknown_text(_('All languages'))
        self.ui.filterLanguageForVideo.selected_language_changed.connect(
            self.on_language_combobox_filter_change)
        # self.ui.filterLanguageForVideo.selected_language_changed.connect(self.onFilterLanguageVideo)

        self.videoModel = VideoModel(self)
        self.ui.videoView.setHeaderHidden(True)
        self.ui.videoView.setModel(self.videoModel)
        self.ui.videoView.activated.connect(self.onClickVideoTreeView)
        self.ui.videoView.clicked.connect(self.onClickVideoTreeView)
        self.ui.videoView.customContextMenuRequested.connect(self.onContext)
        self.videoModel.dataChanged.connect(self.subtitlesCheckedChanged)
        self.language_filter_change.connect(
            self.videoModel.on_filter_languages_change)

        self.ui.buttonSearchSelectVideos.clicked.connect(
            self.onButtonSearchSelectVideos)
        self.ui.buttonSearchSelectFolder.clicked.connect(
            self.onButtonSearchSelectFolder)
        self.ui.buttonDownload.clicked.connect(self.onButtonDownload)
        self.ui.buttonPlay.clicked.connect(self.onButtonPlay)
        self.ui.buttonIMDB.clicked.connect(self.onViewOnlineInfo)
        self.ui.videoView.setContextMenuPolicy(Qt.CustomContextMenu)

        # Drag and Drop files to the videoView enabled
        self.ui.videoView.__class__.dragEnterEvent = self.dragEnterEvent
        self.ui.videoView.__class__.dragMoveEvent = self.dragEnterEvent
        self.ui.videoView.__class__.dropEvent = self.dropEvent
        self.ui.videoView.setAcceptDrops(1)

        # FIXME: ok to drop this connect?
        # self.ui.videoView.clicked.connect(self.onClickMovieTreeView)

        self.retranslate()

    def retranslate(self):
        introduction = '<p align="center"><h2>{title}</h2></p>' \
            '<p><b>{tab1header}</b><br/>{tab1content}</p>' \
            '<p><b>{tab2header}</b><br/>{tab2content}</p>'\
            '<p><b>{tab3header}</b><br/>{tab3content}</p>'.format(
                title=_('How To Use {title}').format(title=PROJECT_TITLE),
                tab1header=_('1st Tab:'),
                tab2header=_('2nd Tab:'),
                tab3header=_('3rd Tab:'),
                tab1content=_('Select, from the Folder Tree on the left, the folder which contains the videos '
                              'that need subtitles. {project} will then try to automatically find available '
                              'subtitles.').format(project=PROJECT_TITLE),
                tab2content=_('If you don\'t have the videos in your machine, you can search subtitles by '
                              'introducing the title/name of the video.').format(project=PROJECT_TITLE),
                tab3content=_('If you have found some subtitle somewhere else that is not in {project}\'s database, '
                              'please upload those subtitles so next users will be able to '
                              'find them more easily.').format(project=PROJECT_TITLE))
        self.ui.introductionHelp.setHtml(introduction)

    @pyqtSlot(Language)
    def on_interface_language_changed(self, language):
        self.ui.retranslateUi(self)
        self.retranslate()

    @pyqtSlot(str)
    def onFileModelDirectoryLoaded(self, path):
        settings = QSettings()
        lastDir = settings.value('mainwindow/workingDirectory',
                                 QDir.homePath())
        qDirLastDir = QDir(lastDir)
        qDirLastDir.cdUp()
        if qDirLastDir.path() == path:
            index = self.fileModel.index(lastDir)
            proxyIndex = self.proxyFileModel.mapFromSource(index)
            self.ui.folderView.scrollTo(proxyIndex)
            self.ui.folderView.setCurrentIndex(proxyIndex)

    @pyqtSlot(int, str)
    def on_login_state_changed(self, state, message):
        log.debug(
            'on_login_state_changed(state={state}, message={message}'.format(
                state=state, message=message))
        if state in (State.LOGIN_STATUS_LOGGED_OUT, State.LOGIN_STATUS_BUSY):
            self.ui.buttonSearchSelectFolder.setEnabled(False)
            self.ui.buttonSearchSelectVideos.setEnabled(False)
            self.ui.buttonFind.setEnabled(False)
        elif state == State.LOGIN_STATUS_LOGGED_IN:
            self.ui.buttonSearchSelectFolder.setEnabled(True)
            self.ui.buttonSearchSelectVideos.setEnabled(True)
            self.ui.buttonFind.setEnabled(
                self.get_current_selected_folder() is not None)
        else:
            log.warning('unknown state')

    @pyqtSlot(Language)
    def on_language_combobox_filter_change(self, language):
        if language.is_generic():
            self.language_filter_change.emit(
                self.get_state().get_permanent_language_filter())
        else:
            self.language_filter_change.emit([language])

    def on_permanent_language_filter_change(self, languages):
        selected_language = self.ui.filterLanguageForVideo.get_selected_language(
        )
        if selected_language.is_generic():
            self.language_filter_change.emit(languages)

    @pyqtSlot()
    def subtitlesCheckedChanged(self):
        subs = self.videoModel.get_checked_subtitles()
        if subs:
            self.ui.buttonDownload.setEnabled(True)
        else:
            self.ui.buttonDownload.setEnabled(False)

    def showInstructions(self):
        self.ui.stackedSearchResult.setCurrentWidget(self.ui.pageIntroduction)

    def hideInstructions(self):
        self.ui.stackedSearchResult.setCurrentWidget(self.ui.pageSearchResult)

    @pyqtSlot(QModelIndex)
    def onFolderTreeClicked(self, proxyIndex):
        """What to do when a Folder in the tree is clicked"""
        if not proxyIndex.isValid():
            return

        index = self.proxyFileModel.mapToSource(proxyIndex)
        settings = QSettings()
        folder_path = self.fileModel.filePath(index)
        settings.setValue('mainwindow/workingDirectory', folder_path)
        # self.ui.buttonFind.setEnabled(self.get_state().)

    def get_current_selected_folder(self):
        proxyIndex = self.ui.folderView.currentIndex()
        index = self.proxyFileModel.mapToSource(proxyIndex)
        folder_path = self.fileModel.filePath(index)
        if not folder_path:
            return None
        return folder_path

    def get_current_selected_item_videomodel(self):
        current_index = self.ui.videoView.currentIndex()
        return self.videoModel.getSelectedItem(current_index)

    @pyqtSlot()
    def onButtonFind(self):
        now = QTime.currentTime()
        if now < self.timeLastSearch.addMSecs(500):
            return
        folder_path = self.get_current_selected_folder()

        settings = QSettings()
        settings.setValue('mainwindow/workingDirectory', folder_path)
        self.search_videos([folder_path])

        self.timeLastSearch = QTime.currentTime()

    @pyqtSlot()
    def onButtonRefresh(self):
        currentPath = self.get_current_selected_folder()
        if not currentPath:
            settings = QSettings()
            currentPath = settings.value('mainwindow/workingDirectory',
                                         QDir.homePath())

        self._refreshing = True

        self.ui.folderView.collapseAll()

        currentPath = self.get_current_selected_folder()
        if not currentPath:
            settings = QSettings()
            currentPath = settings.value('mainwindow/workingDirectory',
                                         QDir.homePath())

        index = self.fileModel.index(currentPath)

        self.ui.folderView.scrollTo(self.proxyFileModel.mapFromSource(index))

    @pyqtSlot(QModelIndex)
    def onFolderViewExpanded(self, proxyIndex):
        if self._refreshing:
            expandedPath = self.fileModel.filePath(
                self.proxyFileModel.mapToSource(proxyIndex))
            if expandedPath == QDir.rootPath():
                currentPath = self.get_current_selected_folder()
                if not currentPath:
                    settings = QSettings()
                    currentPath = settings.value('mainwindow/workingDirectory',
                                                 QDir.homePath())

                index = self.fileModel.index(currentPath)

                self.ui.folderView.scrollTo(
                    self.proxyFileModel.mapFromSource(index))
                self._refreshing = False

    @pyqtSlot()
    def onButtonSearchSelectFolder(self):
        settings = QSettings()
        path = settings.value('mainwindow/workingDirectory', QDir.homePath())
        folder_path = QFileDialog.getExistingDirectory(
            self, _('Select the directory that contains your videos'), path)
        if folder_path:
            settings.setValue('mainwindow/workingDirectory', folder_path)
            self.search_videos([folder_path])

    @pyqtSlot()
    def onButtonSearchSelectVideos(self):
        settings = QSettings()
        currentDir = settings.value('mainwindow/workingDirectory',
                                    QDir.homePath())
        fileNames, t = QFileDialog.getOpenFileNames(
            self, _('Select the video(s) that need subtitles'), currentDir,
            SELECT_VIDEOS)
        if fileNames:
            settings.setValue('mainwindow/workingDirectory',
                              QFileInfo(fileNames[0]).absolutePath())
            self.search_videos(fileNames)

    def search_videos(self, paths):
        if not self.get_state().connected():
            QMessageBox.about(
                self, _("Error"),
                _('You are not connected to the server. Please reconnect first.'
                  ))
            return
        self.ui.buttonFind.setEnabled(False)
        self._search_videos_raw(paths)
        self.ui.buttonFind.setEnabled(True)

    def _search_videos_raw(self, paths):
        # FIXME: must pass mainwindow as argument to ProgressCallbackWidget
        callback = ProgressCallbackWidget(self)
        callback.set_title_text(_("Scanning..."))
        callback.set_label_text(_("Scanning files"))
        callback.set_finished_text(_("Scanning finished"))
        callback.set_block(True)

        try:
            local_videos, local_subs = scan_videopaths(paths,
                                                       callback=callback,
                                                       recursive=True)
        except OSError:
            callback.cancel()
            QMessageBox.warning(self, _('Error'),
                                _('Some directories are not accessible.'))

        if callback.canceled():
            return

        callback.finish()

        log.debug("Videos found: %s" % local_videos)
        log.debug("Subtitles found: %s" % local_subs)
        self.hideInstructions()

        QCoreApplication.processEvents()

        if not local_videos:
            QMessageBox.about(self, _("Scan Results"),
                              _("No video has been found!"))
            return

        total = len(local_videos)

        # FIXME: must pass mainwindow as argument to ProgressCallbackWidget
        # callback = ProgressCallbackWidget(self)
        # callback.set_title_text(_("Asking Server..."))
        # callback.set_label_text(_("Searching subtitles..."))
        # callback.set_updated_text(_("Searching subtitles ( %d / %d )"))
        # callback.set_finished_text(_("Search finished"))
        callback.set_block(True)
        callback.set_range(0, total)

        callback.show()

        callback.set_range(0, 2)

        download_callback = callback.get_child_progress(0, 1)
        # videoSearchResults = self.get_state().get_OSDBServer().SearchSubtitles("", videos_piece)
        remote_subs = self.get_state().get_OSDBServer().search_videos(
            videos=local_videos, callback=download_callback)

        self.videoModel.set_videos(local_videos)
        # self.onFilterLanguageVideo(self.ui.filterLanguageForVideo.get_selected_language())

        if remote_subs is None:
            QMessageBox.about(
                self, _("Error"),
                _("Error contacting the server. Please try again later"))
        callback.finish()

        # TODO: CHECK if our local subtitles are already in the server, otherwise suggest to upload
        # self.OSDBServer.CheckSubHash(sub_hashes)

    @pyqtSlot()
    def onButtonPlay(self):
        settings = QSettings()
        programPath = settings.value('options/VideoPlayerPath', '')
        parameters = settings.value('options/VideoPlayerParameters', '')
        if programPath == '':
            QMessageBox.about(
                self, _('Error'),
                _('No default video player has been defined in Settings.'))
            return

        selected_subtitle = self.get_current_selected_item_videomodel()
        if isinstance(selected_subtitle, SubtitleFileNetwork):
            selected_subtitle = selected_subtitle.get_subtitles()[0]

        if isinstance(selected_subtitle, LocalSubtitleFile):
            subtitle_file_path = selected_subtitle.get_filepath()
        elif isinstance(selected_subtitle, RemoteSubtitleFile):
            subtitle_file_path = QDir.temp().absoluteFilePath(
                'subdownloader.tmp.srt')
            log.debug(
                'Temporary subtitle will be downloaded into: {temp_path}'.
                format(temp_path=subtitle_file_path))
            # FIXME: must pass mainwindow as argument to ProgressCallbackWidget
            callback = ProgressCallbackWidget(self)
            callback.set_title_text(_('Playing video + sub'))
            callback.set_label_text(_('Downloading files...'))
            callback.set_finished_text(_('Downloading finished'))
            callback.set_block(True)
            callback.set_range(0, 100)
            callback.show()
            try:
                subtitle_stream = selected_subtitle.download(
                    self.get_state().get_OSDBServer(), callback=callback)
            except ProviderConnectionError:
                log.debug('Unable to download subtitle "{}"'.format(
                    selected_subtitle.get_filename()),
                          exc_info=True)
                QMessageBox.about(
                    self, _('Error'),
                    _('Unable to download subtitle "{subtitle}"').format(
                        subtitle=selected_subtitle.get_filename()))
                callback.finish()
                return
            callback.finish()
            write_stream(subtitle_stream, subtitle_file_path)

        video = selected_subtitle.get_parent().get_parent().get_parent()

        def windows_escape(text):
            return '"{text}"'.format(text=text.replace('"', '\\"'))

        params = [windows_escape(programPath)]

        for param in parameters.split(' '):
            param = param.format(video.get_filepath(), subtitle_file_path)
            if platform.system() in ('Windows', 'Microsoft'):
                param = windows_escape(param)
            params.append(param)

        pid = None
        log.info('Running this command: {params}'.format(params=params))
        try:
            log.debug('Trying os.spawnvpe ...')
            pid = os.spawnvpe(os.P_NOWAIT, programPath, params, os.environ)
            log.debug('... SUCCESS. pid={pid}'.format(pid=pid))
        except AttributeError:
            log.debug('... FAILED', exc_info=True)
        except Exception as e:
            log.debug('... FAILED', exc_info=True)
        if pid is None:
            try:
                log.debug('Trying os.fork ...')
                pid = os.fork()
                if not pid:
                    log.debug('... SUCCESS. pid={pid}'.format(pid=pid))
                    os.execvpe(os.P_NOWAIT, programPath, params, os.environ)
            except:
                log.debug('... FAIL', exc_info=True)
        if pid is None:
            QMessageBox.about(self, _('Error'),
                              _('Unable to launch videoplayer'))

    @pyqtSlot(QModelIndex)
    def onClickVideoTreeView(self, index):
        data_item = self.videoModel.getSelectedItem(index)

        if isinstance(data_item, SubtitleFile):
            self.ui.buttonPlay.setEnabled(True)
        else:
            self.ui.buttonPlay.setEnabled(False)

        if isinstance(data_item, VideoFile):
            video = data_item
            if True:  # video.getMovieInfo():
                self.ui.buttonIMDB.setEnabled(True)
                self.ui.buttonIMDB.setIcon(QIcon(':/images/info.png'))
                self.ui.buttonIMDB.setText(_('Movie Info'))
        elif isinstance(data_item, RemoteSubtitleFile):
            self.ui.buttonIMDB.setEnabled(True)
            self.ui.buttonIMDB.setIcon(
                QIcon(':/images/sites/opensubtitles.png'))
            self.ui.buttonIMDB.setText(_('Subtitle Info'))
        else:
            self.ui.buttonIMDB.setEnabled(False)

    def onContext(self, point):
        # FIXME: code duplication with Main.onContext and/or SearchNameWidget and/or SearchFileWidget
        menu = QMenu('Menu', self)

        listview = self.ui.videoView

        index = listview.currentIndex()
        data_item = listview.model().getSelectedItem(index)
        if data_item is not None:
            if isinstance(data_item, VideoFile):
                video = data_item
                movie_info = video.getMovieInfo()
                if movie_info:
                    subWebsiteAction = QAction(QIcon(":/images/info.png"),
                                               _("View IMDB info"), self)
                    subWebsiteAction.triggered.connect(self.onViewOnlineInfo)
                else:
                    subWebsiteAction = QAction(QIcon(":/images/info.png"),
                                               _("Set IMDB info..."), self)
                    subWebsiteAction.triggered.connect(self.onSetIMDBInfo)
                menu.addAction(subWebsiteAction)
            elif isinstance(data_item, SubtitleFile):
                downloadAction = QAction(QIcon(":/images/download.png"),
                                         _("Download"), self)
                # Video tab, TODO:Replace me with a enum

                downloadAction.triggered.connect(self.onButtonDownload)
                playAction = QAction(QIcon(":/images/play.png"),
                                     _("Play video + subtitle"), self)
                playAction.triggered.connect(self.onButtonPlay)
                menu.addAction(playAction)

                subWebsiteAction = QAction(
                    QIcon(":/images/sites/opensubtitles.png"),
                    _("View online info"), self)

                menu.addAction(downloadAction)
                subWebsiteAction.triggered.connect(self.onViewOnlineInfo)
                menu.addAction(subWebsiteAction)
            elif isinstance(data_item, Movie):
                subWebsiteAction = QAction(QIcon(":/images/info.png"),
                                           _("View IMDB info"), self)
                subWebsiteAction.triggered.connect(self.onViewOnlineInfo)
                menu.addAction(subWebsiteAction)

        # Show the context menu.
        menu.exec_(listview.mapToGlobal(point))

    def onButtonDownload(self):
        # We download the subtitle in the same folder than the video
        subs = self.videoModel.get_checked_subtitles()
        replace_all = False
        skip_all = False
        if not subs:
            QMessageBox.about(self, _("Error"),
                              _("No subtitles selected to be downloaded"))
            return
        total_subs = len(subs)
        answer = None
        success_downloaded = 0

        # FIXME: must pass mainwindow as argument to ProgressCallbackWidget
        callback = ProgressCallbackWidget(self)
        callback.set_title_text(_("Downloading..."))
        callback.set_label_text(_("Downloading files..."))
        callback.set_updated_text(_("Downloading subtitle {0} ({1}/{2})"))
        callback.set_finished_text(
            _("{0} from {1} subtitles downloaded successfully"))
        callback.set_block(True)
        callback.set_range(0, total_subs)

        callback.show()

        for i, sub in enumerate(subs):
            if callback.canceled():
                break
            destinationPath = self.get_state().getDownloadPath(self, sub)
            if not destinationPath:
                break
            log.debug("Trying to download subtitle '%s'" % destinationPath)
            callback.update(i,
                            QFileInfo(destinationPath).fileName(), i + 1,
                            total_subs)

            # Check if we have write permissions, otherwise show warning window
            while True:
                # If the file and the folder don't have write access.
                if not QFileInfo(
                        destinationPath).isWritable() and not QFileInfo(
                            QFileInfo(destinationPath).absoluteDir().path()
                        ).isWritable():
                    warningBox = QMessageBox(
                        _("Error write permission"),
                        _("%s cannot be saved.\nCheck that the folder exists and you have write-access permissions."
                          ) % destinationPath, QMessageBox.Warning,
                        QMessageBox.Retry | QMessageBox.Default,
                        QMessageBox.Discard | QMessageBox.Escape,
                        QMessageBox.NoButton, self)

                    saveAsButton = warningBox.addButton(
                        _("Save as..."), QMessageBox.ActionRole)
                    answer = warningBox.exec_()
                    if answer == QMessageBox.Retry:
                        continue
                    elif answer == QMessageBox.Discard:
                        break  # Let's get out from the While true
                    # If we choose the SAVE AS
                    elif answer == QMessageBox.NoButton:
                        fileName, t = QFileDialog.getSaveFileName(
                            self, _("Save subtitle as..."), destinationPath,
                            'All (*.*)')
                        if fileName:
                            destinationPath = fileName
                else:  # If we have write access we leave the while loop.
                    break

            # If we have chosen Discard subtitle button.
            if answer == QMessageBox.Discard:
                continue  # Continue the next subtitle

            optionWhereToDownload = QSettings().value(
                "options/whereToDownload", "SAME_FOLDER")
            # Check if doesn't exists already, otherwise show fileExistsBox
            # dialog
            if QFileInfo(destinationPath).exists(
            ) and not replace_all and not skip_all and optionWhereToDownload != "ASK_FOLDER":
                # The "remote filename" below is actually not the real filename. Real name could be confusing
                # since we always rename downloaded sub to match movie
                # filename.
                fileExistsBox = QMessageBox(
                    QMessageBox.Warning, _("File already exists"),
                    _("Local: {local}\n\nRemote: {remote}\n\nHow would you like to proceed?"
                      ).format(local=destinationPath,
                               remote=QFileInfo(destinationPath).fileName()),
                    QMessageBox.NoButton, self)
                skipButton = fileExistsBox.addButton(_("Skip"),
                                                     QMessageBox.ActionRole)
                #                    skipAllButton = fileExistsBox.addButton(_("Skip all"), QMessageBox.ActionRole)
                replaceButton = fileExistsBox.addButton(
                    _("Replace"), QMessageBox.ActionRole)
                replaceAllButton = fileExistsBox.addButton(
                    _("Replace all"), QMessageBox.ActionRole)
                saveAsButton = fileExistsBox.addButton(_("Save as..."),
                                                       QMessageBox.ActionRole)
                cancelButton = fileExistsBox.addButton(_("Cancel"),
                                                       QMessageBox.ActionRole)
                fileExistsBox.exec_()
                answer = fileExistsBox.clickedButton()
                if answer == replaceAllButton:
                    # Don't ask us again (for this batch of files)
                    replace_all = True
                elif answer == saveAsButton:
                    # We will find a uniqiue filename and suggest this to user.
                    # add .<lang> to (inside) the filename. If that is not enough, start adding numbers.
                    # There should also be a preferences setting "Autorename
                    # files" or similar ( =never ask) FIXME
                    suggBaseName, suggFileExt = os.path.splitext(
                        destinationPath)
                    fNameCtr = 0  # Counter used to generate a unique filename
                    suggestedFileName = suggBaseName + '.' + \
                        sub.get_language().xxx() + suggFileExt
                    while (os.path.exists(suggestedFileName)):
                        fNameCtr += 1
                        suggestedFileName = suggBaseName + '.' + \
                            sub.get_language().xxx() + '-' + \
                            str(fNameCtr) + suggFileExt
                    fileName, t = QFileDialog.getSaveFileName(
                        None, _("Save subtitle as..."), suggestedFileName,
                        'All (*.*)')
                    if fileName:
                        destinationPath = fileName
                    else:
                        # Skip this particular file if no filename chosen
                        continue
                elif answer == skipButton:
                    continue  # Skip this particular file
#                    elif answer == skipAllButton:
#                        count += percentage
#                        skip_all = True # Skip all files already downloaded
#                        continue
                elif answer == cancelButton:
                    break  # Break out of DL loop - cancel was pushed
            QCoreApplication.processEvents()
            # FIXME: redundant update?
            callback.update(i,
                            QFileInfo(destinationPath).fileName(), i + 1,
                            total_subs)
            try:
                if not skip_all:
                    log.debug("Downloading subtitle '%s'" % destinationPath)
                    download_callback = ProgressCallback()  # FIXME
                    data_stream = sub.download(
                        provider_instance=self.get_state().get_OSDBServer(),
                        callback=download_callback,
                    )
                    write_stream(data_stream, destinationPath)
            except Exception as e:
                log.exception('Unable to Download subtitle {}'.format(
                    sub.get_filename()))
                QMessageBox.about(
                    self, _("Error"),
                    _("Unable to download subtitle %s") % sub.get_filename())
        callback.finish(success_downloaded, total_subs)

    def onViewOnlineInfo(self):
        # FIXME: code duplication with Main.onContext and/or SearchNameWidget and/or SearchFileWidget
        # Tab for SearchByHash TODO:replace this 0 by an ENUM value
        listview = self.ui.videoView
        index = listview.currentIndex()
        data_item = self.videoModel.getSelectedItem(index)

        if isinstance(data_item, VideoFile):
            video = data_item
            movie_info = video.getMovieInfo()
            if movie_info:
                imdb = movie_info["IDMovieImdb"]
                if imdb:
                    webbrowser.open("http://www.imdb.com/title/tt%s" % imdb,
                                    new=2,
                                    autoraise=1)

        elif isinstance(data_item, RemoteSubtitleFile):
            sub = data_item
            webbrowser.open(sub.get_link(), new=2, autoraise=1)

        elif isinstance(data_item, Movie):
            movie = data_item
            imdb = movie.IMDBId
            if imdb:
                webbrowser.open("http://www.imdb.com/title/tt%s" % imdb,
                                new=2,
                                autoraise=1)

    @pyqtSlot()
    def onSetIMDBInfo(self):
        #FIXME: DUPLICATED WITH SEARCHNAMEWIDGET
        QMessageBox.about(self, _("Info"), "Not implemented yet. Sorry...")
Exemplo n.º 17
0
class SortFilterTableView(QTableView):
    def __init__(self, parent=None):
        super().__init__(parent)
        self._proxy_model = QSortFilterProxyModel(self)
        self._proxy_model.setDynamicSortFilter(True)
        super().setModel(self._proxy_model)
        header = FilterHeader(self)
        header.filter_changed.connect(self.set_filter)
        self.setHorizontalHeader(header)
        self.setSortingEnabled(True)
        self.setSelectionMode(QAbstractItemView.ContiguousSelection)
        self.setContextMenuPolicy(Qt.CustomContextMenu)
        self.customContextMenuRequested.connect(self.show_context_menu)
        self.import_export_manager = ImportExportManager(self)
        self.copy_action = create_action(
            None, "Copy", self.copy_selection_to_clipboard)

    def show_context_menu(self, point):
        self.import_export_manager.set_model_index(self.indexAt(point))
        context_menu = QMenu()
        context_menu.addAction(self.copy_action)
        context_menu.addAction(self.import_export_manager.export_action)
        context_menu.addAction(self.import_export_manager.import_action)
        context_menu.exec(self.mapToGlobal(point))

    def keyPressEvent(self, event: QKeyEvent):
        if event.type() == QKeyEvent.KeyPress \
                and event.matches(QKeySequence.Copy):
            self.copy_selection_to_clipboard()
        else:
            super().keyPressEvent(event)

    def copy_selection_to_clipboard(self):
        selected_indexes = self.selectionModel().selectedIndexes()
        if not selected_indexes or len(selected_indexes) == 0:
            return
        model = self.model()
        result = "\n".join(
            "\t".join(row)
            for row in self.selected_rows(model, selected_indexes)
        )
        cp = QApplication.clipboard()
        cp.setText(result)

    def selected_rows(self, model, selected_indexes):
        row = []
        last_row = selected_indexes[0].row()
        for current in selected_indexes:
            value = str(model.data(current, Qt.DisplayRole))
            if last_row != current.row():
                yield row
                row = [value, ]
            else:
                row.append(value)
            last_row = current.row()

    def set_filter(self, section, filter_text):
        log.debug("set_filter(section: %s, filter: %r)", section, filter_text)
        self._proxy_model.setFilterWildcard(filter_text)
        self._proxy_model.setFilterKeyColumn(section)

    def setModel(self, model):
        self.horizontalHeader().set_filter_boxes(model.columnCount())
        self._proxy_model.setSourceModel(model)
        self._proxy_model.sort(0, Qt.AscendingOrder)
        super().setModel(self._proxy_model)
        font = model.data(0, Qt.FontRole)
        if font is None:
            font = self.font()
        metrics = QFontMetrics(font)
        self.verticalHeader().setDefaultSectionSize(metrics.lineSpacing() * 1.5)
        self.horizontalHeader().setDefaultSectionSize(metrics.maxWidth() * 5)
Exemplo n.º 18
0
class TileStampsDock(QDockWidget):
    setStamp = pyqtSignal(TileStamp)
    
    def __init__(self, stampManager, parent = None):
        super().__init__(parent)
        
        self.mTileStampManager = stampManager
        self.mTileStampModel = stampManager.tileStampModel()
        self.mProxyModel = QSortFilterProxyModel(self.mTileStampModel)
        self.mFilterEdit = QLineEdit(self)
        self.mNewStamp = QAction(self)
        self.mAddVariation = QAction(self)
        self.mDuplicate = QAction(self)
        self.mDelete = QAction(self)
        self.mChooseFolder = QAction(self)

        self.setObjectName("TileStampsDock")
        self.mProxyModel.setSortLocaleAware(True)
        self.mProxyModel.setSortCaseSensitivity(Qt.CaseInsensitive)
        self.mProxyModel.setFilterCaseSensitivity(Qt.CaseInsensitive)
        self.mProxyModel.setSourceModel(self.mTileStampModel)
        self.mProxyModel.sort(0)
        self.mTileStampView = TileStampView(self)
        self.mTileStampView.setModel(self.mProxyModel)
        self.mTileStampView.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel)
        self.mTileStampView.header().setStretchLastSection(False)
        self.mTileStampView.header().setSectionResizeMode(0, QHeaderView.Stretch)
        self.mTileStampView.header().setSectionResizeMode(1, QHeaderView.ResizeToContents)
        self.mTileStampView.setContextMenuPolicy(Qt.CustomContextMenu)
        self.mTileStampView.customContextMenuRequested.connect(self.showContextMenu)
        self.mNewStamp.setIcon(QIcon(":images/16x16/document-new.png"))
        self.mAddVariation.setIcon(QIcon(":/images/16x16/add.png"))
        self.mDuplicate.setIcon(QIcon(":/images/16x16/stock-duplicate-16.png"))
        self.mDelete.setIcon(QIcon(":images/16x16/edit-delete.png"))
        self.mChooseFolder.setIcon(QIcon(":images/16x16/document-open.png"))
        Utils.setThemeIcon(self.mNewStamp, "document-new")
        Utils.setThemeIcon(self.mAddVariation, "add")
        Utils.setThemeIcon(self.mDelete, "edit-delete")
        Utils.setThemeIcon(self.mChooseFolder, "document-open")

        self.mFilterEdit.setClearButtonEnabled(True)
        self.mFilterEdit.textChanged.connect(self.mProxyModel.setFilterFixedString)
        self.mTileStampModel.stampRenamed.connect(self.ensureStampVisible)
        self.mNewStamp.triggered.connect(self.newStamp)
        self.mAddVariation.triggered.connect(self.addVariation)
        self.mDuplicate.triggered.connect(self.duplicate)
        self.mDelete.triggered.connect(self.delete_)
        self.mChooseFolder.triggered.connect(self.chooseFolder)
        self.mDuplicate.setEnabled(False)
        self.mDelete.setEnabled(False)
        self.mAddVariation.setEnabled(False)
        widget = QWidget(self)
        layout = QVBoxLayout(widget)
        layout.setContentsMargins(5, 5, 5, 5)
        
        buttonContainer = QToolBar()
        buttonContainer.setFloatable(False)
        buttonContainer.setMovable(False)
        buttonContainer.setIconSize(QSize(16, 16))
        buttonContainer.addAction(self.mNewStamp)
        buttonContainer.addAction(self.mAddVariation)
        buttonContainer.addAction(self.mDuplicate)
        buttonContainer.addAction(self.mDelete)
        stretch = QWidget()
        stretch.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum)
        buttonContainer.addWidget(stretch)
        buttonContainer.addAction(self.mChooseFolder)
        listAndToolBar = QVBoxLayout()
        listAndToolBar.setSpacing(0)
        listAndToolBar.addWidget(self.mFilterEdit)
        listAndToolBar.addWidget(self.mTileStampView)
        listAndToolBar.addWidget(buttonContainer)
        layout.addLayout(listAndToolBar)
        selectionModel = self.mTileStampView.selectionModel()
        selectionModel.currentRowChanged.connect(self.currentRowChanged)
        self.setWidget(widget)
        self.retranslateUi()
        
    def changeEvent(self, e):
        super().changeEvent(e)
        x = e.type()
        if x==QEvent.LanguageChange:
            self.retranslateUi()
        else:
            pass
            
    def keyPressEvent(self, event):
        x = event.key()
        if x==Qt.Key_Delete or x==Qt.Key_Backspace:
            self.delete_()
            return

        super().keyPressEvent(event)

    def currentRowChanged(self, index):
        sourceIndex = self.mProxyModel.mapToSource(index)
        isStamp = self.mTileStampModel.isStamp(sourceIndex)
        self.mDuplicate.setEnabled(isStamp)
        self.mDelete.setEnabled(sourceIndex.isValid())
        self.mAddVariation.setEnabled(isStamp)
        if (isStamp):
            self.setStamp.emit(self.mTileStampModel.stampAt(sourceIndex))
        else:
            variation = self.mTileStampModel.variationAt(sourceIndex)
            if variation:
                # single variation clicked, use it specifically
                self.setStamp.emit(TileStamp(Map(variation.map)))

    def showContextMenu(self, pos):
        index = self.mTileStampView.indexAt(pos)
        if (not index.isValid()):
            return
        menu = QMenu()
        sourceIndex = self.mProxyModel.mapToSource(index)
        if (self.mTileStampModel.isStamp(sourceIndex)):
            addStampVariation = QAction(self.mAddVariation.icon(), self.mAddVariation.text(), menu)
            deleteStamp = QAction(self.mDelete.icon(), self.tr("Delete Stamp"), menu)
            deleteStamp.triggered.connect(self.delete_)
            addStampVariation.triggered.connect(self.addVariation)
            menu.addAction(addStampVariation)
            menu.addSeparator()
            menu.addAction(deleteStamp)
        else :
            removeVariation = QAction(QIcon(":/images/16x16/remove.png"),
                                                   self.tr("Remove Variation"),
                                                   menu)
            Utils.setThemeIcon(removeVariation, "remove")
            removeVariation.triggered.connect(self.delete_)
            menu.addAction(removeVariation)
        
        menu.exec(self.mTileStampView.viewport().mapToGlobal(pos))
    
    def newStamp(self):
        stamp = self.mTileStampManager.createStamp()
        if (self.isVisible() and not stamp.isEmpty()):
            stampIndex = self.mTileStampModel.index(stamp)
            if (stampIndex.isValid()):
                viewIndex = self.mProxyModel.mapFromSource(stampIndex)
                self.mTileStampView.setCurrentIndex(viewIndex)
                self.mTileStampView.edit(viewIndex)

    
    def delete_(self):
        index = self.mTileStampView.currentIndex()
        if (not index.isValid()):
            return
        sourceIndex = self.mProxyModel.mapToSource(index)
        self.mTileStampModel.removeRow(sourceIndex.row(), sourceIndex.parent())
    
    def duplicate(self):
        index = self.mTileStampView.currentIndex()
        if (not index.isValid()):
            return
        sourceIndex = self.mProxyModel.mapToSource(index)
        if (not self.mTileStampModel.isStamp(sourceIndex)):
            return
        stamp = self.mTileStampModel.stampAt = TileStamp(sourceIndex)
        self.mTileStampModel.addStamp(stamp.clone())

    def addVariation(self):
        index = self.mTileStampView.currentIndex()
        if (not index.isValid()):
            return
        sourceIndex = self.mProxyModel.mapToSource(index)
        if (not self.mTileStampModel.isStamp(sourceIndex)):
            return
        stamp = self.mTileStampModel.stampAt(sourceIndex)
        self.mTileStampManager.addVariation(stamp)
    
    def chooseFolder(self):
        prefs = Preferences.instance()
        stampsDirectory = prefs.stampsDirectory()
        stampsDirectory = QFileDialog.getExistingDirectory(self.window(),
                                                            self.tr("Choose the Stamps Folder"),
                                                            stampsDirectory)
        if (not stampsDirectory.isEmpty()):
            prefs.setStampsDirectory(stampsDirectory)
    
    def ensureStampVisible(self, stamp):
        stampIndex = self.mTileStampModel.index(stamp)
        if (stampIndex.isValid()):
            self.mTileStampView.scrollTo(self.mProxyModel.mapFromSource(stampIndex))

    def retranslateUi(self):
        self.setWindowTitle(self.tr("Tile Stamps"))
        self.mNewStamp.setText(self.tr("Add New Stamp"))
        self.mAddVariation.setText(self.tr("Add Variation"))
        self.mDuplicate.setText(self.tr("Duplicate Stamp"))
        self.mDelete.setText(self.tr("Delete Selected"))
        self.mChooseFolder.setText(self.tr("Set Stamps Folder"))
        self.mFilterEdit.setPlaceholderText(self.tr("Filter"))
Exemplo n.º 19
0
class SearchFileWidget(QWidget):

    language_filter_change = pyqtSignal(list)

    def __init__(self):
        QWidget.__init__(self)

        self._refreshing = False

        self.fileModel = None
        self.proxyFileModel = None
        self.videoModel = None

        self._state = None

        self.timeLastSearch = QTime.currentTime()

        self.ui = Ui_SearchFileWidget()
        self.setup_ui()
        QTimer.singleShot(0, self.on_event_loop_started)

    @pyqtSlot()
    def on_event_loop_started(self):
        lastDir = self._state.get_video_path()
        index = self.fileModel.index(str(lastDir))
        proxyIndex = self.proxyFileModel.mapFromSource(index)
        self.ui.folderView.scrollTo(proxyIndex)

    def set_state(self, state):
        self._state = state
        self._state.signals.interface_language_changed.connect(
            self.on_interface_language_changed)
        self._state.signals.login_status_changed.connect(
            self.on_login_state_changed)

    def setup_ui(self):
        self.ui.setupUi(self)

        self.ui.splitter.setSizes([600, 1000])
        self.ui.splitter.setChildrenCollapsible(False)

        # Set up folder view

        self.fileModel = QFileSystemModel(self)
        self.fileModel.setFilter(QDir.AllDirs | QDir.Dirs | QDir.Drives
                                 | QDir.NoDotAndDotDot | QDir.Readable
                                 | QDir.Executable | QDir.Writable)
        self.fileModel.iconProvider().setOptions(
            QFileIconProvider.DontUseCustomDirectoryIcons)
        self.fileModel.setRootPath(QDir.rootPath())
        self.fileModel.directoryLoaded.connect(self.onFileModelDirectoryLoaded)

        self.proxyFileModel = QSortFilterProxyModel(self)
        self.proxyFileModel.setSortRole(Qt.DisplayRole)
        self.proxyFileModel.setSourceModel(self.fileModel)
        self.proxyFileModel.sort(0, Qt.AscendingOrder)
        self.proxyFileModel.setSortCaseSensitivity(Qt.CaseInsensitive)
        self.ui.folderView.setModel(self.proxyFileModel)

        self.ui.folderView.setHeaderHidden(True)
        self.ui.folderView.hideColumn(3)
        self.ui.folderView.hideColumn(2)
        self.ui.folderView.hideColumn(1)

        self.ui.folderView.expanded.connect(self.onFolderViewExpanded)
        self.ui.folderView.clicked.connect(self.onFolderTreeClicked)
        self.ui.buttonFind.clicked.connect(self.onButtonFind)
        self.ui.buttonRefresh.clicked.connect(self.onButtonRefresh)

        # Setup and disable buttons
        self.ui.buttonFind.setEnabled(False)
        self.ui.buttonSearchSelectFolder.setEnabled(False)
        self.ui.buttonSearchSelectVideos.setEnabled(False)

        # Set up introduction
        self.showInstructions()

        # Set unknown  text here instead of `retranslate()` because widget translates itself
        self.ui.filterLanguageForVideo.set_unknown_text(_('All languages'))
        self.ui.filterLanguageForVideo.set_selected_language(
            UnknownLanguage.create_generic())
        self.ui.filterLanguageForVideo.selected_language_changed.connect(
            self.on_language_combobox_filter_change)

        # Set up video view
        self.videoModel = VideoModel(self)
        self.videoModel.connect_treeview(self.ui.videoView)
        self.ui.videoView.setHeaderHidden(True)
        self.ui.videoView.clicked.connect(self.onClickVideoTreeView)
        self.ui.videoView.selectionModel().currentChanged.connect(
            self.onSelectVideoTreeView)
        self.ui.videoView.customContextMenuRequested.connect(self.onContext)
        self.ui.videoView.setUniformRowHeights(True)
        self.videoModel.dataChanged.connect(self.subtitlesCheckedChanged)
        self.language_filter_change.connect(
            self.videoModel.on_filter_languages_change)

        self.ui.buttonSearchSelectVideos.clicked.connect(
            self.onButtonSearchSelectVideos)
        self.ui.buttonSearchSelectFolder.clicked.connect(
            self.onButtonSearchSelectFolder)
        self.ui.buttonDownload.clicked.connect(self.onButtonDownload)
        self.ui.buttonPlay.clicked.connect(self.onButtonPlay)
        self.ui.buttonIMDB.clicked.connect(self.onViewOnlineInfo)
        self.ui.videoView.setContextMenuPolicy(Qt.CustomContextMenu)

        self.ui.buttonPlay.setEnabled(False)

        # Drag and Drop files to the videoView enabled
        # FIXME: enable drag events for videoView (and instructions view)
        self.ui.videoView.__class__.dragEnterEvent = self.dragEnterEvent
        self.ui.videoView.__class__.dragMoveEvent = self.dragEnterEvent
        self.ui.videoView.__class__.dropEvent = self.dropEvent
        self.ui.videoView.setAcceptDrops(1)

        self.retranslate()

    def retranslate(self):
        introduction = '<p align="center"><h2>{title}</h2></p>' \
            '<p><b>{tab1header}</b><br/>{tab1content}</p>' \
            '<p><b>{tab2header}</b><br/>{tab2content}</p>'\
            '<p><b>{tab3header}</b><br/>{tab3content}</p>'.format(
                title=_('How To Use {title}').format(title=PROJECT_TITLE),
                tab1header=_('1st Tab:'),
                tab2header=_('2nd Tab:'),
                tab3header=_('3rd Tab:'),
                tab1content=_('Select, from the Folder Tree on the left, the folder which contains the videos '
                              'that need subtitles. {project} will then try to automatically find available '
                              'subtitles.').format(project=PROJECT_TITLE),
                tab2content=_('If you don\'t have the videos in your machine, you can search subtitles by '
                              'introducing the title/name of the video.').format(project=PROJECT_TITLE),
                tab3content=_('If you have found some subtitle somewhere else that is not in {project}\'s database, '
                              'please upload those subtitles so next users will be able to '
                              'find them more easily.').format(project=PROJECT_TITLE))
        self.ui.introductionHelp.setHtml(introduction)

    @pyqtSlot(Language)
    def on_interface_language_changed(self, language):
        self.ui.retranslateUi(self)
        self.retranslate()

    @pyqtSlot(str)
    def onFileModelDirectoryLoaded(self, path):
        lastDir = str(self._state.get_video_path())
        qDirLastDir = QDir(lastDir)
        qDirLastDir.cdUp()
        if qDirLastDir.path() == path:
            index = self.fileModel.index(lastDir)
            proxyIndex = self.proxyFileModel.mapFromSource(index)
            self.ui.folderView.scrollTo(proxyIndex)
            self.ui.folderView.setCurrentIndex(proxyIndex)

    @pyqtSlot()
    def on_login_state_changed(self):
        log.debug('on_login_state_changed()')
        nb_connected = self._state.providers.get_number_connected_providers()
        if nb_connected:
            self.ui.buttonSearchSelectFolder.setEnabled(True)
            self.ui.buttonSearchSelectVideos.setEnabled(True)
            self.ui.buttonFind.setEnabled(
                self.get_current_selected_folder() is not None)
        else:
            self.ui.buttonSearchSelectFolder.setEnabled(False)
            self.ui.buttonSearchSelectVideos.setEnabled(False)
            self.ui.buttonFind.setEnabled(False)

    @pyqtSlot(Language)
    def on_language_combobox_filter_change(self, language):
        if language.is_generic():
            self.language_filter_change.emit(
                self._state.get_download_languages())
        else:
            self.language_filter_change.emit([language])

    def on_permanent_language_filter_change(self, languages):
        selected_language = self.ui.filterLanguageForVideo.get_selected_language(
        )
        if selected_language.is_generic():
            self.language_filter_change.emit(languages)

    @pyqtSlot()
    def subtitlesCheckedChanged(self):
        subs = self.videoModel.get_checked_subtitles()
        if subs:
            self.ui.buttonDownload.setEnabled(True)
        else:
            self.ui.buttonDownload.setEnabled(False)

    def showInstructions(self):
        self.ui.stackedSearchResult.setCurrentWidget(self.ui.pageIntroduction)

    def hideInstructions(self):
        self.ui.stackedSearchResult.setCurrentWidget(self.ui.pageSearchResult)

    @pyqtSlot(QModelIndex)
    def onFolderTreeClicked(self, proxyIndex):
        """What to do when a Folder in the tree is clicked"""
        if not proxyIndex.isValid():
            return

        index = self.proxyFileModel.mapToSource(proxyIndex)
        folder_path = self.fileModel.filePath(index)
        self._state.set_video_paths([folder_path])

    def get_current_selected_folder(self):
        proxyIndex = self.ui.folderView.currentIndex()
        index = self.proxyFileModel.mapToSource(proxyIndex)
        folder_path = Path(self.fileModel.filePath(index))
        if not folder_path:
            return None
        return folder_path

    def get_current_selected_item_videomodel(self):
        current_index = self.ui.videoView.currentIndex()
        return self.videoModel.getSelectedItem(current_index)

    @pyqtSlot()
    def onButtonFind(self):
        now = QTime.currentTime()
        if now < self.timeLastSearch.addMSecs(500):
            return
        folder_path = self.get_current_selected_folder()

        self._state.set_video_paths([folder_path])
        self.search_videos([folder_path])

        self.timeLastSearch = QTime.currentTime()

    @pyqtSlot()
    def onButtonRefresh(self):
        currentPath = self.get_current_selected_folder()
        self._refreshing = True

        self.ui.folderView.collapseAll()

        currentPath = self.get_current_selected_folder()
        if not currentPath:
            self._state.set_video_paths([currentPath])

        index = self.fileModel.index(str(currentPath))

        self.ui.folderView.scrollTo(self.proxyFileModel.mapFromSource(index))

    @pyqtSlot(QModelIndex)
    def onFolderViewExpanded(self, proxyIndex):
        if self._refreshing:
            expandedPath = self.fileModel.filePath(
                self.proxyFileModel.mapToSource(proxyIndex))
            if expandedPath == QDir.rootPath():
                currentPath = self.get_current_selected_folder()
                if not currentPath:
                    currentPath = self._state.get_video_path()

                index = self.fileModel.index(str(currentPath))

                self.ui.folderView.scrollTo(
                    self.proxyFileModel.mapFromSource(index))
                self._refreshing = False

    @pyqtSlot()
    def onButtonSearchSelectFolder(self):
        paths = self._state.get_video_paths()
        path = paths[0] if paths else Path()
        selected_path = QFileDialog.getExistingDirectory(
            self, _('Select the directory that contains your videos'),
            str(path))
        if selected_path:
            selected_paths = [Path(selected_path)]
            self._state.set_video_paths(selected_paths)
            self.search_videos(selected_paths)

    @pyqtSlot()
    def onButtonSearchSelectVideos(self):
        paths = self._state.get_video_paths()
        path = paths[0] if paths else Path()
        selected_files, t = QFileDialog.getOpenFileNames(
            self, _('Select the video(s) that need subtitles'), str(path),
            get_select_videos())
        if selected_files:
            selected_files = list(Path(f) for f in selected_files)
            selected_dirs = list(set(p.parent for p in selected_files))
            self._state.set_video_paths(selected_dirs)
            self.search_videos(selected_files)

    def search_videos(self, paths):
        if not self._state.providers.get_number_connected_providers():
            QMessageBox.about(
                self, _('Error'),
                _('You are not connected to a server. Please connect first.'))
            return
        self.ui.buttonFind.setEnabled(False)
        self._search_videos_raw(paths)
        self.ui.buttonFind.setEnabled(True)

    def _search_videos_raw(self, paths):
        # FIXME: must pass mainwindow as argument to ProgressCallbackWidget
        callback = ProgressCallbackWidget(self)
        callback.set_title_text(_('Scanning...'))
        callback.set_label_text(_('Scanning files'))
        callback.set_finished_text(_('Scanning finished'))
        callback.set_block(True)
        callback.show()

        try:
            local_videos, local_subs = scan_videopaths(paths,
                                                       callback=callback,
                                                       recursive=True)
        except OSError:
            callback.cancel()
            QMessageBox.warning(self, _('Error'),
                                _('Some directories are not accessible.'))

        if callback.canceled():
            return

        callback.finish()

        log.debug('Videos found: {}'.format(local_videos))
        log.debug('Subtitles found: {}'.format(local_subs))

        self.hideInstructions()

        if not local_videos:
            QMessageBox.information(self, _('Scan Results'),
                                    _('No video has been found.'))
            return

        total = len(local_videos)

        # FIXME: must pass mainwindow as argument to ProgressCallbackWidget
        # callback = ProgressCallbackWidget(self)
        # callback.set_title_text(_('Asking Server...'))
        # callback.set_label_text(_('Searching subtitles...'))
        # callback.set_updated_text(_('Searching subtitles ( %d / %d )'))
        # callback.set_finished_text(_('Search finished'))
        callback.set_block(True)
        callback.set_range(0, total)

        callback.show()

        try:
            remote_subs = self._state.search_videos(local_videos, callback)
        except ProviderConnectionError:
            log.debug(
                'Unable to search for subtitles of videos: videos={}'.format(
                    list(v.get_filename() for v in local_videos)))
            QMessageBox.about(self, _('Error'),
                              _('Unable to search for subtitles'))
            callback.finish()
            return

        self.videoModel.set_videos(local_videos)

        if remote_subs is None:
            QMessageBox.about(
                self, _('Error'),
                _('Error contacting the server. Please try again later'))
        callback.finish()

        # TODO: CHECK if our local subtitles are already in the server, otherwise suggest to upload

    @pyqtSlot()
    def onButtonPlay(self):
        selected_item = self.get_current_selected_item_videomodel()
        log.debug('Trying to play selected item: {}'.format(selected_item))

        if selected_item is None:
            QMessageBox.warning(self, _('No subtitle selected'),
                                _('Select a subtitle and try again'))
            return

        if isinstance(selected_item, SubtitleFileNetwork):
            selected_item = selected_item.get_subtitles()[0]

        if isinstance(selected_item, VideoFile):
            subtitle_file_path = None
            video = selected_item
        elif isinstance(selected_item, LocalSubtitleFile):
            subtitle_file_path = selected_item.get_filepath()
            video = selected_item.get_super_parent(VideoFile)
        elif isinstance(selected_item, RemoteSubtitleFile):
            video = selected_item.get_super_parent(VideoFile)
            subtitle_file_path = Path(
                tempfile.gettempdir()) / 'subdownloader.tmp.srt'
            log.debug('tmp path is {}'.format(subtitle_file_path))
            log.debug(
                'Temporary subtitle will be downloaded into: {temp_path}'.
                format(temp_path=subtitle_file_path))
            # FIXME: must pass mainwindow as argument to ProgressCallbackWidget
            callback = ProgressCallbackWidget(self)
            callback.set_title_text(_('Playing video + sub'))
            callback.set_label_text(_('Downloading files...'))
            callback.set_finished_text(_('Downloading finished'))
            callback.set_block(True)
            callback.set_range(0, 100)
            callback.show()

            try:
                selected_item.download(
                    subtitle_file_path,
                    self._state.providers.get(selected_item.get_provider),
                    callback)
            except ProviderConnectionError:
                log.debug('Unable to download subtitle "{}"'.format(
                    selected_item.get_filename()),
                          exc_info=sys.exc_info())
                QMessageBox.about(
                    self, _('Error'),
                    _('Unable to download subtitle "{subtitle}"').format(
                        subtitle=selected_item.get_filename()))
                callback.finish()
                return
            callback.finish()
        else:
            QMessageBox.about(
                self, _('Error'),
                '{}\n{}'.format(_('Unknown Error'),
                                _('Please submit bug report')))
            return

        # video = selected_item.get_parent().get_parent().get_parent()
        # FIXME: download subtitle with provider + use returned localSubtitleFile instead of creating one here
        if subtitle_file_path:
            local_subtitle = LocalSubtitleFile(subtitle_file_path)
        else:
            local_subtitle = None
        try:
            player = self._state.get_videoplayer()
            player.play_video(video, local_subtitle)
        except RuntimeError as e:
            QMessageBox.about(self, _('Error'), e.args[0])

    @pyqtSlot(QModelIndex)
    def onClickVideoTreeView(self, index):
        data_item = self.videoModel.getSelectedItem(index)

        if isinstance(data_item, VideoFile):
            video = data_item
            if True:  # video.getMovieInfo():
                self.ui.buttonIMDB.setEnabled(True)
                self.ui.buttonIMDB.setIcon(QIcon(':/images/info.png'))
                self.ui.buttonIMDB.setText(_('Movie Info'))
        elif isinstance(data_item, RemoteSubtitleFile):
            self.ui.buttonIMDB.setEnabled(True)
            self.ui.buttonIMDB.setIcon(
                QIcon(':/images/sites/opensubtitles.png'))
            self.ui.buttonIMDB.setText(_('Subtitle Info'))
        else:
            self.ui.buttonIMDB.setEnabled(False)

    @pyqtSlot(QModelIndex)
    def onSelectVideoTreeView(self, index):
        data_item = self.videoModel.getSelectedItem(index)

        self.ui.buttonPlay.setEnabled(True)
        # if isinstance(data_item, SubtitleFile):
        #     self.ui.buttonPlay.setEnabled(True)
        # else:
        #     self.ui.buttonPlay.setEnabled(False)

    def onContext(self, point):
        # FIXME: code duplication with Main.onContext and/or SearchNameWidget and/or SearchFileWidget
        menu = QMenu('Menu', self)

        listview = self.ui.videoView

        index = listview.currentIndex()
        data_item = listview.model().getSelectedItem(index)
        if data_item is not None:
            if isinstance(data_item, VideoFile):
                video = data_item
                video_identities = video.get_identities()
                if any(video_identities.iter_imdb_identity()):
                    online_action = QAction(QIcon(":/images/info.png"),
                                            _("View IMDb info"), self)
                    online_action.triggered.connect(self.onViewOnlineInfo)
                else:
                    online_action = QAction(QIcon(":/images/info.png"),
                                            _("Set IMDb info..."), self)
                    online_action.triggered.connect(self.on_set_imdb_info)
                menu.addAction(online_action)
            elif isinstance(data_item, SubtitleFile):
                play_action = QAction(QIcon(":/images/play.png"),
                                      _("Play video + subtitle"), self)
                play_action.triggered.connect(self.onButtonPlay)
                menu.addAction(play_action)

                if isinstance(data_item, RemoteSubtitleFile):
                    download_action = QAction(QIcon(":/images/download.png"),
                                              _("Download"), self)
                    download_action.triggered.connect(self.onButtonDownload)
                    menu.addAction(download_action)

                    online_action = QAction(
                        QIcon(":/images/sites/opensubtitles.png"),
                        _("View online info"), self)
                    online_action.triggered.connect(self.onViewOnlineInfo)
                    menu.addAction(online_action)

        # Show the context menu.
        menu.exec_(listview.mapToGlobal(point))

    def _create_choose_target_subtitle_path_cb(self):
        def callback(path, filename):
            selected_path = QFileDialog.getSaveFileName(
                self, _('Choose the target filename'), str(path / filename))
            return selected_path

        return callback

    def onButtonDownload(self):
        # We download the subtitle in the same folder than the video
        rsubs = self.videoModel.get_checked_subtitles()

        sub_downloader = SubtitleDownloadProcess(parent=self.parent(),
                                                 rsubtitles=rsubs,
                                                 state=self._state,
                                                 parent_add=True)
        sub_downloader.download_all()
        new_subs = sub_downloader.downloaded_subtitles()
        self.videoModel.uncheck_subtitles(new_subs)

    def onViewOnlineInfo(self):
        # FIXME: code duplication with Main.onContext and/or SearchNameWidget and/or SearchFileWidget
        # Tab for SearchByHash TODO:replace this 0 by an ENUM value
        listview = self.ui.videoView
        index = listview.currentIndex()
        data_item = self.videoModel.getSelectedItem(index)

        if isinstance(data_item, VideoFile):
            video = data_item
            video_identities = video.get_identities()
            if any(video_identities.iter_imdb_identity()):
                imdb_identity = next(video_identities.iter_imdb_identity())
                webbrowser.open(imdb_identity.get_imdb_url(),
                                new=2,
                                autoraise=1)
            else:
                QMessageBox.information(self.parent(), _('imdb unknown'),
                                        _('imdb is unknown'))

        elif isinstance(data_item, RemoteSubtitleFile):
            sub = data_item
            webbrowser.open(sub.get_link(), new=2, autoraise=1)

    @pyqtSlot()
    def on_set_imdb_info(self):
        # FIXME: DUPLICATED WITH SEARCHNAMEWIDGET
        QMessageBox.about(self, _("Info"), "Not implemented yet. Sorry...")
Exemplo n.º 20
0
class Explorer(QWidget):
    """
    This class implements the diagram predicate node explorer.
    """
    def __init__(self, mainwindow):
        """
        Initialize the Explorer.
        :type mainwindow: MainWindow
        """
        super().__init__(mainwindow)
        self.expanded = {}
        self.searched = {}
        self.scrolled = {}
        self.mainview = None
        self.iconA = QIcon(':/icons/treeview-icon-attribute')
        self.iconC = QIcon(':/icons/treeview-icon-concept')
        self.iconD = QIcon(':/icons/treeview-icon-datarange')
        self.iconI = QIcon(':/icons/treeview-icon-instance')
        self.iconR = QIcon(':/icons/treeview-icon-role')
        self.iconV = QIcon(':/icons/treeview-icon-value')
        self.search = StringField(self)
        self.search.setAcceptDrops(False)
        self.search.setClearButtonEnabled(True)
        self.search.setPlaceholderText('Search...')
        self.search.setFixedHeight(30)
        self.model = QStandardItemModel(self)
        self.proxy = QSortFilterProxyModel(self)
        self.proxy.setDynamicSortFilter(False)
        self.proxy.setFilterCaseSensitivity(Qt.CaseInsensitive)
        self.proxy.setSortCaseSensitivity(Qt.CaseSensitive)
        self.proxy.setSourceModel(self.model)
        self.view = ExplorerView(mainwindow, self)
        self.view.setModel(self.proxy)
        self.mainLayout = QVBoxLayout(self)
        self.mainLayout.setContentsMargins(0, 0, 0, 0)
        self.mainLayout.addWidget(self.search)
        self.mainLayout.addWidget(self.view)
        self.setContentsMargins(0, 0, 0, 0)
        self.setMinimumWidth(216)
        self.setMinimumHeight(160)

        connect(self.view.doubleClicked, self.itemDoubleClicked)
        connect(self.view.pressed, self.itemPressed)
        connect(self.view.collapsed, self.itemCollapsed)
        connect(self.view.expanded, self.itemExpanded)
        connect(self.search.textChanged, self.filterItem)

    ####################################################################################################################
    #                                                                                                                  #
    #   EVENTS                                                                                                         #
    #                                                                                                                  #
    ####################################################################################################################

    def paintEvent(self, paintEvent):
        """
        This is needed for the widget to pick the stylesheet.
        :type paintEvent: QPaintEvent
        """
        option = QStyleOption()
        option.initFrom(self)
        painter = QPainter(self)
        style = self.style()
        style.drawPrimitive(QStyle.PE_Widget, option, painter, self)

    ####################################################################################################################
    #                                                                                                                  #
    #   SLOTS                                                                                                          #
    #                                                                                                                  #
    ####################################################################################################################

    @pyqtSlot('QGraphicsItem')
    def add(self, item):
        """
        Add a node in the tree view.
        :type item: AbstractItem
        """
        if item.node and item.predicate:
            parent = self.parentFor(item)
            if not parent:
                parent = ParentItem(item)
                parent.setIcon(self.iconFor(item))
                self.model.appendRow(parent)
                self.proxy.sort(0, Qt.AscendingOrder)
            child = ChildItem(item)
            child.setData(item)
            parent.appendRow(child)
            self.proxy.sort(0, Qt.AscendingOrder)

    @pyqtSlot(str)
    def filterItem(self, key):
        """
        Executed when the search box is filled with data.
        :type key: str
        """
        if self.mainview:
            self.proxy.setFilterFixedString(key)
            self.proxy.sort(Qt.AscendingOrder)
            self.searched[self.mainview] = key

    @pyqtSlot('QModelIndex')
    def itemCollapsed(self, index):
        """
        Executed when an item in the tree view is collapsed.
        :type index: QModelIndex
        """
        if self.mainview:
            if self.mainview in self.expanded:
                item = self.model.itemFromIndex(self.proxy.mapToSource(index))
                expanded = self.expanded[self.mainview]
                expanded.remove(item.text())

    @pyqtSlot('QModelIndex')
    def itemDoubleClicked(self, index):
        """
        Executed when an item in the tree view is double clicked.
        :type index: QModelIndex
        """
        item = self.model.itemFromIndex(self.proxy.mapToSource(index))
        node = item.data()
        if node:
            self.selectNode(node)
            self.focusNode(node)

    @pyqtSlot('QModelIndex')
    def itemExpanded(self, index):
        """
        Executed when an item in the tree view is expanded.
        :type index: QModelIndex
        """
        if self.mainview:
            item = self.model.itemFromIndex(self.proxy.mapToSource(index))
            if self.mainview not in self.expanded:
                self.expanded[self.mainview] = set()
            expanded = self.expanded[self.mainview]
            expanded.add(item.text())

    @pyqtSlot('QModelIndex')
    def itemPressed(self, index):
        """
        Executed when an item in the tree view is clicked.
        :type index: QModelIndex
        """
        item = self.model.itemFromIndex(self.proxy.mapToSource(index))
        node = item.data()
        if node:
            self.selectNode(node)

    @pyqtSlot('QGraphicsItem')
    def remove(self, item):
        """
        Remove a node from the tree view.
        :type item: AbstractItem
        """
        if item.node and item.predicate:
            parent = self.parentFor(item)
            if parent:
                child = self.childFor(parent, item)
                if child:
                    parent.removeRow(child.index().row())
                if not parent.rowCount():
                    self.model.removeRow(parent.index().row())

    ####################################################################################################################
    #                                                                                                                  #
    #   AUXILIARY METHODS                                                                                              #
    #                                                                                                                  #
    ####################################################################################################################

    @staticmethod
    def childFor(parent, node):
        """
        Search the item representing this node among parent children.
        :type parent: QStandardItem
        :type node: AbstractNode
        """
        key = ChildItem.key(node)
        for i in range(parent.rowCount()):
            child = parent.child(i)
            if child.text() == key:
                return child
        return None

    def parentFor(self, node):
        """
        Search the parent element of the given node.
        :type node: AbstractNode
        :rtype: QStandardItem
        """
        key = ParentItem.key(node)
        for i in self.model.findItems(key, Qt.MatchExactly):
            n = i.child(0).data()
            if node.item is n.item:
                return i
        return None

    ####################################################################################################################
    #                                                                                                                  #
    #   INTERFACE                                                                                                      #
    #                                                                                                                  #
    ####################################################################################################################

    def browse(self, view):
        """
        Set the widget to inspect the given view.
        :type view: MainView
        """
        self.reset()
        self.mainview = view

        if self.mainview:

            scene = self.mainview.scene()
            connect(scene.index.sgnItemAdded, self.add)
            connect(scene.index.sgnItemRemoved, self.remove)

            for item in scene.index.nodes():
                self.add(item)

            if self.mainview in self.expanded:
                expanded = self.expanded[self.mainview]
                for i in range(self.model.rowCount()):
                    item = self.model.item(i)
                    index = self.proxy.mapFromSource(
                        self.model.indexFromItem(item))
                    self.view.setExpanded(index, item.text() in expanded)

            key = ''
            if self.mainview in self.searched:
                key = self.searched[self.mainview]
            self.search.setText(key)

            if self.mainview in self.scrolled:
                rect = self.rect()
                item = first(self.model.findItems(
                    self.scrolled[self.mainview]))
                for i in range(self.model.rowCount()):
                    self.view.scrollTo(
                        self.proxy.mapFromSource(
                            self.model.indexFromItem(self.model.item(i))))
                    index = self.proxy.mapToSource(
                        self.view.indexAt(rect.topLeft()))
                    if self.model.itemFromIndex(index) is item:
                        break

    def reset(self):
        """
        Clear the widget from inspecting the current view.
        """
        if self.mainview:

            rect = self.rect()
            item = self.model.itemFromIndex(
                self.proxy.mapToSource(self.view.indexAt(rect.topLeft())))
            if item:
                node = item.data()
                key = ParentItem.key(node) if node else item.text()
                self.scrolled[self.mainview] = key
            else:
                self.scrolled.pop(self.mainview, None)

            try:
                scene = self.mainview.scene()
                disconnect(scene.index.sgnItemAdded, self.add)
                disconnect(scene.index.sgnItemRemoved, self.remove)
            except RuntimeError:
                pass
            finally:
                self.mainview = None

        self.model.clear()

    def flush(self, view):
        """
        Flush the cache of the given mainview.
        :type view: MainView
        """
        self.expanded.pop(view, None)
        self.searched.pop(view, None)
        self.scrolled.pop(view, None)

    def iconFor(self, node):
        """
        Returns the icon for the given node.
        :type node:
        """
        if node.item is Item.AttributeNode:
            return self.iconA
        if node.item is Item.ConceptNode:
            return self.iconC
        if node.item is Item.ValueDomainNode:
            return self.iconD
        if node.item is Item.ValueRestrictionNode:
            return self.iconD
        if node.item is Item.IndividualNode:
            if node.identity is Identity.Instance:
                return self.iconI
            if node.identity is Identity.Value:
                return self.iconV
        if node.item is Item.RoleNode:
            return self.iconR

    def focusNode(self, node):
        """
        Focus the given node in the main view.
        :type node: AbstractNode
        """
        if self.mainview:
            self.mainview.centerOn(node)

    def selectNode(self, node):
        """
        Select the given node in the main view.
        :type node: AbstractNode
        """
        if self.mainview:
            scene = self.mainview.scene()
            scene.clearSelection()
            node.setSelected(True)
Exemplo n.º 21
0
class MainApp(QtWidgets.QMainWindow, design.Ui_MainWindow):
    def bind_copy_actions(self) -> None:
        def copy_list(model: QStandardItemModel):
            def func(_):
                copy_model_to_clipboard(model)

            return func

        self.db_created_at_label.mousePressEvent = copy_list(self.db_created_at_list_model)
        self.db_columns_label.mousePressEvent = copy_list(self.db_columns_list_model)
        self.db_partitions_label.mousePressEvent = copy_list(self.db_partitions_list_model)
        self.db_based_on_label.mousePressEvent = copy_list(self.db_based_on_list_model)
        self.db_updated_at_label.mousePressEvent = copy_list(self.db_updated_at_list_model)
        self.db_used_in_label.mousePressEvent = copy_list(self.db_used_in_list_model)
        self.wf_predecessors_label.mousePressEvent = copy_list(self.wf_predecessors_list_model)
        self.wf_descendants_label.mousePressEvent = copy_list(self.wf_predecessors_list_model)
        self.wf_source_label.mousePressEvent = copy_list(self.wf_source_list_model)
        self.wf_effected_label.mousePressEvent = copy_list(self.wf_effected_list_model)

    def context_menu_requested(self, view: QListView, reverse_target: bool = False):
        def func(position):
            menu: QMenu = QMenu(self)
            related_tables_action: QAction = menu.addAction('Show related tables')
            related_tables_action.triggered.connect(self.show_related_tables(view, reverse_target))
            menu.exec_(view.viewport().mapToGlobal(position))

        return func

    def create_list_view_dialog(self) -> (QDialog, QStandardItemModel):
        dialog: QDialog = QDialog(self)
        dialog.setWindowTitle('Related tables')
        dialog.resize(800, 600)
        dialog.setMinimumSize(600, 400)
        layout: QVBoxLayout = QVBoxLayout(dialog)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)
        list_view: QListView = QListView()
        list_view.resize(400, 400)
        list_view_model: QStandardItemModel = QStandardItemModel(list_view)
        list_view.setModel(list_view_model)
        save_button: QPushButton = QPushButton('Copy to clipboard')
        save_button.clicked.connect(lambda _: copy_model_to_clipboard(list_view_model))
        layout.addWidget(list_view)
        layout.addWidget(save_button)
        dialog.setLayout(layout)
        return dialog, list_view_model

    def show_related_tables(self, view: QListView, reverse_target: bool):
        def func():
            base_wf: str = view.selectionModel().selectedIndexes()[0].data(Qt.DisplayRole)
            target_wf: str = self.current_workflow.name
            if reverse_target:
                target_wf = base_wf
                base_wf = self.current_workflow.name
            related_tables: List[str] = self.store.get_related_tables(target_wf, base_wf)
            dialog, list_view_model = self.create_list_view_dialog()
            for s in related_tables:
                item: QStandardItem = QStandardItem(s)
                item.setEditable(False)
                list_view_model.appendRow(item)
            dialog.exec_()

        return func

    def sort_by_text_and_color(self, search_box: QLineEdit, color_filter: List[Color],
                               proxy_model: QSortFilterProxyModel, watch_unplugged: bool = False):
        unplugged_tables: List[str] = []
        if watch_unplugged:
            unplugged_tables: List[str] = self.store.get_tables('', only_unplugged=True, only_names=True)

        def func(row: int, _) -> bool:
            model: QStandardItemModel = proxy_model.sourceModel()
            search_text: str = search_box.text()
            item: QStandardItem = model.item(row)
            text: str = item.text()
            if watch_unplugged and self.only_unplugged['value']:
                if text not in unplugged_tables:
                    return False
            color = Color.from_q_color(item.foreground().color())
            return search_text in text and (not len(color_filter) or color in color_filter)

        return func

    def select_workflows_directory(self) -> None:
        dialog: QFileDialog = QFileDialog(self, caption='Select workflows directory')
        self.directory_path = str(dialog.getExistingDirectory(dialog, 'Select workflows directory'))
        dialog.close()
        if self.directory_path:
            try:
                self.loading_progress.setValue(0)
                self.stackedWidget.setCurrentIndex(1)
                table_id_name_pairs: List[Tuple[int, str]] = self.store.get_tables(id_name_pairs=True)
                gen = parse_workflows_coroutine(self.directory_path, table_id_name_pairs)
                while True:
                    progress: int = next(gen)
                    self.loading_progress.setValue(progress)
            except StopIteration as ret:
                sqooped_tables, workflows, table_based_on, table_created_in, table_partitions, table_updated_in, table_used_in = ret.value
                workflows = [Workflow(*w_n) for w_n in workflows]
                self.store.insert_sqooped_tables(sqooped_tables)
                self.store.insert_workflows(workflows)
                self.store.insert_table_based_on(table_based_on)
                self.store.insert_table_created_in(table_created_in)
                self.store.insert_table_used_in(table_used_in)
                self.store.insert_table_updated_in(table_updated_in)
                self.store.insert_table_partitions(table_partitions)
            finally:
                self.stackedWidget.setCurrentIndex(0)
            self.wf_filter_workflows()
            self.set_menu_state()

    def insert_tables_from_schema_coroutine(self, tables_list: List[str]) -> bool:
        progress: int = 0
        length: int = len(tables_list)
        self.store.delete_tables()
        for table_name in tables_list:
            without_schema: str = table_name.split('.')[1]
            tables: List[Table] = self.store.get_tables_by_names([table_name, without_schema])
            if len(tables) != 0:
                for table in tables:
                    table.name = table_name
                    self.store.update_table(table)
            else:
                self.store.insert_new_table(table_name)
            progress += 1
            yield int((progress / length * 100) + 1)
        return True

    def update_tables_columns_from_schema_coroutine(self, tables_dict: Dict[str, List[Tuple[str, str]]]) -> bool:
        progress: int = 0
        length: int = len(list(tables_dict.keys()))
        tables: List[Table] = self.store.get_tables_by_names(list(tables_dict.keys()))
        for table in tables:
            if table.name in tables_dict:
                self.store.insert_table_columns([(table.index, t[0], t[1]) for t in tables_dict[table.name]])
                # del tables_dict[table.name]
                progress += 1
                yield int((progress / length * 100) + 1)
        # for table_name in tables_dict:
        #     table: Table = self.store.insert_new_table(table_name)
        #     self.store.insert_table_columns([(table.index, t[0], t[1]) for t in tables_dict[table.name]])
        #     progress += 1
        #     yield int((progress / length * 100) + 1)
        return True

    def extract_hive_schema(self) -> None:
        dialog: QFileDialog = QFileDialog(self, caption='Select hive schema file')
        schema_filepath: str = str(dialog.getOpenFileName(dialog, 'Select hive schema file')[0])
        dialog.close()
        if schema_filepath:
            tables_list: List[str] = []
            with open(schema_filepath, 'r') as file:
                for line in file.readlines():
                    if 'schema_name,table_name' in line:
                        continue
                    else:
                        table_name: str = line.replace(' ', '').replace(',', '.').strip()
                        tables_list.append(table_name)
            try:
                self.loading_progress.setValue(0)
                self.stackedWidget.setCurrentIndex(1)
                gen = self.insert_tables_from_schema_coroutine(tables_list)
                while True:
                    progress: int = next(gen)
                    self.loading_progress.setValue(progress)
            except StopIteration as ret:
                pass
            finally:
                self.stackedWidget.setCurrentIndex(0)
            self.db_filter_tables()
            self.set_menu_state()

    def extract_impala_schema(self) -> None:
        schema_filepath: str = str(QFileDialog.getOpenFileName(None, 'Select impala schema file')[0])
        if schema_filepath:
            tables_dict: Dict[str, List[Tuple[str, str]]] = {}
            with open(schema_filepath, 'r') as file:
                for line in file.readlines():
                    if 'schema_name,table_name,field_name,field_type' in line:
                        continue
                    else:
                        table: List[str] = line.split(',')
                        table_name: str = table[0].strip() + '.' + table[1].strip()
                        if table_name not in tables_dict:
                            tables_dict[table_name] = [(table[2].strip(), table[3].strip().strip('"'))]
                        else:
                            tables_dict[table_name].append((table[2].strip(), table[3].strip().strip('"')))
            try:
                self.loading_progress.setValue(0)
                self.stackedWidget.setCurrentIndex(1)
                gen = self.update_tables_columns_from_schema_coroutine(tables_dict)
                while True:
                    progress: int = next(gen)
                    self.loading_progress.setValue(progress)
            except StopIteration as ret:
                pass
            finally:
                self.stackedWidget.setCurrentIndex(0)
            self.db_filter_tables()
            self.set_menu_state()

    def clear_database(self) -> None:
        self.store.create_db_tables(force=True)
        self.set_menu_state()
        self.db_filter_tables()
        self.wf_filter_workflows()

    def wf_fill_workflows(self) -> None:
        for workflow in self.store.get_workflows():
            color: QColor = QColor(Color.to_q_color(workflow.color))
            brush: QBrush = QBrush(color)
            item = QStandardItem(workflow.name)
            item.setForeground(brush)
            item.setEditable(False)
            self.wf_workflow_list_model.appendRow(item)

    def wf_filter_workflows(self) -> None:
        self.wf_workflow_proxy_model.invalidateFilter()
        self.wf_workflow_proxy_model.sort(0)

    def wf_select_workflows(self) -> None:
        try:
            workflow_name: str = self.wf_workflow_list.selectionModel().selectedIndexes()[0].data(Qt.DisplayRole)
            workflow: Workflow = self.store.get_workflows(workflow_name)[0]
            self.store.populate_workflow_data(workflow)
            self.current_workflow = workflow
            self.fill_wf_fields()
        except IndexError:
            pass

    def fill_wf_fields(self) -> None:
        if self.current_workflow:
            self.wf_set_color(self.current_workflow.color)
            self.wf_effected_list_model.clear()
            self.wf_source_list_model.clear()
            self.wf_predecessors_list_model.clear()
            self.wf_descendants_list_model.clear()
            for ef_t in sorted(self.current_workflow.effected_tables):
                item = QStandardItem(ef_t)
                item.setEditable(False)
                self.wf_effected_list_model.appendRow(item)
            for src_t in sorted(self.current_workflow.source_tables):
                item = QStandardItem(src_t)
                item.setEditable(False)
                self.wf_source_list_model.appendRow(item)
            for p_w in sorted(self.current_workflow.predecessors):
                item = QStandardItem(p_w)
                item.setEditable(False)
                self.wf_predecessors_list_model.appendRow(item)
            for d_w in sorted(self.current_workflow.descendants):
                item = QStandardItem(d_w)
                item.setEditable(False)
                self.wf_descendants_list_model.appendRow(item)

    def save_wf_fields(self) -> None:
        if self.current_workflow:
            self.current_workflow.color = self.wf_get_color()
            self.store.update_workflow(self.current_workflow)
            change_item_color(self.wf_workflow_list_model, self.current_workflow.name, self.current_workflow.color)

    def db_fill_tables(self) -> None:
        self.db_table_list_model.clear()
        for table in self.store.get_tables():
            color: QColor = QColor(Color.to_q_color(table.color))
            brush: QBrush = QBrush(color)
            item = QStandardItem(table.name)
            item.setEditable(False)
            item.setForeground(brush)
            self.db_table_list_model.appendRow(item)

    def db_filter_tables(self) -> None:
        self.db_table_proxy_model.invalidateFilter()
        self.db_table_proxy_model.sort(0)

    def fill_db_fields(self) -> None:
        if self.current_table:
            self.store.populate_table_data(self.current_table)
            self.db_description_input.setText(self.current_table.meaning)
            self.db_authors_input.setText(self.current_table.authors)
            self.db_set_color(self.current_table.color)
            self.db_based_on_list_model.clear()
            self.db_created_at_list_model.clear()
            self.db_updated_at_list_model.clear()
            self.db_used_in_list_model.clear()
            self.db_partitions_list_model.clear()
            self.db_columns_list_model.clear()
            for s in sorted(self.current_table.first_based_on_tables):
                item = QStandardItem(s)
                font: QFont = item.font()
                font.setBold(True)
                item.setFont(font)
                item.setEditable(False)
                self.db_based_on_list_model.appendRow(item)
            for s in sorted(self.current_table.based_on_tables):
                item = QStandardItem(s)
                item.setEditable(False)
                self.db_based_on_list_model.appendRow(item)
            for s in sorted(self.current_table.created_in_workflows):
                item = QStandardItem(s)
                item.setEditable(False)
                self.db_created_at_list_model.appendRow(item)
            for s in sorted(self.current_table.updated_in_workflows):
                item = QStandardItem(s)
                item.setEditable(False)
                self.db_updated_at_list_model.appendRow(item)
            for s in sorted(self.current_table.used_in_workflows):
                item = QStandardItem(s)
                item.setEditable(False)
                self.db_used_in_list_model.appendRow(item)
            for s in sorted(self.current_table.partitions):
                item = QStandardItem(s)
                item.setEditable(False)
                self.db_partitions_list_model.appendRow(item)
            for s in self.current_table.columns:
                item = QStandardItem(s)
                item.setEditable(False)
                self.db_columns_list_model.appendRow(item)

    def save_db_fields(self) -> None:
        if self.current_table:
            self.current_table.meaning = self.db_description_input.toPlainText()
            self.current_table.authors = self.db_authors_input.toPlainText()
            self.current_table.color = self.db_get_color()
            self.store.update_table(self.current_table)
            change_item_color(self.db_table_list_model, self.current_table.name, self.current_table.color)

    def db_set_color(self, color: Color):
        if color is Color.RED:
            self.db_red_color_button.setChecked(True)
            self.db_blue_color_button.setChecked(False)
            self.db_magenta_color_button.setChecked(False)
            self.db_green_color_button.setChecked(False)
            self.db_none_color_button.setChecked(False)
        elif color is Color.BLUE:
            self.db_red_color_button.setChecked(False)
            self.db_blue_color_button.setChecked(True)
            self.db_magenta_color_button.setChecked(False)
            self.db_green_color_button.setChecked(False)
            self.db_none_color_button.setChecked(False)
        elif color is Color.MAGENTA:
            self.db_red_color_button.setChecked(False)
            self.db_blue_color_button.setChecked(False)
            self.db_magenta_color_button.setChecked(True)
            self.db_green_color_button.setChecked(False)
            self.db_none_color_button.setChecked(False)
        elif color is Color.GREEN:
            self.db_red_color_button.setChecked(False)
            self.db_blue_color_button.setChecked(False)
            self.db_magenta_color_button.setChecked(False)
            self.db_green_color_button.setChecked(True)
            self.db_none_color_button.setChecked(False)
        else:
            self.db_red_color_button.setChecked(False)
            self.db_blue_color_button.setChecked(False)
            self.db_magenta_color_button.setChecked(False)
            self.db_green_color_button.setChecked(False)
            self.db_none_color_button.setChecked(True)

    def wf_set_color(self, color: Color):
        if color is Color.RED:
            self.wf_red_color_button.setChecked(True)
            self.wf_blue_color_button.setChecked(False)
            self.wf_magenta_color_button.setChecked(False)
            self.wf_green_color_button.setChecked(False)
            self.wf_none_color_button.setChecked(False)
        elif color is Color.BLUE:
            self.wf_red_color_button.setChecked(False)
            self.wf_blue_color_button.setChecked(True)
            self.wf_magenta_color_button.setChecked(False)
            self.wf_green_color_button.setChecked(False)
            self.wf_none_color_button.setChecked(False)
        elif color is Color.MAGENTA:
            self.wf_red_color_button.setChecked(False)
            self.wf_blue_color_button.setChecked(False)
            self.wf_magenta_color_button.setChecked(True)
            self.wf_green_color_button.setChecked(False)
            self.wf_none_color_button.setChecked(False)
        elif color is Color.GREEN:
            self.wf_red_color_button.setChecked(False)
            self.wf_blue_color_button.setChecked(False)
            self.wf_magenta_color_button.setChecked(False)
            self.wf_green_color_button.setChecked(True)
            self.wf_none_color_button.setChecked(False)
        else:
            self.wf_red_color_button.setChecked(False)
            self.wf_blue_color_button.setChecked(False)
            self.wf_magenta_color_button.setChecked(False)
            self.wf_green_color_button.setChecked(False)
            self.wf_none_color_button.setChecked(True)

    def db_get_color(self) -> Color:
        if self.db_red_color_button.isChecked():
            return Color.RED
        elif self.db_blue_color_button.isChecked():
            return Color.BLUE
        elif self.db_green_color_button.isChecked():
            return Color.GREEN
        elif self.db_magenta_color_button.isChecked():
            return Color.MAGENTA
        elif self.db_none_color_button.isChecked():
            return Color.NONE

    def wf_get_color(self) -> Color:
        if self.wf_red_color_button.isChecked():
            return Color.RED
        elif self.wf_blue_color_button.isChecked():
            return Color.BLUE
        elif self.wf_green_color_button.isChecked():
            return Color.GREEN
        elif self.wf_magenta_color_button.isChecked():
            return Color.MAGENTA
        elif self.wf_none_color_button.isChecked():
            return Color.NONE

    def db_select_tables(self) -> None:
        try:
            table_name: str = self.db_table_list.selectionModel().selectedIndexes()[0].data(Qt.DisplayRole)
            table: Table = self.store.get_tables(table_name)[0]
            self.current_table = table
            self.fill_db_fields()
        except IndexError:
            pass

    def db_change_tables_filter(self, v: bool):
        self.only_unplugged['value'] = v
        self.db_filter_tables()

    def db_toggle_color_filter(self, color: Color):
        def func(value):
            if value:
                self.db_color_filter.append(color)
            else:
                try:
                    self.db_color_filter.remove(color)
                except ValueError:
                    pass
            self.db_filter_tables()

        return func

    def wf_toggle_color_filter(self, color: Color):
        def func(value):
            if value:
                self.wf_color_filter.append(color)
            else:
                try:
                    self.wf_color_filter.remove(color)
                except ValueError:
                    pass
            self.wf_filter_workflows()

        return func

    def set_menu_state(self):
        status: str = self.store.get_db_status()
        if status == 'db_empty':
            self.action_extract_hive.setEnabled(True)
            self.action_exctract_impala.setEnabled(False)
            self.action_open_workflows.setEnabled(False)
        elif status == 'hive_extracted':
            self.action_extract_hive.setEnabled(False)
            self.action_exctract_impala.setEnabled(True)
            self.action_open_workflows.setEnabled(False)
        elif status == 'impala_extracted':
            self.action_extract_hive.setEnabled(False)
            self.action_exctract_impala.setEnabled(False)
            self.action_open_workflows.setEnabled(True)
        else:
            self.action_extract_hive.setEnabled(False)
            self.action_exctract_impala.setEnabled(False)
            self.action_open_workflows.setEnabled(False)

    def export_list(self):
        tab_id: int = self.tabWidget.currentIndex()
        if tab_id == 0:
            copy_model_to_clipboard(self.db_table_list_model)
        elif tab_id == 1:
            copy_model_to_clipboard(self.wf_workflow_list_model)

    def __init__(self):
        super().__init__()
        self.store: Store = Store('db.sqlite3')
        self.directory_path: str = None
        self.current_table: Table = None
        self.current_workflow: Workflow = None
        self.only_unplugged: Dict[str, bool] = {'value': False}
        self.db_color_filter: List[Color] = []
        self.wf_color_filter: List[Color] = []
        self.setupUi(self)
        self.set_menu_state()

        self.wf_workflow_list_model = QStandardItemModel(self)
        self.wf_workflow_proxy_model = QSortFilterProxyModel(self)
        self.wf_workflow_proxy_model.setSourceModel(self.wf_workflow_list_model)
        self.wf_workflow_list.setModel(self.wf_workflow_proxy_model)
        self.wf_workflow_proxy_model.filterAcceptsRow = self.sort_by_text_and_color(self.wf_workflow_search,
                                                                                    self.wf_color_filter,
                                                                                    self.wf_workflow_proxy_model
                                                                                    )
        self.wf_workflow_proxy_model.lessThan = less_than_name_color(self.wf_workflow_proxy_model)
        self.wf_fill_workflows()
        self.wf_workflow_proxy_model.sort(0)

        self.wf_save_button.clicked.connect(self.save_wf_fields)
        self.wf_source_list_model = QStandardItemModel(self.wf_source_list)
        self.wf_source_list.setModel(self.wf_source_list_model)
        self.wf_effected_list_model = QStandardItemModel(self.wf_effected_list)
        self.wf_effected_list.setModel(self.wf_effected_list_model)
        self.wf_predecessors_list_model = QStandardItemModel(self.wf_predecessors_list)
        self.wf_predecessors_list.setModel(self.wf_predecessors_list_model)
        self.wf_predecessors_list.setContextMenuPolicy(Qt.CustomContextMenu)
        self.wf_predecessors_list.customContextMenuRequested.connect(
            self.context_menu_requested(self.wf_predecessors_list))
        self.wf_descendants_list_model = QStandardItemModel(self.wf_descendants_list)
        self.wf_descendants_list.setModel(self.wf_descendants_list_model)
        self.wf_descendants_list.setContextMenuPolicy(Qt.CustomContextMenu)
        self.wf_descendants_list.customContextMenuRequested.connect(
            self.context_menu_requested(self.wf_descendants_list, reverse_target=True))

        self.action_open_workflows.triggered.connect(self.select_workflows_directory)
        self.action_extract_hive.triggered.connect(self.extract_hive_schema)
        self.action_exctract_impala.triggered.connect(self.extract_impala_schema)
        self.action_clear_database.triggered.connect(self.clear_database)
        self.action_copy_list_to_clipboard.triggered.connect(self.export_list)

        self.wf_workflow_search.textChanged.connect(self.wf_filter_workflows)
        self.wf_workflow_list.selectionModel().selectionChanged.connect(self.wf_select_workflows)
        self.wf_blue_color_filter.stateChanged.connect(self.wf_toggle_color_filter(Color.BLUE))
        self.wf_green_color_filter.stateChanged.connect(self.wf_toggle_color_filter(Color.GREEN))
        self.wf_red_color_filter.stateChanged.connect(self.wf_toggle_color_filter(Color.RED))
        self.wf_magenta_color_filter.stateChanged.connect(self.wf_toggle_color_filter(Color.MAGENTA))
        self.wf_none_color_filter.stateChanged.connect(self.wf_toggle_color_filter(Color.NONE))

        self.db_table_list_model = QStandardItemModel(self)
        self.db_table_proxy_model = QSortFilterProxyModel(self)
        self.db_table_proxy_model.setSourceModel(self.db_table_list_model)
        self.db_table_list.setModel(self.db_table_proxy_model)
        self.db_table_proxy_model.filterAcceptsRow = self.sort_by_text_and_color(self.db_table_search,
                                                                                 self.db_color_filter,
                                                                                 self.db_table_proxy_model,
                                                                                 watch_unplugged=True)
        self.db_table_proxy_model.lessThan = less_than_name_color(self.db_table_proxy_model)
        self.db_fill_tables()
        self.db_table_proxy_model.sort(0)

        self.db_created_at_list_model = QStandardItemModel(self.db_created_at_list)
        self.db_created_at_list.setModel(self.db_created_at_list_model)
        self.db_updated_at_list_model = QStandardItemModel(self.db_updated_at_list)
        self.db_updated_at_list.setModel(self.db_updated_at_list_model)
        self.db_based_on_list_model = QStandardItemModel(self.db_based_on_list)
        self.db_based_on_list.setModel(self.db_based_on_list_model)
        self.db_used_in_list_model = QStandardItemModel(self.db_used_in_list)
        self.db_used_in_list.setModel(self.db_used_in_list_model)
        self.db_partitions_list_model = QStandardItemModel(self.db_partitions_list)
        self.db_partitions_list.setModel(self.db_partitions_list_model)
        self.db_columns_list_model = QStandardItemModel(self.db_columns_list)
        self.db_columns_list.setModel(self.db_columns_list_model)

        self.db_table_search.textChanged.connect(self.db_filter_tables)
        self.db_table_list.selectionModel().selectionChanged.connect(self.db_select_tables)
        self.db_save_button.clicked.connect(self.save_db_fields)
        self.db_show_only_unplugged.stateChanged.connect(self.db_change_tables_filter)
        self.db_blue_color_filter.stateChanged.connect(self.db_toggle_color_filter(Color.BLUE))
        self.db_green_color_filter.stateChanged.connect(self.db_toggle_color_filter(Color.GREEN))
        self.db_red_color_filter.stateChanged.connect(self.db_toggle_color_filter(Color.RED))
        self.db_magenta_color_filter.stateChanged.connect(self.db_toggle_color_filter(Color.MAGENTA))
        self.db_none_color_filter.stateChanged.connect(self.db_toggle_color_filter(Color.NONE))

        self.bind_copy_actions()