Exemple #1
0
    def __init__(self, name, subtitleData, parent = None):
        super(FileList, self).__init__(name, parent)
        self._subtitleData = subtitleData
        self._settings = SubSettings()

        self.__initGui()
        self.__connectSignals()
    def __init__(self, subFormats, parent = None):
        super().__init__(parent)
        self._settings = SubSettings()

        self.__createPropertyFilesDirectory()
        self.__initSubFormats(subFormats)
        self.__initGui()
Exemple #3
0
    def __initGui(self):
        self._settings = SubSettings()
        self.mainWidget = QWidget(self)
        mainLayout = QVBoxLayout()
        mainLayout.setContentsMargins(3, 3, 3, 3)
        mainLayout.setSpacing(2)

        self.setCentralWidget(self.mainWidget)

        self._videoWidget = VideoWidget(self)
        self._tabs = SubtitleWindow.SubTabWidget(self._subtitleData)

        mainLayout.addWidget(self._videoWidget, 1)
        mainLayout.addWidget(self._tabs, 3)

        self.statusBar()
        self.mainWidget.setLayout(mainLayout)

        self.setAcceptDrops(True)
        self.setWindowIcon(QIcon(":/img/logo.png"))
        self.setWindowTitle('Subconvert')
class PropertyFileEditor(QDialog):
    def __init__(self, subFormats, parent = None):
        super().__init__(parent)
        self._settings = SubSettings()

        self.__createPropertyFilesDirectory()
        self.__initSubFormats(subFormats)
        self.__initGui()

    def __createPropertyFilesDirectory(self):
        pfileDir = self._settings.getPropertyFilesPath()
        try:
            os.makedirs(pfileDir)
        except OSError as exc:
            if exc.errno == errno.EEXIST and os.path.isdir(pfileDir):
                pass
            else: raise

    def __initSubFormats(self, formats):
        self._formats = {}
        for f in formats:
            self._formats[f.NAME] = f

    def __initGui(self):
        layout = QVBoxLayout()
        layout.setSpacing(10)

        layout.addWidget(self._createFpsBox())
        layout.addWidget(self._createEncodingBox())
        layout.addWidget(self._createFormatBox())
        layout.addWidget(self._createButtons())

        self.setLayout(layout)
        self.setWindowTitle(_("Subtitle Properties Editor"))
        self.setModal(True)

        # Some signals
        self._closeButton.clicked.connect(self.close)
        self._openButton.clicked.connect(self.openProperties)
        self._saveButton.clicked.connect(self.saveProperties)

        self._autoEncoding.toggled.connect(self._inputEncoding.setDisabled)
        self._changeEncoding.toggled.connect(self._outputEncoding.setEnabled)

    def _createFpsBox(self):
        groupbox = QGroupBox(_("FPS"))
        layout = QHBoxLayout()

        self._autoFps = QCheckBox(_("Auto FPS"), self)

        self._fps = QComboBox(self)
        self._fps.addItems(["23.976", "24", "25", "29.97", "30"])
        self._fps.setEditable(True)

        layout.addWidget(self._autoFps)
        layout.addWidget(self._fps)
        groupbox.setLayout(layout)
        return groupbox

    def _createEncodingBox(self):
        groupbox = QGroupBox(_("File Encoding"))
        layout = QGridLayout()


        self._autoEncoding = QCheckBox(_("Auto input encoding"), self)
        self._inputEncoding = QComboBox(self)
        self._inputEncoding.addItems(ALL_ENCODINGS)
        self._inputEncoding.setDisabled(self._autoEncoding.isChecked())
        inputLabel = QLabel(_("Input encoding"))

        self._changeEncoding = QCheckBox(_("Change encoding on save"), self)
        self._outputEncoding = QComboBox(self)
        self._outputEncoding.addItems(ALL_ENCODINGS)
        self._outputEncoding.setEnabled(self._changeEncoding.isChecked())
        outputLabel = QLabel(_("Output encoding"))

        layout.addWidget(self._autoEncoding, 0, 0)
        layout.addWidget(self._inputEncoding, 1, 0)
        layout.addWidget(inputLabel, 1, 1)
        layout.addWidget(self._changeEncoding, 2, 0)
        layout.addWidget(self._outputEncoding, 3, 0)
        layout.addWidget(outputLabel, 3, 1)
        groupbox.setLayout(layout)
        return groupbox

    def _createFormatBox(self):
        groupbox = QGroupBox(_("Subtitle format"))
        layout = QGridLayout()

        displayedFormats = list(self._formats.keys())
        displayedFormats.sort()
        self._outputFormat = QComboBox(self)
        self._outputFormat.addItems(displayedFormats)
        formatLabel = QLabel(_("Output format"))

        layout.addWidget(self._outputFormat, 0, 0)
        layout.addWidget(formatLabel, 0, 1)
        groupbox.setLayout(layout)
        return groupbox

    def _createButtons(self):
        widget = QWidget(self)
        layout = QHBoxLayout()

        self._openButton = QPushButton(_("Open"))
        self._saveButton = QPushButton(_("Save"))
        self._closeButton = QPushButton(_("Close"))

        layout.addWidget(self._openButton)
        layout.addWidget(self._saveButton)
        layout.addWidget(self._closeButton)

        widget.setLayout(layout)
        return widget

    def _createSubtitleProperties(self):
        subProperties = SubtitleProperties(list(self._formats.values()))

        subProperties.autoFps = self._autoFps.isChecked()
        subProperties.fps = self._fps.currentText()
        subProperties.autoInputEncoding = self._autoEncoding.isChecked()
        subProperties.changeEncoding = self._changeEncoding.isChecked()
        subProperties.inputEncoding = self._inputEncoding.currentText()
        subProperties.outputEncoding = self._outputEncoding.currentText()

        subProperties.outputFormat = self._formats.get(self._outputFormat.currentText())
        return subProperties

    def changeProperties(self, subProperties):
        self._autoFps.setChecked(subProperties.autoFps)
        self._fps.setEditText(str(subProperties.fps))

        self._autoEncoding.setChecked(subProperties.autoInputEncoding)
        self._changeEncoding.setChecked(subProperties.changeEncoding)
        self._inputEncoding.setCurrentIndex(
            self._inputEncoding.findText(subProperties.inputEncoding))
        self._outputEncoding.setCurrentIndex(
            self._outputEncoding.findText(subProperties.outputEncoding))

        if self._formats.get(subProperties.outputFormat.NAME) is subProperties.outputFormat:
            self._outputFormat.setCurrentIndex(
                self._outputFormat.findText(subProperties.outputFormat.NAME))
        else:
            self.close()
            raise RuntimeError(_("Subtitle format (%s) doesn't match any of known formats!") %
                subProperties.outputFormat.NAME)

    def saveProperties(self):
        subProperties = None

        try:
            subProperties = self._createSubtitleProperties()
        except Exception as e:
            dialog = QMessageBox(self)
            dialog.setIcon(QMessageBox.Critical)
            dialog.setWindowTitle(_("Incorrect value"))
            dialog.setText(_("Could not save SPF file because of incorrect parameters."));
            dialog.setDetailedText(str(e));
            dialog.exec()
            return

        fileDialog = FileDialog(
            parent = self,
            caption = _('Save Subtitle Properties'),
            directory = self._settings.getPropertyFilesPath()
        )
        fileDialog.setAcceptMode(QFileDialog.AcceptSave)
        fileDialog.setFileMode(QFileDialog.AnyFile)

        if fileDialog.exec():
            filename = fileDialog.selectedFiles()[0]
            if not filename.endswith(".spf"):
                filename = "%s%s" % (filename, ".spf")
            self._settings.setPropertyFilesPath(os.path.dirname(filename))
            subProperties.save(filename)
            self._settings.addPropertyFile(filename)
            self.close()

    def openProperties(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._settings.setPropertyFilesPath(os.path.dirname(filename))
            subProperties = SubtitleProperties(list(self._formats.values()), filename)
            self.changeProperties(subProperties)
Exemple #5
0
class MainWindow(QMainWindow):
    def __init__(self, args, parser):
        super(MainWindow, self).__init__()
        log.debug(_("Theme search paths: %s") % QIcon.themeSearchPaths())
        log.debug(_("Used theme name: '%s'") % QIcon.themeName())

        self.setObjectName("main_window")

        self._subtitleData = DataController(parser, self)

        self.__initGui()
        self.__initActions()
        self.__initMenuBar()
        self.__initShortcuts()
        self.__updateMenuItemsState()
        self.__connectSignals()
        self.restoreWidgetState()

        self.handleArgs(args)

    def __initGui(self):
        self._settings = SubSettings()
        self.mainWidget = QWidget(self)
        mainLayout = QVBoxLayout()
        mainLayout.setContentsMargins(3, 3, 3, 3)
        mainLayout.setSpacing(2)

        self.setCentralWidget(self.mainWidget)

        self._videoWidget = VideoWidget(self)
        self._tabs = SubtitleWindow.SubTabWidget(self._subtitleData,
                                                 self._videoWidget)

        mainLayout.addWidget(self._videoWidget, 1)
        mainLayout.addWidget(self._tabs, 3)

        self.statusBar()
        self.mainWidget.setLayout(mainLayout)

        self.setAcceptDrops(True)
        self.setWindowIcon(QIcon(":/img/logo.png"))
        self.setWindowTitle('Subconvert')

    def __connectSignals(self):
        self._tabs.tabChanged.connect(self.__updateMenuItemsState)
        self._tabs.tabChanged.connect(self.__updateWindowTitle)
        self._tabs.fileList.selectionChanged.connect(self.__updateMenuItemsState)
        self._subtitleData.fileAdded.connect(self.__updateMenuItemsState, Qt.QueuedConnection)
        self._subtitleData.fileChanged.connect(self.__updateMenuItemsState, Qt.QueuedConnection)
        self._subtitleData.fileRemoved.connect(self.__updateMenuItemsState, Qt.QueuedConnection)
        self._subtitleData.subtitlesAdded.connect(self.__updateMenuItemsState, Qt.QueuedConnection)
        self._subtitleData.subtitlesRemoved.connect(
            self.__updateMenuItemsState, Qt.QueuedConnection)
        self._subtitleData.subtitlesChanged.connect(
            self.__updateMenuItemsState, Qt.QueuedConnection)

    def __initActions(self):
        self._actions = {}
        af = ActionFactory(self)

        # open / save
        self._actions["openFile"] = af.create(
            "document-open", _("&Open"), _("Open subtitle file."), "ctrl+o", self.openFile)
        self._actions["saveFile"] = af.create(
            "document-save", _("&Save"), _("Save current file."), "ctrl+s", self.saveFile)
        self._actions["saveFileAs"] = af.create(
            "document-save-as",_("S&ave as..."), _("Save current file as..."), "ctrl++shift+s",
            self.saveFileAs)
        self._actions["saveAllFiles"] = af.create(
            "document-save", _("Sa&ve all"), _("Save all opened files."), None, self.saveAll)

        # app exit
        self._actions["exit"] = af.create(
            "application-exit", _("&Exit"), _("Exit Subconvert."), None, qApp.quit)

        # tab management
        self._actions["nextTab"] = af.create(
            None, None, None, "ctrl+tab", self.nextTab)
        self._actions["previousTab"] = af.create(
            None, None, None, "ctrl+shift+tab", self.previousTab)
        self._actions["closeTab"] = af.create(
            None, None, None, "ctrl+w", self.closeTab)

        # Subtitles
        self._actions["undo"] = af.create(
            "undo", _("&Undo"), None, "ctrl+z", self.undo)
        self._actions["redo"] = af.create(
            "redo", _("&Redo"), None, "ctrl+shift+z", self.redo)

        for fps in FPS_VALUES:
            fpsStr = str(fps)
            self._actions[fpsStr] = af.create(
                None, fpsStr, None, None, lambda _, fps=fps: self.changeFps(fps))

        for encoding in ALL_ENCODINGS:
            self._actions["in_%s" % encoding] = af.create(
                None, encoding, None, None,
                lambda _, enc = encoding: self.changeInputEncoding(enc))

            self._actions["out_%s" % encoding] = af.create(
                None, encoding, None, None,
                lambda _, enc = encoding: self.changeOutputEncoding(enc))

        for fmt in self._subtitleData.supportedFormats:
            self._actions[fmt.NAME] = af.create(
                None, fmt.NAME, None, None, lambda _, fmt = fmt: self.changeSubFormat(fmt))

        self._actions["linkVideo"] = af.create(
            None, _("&Link video"), None, "ctrl+l", self.linkVideo)

        self._actions["unlinkVideo"] = af.create(
            None, _("U&nlink video"), None, "ctrl+u", lambda: self._setVideoLink(None))

        self._actions["fpsFromMovie"] = af.create(
            None, _("&Get FPS"), None, "ctrl+g", self.getFpsFromMovie)

        self._actions["insertSub"] = af.create(
            "list-add", _("&Insert subtitle"), None, "insert",
            connection = lambda: self._tabs.currentPage().insertNewSubtitle())

        self._actions["addSub"] = af.create(
            "list-add", _("&Add subtitle"), None, "alt+insert",
            connection = lambda: self._tabs.currentPage().addNewSubtitle())

        self._actions["offset"] = af.create(
            None, _("&Offset"), None, None, self.offset)

        self._actions["removeSub"] = af.create(
            "list-remove", _("&Remove subtitles"), None, "delete",
            connection = lambda: self._tabs.currentPage().removeSelectedSubtitles())

        self._actions["findSub"] = af.create(
            "edit-find", _("&Find..."), None, "ctrl+f",
            connection = lambda: self._tabs.currentPage().highlight())

        # Video
        self._videoRatios = [(4, 3), (14, 9), (14, 10), (16, 9), (16, 10)]
        self._actions["openVideo"] = af.create(
            "document-open", _("&Open video"), None, "ctrl+m", self.openVideo)
        self._actions["togglePlayback"] = af.create(
            "media-playback-start", _("&Play/pause"), _("Toggle video playback"), "space",
            self._videoWidget.togglePlayback)
        self._actions["forward"] = af.create(
            "media-skip-forward", _("&Forward"), None, "ctrl+right", self._videoWidget.forward)
        self._actions["rewind"] = af.create(
            "media-skip-backward", _("&Rewind"), None, "ctrl+left", self._videoWidget.rewind)
        self._actions["frameStep"] = af.create(
            None, _("Next &frame"), _("Go to the next frame in a video"), ".",
            self._videoWidget.nextFrame)

        for ratio in self._videoRatios:
            self._actions["changeRatio_%d_%d" % ratio] = af.create(
                None, "%d:%d" % ratio, None, None,
                lambda _, r=ratio: self._videoWidget.changePlayerAspectRatio(r[0], r[1]))

        self._actions["changeRatio_fill"] = af.create(
            None, _("Fill"), None, None, self._videoWidget.fillPlayer)

        self._actions["videoJump"] = af.create(
            None, _("&Jump to subtitle"), None, "ctrl+j", self.jumpToSelectedSubtitle)

        # SPF editor
        self._actions["spfEditor"] = af.create(
            "accessories-text-editor", _("Subtitle &Properties Editor"), None, None, self.openPropertyEditor)

        # View
        self._actions["togglePlayer"] = af.create(
            None, _("&Video player"), _("Show or hide video player"), "F3", self.togglePlayer)
        self._actions["togglePanel"] = af.create(
            None, _("Side &panel"), _("Show or hide left panel"), "F4", self._tabs.togglePanel)

        # Help
        self._actions["helpPage"] = af.create(
            "help-contents", _("&Help"), _("Open &help page"), "F1", self.openHelp)
        self._actions["aboutSubconvert"] = af.create(
            "help-about", _("&About Subconvert"), None, None, self.openAboutDialog)

    def __initMenuBar(self):
        menubar = self.menuBar()
        fileMenu = menubar.addMenu(_('&File'))
        fileMenu.addAction(self._actions["openFile"])
        fileMenu.addSeparator()
        fileMenu.addAction(self._actions["saveFile"])
        fileMenu.addAction(self._actions["saveFileAs"])
        fileMenu.addAction(self._actions["saveAllFiles"])
        fileMenu.addSeparator()
        fileMenu.addAction(self._actions["exit"])

        subtitlesMenu = menubar.addMenu(_("&Subtitles"))
        subtitlesMenu.addAction(self._actions["undo"])
        subtitlesMenu.addAction(self._actions["redo"])
        subtitlesMenu.addSeparator()
        subtitlesMenu.addAction(self._actions["insertSub"])
        subtitlesMenu.addAction(self._actions["addSub"])
        subtitlesMenu.addAction(self._actions["removeSub"])
        subtitlesMenu.addAction(self._actions["findSub"])
        subtitlesMenu.addSeparator()
        self._fpsMenu = subtitlesMenu.addMenu(_("&Frames per second"))
        self._fpsMenu.addSeparator()
        for fps in FPS_VALUES:
            self._fpsMenu.addAction(self._actions[str(fps)])
        self._subFormatMenu = subtitlesMenu.addMenu(_("Subtitles forma&t"))
        for fmt in self._subtitleData.supportedFormats:
            self._subFormatMenu.addAction(self._actions[fmt.NAME])
        self._inputEncodingMenu = subtitlesMenu.addMenu(_("Input &encoding"))
        self._outputEncodingMenu = subtitlesMenu.addMenu(_("&Output encoding"))
        for encoding in ALL_ENCODINGS:
            self._inputEncodingMenu.addAction(self._actions["in_%s" % encoding])
            self._outputEncodingMenu.addAction(self._actions["out_%s" % encoding])
        subtitlesMenu.addAction(self._actions["offset"])
        subtitlesMenu.addSeparator()
        subtitlesMenu.addAction(self._actions["linkVideo"])
        subtitlesMenu.addAction(self._actions["unlinkVideo"])
        subtitlesMenu.addAction(self._actions["fpsFromMovie"])
        subtitlesMenu.addSeparator()

        videoMenu = menubar.addMenu(_("&Video"))
        videoMenu.addAction(self._actions["openVideo"])
        videoMenu.addSeparator()

        playbackMenu = videoMenu.addMenu(_("&Playback"))
        playbackMenu.addAction(self._actions["togglePlayback"])
        playbackMenu.addSeparator()
        playbackMenu.addAction(self._actions["forward"])
        playbackMenu.addAction(self._actions["rewind"])
        playbackMenu.addAction(self._actions["frameStep"])

        self._ratioMenu = videoMenu.addMenu(_("&Aspect ratio"))
        for ratio in self._videoRatios:
            self._ratioMenu.addAction(self._actions["changeRatio_%d_%d" % ratio])
        self._ratioMenu.addSeparator()
        self._ratioMenu.addAction(self._actions["changeRatio_fill"])
        videoMenu.addSeparator()

        videoMenu.addAction(self._actions["videoJump"])

        viewMenu = menubar.addMenu(_("Vie&w"))
        viewMenu.addAction(self._actions["togglePlayer"])
        viewMenu.addAction(self._actions["togglePanel"])

        toolsMenu = menubar.addMenu(_("&Tools"))
        toolsMenu.addAction(self._actions["spfEditor"])

        helpMenu = menubar.addMenu(_("&Help"))
        helpMenu.addAction(self._actions["helpPage"])
        helpMenu.addAction(self._actions["aboutSubconvert"])

    def __initShortcuts(self):
        self.addAction(self._actions["nextTab"])
        self.addAction(self._actions["previousTab"])
        self.addAction(self._actions["closeTab"])

    def cleanup(self):
        self._videoWidget.close()

    def __getAllSubExtensions(self):
        formats = self._subtitleData.supportedFormats
        exts = [_('Default')]
        exts.extend(set([ f.EXTENSION for f in formats ]))
        exts.sort()
        return exts

    def _writeFile(self, filePath, newFilePath=None):
        if newFilePath is None:
            newFilePath = filePath

        data = self._subtitleData.data(filePath)
        converter = SubConverter()
        content = converter.convert(data.outputFormat, data.subtitles)

        if File.exists(newFilePath):
            file_ = File(newFilePath)
            file_.overwrite(content, data.outputEncoding)
        else:
            File.write(newFilePath, content, data.outputEncoding)
        self._subtitleData.setCleanState(filePath)
        self.__updateMenuItemsState()

    def __updateWindowTitle(self):
        tab = self._tabs.currentPage()
        if tab.isStatic:
            self.setWindowTitle("Subconvert")
        else:
            self.setWindowTitle("%s - Subconvert" % tab.name)

    def _openFiles(self, paths, encoding):
        unsuccessfullFiles = []
        for filePath in paths:
            # TODO: separate reading file and adding to the list.
            # TODO: there should be readDataFromFile(filePath, properties=None),
            # TODO: which should set default properties from Subtitle Properties File
            command = NewSubtitles(filePath, encoding)
            try:
                self._subtitleData.execute(command)
            except DoubleFileEntry:
                pass # file already opened
            except Exception as e:
                log.exception(e)
                unsuccessfullFiles.append("%s: %s" % (filePath, str(e)))
        if len(unsuccessfullFiles) > 0:
            dialog = CannotOpenFilesMsg(self)
            dialog.setFileList(unsuccessfullFiles)
            dialog.exec()

    def _setVideoLink(self, videoPath):
        currentTab = self._tabs.currentPage()
        if currentTab.isStatic:
            currentTab.changeSelectedFilesVideoPath(videoPath)
        else:
            currentTab.changeVideoPath(videoPath)

    @pyqtSlot()
    def __updateMenuItemsState(self):
        tab = self._tabs.currentPage()
        dataAvailable = self._subtitleData.count() != 0
        anyTabOpen = tab is not None
        tabIsStatic = tab.isStatic if anyTabOpen else False
        if tabIsStatic:
            cleanState = False
            anyItemSelected = len(tab.selectedItems) > 0
        else:
            cleanState = tab.history.isClean()
            anyItemSelected = False

        canUndo = (tabIsStatic and anyItemSelected) or (not tabIsStatic and tab.history.canUndo())
        canRedo = (tabIsStatic and anyItemSelected) or (not tabIsStatic and tab.history.canRedo())
        canEdit = (tabIsStatic and anyItemSelected) or (not tabIsStatic)

        self._actions["saveAllFiles"].setEnabled(dataAvailable)
        self._actions["saveFile"].setEnabled(not tabIsStatic and not cleanState)
        self._actions["saveFileAs"].setEnabled(not tabIsStatic)

        self._actions["undo"].setEnabled(canUndo)
        self._actions["redo"].setEnabled(canRedo)
        self._fpsMenu.setEnabled(canEdit)
        self._subFormatMenu.setEnabled(canEdit)
        self._inputEncodingMenu.setEnabled(canEdit)
        self._outputEncodingMenu.setEnabled(canEdit)
        self._actions["offset"].setEnabled(canEdit)

        self._actions["linkVideo"].setEnabled(canEdit)
        self._actions["unlinkVideo"].setEnabled(canEdit)
        self._actions["fpsFromMovie"].setEnabled(canEdit)

        self._actions["insertSub"].setEnabled(not tabIsStatic)
        self._actions["addSub"].setEnabled(not tabIsStatic)
        self._actions["removeSub"].setEnabled(not tabIsStatic)
        self._actions["findSub"].setEnabled(not tabIsStatic)

        self._actions["videoJump"].setEnabled(not tabIsStatic)

    def closeEvent(self, ev):
        self.saveWidgetState()

    def saveWidgetState(self):
        self._settings.setGeometry(self, self.saveGeometry())
        self._settings.setState(self, self.saveState())

        self._videoWidget.saveWidgetState(self._settings)
        self._tabs.saveWidgetState(self._settings)

    def restoreWidgetState(self):
        self.restoreGeometry(self._settings.getGeometry(self))
        self.restoreState(self._settings.getState(self))

        self._videoWidget.restoreWidgetState(self._settings)
        self._tabs.restoreWidgetState(self._settings)

    def handleArgs(self, args):
        self._openFiles(args.files, args.inputEncoding)

    def nextTab(self):
        if self._tabs.count() > 0:
            index = self._tabs.currentIndex() + 1
            if index > self._tabs.count() - 1:
                index = 0
            self._tabs.showTab(index)

    def previousTab(self):
        if self._tabs.count() > 0:
            index = self._tabs.currentIndex() - 1
            if index < 0:
                index = self._tabs.count() - 1
            self._tabs.showTab(index)

    def closeTab(self):
        if self._tabs.count() > 0:
            index = self._tabs.currentIndex()
            self._tabs.closeTab(index)

    def openFile(self):
        sub_extensions = self.__getAllSubExtensions()
        str_sub_exts = ' '.join(['*.%s' % ext for ext in sub_extensions[1:]])

        fileDialog = FileDialog(
            parent = self,
            caption = _("Open file"),
            directory = self._settings.getLatestDirectory(),
            filter = _("Subtitles (%s);;All files (*)") % str_sub_exts
        )
        fileDialog.addEncodings(True)
        fileDialog.setFileMode(QFileDialog.ExistingFiles)

        if fileDialog.exec():
            filenames = fileDialog.selectedFiles()
            encoding = fileDialog.getEncoding()
            self._settings.setLatestDirectory(os.path.dirname(filenames[0]))
            self._openFiles(filenames, encoding)


    @pyqtSlot()
    def saveFile(self, newFilePath = None):
        currentTab = self._tabs.currentPage()
        try:
            self._writeFile(currentTab.filePath, newFilePath)
        except SubException as msg:
            dialog = QMessageBox(self)
            dialog.setIcon(QMessageBox.Critical)
            dialog.setWindowTitle(_("Couldn't save file"))
            dialog.setText(str(msg))
            dialog.exec()

    @pyqtSlot()
    def saveFileAs(self):
        fileDialog = FileDialog(
            parent = self,
            caption = _('Save as...'),
            directory = self._settings.getLatestDirectory()
        )

        currentTab = self._tabs.currentPage()

        fileDialog.addFormats(self._subtitleData.supportedFormats)
        fileDialog.setSubFormat(currentTab.outputFormat)
        fileDialog.addEncodings(False)
        fileDialog.setEncoding(currentTab.outputEncoding)
        fileDialog.setAcceptMode(QFileDialog.AcceptSave)
        fileDialog.setFileMode(QFileDialog.AnyFile)

        if fileDialog.exec():
            newFilePath = fileDialog.selectedFiles()[0]
            data = currentTab.data

            outputFormat = fileDialog.getSubFormat()
            outputEncoding = fileDialog.getEncoding() # user can overwrite previous output encoding

            if data.outputFormat != outputFormat or data.outputEncoding != outputEncoding:
                # save user changes
                data.outputFormat = outputFormat
                data.outputEncoding = outputEncoding

            if self._subtitleData.fileExists(newFilePath):
                command = ChangeData(newFilePath, data, _("Overwritten by %s") % currentTab.name)
            else:
                command = CreateSubtitlesFromData(newFilePath, data)
            self._subtitleData.execute(command)
            self._tabs.openTab(newFilePath)

            self.saveFile(newFilePath)
            self._settings.setLatestDirectory(os.path.dirname(newFilePath))

    def saveAll(self):
        dialog = MessageBoxWithList(self)
        dialog.setIcon(QMessageBox.Critical)


        # TODO
        # When asked to save file, it should be should checked if it's parsed and parse it if it's
        # not (in future the parsing moment might be moved to increase responsibility when opening
        # a lot of files - i.e. only a file list will be printed and files will be actually parsed
        # on their first use (e.g. on tab open, fps change etc.). I'll have to think about it).
        # END OF TODO
        for filePath in self._tabs.fileList.filePaths:
            try:
                if not self._subtitleData.isCleanState(filePath):
                    self._writeFile(filePath)
            except SubException as msg:
                dialog.addToList(str(msg))

        if dialog.listCount() > 0:
            dialog.setWindowTitle(P_(
                "Error on saving a file",
                "Error on saving files",
                dialog.listCount()
                ))
            dialog.setText(P_(
                "Following error occured when trying to save a file:",
                "Following errors occured when trying to save files:",
                dialog.listCount()
                ))
            dialog.exec()

    def undo(self):
        currentTab = self._tabs.currentPage()
        if currentTab.isStatic:
            currentTab.undoSelectedFiles()
        else:
            currentTab.history.undo()

    def redo(self):
        currentTab = self._tabs.currentPage()
        if currentTab.isStatic:
            currentTab.redoSelectedFiles()
        else:
            currentTab.history.redo()

    def changeInputEncoding(self, encoding):
        currentTab = self._tabs.currentPage()
        if currentTab.isStatic:
            currentTab.changeSelectedFilesInputEncoding(encoding)
        else:
            currentTab.changeInputEncoding(encoding)

    def changeOutputEncoding(self, encoding):
        currentTab = self._tabs.currentPage()
        if currentTab.isStatic:
            currentTab.changeSelectedFilesOutputEncoding(encoding)
        else:
            currentTab.changeOutputEncoding(encoding)

    def changeSubFormat(self, fmt):
        currentTab = self._tabs.currentPage()
        if currentTab.isStatic:
            currentTab.changeSelectedFilesFormat(fmt)
        else:
            currentTab.changeSubFormat(fmt)

    def offset(self):
        currentTab = self._tabs.currentPage()

        # fps isn't used, but we need one to init starting FrameTime
        dialog = OffsetDialog(self, FrameTime(25, seconds=0))
        if dialog.exec():
            if currentTab.isStatic:
                currentTab.offsetSelectedFiles(dialog.frametime.fullSeconds)
            else:
                currentTab.offset(dialog.frametime.fullSeconds)

    def changeFps(self, fps):
        currentTab = self._tabs.currentPage()
        if currentTab.isStatic:
            currentTab.changeSelectedFilesFps(fps)
        else:
            currentTab.changeFps(fps)

    def togglePlayer(self):
        if self._videoWidget.isHidden():
            self._videoWidget.show()
        else:
            self._videoWidget.hide()

    def getFpsFromMovie(self):
        currentTab = self._tabs.currentPage()
        if currentTab.isStatic:
            currentTab.detectSelectedFilesFps()
        else:
            currentTab.detectFps()

    def openVideo(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._videoWidget.openFile(movieFilePath)

    def jumpToSelectedSubtitle(self):
        currentTab = self._tabs.currentPage()
        subtitleList = currentTab.selectedSubtitles()
        if len(subtitleList) > 0:
            self._videoWidget.jumpTo(subtitleList[0].start)

    def openPropertyEditor(self):
        editor = PropertyFileEditor(self._subtitleData.supportedFormats, self)
        editor.exec()

    def openAboutDialog(self):
        spacer = QSpacerItem(650, 0)
        dialog = QMessageBox(self)
        dialog.setIconPixmap(QPixmap(":/img/logo.png"))
        dialog.setWindowTitle(_("About Subconvert"))
        dialog.setText(aboutText)

        dialogLayout = dialog.layout()
        dialogLayout.addItem(spacer, dialogLayout.rowCount(), 0, 1, dialogLayout.columnCount())
        dialog.exec()

    def openHelp(self):
        url = "https://github.com/mgoral/subconvert/wiki"
        if QDesktopServices.openUrl(QUrl(url)) is False:
            dialog = QMessageBox(self)
            dialog.setIcon(QMessageBox.Critical)
            dialog.setWindowTitle(_("Couldn't open URL"))
            dialog.setText(_("""Failed to open URL: <a href="%(url)s">%(url)s</a>.""") %
                {"url": url})
            dialog.exec()

    def dragEnterEvent(self, event):
        mime = event.mimeData()

        # search for at least local file in dragged urls (and accept drag event only in that case)
        if mime.hasUrls():
            urls = mime.urls()
            for url in urls:
                if url.isLocalFile() and os.path.isfile(url.toLocalFile()):
                    event.acceptProposedAction()
                    return

    def dropEvent(self, event):
        mime = event.mimeData()
        urls = mime.urls()

        subPaths = []
        moviePaths = []
        for url in urls:
            if url.isLocalFile():
                filePath = url.toLocalFile()
                if not os.path.isdir(filePath):
                    fileName, fileExtension = os.path.splitext(filePath)

                    if fileExtension.strip('.').lower() in File.MOVIE_EXTENSIONS:
                        moviePaths.append(filePath)
                    else:
                        subPaths.append(filePath)

        # open all subtitles and only the first movie
        if len(moviePaths) > 0:
            self._videoWidget.openFile(moviePaths[0])
        if len(subPaths) > 0:
            self._openFiles(subPaths, None)

    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._setVideoLink(movieFilePath)
Exemple #6
0
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()
Exemple #7
0
 def saveWidgetState(self, settings):
     settings = SubSettings()
     settings.setHidden(self, self.isHidden())