def setEditorData(self, editor, idx): modeldata = idx.data(Qt.UserRole) # idx.data(Qt.DisplayRole) ##print(modeldata) #print("in setEditorData, db=", editor.window().db) #####dict_ = eval(modeldata) #if editor.window().db: # dict_ = dict_from_DB(editor.window().db, dict_["UUID"]) #else: dict_ = eval(modeldata) # this expanding DbDocFileMetadata for childs, not desirable, but hard to find an alternative to eval #dict_ = ast.literal_eval(modeldata) #jsonString = json.dumps(modeldata) #dict_ = json.loads(jsonString) ###modeldata2 = modeldata.replace("'", '"') ###dict_ = json.loads(modeldata2) model = editor.model() model.clear() model = QStandardItemModel(0, 2) editor.setHeaderHidden(False) model.setHorizontalHeaderLabels(['Attribute', 'Value']) # using QSortFilterProxyModel to facilitate sorting the metdata list # QSortFilterProxyModel (or related) seems to be what is causing this following warning: # 'Trying to create a QVariant instance of QMetaType::Void type, an invalid QVariant will be constructed instead' proxymodel = QSortFilterProxyModel() proxymodel.setSourceModel(model) proxymodel.setDynamicSortFilter(True) proxymodel.setSortCaseSensitivity( Qt.CaseInsensitive ) # none of these 3 lines appear to have any effect on sorting the metadata list #proxymodel.setFilterCaseSensitivity(Qt.CaseInsensitive) #proxymodel.sort(0, Qt.AscendingOrder) # Qt.DescendingOrder #proxymodel.setSortRole(Qt.DisplayRole) editor.setModel(proxymodel) for ii, (key, value) in enumerate(sorted(dict_.items())): model.setItem(ii, 0, QStandardItem(key)) model.setItem(ii, 1, QStandardItem(str(value)) )
def __init__(self, parent): QTableView.__init__(self, parent) proxy_model = QSortFilterProxyModel() proxy_model.setSortCaseSensitivity(Qt.CaseInsensitive) self.setModel(proxy_model) self.horizontalHeader().setResizeMode(QHeaderView.ResizeToContents) self.verticalHeader().setDefaultSectionSize(10) self.setSelectionBehavior(QTableView.SelectRows) self.setEditTriggers(QTableView.NoEditTriggers) self.sortByColumn(1, Qt.AscendingOrder) self.setSortingEnabled(True) self.selectionModel().selectionChanged.connect( self._on_selection_changed) self.channel_list: List[Tuple[str, str, int]] = None
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_())
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)
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"))
class CadastroPedidos(QDialog): def __init__(self, parent=None): super(CadastroPedidos, self).__init__(parent=None) self.setWindowFlags(Qt.WindowMinMaxButtonsHint | Qt.WindowCloseButtonHint) self.setMinimumSize(QSize(h_size, v_size)) self.originalPalette = QApplication.palette() self.setWindowIcon(QIcon(main_icon)) self.setWindowTitle("Cadastro de Pedidos") self.setStyleSheet(style) self.pedidos_selecionados = [] #Sempre que for iniciado criará um objeto data self.data = dict() voltar_btn = QPushButton(self) voltar_btn.setText('Voltar') voltar_btn.clicked.connect(self.goMainWindow) voltar_btn.setFocusPolicy(Qt.NoFocus) self.dadosDoPedido() self.resumoGeral() self.resumoDosItens() '''disableWidgetsCheckBox.toggled.connect( self.bottomLeftGroupBox.setHidden ) ''' topLayout = QHBoxLayout() topLayout.addWidget(voltar_btn) leftLayout = QVBoxLayout() leftLayout.addWidget(self.topLeftGroupBox, 100) #leftLayout.addWidget(self.bottomLeftGroupBox, 50) mainLayout = QGridLayout() mainLayout.addLayout(topLayout, 0, 0, 1, 2) mainLayout.addLayout(leftLayout, 1, 0) mainLayout.addWidget(self.topRightGroupBox, 1, 1) #mainLayout.addWidget(self.submitButtons, 3, 0, 1, 2) mainLayout.setRowStretch(1, 1) mainLayout.setColumnStretch(0, 1) mainLayout.setColumnStretch(1, 3) self.setLayout(mainLayout) def dadosDoPedido(self): self.topLeftGroupBox = QGroupBox("Dados do Pedido") verticalSpacer = QSpacerItem(40, 20, QSizePolicy.Minimum, QSizePolicy.Expanding) formLayout = QFormLayout() self.pedido = QLineEdit(self) self.pedido.setPlaceholderText("ex: 123.456") pedido_label = QLabel("Pedido:") self.n_simafic = QLineEdit(self) self.n_simafic.setPlaceholderText("ex: 08.04.02.507-6") n_simafic_label = QLabel("COD. SIMAFIC:") self.qtd_items = QLineEdit(self) self.qtd_items.setPlaceholderText("ex: 100") qtd_items_label = QLabel("Quantidade de Items:") '''ADD PEDIDO''' add_item = QPushButton('Adicionar Item') add_item.setObjectName('Add') add_item.setIcon(QIcon('assets/check_icon_blue2.png')) add_item.clicked.connect(self.add_items) '''CLEAR BUTTON CONFIG ''' clear_btn = QPushButton('Limpar Campos') clear_btn.setObjectName('Yellow') clear_btn.setIcon(QIcon('assets/eraser.png')) clear_btn.clicked.connect(self.limpar_pedidos) formLayout.addRow(pedido_label, self.pedido) formLayout.addRow(n_simafic_label, self.n_simafic) formLayout.addRow(qtd_items_label, self.qtd_items) formLayout.addItem(verticalSpacer) formLayout.addRow(add_item) formLayout.addRow(clear_btn) '''checkBox = QCheckBox("Tri-state check box") checkBox.setTristate(True) checkBox.setCheckState(Qt.PartiallyChecked)''' #layout.addWidget(checkBox) '''formLayout.addWidget(add_item) formLayout.addWidget(clear_btn)''' layout = QVBoxLayout() layout.addLayout(formLayout) layout.addStretch(2) self.topLeftGroupBox.setLayout(layout) def resumoGeral(self): self.topRightGroupBox = QTabWidget() self.topRightGroupBox.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Ignored) #[First Tab] Create first tab verticalSpacer = QSpacerItem(40, 20, QSizePolicy.Minimum, QSizePolicy.Expanding) tab1ListaPedidos = QWidget() layout = QVBoxLayout(self) #[First Tab] - TextFields searchPedido = QLineEdit(self) searchPedido.setPlaceholderText("Filtrar por pedido: ") searchProduto = QLineEdit(self) searchProduto.setPlaceholderText("Filtrar por produto: ") #[First Tab] - Set TableView self.tabv_pedidos = QTableView() tab2hbox = QHBoxLayout() self.modelAllPedidos = services.get_all_pedidos_pandas() #[First Tab] - Set Filters self.proxyPedidoFilter = QSortFilterProxyModel() self.proxyPedidoFilter.setSourceModel(self.modelAllPedidos) self.proxyPedidoFilter.setFilterKeyColumn(0) self.proxyPedidoFilter.setSortCaseSensitivity(Qt.CaseSensitive) self.proxyPedidoFilterSecondLayer = QSortFilterProxyModel() self.proxyPedidoFilterSecondLayer.setSourceModel( self.proxyPedidoFilter) self.proxyPedidoFilterSecondLayer.setFilterKeyColumn(1) self.proxyPedidoFilterSecondLayer.setSortCaseSensitivity( Qt.CaseSensitive) self.tabv_pedidos.resizeColumnsToContents() self.tabv_pedidos.doubleClicked.connect(self.abrirItensDoPedido) self.tabv_pedidos.setSelectionMode(QAbstractItemView.SingleSelection) self.tabv_pedidos.setSelectionBehavior(QTableView.SelectRows) self.tabv_pedidos.setColumnWidth(2, 100) self.tabv_pedidos.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) tab2hbox.addWidget(self.tabv_pedidos) #[Connect Fields] searchProduto.textChanged.connect( lambda wildcard: self.proxyPedidoFilterSecondLayer. setFilterWildcard(wildcard)) searchPedido.textChanged.connect( lambda wildcard: self.proxyPedidoFilter.setFilterWildcard(wildcard )) self.tabv_pedidos.setModel(self.proxyPedidoFilterSecondLayer) #[First Tab] - Set Layout tabItensValidos = QWidget() tableItensValidos = QTableView() #tableItensValidos.horizontalHeader().sectionClicked.connect(your_callable) model = services.get_simafic_as_dataframe() tableItensValidos.setModel(model) tab1hbox = QHBoxLayout() #tab1hbox.setContentsMargins(5, 5, 5, 5) tableItensValidos.resizeColumnsToContents() tab1hbox.addWidget(tableItensValidos) tabItensValidos.setLayout(tab1hbox) layoutText = QHBoxLayout() layoutText.addWidget(searchPedido) layoutText.addWidget(searchProduto) layout.addLayout(layoutText) layout.addWidget( QLabel( "Para abrir mais opções sobre um pedido, clique duas vezes no item." )) self.tabv_pedidos.verticalHeader() self.tabv_pedidos.resizeColumnsToContents() layout.addWidget(self.tabv_pedidos) tab1ListaPedidos.setLayout(layout) #TODO: Agrupar items por pedido (Drop Down) | Auto-resize nas cells da TView #TODO: Adicionar self.topRightGroupBox.addTab(tab1ListaPedidos, "&Lista de Pedidos: ") self.topRightGroupBox.addTab(tabItensValidos, "&Lista de Itens:") def resumoDosItens(self): self.bottomLeftGroupBox = QGroupBox("Lista de Itens do Pedido nº") self.listDataItens = list() self.listaViewItens = QListWidget() verticalSpacer = QSpacerItem(40, 20, QSizePolicy.Minimum, QSizePolicy.Expanding) self.resumoLayout = QGridLayout() self.resumoLayout.addItem(verticalSpacer) self.bottomLeftGroupBox.setLayout(self.resumoLayout) def abrirItensDoPedido(self, item): print( "[abrirIItensDoPedido] O Item foi selecionado através de um click na: {} x linha: {}" .format(item.column(), item.row())) pedido = self.tabv_pedidos.model().index(item.row(), 0).data() simafic = self.tabv_pedidos.model().index(item.row(), 1).data() print(pedido, simafic) self.pedidos_selecionados = pedido try: pedido_item = services.get_pedido_x_item(pedido, simafic) print(pedido_item) except (ValidationError, DBPedidosException) as error: error_dialog = QErrorMessage() error_dialog.setWindowTitle(error.errors) error_dialog.setWindowIcon(QIcon(main_icon)) error_dialog.showMessage(error.message) error_dialog.exec_() box = QMessageBox() box.setWindowIcon(QIcon(main_icon)) box.setWindowTitle("Pedido {} selecionado.".format( pedido_item.id_pedido)) box.setText("O que deseja fazer com o item {}?".format( pedido_item.cod_simafic)) box.setStandardButtons(QMessageBox.Open | QMessageBox.Discard | QMessageBox.Cancel) buttonOpen = box.button(QMessageBox.Open) buttonOpen.setText('Alterar') buttonDiscard = box.button(QMessageBox.Discard) buttonDiscard.setText('Excluir') buttonCancel = box.button(QMessageBox.Cancel) buttonCancel.setText('Cancelar') box.exec_() if box.clickedButton() == buttonOpen: print("Alterar...") self.cams = UpdateScreen(pedido_item, parent=self) self.cams.show() elif box.clickedButton() == buttonDiscard: print("Excluir ") self.confirmarExclusao(pedido_item) elif box.clickedButton() == buttonCancel: print("Cancelar ") def confirmarExclusao(self, pedido): box = QMessageBox() box.setWindowIcon(QIcon(main_icon)) box.setWindowTitle('Confirmação de Exclusão') box.setText( "Tem certeza que deseja excluir o item: {} do pedido {}?".format( pedido.cod_simafic, pedido.id_pedido)) box.setStandardButtons(QMessageBox.Yes | QMessageBox.No) buttonYes = box.button(QMessageBox.Yes) buttonYes.setText("Excluir") buttonNo = box.button(QMessageBox.No) buttonNo.setText("Cancelar") box.exec_() if box.clickedButton() == buttonYes: self.excluirPedido(pedido) print("Pedido excluido") else: print("Exclusão cancelada") return def excluirPedido(self, pedido): try: services.excluirPedidoItem(pedido) self.update_model_tableview() except (ValidationError, DBPedidosException) as error: error_dialog = QErrorMessage() error_dialog.setWindowTitle(error.errors) error_dialog.setWindowIcon(QIcon(main_icon)) error_dialog.showMessage(error.message) error_dialog.exec_() def add_items(self): try: pedido, n_simafic, qtd_items = self.pedido.text( ), self.n_simafic.text(), self.qtd_items.text() print("Add Pedido: {} {} {}".format(pedido, n_simafic, qtd_items)) if services.validateCadastro(pedido, n_simafic, qtd_items): print("Add Pedido: {} {} {}".format(pedido, n_simafic, qtd_items)) mb = QMessageBox() mb.setIconPixmap(QPixmap('assets/check_icon_blue2')) mb.setWindowTitle("Sucesso") mb.setText( 'O pedido: {} foi criado com sucesso!'.format(pedido)) services.add_pedido(pedido, n_simafic, qtd_items) mb.exec_() self.update_model_tableview() self.limpar_pedidos() except (ValidationError, DBPedidosException) as error: error_dialog = QErrorMessage() error_dialog.setWindowTitle(error.errors) error_dialog.setWindowIcon(QIcon(main_icon)) error_dialog.showMessage(error.message) error_dialog.exec_() pass def update_model_tableview(self): self.modelAllPedidos.setDataFrame(services.get_all_pedidos_df()) self.topRightGroupBox.setCurrentIndex(0) def limpar_pedidos(self): self.n_simafic.clear() self.qtd_items.clear() self.pedido.clear() def goMainWindow(self): self.cams = mainView self.cams.show() self.close()
class EmojisModel(): def update_model(self, clear=True): log.info("updating emoji model.") app = get_app() _ = app._tr # Clear all items if clear: self.model_paths = {} self.model.clear() self.emoji_groups.clear() # Add Headers self.model.setHorizontalHeaderLabels([_("Name")]) # Get emoji metadata emoji_metadata_path = os.path.join(info.PATH, "emojis", "data", "openmoji-optimized.json") with open(emoji_metadata_path, 'r', encoding="utf-8") as f: emoji_lookup = json.load(f) # get a list of files in the OpenShot /emojis directory emojis_dir = os.path.join(info.PATH, "emojis", "color", "svg") emoji_paths = [{"type": "common", "dir": emojis_dir, "files": os.listdir(emojis_dir)}, ] # Add optional user-defined transitions folder if os.path.exists(info.EMOJIS_PATH) and os.listdir(info.EMOJIS_PATH): emoji_paths.append({"type": "user", "dir": info.EMOJIS_PATH, "files": os.listdir(info.EMOJIS_PATH)}) for group in emoji_paths: dir = group["dir"] files = group["files"] for filename in sorted(files): path = os.path.join(dir, filename) fileBaseName = os.path.splitext(filename)[0] # Skip hidden files (such as .DS_Store, etc...) if filename[0] == "." or "thumbs.db" in filename.lower(): continue # get name of transition emoji = emoji_lookup.get(fileBaseName, {}) emoji_name = _(emoji.get("annotation", fileBaseName).capitalize()) emoji_type = _(emoji.get("group", "user").split('-')[0].capitalize()) # Track unique emoji groups if emoji_type not in self.emoji_groups: self.emoji_groups.append(emoji_type) # Check for thumbnail path (in build-in cache) thumb_path = os.path.join(info.IMAGES_PATH, "cache", "{}.png".format(fileBaseName)) # Check built-in cache (if not found) if not os.path.exists(thumb_path): # Check user folder cache thumb_path = os.path.join(info.CACHE_PATH, "{}.png".format(fileBaseName)) # Generate thumbnail (if needed) if not os.path.exists(thumb_path): try: # Reload this reader clip = openshot.Clip(path) reader = clip.Reader() # Open reader reader.Open() # Save thumbnail reader.GetFrame(0).Thumbnail( thumb_path, 75, 75, os.path.join(info.IMAGES_PATH, "mask.png"), "", "#000", True, "png", 85 ) reader.Close() clip.Close() except Exception: # Handle exception log.info('Invalid emoji image file: %s' % filename) msg = QMessageBox() msg.setText(_("{} is not a valid image file.".format(filename))) msg.exec_() continue row = [] # Set emoji data col = QStandardItem("Name") col.setIcon(QIcon(thumb_path)) col.setText(emoji_name) col.setToolTip(emoji_name) col.setData(path) col.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsUserCheckable | Qt.ItemIsDragEnabled) row.append(col) # Append filterable group col = QStandardItem(emoji_type) row.append(col) # Append ROW to MODEL (if does not already exist in model) if path not in self.model_paths: self.model.appendRow(row) self.model_paths[path] = path def __init__(self, *args): # Create standard model self.app = get_app() self.model = EmojiStandardItemModel() self.model.setColumnCount(2) self.model_paths = {} self.emoji_groups = [] # Create proxy models (for grouping, sorting and filtering) self.group_model = QSortFilterProxyModel() self.group_model.setDynamicSortFilter(False) self.group_model.setFilterCaseSensitivity(Qt.CaseInsensitive) self.group_model.setSortCaseSensitivity(Qt.CaseSensitive) self.group_model.setSourceModel(self.model) self.group_model.setSortLocaleAware(True) self.group_model.setFilterKeyColumn(1) self.proxy_model = QSortFilterProxyModel() self.proxy_model.setDynamicSortFilter(False) self.proxy_model.setFilterCaseSensitivity(Qt.CaseInsensitive) self.proxy_model.setSortCaseSensitivity(Qt.CaseSensitive) self.proxy_model.setSourceModel(self.group_model) self.proxy_model.setSortLocaleAware(True) # Attempt to load model testing interface, if requested # (will only succeed with Qt 5.11+) if info.MODEL_TEST: try: # Create model tester objects from PyQt5.QtTest import QAbstractItemModelTester self.model_tests = [] for m in [self.proxy_model, self.group_model, self.model]: self.model_tests.append( QAbstractItemModelTester( m, QAbstractItemModelTester.FailureReportingMode.Warning) ) log.info("Enabled {} model tests for emoji data".format(len(self.model_tests))) except ImportError: pass
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...")
class MainWindow(QMainWindow): def __init__(self): super().__init__() self.filename = None self.data_changed = False self.file_data = None self.open_file_dir = os.path.expanduser('~') self.ui = loadUi('gui/MainWindow.ui', self) self.ui.action_Open.triggered.connect(self.menu_open) self.ui.action_Save.triggered.connect(self.menu_save) self.ui.action_Close.triggered.connect(self.menu_close) self.ui.action_Exit.triggered.connect(qApp.quit) self.ui.action_ShowMap.triggered.connect(self.show_map) self.ui.action_BoltChecker.triggered.connect(self.show_boltchecker) self.datamodel = QSortFilterProxyModel() self.datamodel.setFilterCaseSensitivity(Qt.CaseInsensitive) self.datamodel.setSortCaseSensitivity(Qt.CaseInsensitive) self.ui.treeView.setSortingEnabled(False) self.ui.treeView.setModel(self.datamodel) self.ui.treeView.setSortingEnabled(True) self.ui.treeView.doubleClicked.connect(self.treeView_doubleClicked) self.ui.treeView.selectionModel().selectionChanged.connect( self.treeView_selectionChanged) self.ui.searchField.textChanged.connect(self.searchField_textChanged) def menu_open(self): fname = QFileDialog.getOpenFileName(self, 'Open file', self.open_file_dir) if fname[0]: self.open_file(fname[0]) def open_file(self, filename): self.filename = filename self.open_file_dir = os.path.dirname(self.filename) with open(filename, 'rb') as f: reader = ES2Reader(f) self.file_data = reader.read_all() self.update_tree(self.file_data) self.set_changed(False) self.ui.action_Close.setEnabled(True) def set_changed(self, changed=True): self.data_changed = changed self.ui.action_Save.setEnabled(self.data_changed) def menu_save(self): for i in range(100): backup_filename = '{}.{}'.format(self.filename, i) if not os.path.exists(backup_filename): shutil.copy2(self.filename, backup_filename) break else: raise Exception('Too many backups!') with open(self.filename, 'wb') as f: writer = ES2Writer(f) for k, v in self.file_data.items(): writer.save(k, v) writer.save_all() self.set_changed(False) def menu_close(self): self.ui.action_Save.setEnabled(False) self.ui.action_Close.setEnabled(False) self.filename = None self.file_data = None self.update_tree(None) def update_tree(self, file_data=None): self.datamodel.setSourceModel(TreeModel(file_data)) self.ui.treeView.sortByColumn(0, Qt.AscendingOrder) self.ui.treeView.resizeColumnToContents(0) def treeView_selectionChanged(self): pass # indexes = self.ui.treeView.selectionModel().selection().indexes() # if len(indexes) > 0: # index = indexes[0] # tag = self.datamodel.data(index) # print('selectionChanged') # print(self.file_data[item].header.value_type) # print(self.file_data[item].value) def treeView_doubleClicked(self): index = self.ui.treeView.selectionModel().selection().indexes()[0] self.edit_index(index) def edit_index(self, index): tag = index.data() dialog = EditDialog(tag, self.file_data[tag], self) result = dialog.exec_() if result == QDialog.Accepted: dialog_result = dialog.get_value() if self.file_data[tag].value == dialog_result: return self.set_changed() self.file_data[tag].value = dialog_result value_index = self.datamodel.index(index.row(), 2) self.datamodel.setData(value_index, dialog_result) def searchField_textChanged(self, text): self.datamodel.setFilterWildcard(text) def show_map(self): dialog = MapViewDialog(self.file_data, self) dialog.exec_() def show_boltchecker(self): dialog = BoltCheckerDialog(self.file_data, self) dialog.exec_()
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...")
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"))