def __initGui(self): self._contextMenu = None mainLayout = QVBoxLayout(self) mainLayout.setContentsMargins(0, 3, 0, 0) mainLayout.setSpacing(0) self.__fileList = SubtitleList() fileListHeader = self.__fileList.header() self.__resizeHeader(fileListHeader) self.__fileList.setSelectionMode(QAbstractItemView.ExtendedSelection) self.__fileList.setColumnCount(4) self.__fileList.setHeaderLabels([ _("File name"), _("Input encoding"), _("Output encoding"), _("Subtitle format"), _("FPS")]) mainLayout.addWidget(self.__fileList) self.setContextMenuPolicy(Qt.CustomContextMenu) self.setLayout(mainLayout)
class FileList(SubTab): requestOpen = pyqtSignal(str, bool) requestRemove = pyqtSignal(str) selectionChanged = pyqtSignal() def __init__(self, name, subtitleData, parent = None): super(FileList, self).__init__(name, parent) self._subtitleData = subtitleData self._settings = SubSettings() self.__initGui() self.__connectSignals() def __initGui(self): self._contextMenu = None mainLayout = QVBoxLayout(self) mainLayout.setContentsMargins(0, 3, 0, 0) mainLayout.setSpacing(0) self.__fileList = SubtitleList() fileListHeader = self.__fileList.header() self.__resizeHeader(fileListHeader) self.__fileList.setSelectionMode(QAbstractItemView.ExtendedSelection) self.__fileList.setColumnCount(4) self.__fileList.setHeaderLabels([ _("File name"), _("Input encoding"), _("Output encoding"), _("Subtitle format"), _("FPS")]) mainLayout.addWidget(self.__fileList) self.setContextMenuPolicy(Qt.CustomContextMenu) self.setLayout(mainLayout) def __resizeHeader(self, header): # TODO: add an option (in subconvert settings) to set the following: # header.setResizeMode(0, QHeaderView.ResizeToContents); header.setStretchLastSection(False) header.setDefaultSectionSize(130) header.resizeSection(0, 500) def __initContextMenu(self): if self._contextMenu is not None: self._contextMenu.deleteLater() self._contextMenu = None self._contextMenu = QMenu() af = ActionFactory(self) selectedItems = self.__fileList.selectedItems() anyItemSelected = len(selectedItems) > 0 # Open in tab actionOpenInTab = af.create( icon = "window-new", title = _("&Open in tab"), connection = self.requestOpeningSelectedFiles) actionOpenInTab.setEnabled(anyItemSelected) self._contextMenu.addAction(actionOpenInTab) self._contextMenu.addSeparator() # Property Files pfileMenu = self._contextMenu.addMenu(_("Use Subtitle &Properties")) pfileMenu.setEnabled(anyItemSelected) for pfile in self._settings.getLatestPropertyFiles(): # A hacky way to store pfile in lambda action = af.create( title = pfile, connection = lambda _, pfile=pfile: self._useSubProperties(pfile) ) pfileMenu.addAction(action) pfileMenu.addSeparator() pfileMenu.addAction(af.create( title = _("Open file"), connection = self._chooseSubProperties)) self._contextMenu.addSeparator() # Single properties fpsMenu = self._contextMenu.addMenu(_("&Frames per second")) fpsMenu.setEnabled(anyItemSelected) for fps in FPS_VALUES: fpsStr = str(fps) action = af.create( title = fpsStr, connection = lambda _, fps=fps: self.changeSelectedFilesFps(fps)) fpsMenu.addAction(action) formatsMenu = self._contextMenu.addMenu(_("Subtitles forma&t")) formatsMenu.setEnabled(anyItemSelected) for fmt in self._subtitleData.supportedFormats: action = af.create( title = fmt.NAME, connection = lambda _, fmt=fmt: self.changeSelectedFilesFormat(fmt) ) formatsMenu.addAction(action) inputEncodingsMenu = self._contextMenu.addMenu(_("Input &encoding")) inputEncodingsMenu.setEnabled(anyItemSelected) outputEncodingsMenu = self._contextMenu.addMenu(_("&Output encoding")) outputEncodingsMenu.setEnabled(anyItemSelected) for encoding in ALL_ENCODINGS: outAction = af.create( title = encoding, connection = lambda _, enc=encoding: self.changeSelectedFilesOutputEncoding(enc) ) outputEncodingsMenu.addAction(outAction) inAction = af.create( title = encoding, connection = lambda _, enc=encoding: self.changeSelectedFilesInputEncoding(enc) ) inputEncodingsMenu.addAction(inAction) offset = af.create(None, _("&Offset"), None, None, self._offsetDialog) offset.setEnabled(anyItemSelected) self._contextMenu.addAction(offset) self._contextMenu.addSeparator() # Link/unlink video actionLink = af.create(None, _("&Link video"), None, None, self.linkVideo) actionLink.setEnabled(anyItemSelected) self._contextMenu.addAction(actionLink) actionLink = af.create( None, _("U&nlink video"), None, None, lambda: self.changeSelectedFilesVideoPath(None)) actionLink.setEnabled(anyItemSelected) self._contextMenu.addAction(actionLink) actionLink = af.create(None, _("&Get FPS"), None, None, self.detectSelectedFilesFps) actionLink.setEnabled(anyItemSelected) self._contextMenu.addAction(actionLink) self._contextMenu.addSeparator() # Show/Remove files # Key shortcuts are actually only a hack to provide some kind of info to user that he can # use "enter/return" and "delete" to open/close subtitles. Keyboard is handled via # keyPressed -> _handleKeyPress. This is because __fileList has focus most of time anyway # (I think...) actionOpen = af.create( None, _("&Show subtitles"), None, "Enter", lambda: self._handleKeyPress(Qt.Key_Enter)) actionOpen.setEnabled(anyItemSelected) self._contextMenu.addAction(actionOpen) actionClose = af.create( None, _("&Close subtitles"), None, "Delete", lambda: self._handleKeyPress(Qt.Key_Delete)) actionClose.setEnabled(anyItemSelected) self._contextMenu.addAction(actionClose) self._contextMenu.addSeparator() # Undo/redo actionUndo = af.create("undo", _("&Undo"), None, None, self.undoSelectedFiles) actionUndo.setEnabled(anyItemSelected) self._contextMenu.addAction(actionUndo) actionRedo = af.create("redo", _("&Redo"), None, None, self.redoSelectedFiles) actionRedo.setEnabled(anyItemSelected) self._contextMenu.addAction(actionRedo) def __connectSignals(self): self.__fileList.mouseButtonDoubleClicked.connect(self._handleDoubleClick) self.__fileList.mouseButtonClicked.connect(self._handleClick) self.__fileList.keyPressed.connect(self._handleKeyPress) self.__fileList.selectionModel().selectionChanged.connect(self._selectionChangedHandle) self.customContextMenuRequested.connect(self._showContextMenu) self._subtitleData.fileAdded.connect(self._addFile) self._subtitleData.fileRemoved.connect(self._removeFile) self._subtitleData.fileChanged.connect(self._updateFile) def _addFile(self, filePath): data = self._subtitleData.data(filePath) item = QTreeWidgetItem( [filePath, data.inputEncoding, data.outputEncoding, data.outputFormat.NAME, str(data.fps)]) item.setToolTip(0, filePath) subtitleIcon = QIcon(":/img/ok.png") item.setIcon(0, subtitleIcon) videoIcon = QIcon(":/img/film.png") if data.videoPath is not None else QIcon() item.setIcon(4, videoIcon) self.__fileList.addTopLevelItem(item) self._subtitleData.history(filePath).cleanChanged.connect( lambda clean: self._cleanStateChanged(filePath, clean)) def _removeFile(self, filePath): items = self.__fileList.findItems(filePath, Qt.MatchExactly) for item in items: index = self.__fileList.indexOfTopLevelItem(item) toDelete = self.__fileList.takeTopLevelItem(index) toDelete = None def _updateFile(self, filePath): items = self.__fileList.findItems(filePath, Qt.MatchExactly) if len(items) > 0: data = self._subtitleData.data(filePath) for item in items: item.setText(1, data.inputEncoding) item.setText(2, data.outputEncoding) item.setText(3, data.outputFormat.NAME) item.setText(4, str(data.fps)) videoIcon = QIcon(":/img/film.png") if data.videoPath is not None else QIcon() item.setIcon(4, videoIcon) def _cleanStateChanged(self, filePath, clean): items = self.__fileList.findItems(filePath, Qt.MatchExactly) for item in items: if clean: icon = QIcon(":/img/ok.png") else: icon = QIcon(":/img/not_clean.png") item.setIcon(0, icon) def _selectionChangedHandle(self, selected, deselected): self.selectionChanged.emit() @property def selectedItems(self): return self.__fileList.selectedItems() def canClose(self): return False @property def isStatic(self): return True def getCurrentFile(self): return self.__fileList.currentItem() def _handleClick(self, button): item = self.__fileList.currentItem() if item is not None and button == Qt.MiddleButton: self.requestOpen.emit(item.text(0), True) def _handleDoubleClick(self, button): item = self.__fileList.currentItem() if item is not None and button == Qt.LeftButton: self.requestOpen.emit(item.text(0), False) def _handleKeyPress(self, key): items = self.__fileList.selectedItems() if key in (Qt.Key_Enter, Qt.Key_Return): for item in items: self.requestOpen.emit(item.text(0), False) elif key == Qt.Key_Delete: for item in items: self.requestRemove.emit(item.text(0)) def _showContextMenu(self): self.__initContextMenu() # redraw menu self._contextMenu.exec(QCursor.pos()) def changeSelectedSubtitleProperties(self, subProperties): # TODO: indicate the change somehow items = self.__fileList.selectedItems() applier = PropertiesFileApplier(subProperties) for item in items: filePath = item.text(0) data = self._subtitleData.data(filePath) applier.applyFor(filePath, data) command = ChangeData(filePath, data, _("Property file: %s") % filePath) self._subtitleData.execute(command) def _chooseSubProperties(self): fileDialog = FileDialog( parent = self, caption = _("Open Subtitle Properties"), directory = self._settings.getPropertyFilesPath(), filter = _("Subtitle Properties (*.spf);;All files (*)") ) fileDialog.setFileMode(QFileDialog.ExistingFile) if fileDialog.exec(): filename = fileDialog.selectedFiles()[0] self._useSubProperties(filename) def _useSubProperties(self, propertyPath): if propertyPath: try: subProperties = SubtitleProperties( self._subtitleData.supportedFormats, propertyPath) except: log.error(_("Cannot read %s as Subtitle Property file.") % propertyPath) self._settings.removePropertyFile(propertyPath) return # Don't change the call order. We don't want to change settings or redraw context menu # if something goes wrong. self.changeSelectedSubtitleProperties(subProperties) self._settings.addPropertyFile(propertyPath) @property def filePaths(self): fileList = self.__fileList # shorten notation return [fileList.topLevelItem(i).text(0) for i in range(fileList.topLevelItemCount())] def linkVideo(self): movieExtensions = "%s%s" % ("*.", ' *.'.join(File.MOVIE_EXTENSIONS)) fileDialog = FileDialog( parent = self, caption = _("Select a video"), directory = self._settings.getLatestDirectory(), filter = _("Video files (%s);;All files (*)") % movieExtensions) fileDialog.setFileMode(QFileDialog.ExistingFile) if fileDialog.exec(): movieFilePath = fileDialog.selectedFiles()[0] self.changeSelectedFilesVideoPath(movieFilePath) def requestOpeningSelectedFiles(self): # Open all files, but focus only the last one items = self.__fileList.selectedItems() for item in items: self.requestOpen.emit(item.text(0), True) self.requestOpen.emit(items[-1].text(0), False) def changeSelectedFilesFps(self, fps): items = self.__fileList.selectedItems() for item in items: filePath = item.text(0) data = self._subtitleData.data(filePath) if data.fps != fps: data.subtitles.changeFps(fps) data.fps = fps command = ChangeData(filePath, data, _("FPS: %s") % fps) self._subtitleData.execute(command) def changeSelectedFilesVideoPath(self, path): items = self.__fileList.selectedItems() for item in items: filePath = item.text(0) data = self._subtitleData.data(filePath) if data.videoPath != path: data.videoPath = path command = ChangeData(filePath, data, _("Video path: %s") % path) self._subtitleData.execute(command) def _offsetDialog(self): dialog = OffsetDialog(self, FrameTime(25, seconds=0)) if dialog.exec(): self.offsetSelectedFiles(dialog.frametime.fullSeconds) def offsetSelectedFiles(self, seconds): if seconds == 0: return items = self.__fileList.selectedItems() for item in items: filePath = item.text(0) data = self._subtitleData.data(filePath) fps = data.subtitles.fps if fps is None: log.error(_("No FPS for '%s' (empty subtitles)." % filePath)) continue ft = FrameTime(fps, seconds=seconds) data.subtitles.offset(ft) command = ChangeData(filePath, data, _("Offset by: %s") % ft.toStr()) self._subtitleData.execute(command) def detectSelectedFilesFps(self): items = self.__fileList.selectedItems() for item in items: filePath = item.text(0) data = self._subtitleData.data(filePath) if data.videoPath is not None: fpsInfo = File.detectFpsFromMovie(data.videoPath) if data.videoPath != fpsInfo.videoPath or data.fps != fpsInfo.fps: data.videoPath = fpsInfo.videoPath data.subtitles.changeFps(fpsInfo.fps) data.fps = fpsInfo.fps command = ChangeData(filePath, data, _("Detected FPS: %s") % data.fps) self._subtitleData.execute(command) def changeSelectedFilesFormat(self, fmt): items = self.__fileList.selectedItems() for item in items: filePath = item.text(0) data = self._subtitleData.data(filePath) if data.outputFormat != fmt: data.outputFormat = fmt command = ChangeData(filePath, data, _("Format: %s ") % fmt.NAME) self._subtitleData.execute(command) def changeSelectedFilesInputEncoding(self, inputEncoding): items = self.__fileList.selectedItems() for item in items: filePath = item.text(0) data = self._subtitleData.data(filePath) if data.inputEncoding != inputEncoding: try: data.encode(inputEncoding) except UnicodeDecodeError: # TODO: indicate with something more than log entry log.error(_("Cannot decode subtitles to '%s' encoding.") % inputEncoding) else: command = ChangeData(filePath, data, _("Input encoding: %s") % inputEncoding) self._subtitleData.execute(command) def changeSelectedFilesOutputEncoding(self, outputEncoding): items = self.__fileList.selectedItems() for item in items: filePath = item.text(0) data = self._subtitleData.data(filePath) if data.outputEncoding != outputEncoding: data.outputEncoding = outputEncoding command = ChangeData(filePath, data, _("Output encoding: %s") % outputEncoding) self._subtitleData.execute(command) def undoSelectedFiles(self): items = self.__fileList.selectedItems() for item in items: filePath = item.text(0) history = self._subtitleData.history(filePath) if history.canUndo(): history.undo() def redoSelectedFiles(self): items = self.__fileList.selectedItems() for item in items: filePath = item.text(0) history = self._subtitleData.history(filePath) if history.canRedo(): history.redo()