Пример #1
0
class IssueBook(QWidget):
    session = Session()

    def __init__(self):
        super().__init__()
        self.title = "Admin Panel"
        self.width = 640
        self.height = 400
        self.top = 100
        self.left = 100
        self.initUI()

    def initUI(self):

        self.lblIssuedTo = QLabel(self)
        self.lblIssuedTo.setText("Issued To")
        self.lblIssuedTo.move(180, 100)

        self.lblBook = QLabel(self)
        self.lblBook.setText("Book")
        self.lblBook.move(180, 130)

        self.issuedTo = QComboBox(self)
        self.issuedTo.move(220, 100)
        self.populateUsers(self.issuedTo)

        self.Book = QComboBox(self)
        self.Book.move(220, 130)
        self.populateBooks(self.Book)

        self.saveBtn = QPushButton("Issue Book", self)
        self.saveBtn.move(250, 200)
        self.saveBtn.clicked.connect(self.saveIssue)

        self.setWindowTitle(self.title)
        self.setGeometry(self.left, self.top, self.width, self.height)
        self.show()

    def populateUsers(self, combobox):
        users = self.session.query(User).filter(User.role == "Student")
        for user in users:
            combobox.addItem(user.username)

    def populateBooks(self, combobox):
        books = self.session.query(Book).all()
        for book in books:
            combobox.addItem(book.name)

    def saveIssue(self):
        issued_to = self.session.query(User).filter(
            User.username == self.issuedTo.currentText()).first()
        book = self.session.query(Book).filter(
            Book.name == self.Book.currentText()).first()
        try:
            issued = Issue(issued_to.id, book.id)
            self.session.add(issued)
            self.session.commit()
        except:
            print("An error occured")
Пример #2
0
    def set_module_layout(self, module_name: str = "screenshot"):
        """Sets the layout which can execute modules."""
        self._current_layout = "Module"
        self._clear_layout()

        command_type_label = QLabel("Command type: ")
        command_type_combobox = QComboBox()

        command_type_combobox.addItem("Module")
        command_type_combobox.addItem("Shell")

        module_label = QLabel("Module name: ")
        module_combobox = QComboBox()

        for module_name in modules.get_names():
            module_combobox.addItem(module_name)

        module_combobox.currentTextChanged.connect(self._on_module_change)
        command_type_combobox.currentTextChanged.connect(
            self._on_command_type_change)

        self._layout.setColumnStretch(1, 1)
        self._layout.addWidget(command_type_label, 0, 0)
        self._layout.addWidget(command_type_combobox, 0, 1)
        self._layout.addWidget(module_label, 1, 0)
        self._layout.addWidget(module_combobox, 1, 1)

        # Module layout
        cached_module = modules.get_module(module_name)

        if not cached_module:
            cached_module = modules.load_module(module_name, self._module_view,
                                                self._model)

        input_fields = []

        for option_name in cached_module.get_setup_messages():
            input_field = QLineEdit()

            self._sub_layout.addWidget(QLabel(option_name))
            self._sub_layout.addWidget(input_field)
            input_fields.append(input_field)

        run_button = QPushButton("Run")
        run_button.setMaximumWidth(250)
        run_button.setMinimumHeight(25)

        run_button.pressed.connect(lambda: self._on_module_run(
            module_combobox.currentText(), input_fields))

        self._sub_layout.addWidget(QLabel(""))
        self._sub_layout.addWidget(run_button)
        self._sub_layout.setContentsMargins(0, 15, 0, 0)
        self._layout.addLayout(self._sub_layout,
                               self._layout.rowCount() + 2, 0, 1, 2)

        self._on_module_change(module_combobox.currentText())
class ConfigWidget(QWidget):
    def __init__(self, plugin_action):
        QWidget.__init__(self)
        self.plugin_action = plugin_action
        layout = QVBoxLayout(self)
        self.setLayout(layout)

        # copy of preferences
        self.tmpserials = plugin_prefs['kobo_serials']

        combo_label = QLabel(_('When should Obok try to insert EPUBs into existing calibre entries?'), self)
        layout.addWidget(combo_label)
        self.find_homes = QComboBox()
        self.find_homes.setToolTip(_('<p>Default behavior when duplicates are detected. None of the choices will cause calibre ebooks to be overwritten'))
        layout.addWidget(self.find_homes)
        self.find_homes.addItems([_('Ask'), _('Always'), _('Never')])
        index = self.find_homes.findText(plugin_prefs['finding_homes_for_formats'])
        self.find_homes.setCurrentIndex(index)

        self.serials_button = QtGui.QPushButton(self)
        self.serials_button.setToolTip(_(u"Click to manage Kobo serial numbers for Kobo ebooks"))
        self.serials_button.setText(u"Kobo devices serials")
        self.serials_button.clicked.connect(self.edit_serials)
        layout.addWidget(self.serials_button)


    def edit_serials(self):
        d = ManageKeysDialog(self,u"Kobo device serial numbers",self.tmpserials, AddSerialDialog)
        d.exec_()

    
    def save_settings(self):
        plugin_prefs['finding_homes_for_formats'] = unicode(self.find_homes.currentText())
        plugin_prefs['kobo_serials'] = self.tmpserials
Пример #4
0
class HashGeneratorTab(QWidget):
    def __init__(self, console):
        super().__init__()
        self.layout = QVBoxLayout(self)

        self.titleText = QLabel("<H1>HashGenerator</H1>\nText Einfügen")
        self.layout.addWidget(self.titleText)

        self.textField = QTextEdit()
        self.layout.addWidget(self.textField)

        self.selectHash = QComboBox()
        self.selectHash.addItems(
            ["SHA1", "SHA224", "SHA256", "SHA384", "SHA512", "MD5"])
        self.selectHash.setCurrentText("SHA512")
        self.layout.addWidget(self.selectHash)

        self.genButton = QPushButton("Generiere Hash")
        self.hashGenerator = HashGenerator()
        self.genButton.clicked.connect(self.generateHash)
        self.layout.addWidget(self.genButton)

        self.output = QTextEdit("Output")
        self.output.setReadOnly(True)
        self.layout.addWidget(self.output)

        self.layout.addStretch(1)

    def generateHash(self):
        text = self.textField.toPlainText()
        hash = self.selectHash.currentText()

        self.output.setText(self.hashGenerator.getHash(text, hash))
Пример #5
0
class ConfigWidget(QWidget):
    def __init__(self):
        QWidget.__init__(self)
        self.l = QVBoxLayout()
        self.setLayout(self.l)

        self.label = QLabel("Voice")
        self.l.addWidget(self.label)

        self.voiceOptions = QComboBox()
        self.l.addWidget(self.voiceOptions)

        self.rateLabel = QLabel("Rate (-10 to 10)")
        self.l.addWidget(self.rateLabel)

        self.rateEdit = QLineEdit()
        self.rateEdit.setValidator(QIntValidator(-10, 10))
        self.rateEdit.setText(str(prefs['rate']))

        self.l.addWidget(self.rateEdit)

        self.volumeLabel = QLabel("Volume (0 to 100)")
        self.volumeEdit = QLineEdit()
        self.volumeEdit.setValidator(QIntValidator(0, 100))
        self.volumeEdit.setText(str(prefs['volume']))
        self.l.addWidget(self.volumeLabel)
        self.l.addWidget(self.volumeEdit)

        import win32com.client
        self.spVoice = win32com.client.Dispatch("SAPI.SpVoice")
        voices = self.spVoice.GetVoices("", "")

        for i in range(voices.Count):
            self.voiceOptions.addItem(voices.Item(i).GetDescription())

            if voices.Item(i).GetDescription() == prefs['voice']:
                self.voiceOptions.setCurrentIndex(i)

        self.pauseHotKey = HotkeyWidget(prefs, "pause",
                                        "Enable Pause/Play hotkey")
        self.l.addWidget(self.pauseHotKey)

        self.stopHotKey = HotkeyWidget(prefs, "stop", "Enable Stop hotkey")
        self.l.addWidget(self.stopHotKey)

        self.selectHotKey = HotkeyWidget(prefs, "select",
                                         "Enable Select Mode hotkey")
        self.l.addWidget(self.selectHotKey)

    def save_settings(self):
        from calibre_plugins.tts_ebook_viewer.hotkeys import keycodes

        prefs['voice'] = unicode(self.voiceOptions.currentText())
        prefs['rate'] = int(self.rateEdit.text())
        prefs['volume'] = int(self.volumeEdit.text())

        self.pauseHotKey.save_settings(prefs)
        self.stopHotKey.save_settings(prefs)
        self.selectHotKey.save_settings(prefs)
Пример #6
0
class ConfigWidget(QWidget):
    def __init__(self):
        super().__init__()

        vl = QVBoxLayout()
        self.setLayout(vl)

        self.search_people_box = QCheckBox('Search people')
        self.search_people_box.setChecked(prefs['search_people'])
        vl.addWidget(self.search_people_box)

        model_size_hl = QHBoxLayout()
        model_size_label = QLabel('spaCy model size')
        self.model_size_box = QComboBox()
        for size in ['sm', 'md', 'lg']:
            self.model_size_box.addItem(size)
        self.model_size_box.setCurrentText(prefs['model_size'])
        model_size_hl.addWidget(model_size_label)
        model_size_hl.addWidget(self.model_size_box)
        vl.addLayout(model_size_hl)

        zh_wiki_hl = QHBoxLayout()
        zh_label = QLabel('Chinese Wikipedia variant')
        self.zh_wiki_box = QComboBox()
        zh_variants = {
            'cn': '大陆简体',
            'hk': '香港繁體',
            'mo': '澳門繁體',
            'my': '大马简体',
            'sg': '新加坡简体',
            'tw': '臺灣正體'
        }
        for variant, text in zh_variants.items():
            self.zh_wiki_box.addItem(text, variant)
        self.zh_wiki_box.setCurrentText(zh_variants[prefs['zh_wiki_variant']])
        zh_wiki_hl.addWidget(zh_label)
        zh_wiki_hl.addWidget(self.zh_wiki_box)
        vl.addLayout(zh_wiki_hl)

        donate_button = QPushButton('Donate')
        donate_button.clicked.connect(self.donate)
        vl.addWidget(donate_button)

        github_button = QPushButton('Source code')
        github_button.clicked.connect(self.github)
        vl.addWidget(github_button)

    @staticmethod
    def donate():
        webbrowser.open('https://liberapay.com/xxyzz/donate')

    def github(self):
        webbrowser.open('https://github.com/xxyzz/WordDumb')

    def save_settings(self):
        prefs['search_people'] = self.search_people_box.isChecked()
        prefs['model_size'] = self.model_size_box.currentText()
        prefs['zh_wiki_variant'] = self.zh_wiki_box.currentData()
Пример #7
0
class ConfigWidget(QWidget):
    def __init__(self, plugin_action):
        QWidget.__init__(self)
        self.plugin_action = plugin_action
        layout = QVBoxLayout(self)
        self.setLayout(layout)

        # copy of preferences
        self.tmpserials = plugin_prefs['kobo_serials']
        self.kobodirectory = plugin_prefs['kobo_directory']

        combo_label = QLabel(
            _('When should Obok try to insert EPUBs into existing calibre entries?'
              ), self)
        layout.addWidget(combo_label)
        self.find_homes = QComboBox()
        self.find_homes.setToolTip(
            _('<p>Default behavior when duplicates are detected. None of the choices will cause calibre ebooks to be overwritten'
              ))
        layout.addWidget(self.find_homes)
        self.find_homes.addItems([_('Ask'), _('Always'), _('Never')])
        index = self.find_homes.findText(
            plugin_prefs['finding_homes_for_formats'])
        self.find_homes.setCurrentIndex(index)

        self.serials_button = QtGui.QPushButton(self)
        self.serials_button.setToolTip(
            _("Click to manage Kobo serial numbers for Kobo ebooks"))
        self.serials_button.setText("Kobo devices serials")
        self.serials_button.clicked.connect(self.edit_serials)
        layout.addWidget(self.serials_button)

        self.kobo_directory_button = QtGui.QPushButton(self)
        self.kobo_directory_button.setToolTip(
            _("Click to specify the Kobo directory"))
        self.kobo_directory_button.setText("Kobo directory")
        self.kobo_directory_button.clicked.connect(self.edit_kobo_directory)
        layout.addWidget(self.kobo_directory_button)

    def edit_serials(self):
        d = ManageKeysDialog(self, "Kobo device serial number",
                             self.tmpserials, AddSerialDialog)
        d.exec_()

    def edit_kobo_directory(self):
        tmpkobodirectory = QFileDialog.getExistingDirectory(
            self, "Select Kobo directory", self.kobodirectory or "/home",
            QFileDialog.Option.ShowDirsOnly)

        if tmpkobodirectory != u"" and tmpkobodirectory is not None:
            self.kobodirectory = tmpkobodirectory

    def save_settings(self):
        plugin_prefs[
            'finding_homes_for_formats'] = self.find_homes.currentText()
        plugin_prefs['kobo_serials'] = self.tmpserials
        plugin_prefs['kobo_directory'] = self.kobodirectory
Пример #8
0
    def __init__(self):
        super().__init__()

        self._layout = QVBoxLayout()

        host_label = QLabel("Server host (where EvilOSX will connect to):")
        self._host_field = QLineEdit()

        self._layout.addWidget(host_label)
        self._layout.addWidget(self._host_field)

        port_label = QLabel("Server port:")
        self._port_field = QLineEdit()

        self._layout.addWidget(port_label)
        self._layout.addWidget(self._port_field)

        live_label = QLabel(
            "Where should EvilOSX live? (Leave empty for ~/Library/Containers/.<RANDOM>): "
        )
        self._live_field = QLineEdit()

        self._layout.addWidget(live_label)
        self._layout.addWidget(self._live_field)

        launcher_label = QLabel("Launcher name:")
        self._launcher_combobox = QComboBox()

        for launcher_name in launchers.get_names():
            self._launcher_combobox.addItem(launcher_name)

        self._layout.addWidget(launcher_label)
        self._layout.addWidget(self._launcher_combobox)

        loader_label = QLabel("Loader name:")
        loader_combobox = QComboBox()
        self._loader_layout = QVBoxLayout()

        for loader_name in loaders.get_names():
            loader_combobox.addItem(loader_name)

        self._layout.addWidget(loader_label)
        self._layout.addWidget(loader_combobox)
        loader_combobox.currentTextChanged.connect(self._set_on_loader_change)

        # Dynamically loaded loader layout
        self._layout.addLayout(self._loader_layout)
        self._set_on_loader_change(loader_combobox.currentText())

        self._layout.setContentsMargins(10, 10, 10, 0)
        self._layout.setAlignment(Qt.AlignTop)
        self.setLayout(self._layout)
Пример #9
0
class editpopup(QWidget):
    user_id = ""
    session = Session()

    def __init__(self):
        super().__init__()
        self.title = "Edit Pop Up"
        self.width = 500
        self.height = 500
        self.top = 100
        self.left = 100
        self.initUI()

    def initUI(self):
        self.username = QLineEdit(self)
        self.username.move(200, 200)

        self.password = QLineEdit(self)
        self.password.move(200, 240)
        self.password.setEchoMode(QLineEdit.Password)

        self.role = QComboBox(self)
        self.role.addItem("Student")
        self.role.addItem("Librarian")
        self.role.addItem("Admin")
        self.role.move(200, 280)

        self.savechanges = QPushButton("Save changes", self)
        self.savechanges.move(250, 350)
        self.savechanges.clicked.connect(self.updateValues)
        self.setWindowTitle(self.title)
        self.setGeometry(self.left, self.top, self.width, self.height)

    def setValues(self, user_id, user_name, user_password, user_role):
        self.user_id = user_id
        self.username.setText(user_name)
        self.password.setText(user_password)
        self.role.setCurrentText(user_role)

    def updateValues(self):
        result = self.session.query(User).filter(
            User.id == self.user_id).first()
        result.username = self.username.text()
        result.password = self.password.text()
        result.role = self.role.currentText()
        try:
            self.session.add(result)
            self.session.commit()
            self.close()
        except:
            print("Error occured while saving")
Пример #10
0
class AddUserUI(QMainWindow):
    def __init__(self):
        super().__init__()
        self.width = 640
        self.height = 400
        self.top = 100
        self.left = 100
        self.initUI()

    def initUI(self):
        self.labelUsername = QLabel(self)
        self.labelUsername.setText("Username")
        self.labelPassword = QLabel(self)
        self.labelPassword.setText("Password")
        self.labelRole = QLabel(self)
        self.labelRole.setText("Role")
        self.labelUsername.move(250, 100)
        self.labelPassword.move(250, 130)
        self.labelRole.move(250, 160)
        self.username = QLineEdit(self)
        self.username.move(300, 100)
        self.password = QLineEdit(self)
        self.password.setEchoMode(QLineEdit.Password)
        self.password.move(300, 130)
        self.role = QComboBox(self)
        self.role.addItem("Student")
        self.role.addItem("Librarian")
        self.role.addItem("Admin")
        self.role.move(300, 160)

        button = QPushButton("Add User", self)
        button.move(300, 190)
        button.clicked.connect(self.add_user)

        self.setWindowTitle("Add Users")
        self.setGeometry(self.left, self.top, self.width, self.height)

    def add_user(self):
        uname = self.username.text()
        password = self.password.text()
        role = self.role.currentText()
        print(uname)
        print(password)
        print(role)
        user = User(uname, password, role)
        session.add(user)
        session.commit()
        self.statusBar().showMessage("User Saved Successfully")
Пример #11
0
class ConfigWidget(QWidget):
    def __init__(self, plugin_action):
        QWidget.__init__(self)
        self.plugin_action = plugin_action
        layout = QVBoxLayout(self)
        self.setLayout(layout)
        
        combo_label = QLabel(_('When should Obok try to insert EPUBs into existing calibre entries?'), self)
        layout.addWidget(combo_label)
        self.find_homes = QComboBox()
        self.find_homes.setToolTip(_('<p>Default behavior when duplicates are detected. None of the choices will cause calibre ebooks to be overwritten'))
        layout.addWidget(self.find_homes)
        self.find_homes.addItems([_('Ask'), _('Always'), _('Never')])
        index = self.find_homes.findText(plugin_prefs['finding_homes_for_formats'])
        self.find_homes.setCurrentIndex(index)
    
    def save_settings(self):
        plugin_prefs['finding_homes_for_formats'] = unicode(self.find_homes.currentText())
class ConfigWidget(QWidget):
    def __init__(self):
        QWidget.__init__(self)
        self.l = QVBoxLayout()
        self.setLayout(self.l)

        self.cbox1 = QCheckBox("Reformatting")
        self.cbox1.setChecked(prefs["reformat"])
        self.l.addWidget(self.cbox1)

        self.cbox5 = QCheckBox("Paragraph by Ending mark")
        self.cbox5.setChecked(prefs["para_by_mark"])
        self.l.addWidget(self.cbox5)

        self.cbox2 = QCheckBox("Pretty Quote char")
        self.cbox2.setChecked(prefs["pretty_quote"])
        self.l.addWidget(self.cbox2)

        self.cbox3 = QCheckBox("Guess Chapter")
        self.cbox3.setChecked(prefs["guess_chapter"])
        self.l.addWidget(self.cbox3)

        self.cbox4 = QCheckBox("Allow Empty Paragraph")
        self.cbox4.setChecked(prefs["insert_empty_paragraph"])
        self.l.addWidget(self.cbox4)

        cl = QHBoxLayout()
        cl.addWidget(QLabel("Broken Word over lines"))
        self.combo1 = QComboBox()
        wbrk_list = ["None", "Pattern", "Naver"]
        self.combo1.addItems(wbrk_list)
        self.combo1.setCurrentIndex(wbrk_list.index(prefs["correct_word_break"]))
        cl.addWidget(self.combo1)
        self.l.addLayout(cl)

    def save_settings(self):
        prefs["reformat"] = self.cbox1.isChecked()
        prefs["para_by_mark"] = self.cbox5.isChecked()
        prefs["pretty_quote"] = self.cbox2.isChecked()
        prefs["guess_chapter"] = self.cbox3.isChecked()
        prefs["insert_empty_paragraph"] = self.cbox4.isChecked()
        prefs["correct_word_break"] = str(self.combo1.currentText())
Пример #13
0
class ConfigWidget(QWidget):
    def __init__(self, plugin_action):
        QWidget.__init__(self)
        self.plugin_action = plugin_action
        layout = QVBoxLayout(self)
        self.setLayout(layout)

        combo_label = QLabel(_("When should Obok try to insert EPUBs into existing calibre entries?"), self)
        layout.addWidget(combo_label)
        self.find_homes = QComboBox()
        self.find_homes.setToolTip(
            _(
                "<p>Default behavior when duplicates are detected. None of the choices will cause calibre ebooks to be overwritten"
            )
        )
        layout.addWidget(self.find_homes)
        self.find_homes.addItems([_("Ask"), _("Always"), _("Never")])
        index = self.find_homes.findText(plugin_prefs["finding_homes_for_formats"])
        self.find_homes.setCurrentIndex(index)

    def save_settings(self):
        plugin_prefs["finding_homes_for_formats"] = unicode(self.find_homes.currentText())
Пример #14
0
class AddTagDialog(QDialog):
    def __init__(self, parent, modal=True, flags=Qt.WindowFlags()):
        QDialog.__init__(self, parent, flags)
        self.setModal(modal)
        self.setWindowTitle("Add Tag")
        lo = QVBoxLayout(self)
        lo.setContentsMargins(10, 10, 10, 10)
        lo.setSpacing(5)
        # tag selector
        lo1 = QHBoxLayout()
        lo.addLayout(lo1)
        lo1.setSpacing(5)
        self.wtagsel = QComboBox(self)
        self.wtagsel.setEditable(True)
        wtagsel_lbl = QLabel("&Tag:", self)
        wtagsel_lbl.setBuddy(self.wtagsel)
        lo1.addWidget(wtagsel_lbl, 0)
        lo1.addWidget(self.wtagsel, 1)
        self.wtagsel.activated[int].connect(self._check_tag)
        self.wtagsel.editTextChanged['QString'].connect(self._check_tag_text)
        # value editor
        self.valedit = ValueTypeEditor(self)
        lo.addWidget(self.valedit)
        # buttons
        lo.addSpacing(10)
        lo2 = QHBoxLayout()
        lo.addLayout(lo2)
        lo2.setContentsMargins(0, 0, 0, 0)
        lo2.setContentsMargins(5, 5, 5, 5)
        self.wokbtn = QPushButton("OK", self)
        self.wokbtn.setMinimumWidth(128)
        self.wokbtn.clicked.connect(self.accept)
        self.wokbtn.setEnabled(False)
        cancelbtn = QPushButton("Cancel", self)
        cancelbtn.setMinimumWidth(128)
        cancelbtn.clicked.connect(self.reject)
        lo2.addWidget(self.wokbtn)
        lo2.addStretch(1)
        lo2.addWidget(cancelbtn)
        self.setMinimumWidth(384)

    def setTags(self, tagnames):
        self.wtagsel.clear()
        self.wtagsel.addItems(list(tagnames))
        self.wtagsel.addItem("")
        self.wtagsel.setCurrentIndex(len(tagnames))

    def setValue(self, value):
        self.valedit.setValue(value)

    def _check_tag(self, tag):
        self.wokbtn.setEnabled(True)

    def _check_tag_text(self, text):
        self.wokbtn.setEnabled(bool(str(text) != ""))

    def accept(self):
        """When dialog is accepted with a default (bool) tag type,
        check if the user hasn't entered a name=value entry in the tag name field.
        This is a common mistake, and should be treated as a shortcut for setting string tags."""
        if isinstance(self.valedit.getValue(), bool):
            tagval = str(self.wtagsel.currentText()).split("=", 1)
            if len(tagval) > 1:
                #        print tagval
                if QMessageBox.warning(self,
                                       "Set a string tag instead?", """<P>You have included an "=" sign in the tag name.
            Perhaps you actually mean to set tag "%s" to the string value "%s"?</P>""" % tuple(tagval),
                                       QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes) == QMessageBox.No:
                    return
                self.wtagsel.setEditText(tagval[0])
                self.valedit.setValue(tagval[1])
        return QDialog.accept(self)

    def getTag(self):
        return str(self.wtagsel.currentText()), self.valedit.getValue()
class RecollFulltextSearchDialog(QDialog):
    def __init__(self, gui, icon, do_user_config):
        QDialog.__init__(self, gui)
        self.gui = gui
        self.do_user_config = do_user_config

        # The current database shown in the GUI
        # db is an instance of the class LibraryDatabase2 from database.py
        # This class has many, many methods that allow you to do a lot of
        # things.
        self.db = gui.current_db

        self.l = QVBoxLayout()
        self.setLayout(self.l)

        # Label
        self.labelText = QLabel('Use "and" and "or" for the search.')
        self.l.addWidget(self.labelText)

        # Title
        self.setWindowTitle('Recoll Full Text Search')
        self.setWindowIcon(icon)

        # Search window
        self.searchTextWindow = QComboBox()
        self.searchTextWindow.setEditable(True)
        self.l.addWidget(self.searchTextWindow)
        self.searchTextWindow.setFocus()
        self.searchTextWindow.setInsertPolicy(QComboBox.NoInsert)
        self.searchTextWindow.setDuplicatesEnabled(False)

        #Completer for the seach window
        self.completer = QCompleter()
        self.completer.setCompletionMode(QCompleter.UnfilteredPopupCompletion)
        self.searchTextWindow.setCompleter(self.completer)

        # output window
        self.outputWindow = QLabel()
        self.l.addWidget(self.outputWindow)

        # search button 1
        self.doSearchButton = QPushButton('Search and replace the filter',
                                          self)
        self.doSearchButton.clicked.connect(self.recollSearchNew)
        self.l.addWidget(self.doSearchButton)
        self.doSearchButton.setDefault(True)

        # search button 2
        self.doSearchButton = QPushButton('Search and add to filter', self)
        self.doSearchButton.clicked.connect(self.recollSearchAdd)
        self.l.addWidget(self.doSearchButton)

        # update database button 1
        self.updateDatabaseButton = QPushButton('Update recoll database', self)
        self.updateDatabaseButton.clicked.connect(self.updateDatabase)
        self.l.addWidget(self.updateDatabaseButton)

        # update database button 2
        self.newDatabaseButton = QPushButton('Make new recoll database', self)
        self.newDatabaseButton.clicked.connect(self.newDatabase)
        self.l.addWidget(self.newDatabaseButton)

        # config button
        self.configButton = QPushButton('Configure this plugin', self)
        self.configButton.clicked.connect(self.config)
        self.l.addWidget(self.configButton)

        # about button
        self.aboutButton = QPushButton('About', self)
        self.aboutButton.clicked.connect(self.about)
        self.l.addWidget(self.aboutButton)

        self.resize(self.sizeHint())
        #self.resize(500, self.height())

    def about(self):
        # Get the about text from a file inside the plugin zip file
        # The get_resources function is a builtin function defined for all your
        # plugin code. It loads files from the plugin zip file. It returns
        # the bytes from the specified file.

        text = get_resources('about.txt')
        #box = QMessageBox()
        #box.about(self, 'About the Recoll Full Text Search \t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t',text.decode('utf-8'))
        #self.resize(600, self.height())

        self.box = AboutWindow()
        self.box.setWindowTitle("About the Recoll Full Text Search Plugin")
        self.box.textWindow.setText(text)
        self.box.textWindow.setReadOnly(True)
        self.box.resize(600, 500)
        self.box.show()

    def updateDatabase(self):
        self.replaceDatabase = False
        self.makeDatabase()

    def newDatabase(self):
        self.replaceDatabase = True
        self.makeDatabase()

    def recollSearchNew(self):
        self.searchAdd = False
        self.recollSearch()

    def recollSearchAdd(self):
        self.searchAdd = True
        self.recollSearch()

    def makeDatabase(self):
        '''Runs recollindex outside calibre like in a terminal. 
        Look for recollindex for more information about the flags and options'''
        self.cmd = [
            prefs['pathToRecoll'] + '/recollindex', '-c',
            prefs['pathToCofig'] + '/plugins/recollFullTextSearchPlugin'
        ]
        #TODO: Fix for Linux
        #self.cmd = 'LD_LIBRARY_PATH="" ' + prefs['pathToRecoll'] + '/recollindex -c ' + prefs['pathToCofig'] + '/plugins/recollFullTextSearchPlugin'
        if self.replaceDatabase == True:
            self.cmd += [' -z']
        self.p = Popen(self.cmd, shell=False)
        # TODO: Was close_fds nessesary? check it on linux
        #self.p = Popen(self.cmd,  shell=True, stdin=PIPE, stdout=PIPE, stderr=STDOUT, close_fds=True)

        box = QMessageBox()
        box.about(
            self, 'Please read! \t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t',
            'Depending on you library size this operation can take a lot of time.\nThe process runs outside calibre so you can use or close it, but do not use this plugin.\nFor now there is no information about when recoll finishs,\nso look up, whether a recoll of recollindex process is running on you system.'
        )

    def recollSearch(self):
        '''Runs recoll outside calibre like in a terminal. 
        Look for recollindex for more information about the flags and options'''
        self.searchText = str(self.searchTextWindow.currentText()
                              )  # search text from the plugin gui
        self.searchTextWindow.insertItem(0, self.searchText)
        #TODO: Fix Linux
        #self.cmd = 'LD_LIBRARY_PATH="" ' + prefs['pathToRecoll'] + '/recoll -c ' + prefs['pathToCofig'] + '/plugins/recollFullTextSearchPlugin -b -t '
        self.cmd = [
            prefs['pathToRecoll'] + '/recoll', '-c',
            prefs['pathToCofig'] + '/plugins/recollFullTextSearchPlugin', '-b',
            '-t'
        ]
        self.cmdString = self.cmd + [self.searchText]
        # TODO: Was close_fds nessesary? check it on linux
        #self.p = Popen(self.cmdString,  shell=True, stdin=PIPE, stdout=PIPE, stderr=STDOUT, close_fds=True)
        self.p = Popen(self.cmdString,
                       shell=True,
                       stdin=PIPE,
                       stdout=PIPE,
                       stderr=STDOUT)
        self.output = self.p.stdout.read()  # output from the recoll search

        self.found = list(set(re.findall(
            r" \((\d+)\)\/[^/]*",
            self.output)))  # regex to find the calibre ids in the folder names

        self.wholeString = ''
        if len(self.found) == 0:
            self.outputWindow.setText('no books found' + ' for ' +
                                      self.searchText)
        else:
            for elem in self.found[:400]:
                self.wholeString += 'id:=' + elem + ' or '
            self.wholeString = self.wholeString[:-4]
            if len(self.found) > 400:
                self.outputWindow.setText(
                    str(len(self.found)) + ' books found' + ' for ' +
                    self.searchText + '. Only the first 400 books are shown')
            else:
                self.outputWindow.setText(
                    str(len(self.found)) + ' books found' + ' for ' +
                    self.searchText)

        if self.searchAdd == True:
            self.oldFilter = self.gui.search.text()
            self.wholeString = self.oldFilter + ' and (' + self.wholeString + ')'

        self.searchTextWindow.clearEditText()
        self.gui.search.setEditText(
            self.wholeString
        )  # set calibre search to the string found by recoll
        self.gui.search.do_search()

    def config(self):
        self.do_user_config(parent=self)
Пример #16
0
class ConvertDialog(QDialog):

    hide_text = _('&Hide styles')
    show_text = _('&Show styles')
    prince_log = ''
    prince_file = ''
    prince_css = ''

    # GUI definition
    def __init__(self, mi, fmt, opf, oeb, icon):
        '''
        :param mi: The book metadata
        :param fmt: The source format used for conversion
        :param opf: The path to the OPF file
        :param oeb: An OEB object for the unpacked book
        :param icon: The window icon
        '''
        self.opf = opf
        self.oeb = oeb
        self.mi = mi
        # The unpacked book needs to be parsed before, to read the contents
        # of the prince-style file, if it exists
        self.parse()

        QDialog.__init__(self)

        self.setAttribute(Qt.WA_DeleteOnClose)

        self.setWindowTitle(_('Convert to PDF with Prince'))
        self.setWindowIcon(icon)

        self.l = QVBoxLayout()
        self.setLayout(self.l)

        self.title_label = QLabel(_('<b>Title:</b> %s') % self.mi.title)
        self.l.addWidget(self.title_label)

        self.format_label = QLabel(_('<b>Source format:</b> %s') % fmt)
        self.l.addWidget(self.format_label)

        self.add_book = QCheckBox(_('&Add PDF to the book record'))
        self.add_book.setToolTip(_('<qt>Add the converted PDF to the selected book record</qt>'))
        self.add_book.setChecked(prefs['add_book'])
        self.add_book.stateChanged.connect(self.set_add_book)
        self.l.addWidget(self.add_book)

        self.ll = QHBoxLayout()
        self.ll.setAlignment(Qt.AlignLeft)
        self.l.addLayout(self.ll)

        self.label_css = QLabel(_('&Custom style:'))
        self.ll.addWidget(self.label_css)

        self.css_list = QComboBox()
        self.css_list.setToolTip(_('<qt>Select one style to use. Additional styles can be created in the plugin configuration</qt>'))
        for key in sorted(prefs['custom_CSS_list'], key=lambda x: x.lower()):
            self.css_list.addItem(key, key)
        self.css_list.setCurrentIndex(self.css_list.findText(prefs['default_CSS']))
        self.css_list.currentIndexChanged.connect(self.set_css)
        self.ll.addWidget(self.css_list)
        self.label_css.setBuddy(self.css_list)

        self.ll_ = QHBoxLayout()
        self.l.addLayout(self.ll_)

        self.label_args = QLabel(_('A&dditional command-line arguments:'))
        self.ll_.addWidget(self.label_args)

        self.args = QLineEdit(self)
        self.args.setText(prefs['custom_args_list'][prefs['default_CSS']])
        self.args.setToolTip(_('<qt>Specify additional command-line arguments for the conversion</qt>'))
        self.ll_.addWidget(self.args)
        self.label_args.setBuddy(self.args)

        self.css = QTabWidget()
        self.l.addWidget(self.css)

        self.css1 = TextEditWithTooltip(self, expected_geometry=(80,20))
        self.css1.setLineWrapMode(TextEditWithTooltip.NoWrap)
        self.css1.load_text(self.replace_templates(prefs['custom_CSS_list'][prefs['default_CSS']]),'css')
        self.css1.setToolTip(_('<qt>This stylesheet can be modified<br/>The default can be configured</qt>'))
        i = self.css.addTab(self.css1, _('C&ustom CSS'))
        self.css.setTabToolTip(i, _('<qt>Custom CSS stylesheet to be used for this conversion</qt>'))

        monofont = QFont('')
        monofont.setStyleHint(QFont.TypeWriter)

        if (self.prince_css):
            self.css2 = QPlainTextEdit()
            self.css2.setStyleSheet('* { font-family: monospace }')
            self.css2.setLineWrapMode(QPlainTextEdit.NoWrap)
            self.css2.setPlainText(self.prince_css)
            self.css2.setReadOnly(True)
            self.css2.setToolTip(_('<qt>This stylesheet cannot be modified</qt>'))
            i = self.css.addTab(self.css2, _('&Book CSS'))
            self.css.setTabToolTip(i, _('<qt>Book-specific CSS stylesheet included in the ebook file</qt>'))

        self.ll = QHBoxLayout()
        self.l.addLayout(self.ll)

        if (prefs['show_CSS']):
            self.toggle = QPushButton(self.hide_text, self)
        else:
            self.toggle = QPushButton(self.show_text, self)
        self.toggle.setToolTip(_('<qt>Show/hide the additional styles used for the conversion</qt>'))
        self.toggle.clicked.connect(self.toggle_tabs)

        self.convert = QPushButton(_('Con&vert'), self)
        self.convert.setToolTip(_('<qt>Run the conversion with Prince</qt>'))
        self.convert.setDefault(True)

        self.buttons = QDialogButtonBox(QDialogButtonBox.Cancel)
        self.buttons.addButton(self.toggle, QDialogButtonBox.ResetRole)
        self.buttons.addButton(self.convert, QDialogButtonBox.AcceptRole)
        self.l.addWidget(self.buttons)
        self.buttons.accepted.connect(self.prince_convert)
        self.buttons.rejected.connect(self.reject)

        if (not prefs['show_CSS']):
            self.css.hide()
        self.adjustSize()

    def toggle_tabs(self):
        '''
        Enable/disable the CSS tabs, and store the setting
        '''
        if (self.css.isVisible()):
            self.css.hide()
            self.label_args.hide()
            self.args.hide()
            self.toggle.setText(self.show_text)
            self.adjustSize()
        else:
            self.css.show()
            self.label_args.show()
            self.args.show()
            self.toggle.setText(self.hide_text)
            self.adjustSize()
        prefs['show_CSS'] = self.css.isVisible()

    def set_add_book(self):
        '''
        Save the status of the add_book checkbox
        '''
        prefs['add_book'] = self.add_book.isChecked()

    def set_css(self):
        '''
        Fill the custom CSS text box with the selected stylesheet (and command-line arguments)
        '''
        style = unicode(self.css_list.currentText())
        self.css1.load_text(self.replace_templates(prefs['custom_CSS_list'][style]),'css')
        self.args.setText(prefs['custom_args_list'][style])
        prefs['default_CSS'] = style

    def parse(self):
        '''
        Parse the unpacked OPF file to find and read the prince-style file
        '''
        from calibre.constants import DEBUG
        from os.path import dirname, join
        from lxml import etree
        import codecs

        if DEBUG: print(_('Parsing book...'))
        opf_dir = dirname(self.opf)
        root = etree.parse(self.opf).getroot()
        metadata = root.find('{*}metadata')
        for meta in metadata.findall("{*}meta[@name='prince-style']"):
            prince_id = meta.get('content')
            for item in self.oeb.manifest:
                if (item.id == prince_id):
                    self.prince_file = item.href
                    break
        if (self.prince_file):
            fl = codecs.open(join(opf_dir, self.prince_file), 'rb', 'utf-8')
            self.prince_css = fl.read()
            fl.close()

    def replace_templates(self, text):
        '''
        Replace templates (enclosed by '@{@', '@}@') in the input text
        '''
        import re
        import json
        from calibre.ebooks.metadata.book.formatter import SafeFormat
        from calibre.constants import DEBUG

        matches = list(re.finditer('@{@(.+?)@}@',text,re.DOTALL))
        results = {}
        for match in reversed(matches):
            result = SafeFormat().safe_format(match.group(1), self.mi, ('EXCEPTION: '), self.mi)
            # Escape quotes, backslashes and newlines
            result = re.sub(r'''['"\\]''', r'\\\g<0>', result)
            result = re.sub('\n', r'\A ', result)
            results[match.group(1)] = result
            text = text[:match.start(0)] + result + text[match.end(0):]
        if DEBUG:
            print(_('Replacing templates'))
            for match in matches:
                print(_('Found: %s (%d-%d)') % (match.group(1), match.start(0), match.end(0)))
                print(_('Replace with: %s') % results[match.group(1)])
        return text

    def prince_convert(self):
        '''
        Call the actual Prince command to convert to PDF
        '''
        from os import makedirs
        from os.path import dirname, join, exists
        from calibre.ptempfile import PersistentTemporaryFile
        from calibre.constants import DEBUG
        from shlex import split as shsplit

        # All files are relative to the OPF location
        opf_dir = dirname(self.opf)
        base_dir = dirname(self.pdf_file)
        base_dir = join(opf_dir, base_dir)
        try:
            makedirs(base_dir)
        except BaseException:
            if not exists(base_dir): raise

        # Create a temporary CSS file with the box contents
        custom_CSS = PersistentTemporaryFile()
        custom_CSS.write(unicode(self.css1.toPlainText()))
        custom_CSS.close()
        # Create a temporary file with the list of input files
        file_list = PersistentTemporaryFile()
        for item in self.oeb.spine:
            file_list.write(item.href + "\n")
        file_list.close()
        # Build the command line
        command = prefs['prince_exe']
        args = ['-v']
        if self.prince_file:
            args.append('-s')
            args.append(self.prince_file)
        args.append('-s')
        args.append(custom_CSS.name)
        args.append('-l')
        args.append(file_list.name)
        args.append('-o')
        args.append(self.pdf_file)
        # Additional command-line arguments
        args.extend(shsplit(self.args.text()))

        # Hide the convert button and show a busy indicator
        self.convert.setEnabled(False)
        self.progress_bar = QProgressBar()
        self.progress_bar.setRange(0,0)
        self.progress_bar.setValue(0)
        self.l.addWidget(self.progress_bar)

        # Run the command and return the path to the PDF file
        if DEBUG: print(_('Converting book...'))
        process = QProcess(self)
        process.setWorkingDirectory(opf_dir)
        process.setProcessChannelMode(QProcess.MergedChannels);
        process.error.connect(self.error)
        process.finished.connect(self.end)
        self.process = process
        if DEBUG:
          from subprocess import list2cmdline
          line = list2cmdline([command] + args)
          print(_('Command line: %s') % line)
        process.start(command, args)

    def error(self, rc):
        '''
        Show a message when there is an error in the command
        :param rc: The error code
        '''
        from calibre.gui2 import error_dialog

        # Remove the progress bar while the error message is displayed
        self.progress_bar.hide()
        self.progress_bar.deleteLater()
        error_dialog(self, _('Process error'), _('<p>Error code: %s'
            '<p>make sure Prince (<a href="http://www.princexml.com">www.princexml.com</a>) is installed '
            'and the correct command-line-interface executable is set in the configuration of this plugin, '
            'which is usually:'
            '<ul><li>In Windows: <code><i>Prince_folder</i>\\Engine\\bin\\prince.exe</code>'
            '    <li>In Linux: <code>prince</code>'
            '</ul>') % rc, show=True)
        self.pdf_file = None
        self.accept()

    def end(self, rc):
        '''
        Close and return the filename when the process ends
        :param rc: The return code (0 if successful)
        '''
        from os.path import join

        self.prince_log = unicode(self.process.readAllStandardOutput().data())
        opf_dir = unicode(self.process.workingDirectory())
        if (rc == 0):
            self.pdf_file = join(opf_dir, self.pdf_file)
        else:
            self.pdf_file = None
        self.accept()
Пример #17
0
class XArmUI(object):
    def __init__(self, ui, layout):
        self.main_ui = ui
        self.layout = layout
        super(XArmUI, self).__init__()
        self.handler = XArmHandler(self)
        self.lang = self.main_ui.lang
        self.set_ui()
        self.set_disable(True)

    def set_ui(self):
        self._set_common_top_ui()
        self._set_tab()
        self._set_common_down_ui()
        self.connect_slot()

    def _set_common_top_ui(self):
        top_frame = QFrame()
        top_frame.setMaximumHeight(60)
        top_layout = QVBoxLayout(top_frame)
        self.layout.addWidget(top_frame)

        common_top_frame = QFrame()
        common_top_frame.setMinimumHeight(50)
        common_top_layout = QHBoxLayout(common_top_frame)
        top_layout.addWidget(common_top_frame)

        label_1 = QLabel(i18n[self.lang]['Connected'] + ':')
        self.label_connected = QLabel()
        img = QImage()
        self.label_connected.setMaximumHeight(20)
        self.label_connected.setMaximumWidth(20)
        self.label_connected.setScaledContents(True)
        if img.load(disconnect_icon_path):
            self.label_connected.setPixmap(QPixmap.fromImage(img))

        label_2 = QLabel(i18n[self.lang]['Reported'] + ':')
        self.label_reported = QLabel()
        img = QImage()
        self.label_reported.setMaximumHeight(20)
        self.label_reported.setMaximumWidth(20)
        self.label_reported.setScaledContents(True)
        if img.load(disconnect_icon_path):
            self.label_reported.setPixmap(QPixmap.fromImage(img))

        self.lnt_addr = QLineEdit('192.168.1.182')
        self.lnt_addr.setMaximumWidth(100)
        self.lnt_addr.setMinimumWidth(60)
        self.btn_connect = QPushButton(i18n[self.lang]['Connect'])
        # self.btn_connect.setMaximumWidth(50)

        # common_top_layout.addStretch(0)
        common_top_layout.setSpacing(10)
        common_top_layout.addWidget(label_1)
        common_top_layout.addWidget(self.label_connected)
        common_top_layout.addWidget(label_2)
        common_top_layout.addWidget(self.label_reported)
        common_top_layout.addWidget(self.lnt_addr)
        common_top_layout.addWidget(self.btn_connect)

        # common_down_frame = QFrame()
        # common_down_layout = QHBoxLayout(common_down_frame)
        # common_down_layout.setSpacing(0)
        # top_layout.addWidget(common_down_frame)
        common_down_layout = common_top_layout

        label = QLabel(i18n[self.lang]['WarnCode'] + ':')
        self.label_warn_code = QLabel('0')
        self.label_warn_code.setStyleSheet('''color: gray;font:bold;''')
        common_down_layout.addWidget(label)
        common_down_layout.addWidget(self.label_warn_code)

        label = QLabel(i18n[self.lang]['ErrorCode'] + ':')
        self.label_error_code = QLabel('0')
        self.label_error_code.setStyleSheet('''color: gray;font:bold;''')
        common_down_layout.addWidget(label)
        common_down_layout.addWidget(self.label_error_code)

        label = QLabel(i18n[self.lang]['CmdCount'] + ':')
        self.label_cmd_count = QLabel('0')
        self.label_cmd_count.setStyleSheet('''color: gray;font:bold;''')
        common_down_layout.addWidget(label)
        common_down_layout.addWidget(self.label_cmd_count)

        label = QLabel(i18n[self.lang]['State'] + ':')
        self.label_state = QLabel('4')
        self.label_state.setStyleSheet('''color: gray;font:bold;''')
        common_down_layout.addWidget(label)
        common_down_layout.addWidget(self.label_state)

        label = QLabel(i18n[self.lang]['Maable'] + ':')
        self.label_maable = QLabel('128')
        self.label_maable.setStyleSheet('''color: gray;font:bold;''')
        common_down_layout.addWidget(label)
        common_down_layout.addWidget(self.label_maable)

        label = QLabel(i18n[self.lang]['Mtbrake'] + ':')
        self.label_mtbrake = QLabel('128')
        self.label_mtbrake.setStyleSheet('''color: gray;font:bold;''')
        common_down_layout.addWidget(label)
        common_down_layout.addWidget(self.label_mtbrake)

    def _set_tab(self):
        tab_widget = QTabWidget()
        tab_widget.setMaximumHeight(self.main_ui.window.geometry().height() //
                                    2)
        self.layout.addWidget(tab_widget)

        toolBox1 = QToolBox()
        toolBox2 = QToolBox()

        groupBox1 = QGroupBox()
        groupBox2 = QGroupBox()

        toolBox1.addItem(groupBox1, "")
        toolBox2.addItem(groupBox2, "")

        tab_widget.addTab(toolBox1, i18n[self.lang]['Joint'])
        tab_widget.addTab(toolBox2, i18n[self.lang]['Cartesian'])

        joint_layout = QVBoxLayout(groupBox1)
        cartesian_layout = QVBoxLayout(groupBox2)

        self.cartesian_ui = CartesianUI(self, cartesian_layout)
        self.axis_ui = JointUI(self, joint_layout)

    def _set_common_down_ui(self):
        slider_frame = QFrame()
        slider_layout = QGridLayout(slider_frame)
        self.layout.addWidget(slider_frame)

        label = QLabel(i18n[self.lang]['Speed'] + ':')
        self.slider_speed = QSlider(Qt.Horizontal)
        self.spinbox_speed = QSpinBox()
        self.slider_speed.setMinimum(1)
        self.slider_speed.setMaximum(1000)
        self.slider_speed.setValue(50)
        self.spinbox_speed.setSingleStep(1)
        self.spinbox_speed.setMinimum(1)
        self.spinbox_speed.setMaximum(1000)
        self.spinbox_speed.setValue(50)
        slider_layout.addWidget(label, 0, 0)
        slider_layout.addWidget(self.slider_speed, 0, 1)
        slider_layout.addWidget(self.spinbox_speed, 0, 2)

        label = QLabel(i18n[self.lang]['Acc'] + ':')
        self.slider_acc = QSlider(Qt.Horizontal)
        self.spinbox_acc = QSpinBox()
        self.slider_acc.setMinimum(1)
        self.slider_acc.setMaximum(100000)
        self.slider_acc.setValue(5000)
        self.spinbox_acc.setSingleStep(1)
        self.spinbox_acc.setMinimum(1)
        self.spinbox_acc.setMaximum(100000)
        self.spinbox_acc.setValue(5000)
        slider_layout.addWidget(label, 0, 3)
        slider_layout.addWidget(self.slider_acc, 0, 4)
        slider_layout.addWidget(self.spinbox_acc, 0, 5)

        common_frame = QFrame()
        common_layout = QGridLayout(common_frame)
        self.layout.addWidget(common_frame)

        self.btn_stop = QPushButton(i18n[self.lang]['Stop'])
        self.btn_clean = QPushButton(i18n[self.lang]['CleanErrorWarn'])
        self.btn_reset = QPushButton(i18n[self.lang]['Reset'])
        self.btn_get_servo_dbmsg = QPushButton(
            i18n[self.lang]['GetServoDebugMsg'])

        common_layout.addWidget(self.btn_stop, 0, 0)
        common_layout.addWidget(self.btn_clean, 0, 2)
        common_layout.addWidget(self.btn_reset, 0, 3)
        common_layout.addWidget(self.btn_get_servo_dbmsg, 0, 4)

        btn_frame = QFrame()
        btn_layout = QGridLayout(btn_frame)
        self.layout.addWidget(btn_frame)

        self.combobox_servo = QComboBox()
        self.combobox_servo.setStyleSheet('''color: blue;''')
        for item in [
                'axis-all', 'axis-1', 'axis-2', 'axis-3', 'axis-4', 'axis-5',
                'axis-6', 'axis-7'
        ]:
            self.combobox_servo.addItem(item)
        self.combobox_servo.setCurrentIndex(1)
        btn_layout.addWidget(self.combobox_servo, 0, 0)

        self.btn_motion_enable = QPushButton(i18n[self.lang]['MotionEnable'])
        self.btn_motion_disable = QPushButton(i18n[self.lang]['MotionDisable'])
        self.btn_servo_attach = QPushButton(i18n[self.lang]['ServoAttach'])
        self.btn_servo_detach = QPushButton(i18n[self.lang]['ServoDetach'])

        self.combobox_state = QComboBox()
        self.combobox_state.setStyleSheet('''color: blue;''')
        for item in ['sport', 'pause', 'stop']:
            self.combobox_state.addItem(item)
            self.combobox_state.setCurrentIndex(0)
        self.btn_set_state = QPushButton(i18n[self.lang]['SetState'])

        btn_layout.addWidget(self.btn_motion_enable, 0, 1)
        btn_layout.addWidget(self.btn_motion_disable, 0, 2)
        btn_layout.addWidget(self.btn_servo_attach, 0, 3)
        btn_layout.addWidget(self.btn_servo_detach, 0, 4)
        btn_layout.addWidget(self.combobox_state, 0, 5)
        btn_layout.addWidget(self.btn_set_state, 0, 6)

        self.lnt_servo_addr = QLineEdit('servo_addr')
        self.lnt_servo_addr_value = QLineEdit('value')
        self.btn_get_servo_addr16 = QPushButton(
            i18n[self.lang]['GetServoAddr16'])
        self.btn_set_servo_addr16 = QPushButton(
            i18n[self.lang]['SetServoAddr16'])
        self.btn_get_servo_addr32 = QPushButton(
            i18n[self.lang]['GetServoAddr32'])
        self.btn_set_servo_addr32 = QPushButton(
            i18n[self.lang]['SetServoAddr32'])
        self.btn_set_servo_zero = QPushButton(i18n[self.lang]['SetServoZero'])

        btn_layout.addWidget(self.lnt_servo_addr, 1, 0)
        btn_layout.addWidget(self.lnt_servo_addr_value, 1, 1)
        btn_layout.addWidget(self.btn_get_servo_addr16, 1, 2)
        btn_layout.addWidget(self.btn_set_servo_addr16, 1, 3)
        btn_layout.addWidget(self.btn_get_servo_addr32, 1, 4)
        btn_layout.addWidget(self.btn_set_servo_addr32, 1, 5)
        btn_layout.addWidget(self.btn_set_servo_zero, 1, 6)

    def connect_slot(self):
        self.btn_connect.clicked.connect(self.connect)
        self.slider_speed.valueChanged.connect(
            functools.partial(self.slider_spinbox_related,
                              slave=self.spinbox_speed,
                              scale=1))
        self.spinbox_speed.valueChanged.connect(
            functools.partial(self.slider_spinbox_related,
                              slave=self.slider_speed,
                              scale=1))
        self.slider_acc.valueChanged.connect(
            functools.partial(self.slider_spinbox_related,
                              slave=self.spinbox_acc,
                              scale=1))
        self.spinbox_acc.valueChanged.connect(
            functools.partial(self.slider_spinbox_related,
                              slave=self.slider_acc,
                              scale=1))
        self.btn_stop.clicked.connect(self.stop)
        self.btn_clean.clicked.connect(self.clean)
        self.btn_reset.clicked.connect(self.reset)
        self.btn_get_servo_dbmsg.clicked.connect(
            functools.partial(self.handler.get_servo_debug_msg,
                              only_log_error_servo=False))
        self.btn_motion_enable.clicked.connect(self.motion_enable)
        self.btn_motion_disable.clicked.connect(self.motion_disable)
        self.btn_servo_attach.clicked.connect(self.set_servo_attach)
        self.btn_servo_detach.clicked.connect(self.set_servo_detach)
        self.btn_set_state.clicked.connect(self.set_state)
        self.btn_get_servo_addr16.clicked.connect(self.get_servo_addr_16)
        self.btn_set_servo_addr16.clicked.connect(self.set_servo_addr_16)
        self.btn_get_servo_addr32.clicked.connect(self.get_servo_addr_32)
        self.btn_set_servo_addr32.clicked.connect(self.set_servo_addr_32)
        self.btn_set_servo_zero.clicked.connect(self.set_servo_zero)

    @staticmethod
    def slider_spinbox_related(value, master=None, slave=None, scale=1):
        try:
            slave.setValue(value * scale)
        except Exception as e:
            print(e)

    def reset_flag(self):
        self.cartesian_ui.reset_flag()
        self.axis_ui.reset_flag()

    def update_maable_mtbrake(self, maable, mtbrake):
        try:
            self.label_maable.setText(str(maable))
            self.label_mtbrake.setText(str(mtbrake))
            self.label_maable.setStyleSheet('''color: green;font:bold;''')
            self.label_mtbrake.setStyleSheet('''color: green;font:bold;''')
        except Exception as e:
            print(e)

    def update_cmd_count(self, cmdnum):
        try:
            self.label_cmd_count.setText(str(cmdnum))
            self.label_cmd_count.setStyleSheet('''color: green;font:bold;''')
        except Exception as e:
            print(e)

    def update_state(self, state):
        try:
            if state == 1:
                state_str = 'sport'
                self.label_state.setText(state_str)
                self.label_state.setStyleSheet('''color: green;font:bold;''')
            elif state == 2:
                state_str = 'sleep'
                self.label_state.setText(state_str)
                self.label_state.setStyleSheet('''color: gray;font:bold;''')
            elif state == 3:
                state_str = 'pause'
                self.label_state.setText(state_str)
                self.label_state.setStyleSheet('''color: orange;font:bold;''')
            elif state == 4:
                state_str = 'stop'
                self.label_state.setText(state_str)
                self.label_state.setStyleSheet('''color: red;font:bold;''')
        except Exception as e:
            print(e)
        # getattr(self, 'label_state').setText(state_str)
        # getattr(self, 'label_state').setText(str(state))
        if state != 1:
            self.reset_flag()

    def update_warn_error(self, item):
        try:
            warn, error = item
            self.label_warn_code.setText(str(warn))
            self.label_error_code.setText(str(error))
            if warn != 0:
                self.label_warn_code.setStyleSheet('''color: red;font:bold;''')
            else:
                self.label_warn_code.setStyleSheet(
                    '''color: green;font:bold;''')
            if error != 0:
                self.label_error_code.setStyleSheet(
                    '''color: red;font:bold;''')
            else:
                self.label_error_code.setStyleSheet(
                    '''color: green;font:bold;''')
        except Exception as e:
            print(e)

    def update_connect_status(self, item):
        try:
            img = QImage()
            if item[0]:
                logger.info('connect to {} success, report: {}'.format(
                    self.handler.addr, self.handler.report_type))
                if img.load(connect_icon_path):
                    self.label_connected.setPixmap(QPixmap.fromImage(img))
                    self.btn_connect.setText(i18n[self.lang]['Disconnect'])
                    self.btn_connect.setStyleSheet('''color: red;font:bold;''')
                self.set_disable(False)
            else:
                logger.info('disconnect from or failed connect {}'.format(
                    self.handler.addr))
                self.handler.cmd_que.queue.clear()
                if img.load(disconnect_icon_path):
                    self.label_connected.setPixmap(QPixmap.fromImage(img))
                    self.btn_connect.setText(i18n[self.lang]['Connect'])
                    self.btn_connect.setStyleSheet(
                        '''color: green;font:bold;''')
                self.set_disable(True)
            if item[1]:
                if img.load(connect_icon_path):
                    self.label_reported.setPixmap(QPixmap.fromImage(img))
            else:
                if img.load(disconnect_icon_path):
                    self.label_reported.setPixmap(QPixmap.fromImage(img))
        except Exception as e:
            print(e)

    def connect(self, event):
        try:
            if str(self.btn_connect.text()) == i18n[self.lang]['Connect']:
                addr = self.lnt_addr.text().strip()
                if addr == '192.168.1.':
                    addr = 'localhost'
                    report_type = 'normal'
                else:
                    tmp = addr.split(':')
                    addr = tmp[0]
                    report_type = tmp[1] if len(tmp) > 1 else 'normal'
                self.btn_connect.setText('Connecting')
                self.btn_connect.setStyleSheet('''color: orange;font:bold;''')
                self.handler.connect(addr, report_type=report_type)
                # if self.window.connect(addr, report_type=report_type):
                #     self.btn_connect.setText(self.disconnect_label)
                #     self.btn_connect.setStyleSheet('''color: red;font:bold;''')
            elif str(self.btn_connect.text()) == i18n[self.lang]['Disconnect']:
                self.handler.disconnect()
                self.btn_connect.setText(i18n[self.lang]['Connect'])
                self.btn_connect.setStyleSheet('''color: green;font:bold;''')
        except Exception as e:
            print(e)

    def stop(self, event):
        try:
            self.handler.cmd_que.queue.clear()
            if self.handler.xarm and self.handler.xarm.warn_code != 0:
                item = {
                    'cmd': 'clean_warn',
                }
                self.handler.put_cmd_que(item)
            if self.handler.xarm and self.handler.xarm.error_code != 0:
                item = {
                    'cmd': 'clean_error',
                }
                self.handler.put_cmd_que(item)
                item = {
                    'cmd': 'motion_enable',
                    'kwargs': {
                        'servo_id': 0,
                        'enable': True
                    }
                }
                self.handler.put_cmd_que(item)
            item = {
                'cmd': 'urgent_stop',
            }
            self.handler.put_cmd_que(item)
            self.reset_flag()
        except Exception as e:
            print(e)

    def clean(self, event):
        try:
            self.handler.cmd_que.queue.clear()
            if self.handler.xarm and self.handler.xarm.warn_code != 0:
                item = {
                    'cmd': 'clean_warn',
                }
                self.handler.put_cmd_que(item)
            if self.handler.xarm and self.handler.xarm.error_code != 0:
                item = {
                    'cmd': 'clean_error',
                }
                self.handler.put_cmd_que(item)
        except Exception as e:
            print(e)

    def reset(self, event):
        try:
            self.handler.cmd_que.queue.clear()
            if self.handler.xarm and self.handler.xarm.warn_code != 0:
                item = {
                    'cmd': 'clean_warn',
                }
                self.handler.put_cmd_que(item)
            if self.handler.xarm and self.handler.xarm.error_code != 0:
                item = {
                    'cmd': 'clean_error',
                }
                self.handler.put_cmd_que(item)
                item = {
                    'cmd': 'motion_enable',
                    'kwargs': {
                        'servo_id': 0,
                        'enable': True
                    }
                }
                self.handler.put_cmd_que(item)
                item = {'cmd': 'set_state', 'kwargs': {'state': 0}}
                self.handler.put_cmd_que(item)
            item = {
                'cmd': 'move_gohome',
                'kwargs': {
                    'speed': 30,
                    'mvacc': 5000,
                }
            }
            self.handler.put_cmd_que(item)
            self.reset_flag()
        except Exception as e:
            print(e)

    def get_servo_addr_16(self, event):
        try:
            addr = self.lnt_servo_addr.text().strip()
            try:
                if addr.lower().startswith('0x'):
                    addr = int(addr, base=16)
                else:
                    addr = int(addr)
            except:
                QMessageBox.warning(self.main_ui.window, '错误',
                                    '请输入正确的地址, 地址必须是u16类型')
                return
            text = self.combobox_servo.currentText()
            if text == 'axis-all':
                QMessageBox.warning(self.main_ui.window, '警告',
                                    '请选择其中一个电机,不能选择所有电机')
                return
            else:
                servo_id = int(text.split('-')[-1])
            tmp = '你确定要获取电机{}的地址{}的值吗?'.format(servo_id, addr)
            if QMessageBox.question(self.main_ui.window, '警告',
                                    tmp) == QMessageBox.Yes:
                item = {
                    'cmd': 'get_servo_addr_16',
                    'kwargs': {
                        'servo_id': servo_id,
                        'addr': addr
                    }
                }
                self.handler.put_cmd_que(item)
        except Exception as e:
            print(e)

    def get_servo_addr_32(self, event):
        try:
            addr = self.lnt_servo_addr.text().strip()
            try:
                if addr.lower().startswith('0x'):
                    addr = int(addr, base=16)
                else:
                    addr = int(addr)
            except:
                QMessageBox.warning(self.main_ui.window, '错误',
                                    '请输入正确的地址, 地址必须是u16类型')
                return
            text = self.combobox_servo.currentText()
            if text == 'axis-all':
                QMessageBox.warning(self.main_ui.window, '警告',
                                    '请选择其中一个电机,不能选择所有电机')
                return
            else:
                servo_id = int(text.split('-')[-1])
            tmp = '你确定要获取电机{}的地址{}的值吗?'.format(servo_id, addr)
            if QMessageBox.question(self.main_ui.window, '警告',
                                    tmp) == QMessageBox.Yes:
                item = {
                    'cmd': 'get_servo_addr_32',
                    'kwargs': {
                        'servo_id': servo_id,
                        'addr': addr
                    }
                }
                self.handler.put_cmd_que(item)
        except Exception as e:
            print(e)

    def set_servo_addr_16(self, event):
        try:
            addr = self.lnt_servo_addr.text().strip()
            try:
                if addr.lower().startswith('0x'):
                    addr = int(addr, base=16)
                else:
                    addr = int(addr)
            except:
                QMessageBox.warning(self.main_ui.window, '错误',
                                    '请输入正确的地址, 地址必须是u16类型')
                return
            value = self.lnt_servo_addr_value.text().strip()
            try:
                value = float(value)
            except:
                QMessageBox.warning(self.main_ui.window, '错误',
                                    '请输入正确的值, 值必须是float32类型')
                return
            text = self.combobox_servo.currentText()
            if text == 'axis-all':
                QMessageBox.warning(self.main_ui.window, '警告',
                                    '请选择其中一个电机,不能选择所有电机')
                return
            else:
                servo_id = int(text.split('-')[-1])
            tmp = '你确定要设置电机{}的地址{}的值为{}吗?'.format(servo_id, addr, value)
            if QMessageBox.question(self.main_ui.window, '警告',
                                    tmp) == QMessageBox.Yes:
                item = {
                    'cmd': 'set_servo_addr_16',
                    'kwargs': {
                        'servo_id': servo_id,
                        'addr': addr,
                        'value': value
                    }
                }
                self.handler.put_cmd_que(item)
        except Exception as e:
            print(e)

    def set_servo_addr_32(self, event):
        try:
            addr = self.lnt_servo_addr.text().strip()
            try:
                if addr.lower().startswith('0x'):
                    addr = int(addr, base=16)
                else:
                    addr = int(addr)
            except:
                QMessageBox.warning(self.main_ui.window, '错误',
                                    '请输入正确的地址, 地址必须是u16类型')
                return
            value = self.lnt_servo_addr_value.text().strip()
            try:
                value = float(value)
            except:
                QMessageBox.warning(self.main_ui.window, '错误',
                                    '请输入正确的值, 值必须是float32类型')
                return
            text = self.combobox_servo.currentText()
            if text == 'axis-all':
                QMessageBox.warning(self.main_ui.window, '警告',
                                    '请选择其中一个电机,不能选择所有电机')
                return
            else:
                servo_id = int(text.split('-')[-1])

            tmp = '你确定要设置电机{}的地址{}的值为{}吗?'.format(servo_id, addr, value)
            if QMessageBox.question(self.main_ui.window, '警告',
                                    tmp) == QMessageBox.Yes:
                item = {
                    'cmd': 'set_servo_addr_32',
                    'kwargs': {
                        'servo_id': servo_id,
                        'addr': addr,
                        'value': value
                    }
                }
                self.handler.put_cmd_que(item)
        except Exception as e:
            print(e)

    def set_servo_zero(self, event):
        try:
            text = self.combobox_servo.currentText()
            if text == 'axis-all':
                servo_id = 8
            else:
                servo_id = int(text.split('-')[-1])
            if servo_id == 8:
                tmp = '你确定要设置所有电机的零点吗?'
            else:
                tmp = '你确定要设置电机{}的零点吗?'.format(servo_id)
            if QMessageBox.question(self.main_ui.window, '警告',
                                    tmp) == QMessageBox.Yes:
                item = {
                    'cmd': 'set_servo_zero',
                    'kwargs': {
                        'servo_id': servo_id
                    }
                }
                self.handler.put_cmd_que(item)
        except Exception as e:
            print(e)

    def set_servo_attach(self, event):
        try:
            text = self.combobox_servo.currentText()
            if text == 'axis-all':
                servo_id = 8
            else:
                servo_id = int(text.split('-')[-1])

            item = {
                'cmd': 'set_servo_attach',
                'kwargs': {
                    'servo_id': servo_id
                }
            }
            self.handler.put_cmd_que(item)
            item = {
                'cmd': 'motion_enable',
                'kwargs': {
                    'servo_id': servo_id,
                    'enable': True
                }
            }
            self.handler.put_cmd_que(item)
            item = {'cmd': 'set_state', 'kwargs': {'state': 0}}
            self.handler.put_cmd_que(item)
        except Exception as e:
            print(e)

    def set_servo_detach(self, event):
        try:
            text = self.combobox_servo.currentText()
            if text == 'axis-all':
                servo_id = 8
            else:
                servo_id = int(text.split('-')[-1])

            if servo_id == 8:
                tmp = '你确定要解锁所有电机吗?'
            else:
                tmp = '你确定要解锁电机{}吗?'.format(servo_id)
            if QMessageBox.question(self.main_ui.window, '警告',
                                    tmp) == QMessageBox.Yes:
                item = {
                    'cmd': 'set_servo_detach',
                    'kwargs': {
                        'servo_id': servo_id
                    }
                }
                self.handler.put_cmd_que(item)
        except Exception as e:
            print(e)

    def motion_enable(self, event):
        self._motion_enable(True)

    def motion_disable(self, event):
        self._motion_enable(False)

    def _motion_enable(self, enable=True):
        try:
            text = self.combobox_servo.currentText()
            if text == 'axis-all':
                servo_id = 8
            else:
                servo_id = int(text.split('-')[-1])
            item = {
                'cmd': 'motion_enable',
                'kwargs': {
                    'servo_id': servo_id,
                    'enable': enable
                }
            }
            self.handler.put_cmd_que(item)
        except Exception as e:
            print(e)

    def set_state(self, event):
        try:
            text = self.combobox_state.currentText()
            if text == 'sport':
                state = 0
            elif text == 'pause':
                state = 3
            elif text == 'stop':
                state = 4
            else:
                return
            if state in [0, 3, 4]:
                item = {'cmd': 'set_state', 'kwargs': {'state': state}}
                self.handler.put_cmd_que(item)
        except Exception as e:
            print(e)

    def set_disable(self, disable):
        try:
            self.btn_stop.setDisabled(disable)
            self.btn_clean.setDisabled(disable)
            self.btn_reset.setDisabled(disable)
            self.btn_get_servo_dbmsg.setDisabled(disable)
            self.combobox_servo.setDisabled(disable)
            self.combobox_state.setDisabled(disable)
            self.btn_motion_enable.setDisabled(disable)
            self.btn_motion_disable.setDisabled(disable)
            self.btn_servo_attach.setDisabled(disable)
            self.btn_servo_detach.setDisabled(disable)
            self.btn_set_state.setDisabled(disable)
            self.lnt_servo_addr.setDisabled(disable)
            self.lnt_servo_addr_value.setDisabled(disable)
            self.btn_get_servo_addr16.setDisabled(disable)
            self.btn_set_servo_addr16.setDisabled(disable)
            self.btn_get_servo_addr32.setDisabled(disable)
            self.btn_set_servo_addr32.setDisabled(disable)
            self.btn_set_servo_zero.setDisabled(disable)

            self.slider_speed.setDisabled(disable)
            self.spinbox_speed.setDisabled(disable)
            self.slider_acc.setDisabled(disable)
            self.spinbox_acc.setDisabled(disable)

            self.axis_ui.set_disable(disable)
            self.cartesian_ui.set_disable(disable)
        except Exception as e:
            print(e)
Пример #18
0
class SourceSelectorDialog(QDialog):
    def __init__(self, parent, flags=Qt.WindowFlags()):
        QDialog.__init__(self, parent, flags)
        self.setModal(False)
        self.setWindowTitle("Select sources by...")
        lo = QVBoxLayout(self)
        lo.setContentsMargins(10, 10, 10, 10)
        lo.setSpacing(5)
        # select by
        lo1 = QHBoxLayout()
        lo.addLayout(lo1)
        lo1.setContentsMargins(0, 0, 0, 0)
        #    lab = QLabel("Select:")
        #   lo1.addWidget(lab)
        self.wselby = QComboBox(self)
        lo1.addWidget(self.wselby, 0)
        self.wselby.activated[str].connect(self._setup_selection_by)
        # under/over
        self.wgele = QComboBox(self)
        lo1.addWidget(self.wgele, 0)
        self.wgele.addItems([">", ">=", "<=", "<", "sum<=", "sum>", "=="])
        self.wgele.activated[str].connect(self._select_threshold)
        # threshold value
        self.wthreshold = QLineEdit(self)
        self.wthreshold.editingFinished.connect(self._select_threshold)
        lo1.addWidget(self.wthreshold, 1)
        # min and max label
        self.wminmax = QLabel(self)
        lo.addWidget(self.wminmax)
        # selection slider
        lo1 = QHBoxLayout()
        lo.addLayout(lo1)
        self.wpercent = QSlider(self)
        self.wpercent.setTracking(False)
        self.wpercent.valueChanged[int].connect(self._select_percentile)
        self.wpercent.sliderMoved[int].connect(
            self._select_percentile_threshold)
        self.wpercent.setRange(0, 100)
        self.wpercent.setOrientation(Qt.Horizontal)
        lo1.addWidget(self.wpercent)
        self.wpercent_lbl = QLabel("0%", self)
        self.wpercent_lbl.setMinimumWidth(64)
        lo1.addWidget(self.wpercent_lbl)
        #    # hide button
        #    lo.addSpacing(10)
        #    lo2 = QHBoxLayout()
        #    lo.addLayout(lo2)
        #    lo2.setContentsMargins(0,0,0,0)
        #    hidebtn = QPushButton("Close",self)
        #    hidebtn.setMinimumWidth(128)
        #    QObject.connect(hidebtn,pyqtSignal("clicked()"),self.hide)
        #    lo2.addStretch(1)
        #    lo2.addWidget(hidebtn)
        #    lo2.addStretch(1)
        #    self.setMinimumWidth(384)
        self._in_select_threshold = False
        self._sort_index = None
        self.qerrmsg = QErrorMessage(self)

    def resetModel(self):
        """Resets dialog based on current model."""
        if not self.model:
            return
        # getset of model tags, and remove the non-sorting tags
        alltags = set(self.model.tagnames)
        alltags -= NonSortingTags
        # make list of tags from StandardTags that are present in model
        self.sorttags = [
            tag for tag in StandardTags
            if tag in alltags or tag in TagAccessors
        ]
        # append model tags that were not in StandardTags
        self.sorttags += list(alltags - set(self.sorttags))
        # set selector
        self.wselby.clear()
        self.wselby.addItems(self.sorttags)
        for tag in "Iapp", "I":
            if tag in self.sorttags:
                self.wselby.setCurrentIndex(self.sorttags.index(tag))
                break
        self._setup_selection_by(self.wselby.currentText())

    def _reset_percentile(self):
        self.wthreshold.setText("")
        self.wpercent.setValue(50)
        self.wpercent_lbl.setText("--%")

    def _setup_selection_by(self, tag):
        tag = str(tag)  # may be QString
        # clear threshold value and percentiles
        self._reset_percentile()
        # get min/max values, and sort indices
        # _sort_index will be an array of (value,src,cumsum) tuples, sorted by tag value (high to low),
        # where src is the source, and cumsum is the sum of all values in the list from 0 up to and including the current one
        self._sort_index = []
        minval = maxval = value = None
        for isrc, src in enumerate(self.model.sources):
            try:
                if hasattr(src, tag):
                    # test if item can be cast to float
                    try:
                        float(getattr(src, tag))
                    except:
                        continue
                    else:
                        value = float(getattr(src, tag))

                elif tag in TagAccessors:
                    value = float(TagAccessors[tag](src))
                else:  # not existant for this source (maybe a tag or something??)
                    value = np.nan
            # skip source if failed to access this tag as a float
            except:
                traceback.print_exc()
                continue
            if value is not None:
                self._sort_index.append(
                    [value if not np.isnan(value) else -np.inf, src, 0.])
                minval = min(minval, value if not np.isnan(value) else
                             np.inf) if minval is not None else value
                maxval = max(maxval, value if not np.isnan(value) else
                             -np.inf) if maxval is not None else value
        # add label
        if minval is None:
            self._range = None
            self.wminmax.setText(
                "<font color=red>'%s' is not a numeric attribute</font>" % tag)
            for w in self.wgele, self.wthreshold, self.wpercent, self.wpercent_lbl:
                w.setEnabled(False)
        else:
            self._range = (minval, maxval)
            self.wminmax.setText("min: %g max: %g" % self._range)
            for w in self.wgele, self.wthreshold, self.wpercent, self.wpercent_lbl:
                w.setEnabled(True)
        # sort index by descending values
        self._sort_index.sort(reverse=True, key=operator.itemgetter(0))
        # generate cumulative sums
        cumsum = 0.
        for entry in self._sort_index:
            if not np.isneginf(entry[0]):
                cumsum += entry[0]
            entry[2] = cumsum

    # Maps comparison operators to callables. Used in _select_threshold.
    # Each callable takes two arguments: e is a tuple of (value,src,cumsum) (see _sort_index above), and x is a threshold
    # Second argument is a flag: if False, selection is inverted w.r.t. operator
    Operators = {
        "<": ((lambda e, x: e[0] >= x), False),
        "<=": ((lambda e, x: e[0] > x), False),
        ">": ((lambda e, x: e[0] > x), True),
        ">=": ((lambda e, x: e[0] >= x), True),
        "sum<=": ((lambda e, x: e[2] <= x), True),
        "sum>": ((lambda e, x: e[2] <= x), False),
        "==": ((lambda e, x: np.abs(e[0] - x) < 1.0e-8), True),
    }

    def _select_threshold(self, *dum):
        dprint(1, "select_threshold", dum)
        self._in_select_threshold = True
        busy = BusyIndicator()
        try:
            # get threshold, ignore if not set
            threshold = str(self.wthreshold.text())
            if not threshold:
                self._reset_percentile()
                return
            # try to parse threshold, ignore if invalid
            try:
                threshold = float(threshold)
            except:
                self._reset_percentile()
                return
            # get comparison operator
            op, select = self.Operators[str(self.wgele.currentText())]
            # apply to initial segment (that matches operator)
            for num, entry in enumerate(self._sort_index):
                if not op(entry, threshold):
                    break
                entry[1].selected = select
            else:
                num = len(self._sort_index)
            # apply to remaining segment
            for val, src, cumsum in self._sort_index[num:]:
                src.selected = not select
            # set percentile
            percent = round(float(num * 100) / len(self._sort_index))
            if not select:
                percent = 100 - percent
            self.wpercent.setValue(percent)
            self.wpercent_lbl.setText("%3d%%" % percent)
            # emit signal
            self.model.emitSelection(self)
        finally:
            self._in_select_threshold = False
            busy.reset_cursor()

    def _select_percentile(self, percent):
        self._select_percentile_threshold(percent, do_select=True)

    def _select_percentile_threshold(self, percent, do_select=False):
        # ignore if no sort index set up, or if _select_threshold() is being called
        if self._sort_index is None or self._in_select_threshold:
            return
        dprint(1, "select_precentile_threshold", percent)
        busy = BusyIndicator()
        # number of objects to select
        nsrc = len(self._sort_index)
        nsel = int(math.ceil(nsrc * float(percent) / 100))
        # get comparison operator
        opstr = str(self.wgele.currentText())
        op, select = self.Operators[opstr]
        # select head or tail of list, depending on direction of operator
        if select:
            thr = self._sort_index[min(nsel, nsrc - 1)]
            slc1 = slice(0, nsel)
            slc2 = slice(nsel, None)
        else:
            thr = self._sort_index[-min(nsel + 1, nsrc)]
            slc1 = slice(nsrc - nsel, None)
            slc2 = slice(0, nsrc - nsel)
        if do_select:
            for val, src, cumsum in self._sort_index[slc1]:
                src.selected = True
            for val, src, cumsum in self._sort_index[slc2]:
                src.selected = False
            self.model.emitSelection(self)
        self.wpercent_lbl.setText("%3d%%" % percent)
        self.wthreshold.setText(
            "%g" % (thr[2] if opstr.startswith("sum") else thr[0]))
        busy.reset_cursor()
        return nsel

    def setModel(self, model):
        """Sets the current model. If dialog is visible, applies the changes"""
        self.model = model
        if self.isVisible():
            self.resetModel()
        if not model:
            self.hide()

    def show(self):
        """Shows dialog, resetting the model if it was invisible."""
        if not self.isVisible():
            self.resetModel()
        QDialog.show(self)
class CreateVirtualLibrary(QDialog):  # {{{
    def __init__(self, gui, existing_names, editing=None):
        QDialog.__init__(self, gui)

        self.gui = gui
        self.existing_names = existing_names

        if editing:
            self.setWindowTitle(_('Edit virtual library'))
        else:
            self.setWindowTitle(_('Create virtual library'))
        self.setWindowIcon(QIcon(I('lt.png')))

        gl = QGridLayout()
        self.setLayout(gl)
        self.la1 = la1 = QLabel(_('Virtual library &name:'))
        gl.addWidget(la1, 0, 0)
        self.vl_name = QComboBox()
        self.vl_name.setEditable(True)
        self.vl_name.lineEdit().setMaxLength(MAX_VIRTUAL_LIBRARY_NAME_LENGTH)
        la1.setBuddy(self.vl_name)
        gl.addWidget(self.vl_name, 0, 1)
        self.editing = editing

        self.saved_searches_label = sl = QTextBrowser(self)
        sl.viewport().setAutoFillBackground(False)
        gl.addWidget(sl, 2, 0, 1, 2)

        self.la2 = la2 = QLabel(_('&Search expression:'))
        gl.addWidget(la2, 1, 0)
        self.vl_text = QLineEdit()
        self.vl_text.textChanged.connect(self.search_text_changed)
        la2.setBuddy(self.vl_text)
        gl.addWidget(self.vl_text, 1, 1)
        self.vl_text.setText(_build_full_search_string(self.gui))

        self.sl = sl = QLabel(
            '<p>' + _('Create a virtual library based on: ') +
            ('<a href="author.{0}">{0}</a>, '
             '<a href="tag.{1}">{1}</a>, '
             '<a href="publisher.{2}">{2}</a>, '
             '<a href="series.{3}">{3}</a>, '
             '<a href="search.{4}">{4}</a>.').format(_('Authors'), _(
                 'Tags'), _('Publishers'), _('Series'), _('Saved searches')))
        sl.setWordWrap(True)
        sl.setTextInteractionFlags(Qt.LinksAccessibleByMouse)
        sl.linkActivated.connect(self.link_activated)
        gl.addWidget(sl, 3, 0, 1, 2)
        gl.setRowStretch(3, 10)

        self.hl = hl = QLabel(
            _('''
            <h2>Virtual libraries</h2>

            <p>Using <i>virtual libraries</i> you can restrict calibre to only show
            you books that match a search. When a virtual library is in effect, calibre
            behaves as though the library contains only the matched books. The Tag browser
            display only the tags/authors/series/etc. that belong to the matched books and any searches
            you do will only search within the books in the virtual library. This
            is a good way to partition your large library into smaller and easier to work with subsets.</p>

            <p>For example you can use a Virtual library to only show you books with the Tag <i>"Unread"</i>
            or only books by <i>"My favorite author"</i> or only books in a particular series.</p>

            <p>More information and examples are available in the
            <a href="%s">User Manual</a>.</p>
            ''') % localize_user_manual_link(
                'https://manual.calibre-ebook.com/virtual_libraries.html'))
        hl.setWordWrap(True)
        hl.setOpenExternalLinks(True)
        hl.setFrameStyle(hl.StyledPanel)
        gl.addWidget(hl, 0, 3, 4, 1)

        bb = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
        bb.accepted.connect(self.accept)
        bb.rejected.connect(self.reject)
        gl.addWidget(bb, 4, 0, 1, 0)

        if editing:
            db = self.gui.current_db
            virt_libs = db.prefs.get('virtual_libraries', {})
            for dex, vl in enumerate(sorted(virt_libs.keys(), key=sort_key)):
                self.vl_name.addItem(vl, virt_libs.get(vl, ''))
                if vl == editing:
                    self.vl_name.setCurrentIndex(dex)
                    self.original_index = dex
            self.original_search = virt_libs.get(editing, '')
            self.vl_text.setText(self.original_search)
            self.new_name = editing
            self.vl_name.currentIndexChanged[int].connect(
                self.name_index_changed)
            self.vl_name.lineEdit().textEdited.connect(self.name_text_edited)

        self.resize(self.sizeHint() + QSize(150, 25))

    def search_text_changed(self, txt):
        db = self.gui.current_db
        searches = [_('Saved searches recognized in the expression:')]
        txt = unicode(txt)
        while txt:
            p = txt.partition('search:')
            if p[1]:  # found 'search:'
                possible_search = p[2]
                if possible_search:  # something follows the 'search:'
                    if possible_search[0] == '"':  # strip any quotes
                        possible_search = possible_search[1:].partition('"')
                    else:  # find end of the search name. Is EOL, space, rparen
                        sp = possible_search.find(' ')
                        pp = possible_search.find(')')
                        if pp < 0 or (sp > 0 and sp <= pp):
                            # space in string before rparen, or neither found
                            possible_search = possible_search.partition(' ')
                        else:
                            # rparen in string before space
                            possible_search = possible_search.partition(')')
                    txt = possible_search[2]  # grab remainder of the string
                    search_name = possible_search[0]
                    if search_name.startswith('='):
                        search_name = search_name[1:]
                    if search_name in db.saved_search_names():
                        searches.append(search_name + '=' +
                                        db.saved_search_lookup(search_name))
                else:
                    txt = ''
            else:
                txt = ''
        if len(searches) > 1:
            self.saved_searches_label.setPlainText('\n'.join(searches))
        else:
            self.saved_searches_label.setPlainText('')

    def name_text_edited(self, new_name):
        self.new_name = unicode(new_name)

    def name_index_changed(self, dex):
        if self.editing and (self.vl_text.text() != self.original_search
                             or self.new_name != self.editing):
            if not question_dialog(
                    self.gui,
                    _('Search text changed'),
                    _('The virtual library name or the search text has changed. '
                      'Do you want to discard these changes?'),
                    default_yes=False):
                self.vl_name.blockSignals(True)
                self.vl_name.setCurrentIndex(self.original_index)
                self.vl_name.lineEdit().setText(self.new_name)
                self.vl_name.blockSignals(False)
                return
        self.new_name = self.editing = self.vl_name.currentText()
        self.original_index = dex
        self.original_search = unicode(self.vl_name.itemData(dex) or '')
        self.vl_text.setText(self.original_search)

    def link_activated(self, url):
        db = self.gui.current_db
        f, txt = unicode(url).partition('.')[0::2]
        if f == 'search':
            names = db.saved_search_names()
        else:
            names = getattr(db, 'all_%s_names' % f)()
        d = SelectNames(names, txt, parent=self)
        if d.exec_() == d.Accepted:
            prefix = f + 's' if f in {'tag', 'author'} else f
            if f == 'search':
                search = [
                    '(%s)' % (db.saved_search_lookup(x)) for x in d.names
                ]
            else:
                search = [
                    '%s:"=%s"' % (prefix, x.replace('"', '\\"'))
                    for x in d.names
                ]
            if search:
                if not self.editing:
                    self.vl_name.lineEdit().setText(d.names.next())
                    self.vl_name.lineEdit().setCursorPosition(0)
                self.vl_text.setText(d.match_type.join(search))
                self.vl_text.setCursorPosition(0)

    def accept(self):
        n = unicode(self.vl_name.currentText()).strip()
        if not n:
            error_dialog(
                self.gui,
                _('No name'),
                _('You must provide a name for the new virtual library'),
                show=True)
            return

        if n.startswith('*'):
            error_dialog(self.gui,
                         _('Invalid name'),
                         _('A virtual library name cannot begin with "*"'),
                         show=True)
            return

        if n in self.existing_names and n != self.editing:
            if not question_dialog(
                    self.gui,
                    _('Name already in use'),
                    _('That name is already in use. Do you want to replace it '
                      'with the new search?'),
                    default_yes=False):
                return

        v = unicode(self.vl_text.text()).strip()
        if not v:
            error_dialog(
                self.gui,
                _('No search string'),
                _('You must provide a search to define the new virtual library'
                  ),
                show=True)
            return

        try:
            db = self.gui.library_view.model().db
            recs = db.data.search_getting_ids('',
                                              v,
                                              use_virtual_library=False,
                                              sort_results=False)
        except ParseException as e:
            error_dialog(self.gui,
                         _('Invalid search'),
                         _('The search in the search box is not valid'),
                         det_msg=e.msg,
                         show=True)
            return

        if not recs and not question_dialog(
                self.gui,
                _('Search found no books'),
                _('The search found no books, so the virtual library '
                  'will be empty. Do you really want to use that search?'),
                default_yes=False):
            return

        self.library_name = n
        self.library_search = v
        QDialog.accept(self)
Пример #20
0
class ConfigWidget(QWidget):

    # GUI definition
    def __init__(self):
        QWidget.__init__(self)
        self.l = QVBoxLayout()
        self.setLayout(self.l)

        self.ll = QHBoxLayout()
        self.l.addLayout(self.ll)

        self.label_exe = QLabel(_('&Prince executable:'))
        self.ll.addWidget(self.label_exe)

        self.exe = QLineEdit(self)
        self.exe.setText(prefs['prince_exe'])
        self.exe.setToolTip(_('<qt>Executable for the Prince program (command-line interface)</qt>'))
        self.ll.addWidget(self.exe)
        self.label_exe.setBuddy(self.exe)

        self.browse = QPushButton(_('&Browse') + '...', self)
        self.browse.setToolTip(_('<qt>Search the Prince executable in your computer</qt>'))
        self.browse.clicked.connect(self.select_exe)
        self.ll.addWidget(self.browse)

        self.lll = QHBoxLayout()
        self.l.addLayout(self.lll)

        self.label_fmts = QLabel(_('Preferred &formats:'))
        self.lll.addWidget(self.label_fmts)

        self.fmts = QLineEdit(self)
        self.fmts.setText(','.join(prefs['formats']))
        self.fmts.setToolTip(_('<qt>Comma-separated list of preferred formats to use as source, the first that matches will be used</qt>'))
        self.lll.addWidget(self.fmts)
        self.label_fmts.setBuddy(self.fmts)

        self.add_book = QCheckBox(_('&Add PDF to the book record'))
        self.add_book.setToolTip(_('<qt>Add the converted PDF to the selected book record</qt>'))
        self.add_book.setChecked(prefs['add_book'])
        self.l.addWidget(self.add_book)

        self.show_css = QCheckBox(_('&Show CSS in the Convert dialog'))
        self.show_css.setToolTip(_('<qt>Show by default the stylesheets in the Convert dialog</qt>'))
        self.show_css.setChecked(prefs['show_CSS'])
        self.l.addWidget(self.show_css)

        self.css_layout = QVBoxLayout()

        self.llll = QHBoxLayout()
        self.css_layout.addLayout(self.llll)

        self.css_list = QComboBox()
        self.css_list.setToolTip(_('<qt>List of custom stylesheets defined. Select one to edit</qt>'))
        self.css_list.setSizeAdjustPolicy(QComboBox.AdjustToContents)
        self.CSS_list = prefs['custom_CSS_list'].copy()
        self.default_CSS = prefs['default_CSS']
        if 'custom_CSS' in prefs:
            self.CSS_list[_('old')] = prefs['custom_CSS']
            self.default_CSS = _('old')
        if self.default_CSS not in self.CSS_list:
            self.default_CSS = sorted(self.CSS_list, key=lambda x: x.lower())[0]
        for key in sorted(self.CSS_list, key=lambda x: x.lower()):
            self.css_list.addItem(key, key)
        self.css_list.setCurrentIndex(self.css_list.findText(self.default_CSS))
        self.css_list.currentIndexChanged.connect(self.set_css)
        self.llll.addWidget(self.css_list)

        self.css_rename = QPushButton(_('Re&name'))
        self.css_rename.setToolTip(_('<qt>Rename the current stylesheet to the name on the right</qt>'))
        self.css_rename.clicked.connect(self.rename_css)
        self.css_rename.setEnabled(False)
        self.llll.addWidget(self.css_rename)

        self.css_name = QLineEdit(self)
        self.css_name.setToolTip(_('<qt>Name for the new or renamed stylesheet</qt>'))
        self.css_name.setText(self.css_list.currentText())
        self.css_name.textChanged.connect(self.check_names)
        self.llll.addWidget(self.css_name)

        self.css_add = QPushButton(_('A&dd'))
        self.css_add.setToolTip(_('<qt>Add a new empty stylesheet with the name on the left</qt>'))
        self.css_add.clicked.connect(self.add_css)
        self.css_add.setEnabled(False)
        self.llll.addWidget(self.css_add)

        self.css_remove = QPushButton(_('Re&move'))
        self.css_remove.setToolTip(_('<qt>Remove the current stylesheet</qt>'))
        self.css_remove.clicked.connect(self.remove_css)
        self.llll.addWidget(self.css_remove)

        self.css = TextEditWithTooltip()
        self.css.setLineWrapMode(TextEditWithTooltip.NoWrap)
        self.css.load_text(self.CSS_list[unicode(self.css_list.currentText())],'css')
        self.css.setToolTip(_('<qt>Custom stylesheet that will be applied, if selected, to all Prince PDF conversions</qt>'))
        self.css_layout.addWidget(self.css)

        self.css_templates = QLabel(_('Book metadata can be used in the stylesheet. Anything between %(s1)s and %(s2)s will be processed as a calibre template. For instance, %(s3)s in the stylesheet will be replaced with the book title in the conversion.') % \
          {'s1':'<span style="font-family:monospace ; font-weight:bold">@{@</span>', \
           's2':'<span style="font-family:monospace ; font-weight:bold">@}@</span>', \
           's3':'<span style="font-family:monospace ; font-weight:bold">@{@{title}@}@</span>'})
        self.css_templates.setWordWrap(True)
        self.css_layout.addWidget(self.css_templates)

        self.css_box = QGroupBox(_('&Custom CSS:'))
        self.css_box.setLayout(self.css_layout)
        self.l.addWidget(self.css_box)

        self.lllll = QHBoxLayout()
        self.lllll.setAlignment(Qt.AlignLeft)
        self.l.addLayout(self.lllll)

        self.defaults = QPushButton(_('&Restore defaults'))
        self.defaults.setToolTip(_('<qt>Restore the default settings</qt>'))
        self.defaults.clicked.connect(self.restore_defaults)
        self.lllll.addWidget(self.defaults, alignment=Qt.AlignLeft)

        self.warning = QLabel(_('<b>Warning</b>: Deletes modified stylesheets'))
        self.lllll.addWidget(self.warning)

        self.adjustSize()

    def select_exe(self):
        '''
        Create a dialog to select the Prince executable
        '''
        dialog = QFileDialog()
        dialog.setFileMode(QFileDialog.ExistingFile)
        filename = dialog.getOpenFileName(self, _('Select Prince executable'), '', '')
        if filename:
            try:
                self.exe.setText(filename)
            except(TypeError):
                self.exe.setText(filename[0])

    def restore_defaults(self):
        '''
        Restore the default settings
        '''
        self.exe.setText(prefs.defaults['prince_exe'])
        self.fmts.setText(','.join(prefs.defaults['formats']).lower())
        self.show_css.setChecked(prefs.defaults['show_CSS'])
        self.add_book.setChecked(prefs.defaults['add_book'])
        self.css_list.currentIndexChanged.disconnect()
        self.css_list.clear()
        self.CSS_list = prefs.defaults['custom_CSS_list'].copy()
        self.default_CSS = prefs.defaults['default_CSS']
        for key in sorted(self.CSS_list, key=lambda x: x.lower()):
            self.css_list.addItem(key, key)
        self.css_list.setCurrentIndex(self.css_list.findText(self.default_CSS))
        self.css_name.setText(self.default_CSS)
        self.css.load_text(self.CSS_list[unicode(self.css_list.currentText())],'css')
        self.css_list.currentIndexChanged.connect(self.set_css)

    def save_settings(self):
        '''
        Save the current settings
        '''
        prefs['prince_exe'] = unicode(self.exe.text())
        prefs['formats'] = unicode(self.fmts.text().lower()).split(',')
        prefs['show_CSS'] = self.show_css.isChecked()
        prefs['add_book'] = self.add_book.isChecked()
        self.set_css()
        prefs['default_CSS'] = self.default_CSS
        prefs['custom_CSS_list'] = self.CSS_list.copy()
        if 'custom_CSS' in prefs:
            del prefs['custom_CSS']

    def set_css(self):
        '''
        Fill the CSS text box with the selected stylesheet
        '''
        self.CSS_list[self.default_CSS] = unicode(self.css.toPlainText())
        self.default_CSS = unicode(self.css_list.currentText())
        self.css.load_text(self.CSS_list[self.default_CSS],'css')
        self.css_name.setText(self.css_list.currentText())

    def add_css(self):
        '''
        Add a new stylesheet
        '''
        from calibre.gui2 import error_dialog

        name = unicode(self.css_name.text())
        if name in self.CSS_list:
            error_dialog(self, _('Cannot add stylesheet'), _('A stylesheet with the name "%s" is already defined, use a different name.') % name, show=True)
        else:
            self.CSS_list[name] = ''
            self.css_list.addItem(name, name)
            self.css_list.setCurrentIndex(self.css_list.findText(name))
            self.css_add.setEnabled(False)
            self.css_rename.setEnabled(False)

    def remove_css(self):
        '''
        Remove an existing stylesheet
        '''
        from calibre.gui2 import error_dialog

        if (self.css_list.count() > 1):
            self.css_list.currentIndexChanged.disconnect()
            self.css_list.removeItem(self.css_list.currentIndex())
            del self.CSS_list[self.default_CSS]
            self.default_CSS = unicode(self.css_list.currentText())
            self.css.load_text(self.CSS_list[self.default_CSS],'css')
            self.css_list.currentIndexChanged.connect(self.set_css)
            self.css_name.setText(self.css_list.currentText())
        else:
            error_dialog(self, _('Cannot delete the last stylesheet'), _('The last stylesheet cannot be removed. You can rename it and/or remove its contents.'), show=True)

    def rename_css(self):
        '''
        Rename a stylesheet
        '''
        from calibre.gui2 import error_dialog

        name = unicode(self.css_name.text())
        if name in self.CSS_list:
            error_dialog(self, _('Cannot rename stylesheet'), _('A stylesheet with the name "%s" is already defined, use a different name.') % name, show=True)
        else:
            self.CSS_list[name] = self.CSS_list.pop(self.default_CSS)
            self.css_list.setItemText(self.css_list.currentIndex(),name)
            self.default_CSS = name

    def check_names(self, text):
        name = unicode(text)
        if name in self.CSS_list:
            self.css_add.setEnabled(False)
            self.css_rename.setEnabled(False)
        else:
            self.css_add.setEnabled(True)
            self.css_rename.setEnabled(True)
Пример #21
0
class OpdsDialog(QDialog):

    def __init__(self, gui, icon, do_user_config):
        QDialog.__init__(self, gui)
        self.gui = gui
        self.do_user_config = do_user_config

        self.db = gui.current_db.new_api

        # The model for the book list
        self.model = OpdsBooksModel(None, self.dummy_books(), self.db)
        self.searchproxymodel = QSortFilterProxyModel(self)
        self.searchproxymodel.setFilterCaseSensitivity(Qt.CaseInsensitive)
        self.searchproxymodel.setFilterKeyColumn(-1)
        self.searchproxymodel.setSourceModel(self.model)

        self.layout = QGridLayout()
        self.setLayout(self.layout)

        self.setWindowTitle('OPDS Client')
        self.setWindowIcon(icon)

        labelColumnWidths = []

        self.opdsUrlLabel = QLabel('OPDS URL: ')
        self.layout.addWidget(self.opdsUrlLabel, 0, 0)
        labelColumnWidths.append(self.layout.itemAtPosition(0, 0).sizeHint().width())

        config.convertSingleStringOpdsUrlPreferenceToListOfStringsPreference()
        self.opdsUrlEditor = QComboBox(self)
        self.opdsUrlEditor.activated.connect(self.opdsUrlEditorActivated)
        self.opdsUrlEditor.addItems(prefs['opds_url'])
        self.opdsUrlEditor.setEditable(True)
        self.opdsUrlEditor.setInsertPolicy(QComboBox.InsertAtTop)
        self.layout.addWidget(self.opdsUrlEditor, 0, 1, 1, 3)
        self.opdsUrlLabel.setBuddy(self.opdsUrlEditor)

        buttonColumnNumber = 7
        buttonColumnWidths = []
        self.about_button = QPushButton('About', self)
        self.about_button.setAutoDefault(False)
        self.about_button.clicked.connect(self.about)
        self.layout.addWidget(self.about_button, 0, buttonColumnNumber)
        buttonColumnWidths.append(self.layout.itemAtPosition(0, buttonColumnNumber).sizeHint().width()) 

        # Initially download the catalogs found in the root catalog of the URL
        # selected at startup.  Fail quietly on failing to open the URL
        catalogsTuple = self.model.downloadOpdsRootCatalog(self.gui, self.opdsUrlEditor.currentText(), False)
        print catalogsTuple
        firstCatalogTitle = catalogsTuple[0]
        self.currentOpdsCatalogs = catalogsTuple[1] # A dictionary of title->feedURL

        self.opdsCatalogSelectorLabel = QLabel('OPDS Catalog:')
        self.layout.addWidget(self.opdsCatalogSelectorLabel, 1, 0)
        labelColumnWidths.append(self.layout.itemAtPosition(1, 0).sizeHint().width())

        self.opdsCatalogSelector = QComboBox(self)
        self.opdsCatalogSelector.setEditable(False)
        self.opdsCatalogSelectorModel = QStringListModel(self.currentOpdsCatalogs.keys())
        self.opdsCatalogSelector.setModel(self.opdsCatalogSelectorModel)
        self.opdsCatalogSelector.setCurrentText(firstCatalogTitle)
        self.layout.addWidget(self.opdsCatalogSelector, 1, 1, 1, 3)

        self.download_opds_button = QPushButton('Download OPDS', self)
        self.download_opds_button.setAutoDefault(False)
        self.download_opds_button.clicked.connect(self.download_opds)
        self.layout.addWidget(self.download_opds_button, 1, buttonColumnNumber)
        buttonColumnWidths.append(self.layout.itemAtPosition(1, buttonColumnNumber).sizeHint().width()) 

        # Search GUI
        self.searchEditor = QLineEdit(self)
        self.searchEditor.returnPressed.connect(self.searchBookList)
        self.layout.addWidget(self.searchEditor, 2, buttonColumnNumber - 2, 1, 2)

        self.searchButton = QPushButton('Search', self)
        self.searchButton.setAutoDefault(False)
        self.searchButton.clicked.connect(self.searchBookList)
        self.layout.addWidget(self.searchButton, 2, buttonColumnNumber)
        buttonColumnWidths.append(self.layout.itemAtPosition(2, buttonColumnNumber).sizeHint().width())

        # The main book list
        self.library_view = QTableView(self)
        self.library_view.setAlternatingRowColors(True)
        self.library_view.setModel(self.searchproxymodel)
        self.library_view.horizontalHeader().setSectionResizeMode(0, QHeaderView.Stretch)
        self.library_view.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch)
        self.library_view.horizontalHeader().setSectionResizeMode(2, QHeaderView.Stretch)
        self.library_view.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.resizeAllLibraryViewLinesToHeaderHeight()
        self.library_view.resizeColumnsToContents()
        self.layout.addWidget(self.library_view, 3, 0, 3, buttonColumnNumber + 1)

        self.hideNewsCheckbox = QCheckBox('Hide Newspapers', self)
        self.hideNewsCheckbox.clicked.connect(self.setHideNewspapers)
        self.hideNewsCheckbox.setChecked(prefs['hideNewspapers'])
        self.layout.addWidget(self.hideNewsCheckbox, 6, 0, 1, 3)

        self.hideBooksAlreadyInLibraryCheckbox = QCheckBox('Hide books already in library', self)
        self.hideBooksAlreadyInLibraryCheckbox.clicked.connect(self.setHideBooksAlreadyInLibrary)
        self.hideBooksAlreadyInLibraryCheckbox.setChecked(prefs['hideBooksAlreadyInLibrary'])
        self.layout.addWidget(self.hideBooksAlreadyInLibraryCheckbox, 7, 0, 1, 3)

        # Let the checkbox initial state control the filtering
        self.model.setFilterBooksThatAreNewspapers(self.hideNewsCheckbox.isChecked())
        self.model.setFilterBooksThatAreAlreadyInLibrary(self.hideBooksAlreadyInLibraryCheckbox.isChecked())

        self.downloadButton = QPushButton('Download selected books', self)
        self.downloadButton.setAutoDefault(False)
        self.downloadButton.clicked.connect(self.downloadSelectedBooks)
        self.layout.addWidget(self.downloadButton, 6, buttonColumnNumber)
        buttonColumnWidths.append(self.layout.itemAtPosition(6, buttonColumnNumber).sizeHint().width()) 

        self.fixTimestampButton = QPushButton('Fix timestamps of selection', self)
        self.fixTimestampButton.setAutoDefault(False)
        self.fixTimestampButton.clicked.connect(self.fixBookTimestamps)
        self.layout.addWidget(self.fixTimestampButton, 7, buttonColumnNumber)
        buttonColumnWidths.append(self.layout.itemAtPosition(7, buttonColumnNumber).sizeHint().width()) 

        # Make all columns of the grid layout the same width as the button column
        buttonColumnWidth = max(buttonColumnWidths)
        for columnNumber in range(0, buttonColumnNumber):
            self.layout.setColumnMinimumWidth(columnNumber, buttonColumnWidth)

        # Make sure the first column isn't wider than the labels it holds
        labelColumnWidth = max(labelColumnWidths)
        self.layout.setColumnMinimumWidth(0, labelColumnWidth)

        self.resize(self.sizeHint())

    def opdsUrlEditorActivated(self, text):
        prefs['opds_url'] = config.saveOpdsUrlCombobox(self.opdsUrlEditor)
        catalogsTuple = self.model.downloadOpdsRootCatalog(self.gui, self.opdsUrlEditor.currentText(), True)
        firstCatalogTitle = catalogsTuple[0]
        self.currentOpdsCatalogs = catalogsTuple[1] # A dictionary of title->feedURL
        self.opdsCatalogSelectorModel.setStringList(self.currentOpdsCatalogs.keys())
        self.opdsCatalogSelector.setCurrentText(firstCatalogTitle)

    def setHideNewspapers(self, checked):
        prefs['hideNewspapers'] = checked
        self.model.setFilterBooksThatAreNewspapers(checked)
        self.resizeAllLibraryViewLinesToHeaderHeight()

    def setHideBooksAlreadyInLibrary(self, checked):
        prefs['hideBooksAlreadyInLibrary'] = checked
        self.model.setFilterBooksThatAreAlreadyInLibrary(checked)
        self.resizeAllLibraryViewLinesToHeaderHeight()

    def searchBookList(self):
        searchString = self.searchEditor.text()
        print "starting book list search for: %s" % searchString
        self.searchproxymodel.setFilterFixedString(searchString)

    def about(self):
        text = get_resources('about.txt')
        QMessageBox.about(self, 'About the OPDS Client plugin', text.decode('utf-8'))

    def download_opds(self):
        opdsCatalogUrl = self.currentOpdsCatalogs.get(self.opdsCatalogSelector.currentText(), None)
        if opdsCatalogUrl is None:
            # Just give up quietly
            return
        self.model.downloadOpdsCatalog(self.gui, opdsCatalogUrl)
        if self.model.isCalibreOpdsServer():
            self.model.downloadMetadataUsingCalibreRestApi(self.opdsUrlEditor.currentText())
        self.library_view.horizontalHeader().setSectionResizeMode(0, QHeaderView.Stretch)
        self.library_view.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch)
        self.library_view.horizontalHeader().setSectionResizeMode(2, QHeaderView.Stretch)
        self.resizeAllLibraryViewLinesToHeaderHeight()
        self.resize(self.sizeHint())

    def config(self):
        self.do_user_config(parent=self)

    def downloadSelectedBooks(self):
        selectionmodel = self.library_view.selectionModel()
        if selectionmodel.hasSelection():
            rows = selectionmodel.selectedRows()
            for row in reversed(rows):
                book = row.data(Qt.UserRole)
                self.downloadBook(book)

    def downloadBook(self, book):
        if len(book.links) > 0:
            self.gui.download_ebook(book.links[0])

    def fixBookTimestamps(self):
        selectionmodel = self.library_view.selectionModel()
        if selectionmodel.hasSelection():
            rows = selectionmodel.selectedRows()
            for row in reversed(rows):
                book = row.data(Qt.UserRole)
                self.fixBookTimestamp(book)

    def fixBookTimestamp(self, book):
        bookTimestamp = book.timestamp
        identicalBookIds = self.findIdenticalBooksForBooksWithMultipleAuthors(book)
        bookIdToValMap = {}
        for identicalBookId in identicalBookIds:
            bookIdToValMap[identicalBookId] = bookTimestamp
        if len(bookIdToValMap) < 1:
            print "Failed to set timestamp of book: %s" % book
        self.db.set_field('timestamp', bookIdToValMap)

    def findIdenticalBooksForBooksWithMultipleAuthors(self, book):
        authorsList = book.authors
        if len(authorsList) < 2:
            return self.db.find_identical_books(book)
        # Try matching the authors one by one
        identicalBookIds = set()
        for author in authorsList:
            singleAuthorBook = Metadata(book.title, [author])
            singleAuthorIdenticalBookIds = self.db.find_identical_books(singleAuthorBook)
            identicalBookIds = identicalBookIds.union(singleAuthorIdenticalBookIds)
        return identicalBookIds

    def dummy_books(self):
        dummy_author = ' ' * 40
        dummy_title = ' ' * 60
        books_list = []
        for line in range (1, 10):
            book = DynamicBook()
            book.author = dummy_author
            book.title = dummy_title
            book.updated = datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%S+00:00')
            book.id = ''
            books_list.append(book)
        return books_list

    def resizeAllLibraryViewLinesToHeaderHeight(self):
        rowHeight = self.library_view.horizontalHeader().height()
        for rowNumber in range (0, self.library_view.model().rowCount()):
            self.library_view.setRowHeight(rowNumber, rowHeight)
Пример #22
0
class OpdsDialog(QDialog):
    def __init__(self, gui, icon, do_user_config):
        QDialog.__init__(self, gui)
        self.gui = gui
        self.do_user_config = do_user_config

        self.db = gui.current_db.new_api

        # The model for the book list
        self.model = OpdsBooksModel(None, self.dummy_books(), self.db)
        self.searchproxymodel = QSortFilterProxyModel(self)
        self.searchproxymodel.setFilterCaseSensitivity(Qt.CaseInsensitive)
        self.searchproxymodel.setFilterKeyColumn(-1)
        self.searchproxymodel.setSourceModel(self.model)

        self.layout = QGridLayout()
        self.setLayout(self.layout)

        self.setWindowTitle("OPDS Client")
        self.setWindowIcon(icon)

        labelColumnWidths = []

        self.opdsUrlLabel = QLabel("OPDS URL: ")
        self.layout.addWidget(self.opdsUrlLabel, 0, 0)
        labelColumnWidths.append(self.layout.itemAtPosition(0, 0).sizeHint().width())

        config.convertSingleStringOpdsUrlPreferenceToListOfStringsPreference()
        self.opdsUrlEditor = QComboBox(self)
        self.opdsUrlEditor.activated.connect(self.opdsUrlEditorActivated)
        self.opdsUrlEditor.addItems(prefs["opds_url"])
        self.opdsUrlEditor.setEditable(True)
        self.opdsUrlEditor.setInsertPolicy(QComboBox.InsertAtTop)
        self.layout.addWidget(self.opdsUrlEditor, 0, 1, 1, 3)
        self.opdsUrlLabel.setBuddy(self.opdsUrlEditor)

        buttonColumnNumber = 7
        buttonColumnWidths = []
        self.about_button = QPushButton("About", self)
        self.about_button.setAutoDefault(False)
        self.about_button.clicked.connect(self.about)
        self.layout.addWidget(self.about_button, 0, buttonColumnNumber)
        buttonColumnWidths.append(
            self.layout.itemAtPosition(0, buttonColumnNumber).sizeHint().width()
        )

        # Initially download the catalogs found in the root catalog of the URL
        # selected at startup.  Fail quietly on failing to open the URL
        catalogsTuple = self.model.downloadOpdsRootCatalog(
            self.gui, self.opdsUrlEditor.currentText(), False
        )
        print(catalogsTuple)
        firstCatalogTitle = catalogsTuple[0]
        self.currentOpdsCatalogs = catalogsTuple[1]  # A dictionary of title->feedURL

        self.opdsCatalogSelectorLabel = QLabel("OPDS Catalog:")
        self.layout.addWidget(self.opdsCatalogSelectorLabel, 1, 0)
        labelColumnWidths.append(self.layout.itemAtPosition(1, 0).sizeHint().width())

        self.opdsCatalogSelector = QComboBox(self)
        self.opdsCatalogSelector.setEditable(False)
        self.opdsCatalogSelectorModel = QStringListModel(self.currentOpdsCatalogs.keys())
        self.opdsCatalogSelector.setModel(self.opdsCatalogSelectorModel)
        self.opdsCatalogSelector.setCurrentText(firstCatalogTitle)
        self.layout.addWidget(self.opdsCatalogSelector, 1, 1, 1, 3)

        self.download_opds_button = QPushButton("Download OPDS", self)
        self.download_opds_button.setAutoDefault(False)
        self.download_opds_button.clicked.connect(self.download_opds)
        self.layout.addWidget(self.download_opds_button, 1, buttonColumnNumber)
        buttonColumnWidths.append(
            self.layout.itemAtPosition(1, buttonColumnNumber).sizeHint().width()
        )

        # Search GUI
        self.searchEditor = QLineEdit(self)
        self.searchEditor.returnPressed.connect(self.searchBookList)
        self.layout.addWidget(self.searchEditor, 2, buttonColumnNumber - 2, 1, 2)

        self.searchButton = QPushButton("Search", self)
        self.searchButton.setAutoDefault(False)
        self.searchButton.clicked.connect(self.searchBookList)
        self.layout.addWidget(self.searchButton, 2, buttonColumnNumber)
        buttonColumnWidths.append(
            self.layout.itemAtPosition(2, buttonColumnNumber).sizeHint().width()
        )

        # The main book list
        self.library_view = QTableView(self)
        self.library_view.setAlternatingRowColors(True)
        self.library_view.setModel(self.searchproxymodel)
        self.library_view.horizontalHeader().setSectionResizeMode(0, QHeaderView.Stretch)
        self.library_view.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch)
        self.library_view.horizontalHeader().setSectionResizeMode(2, QHeaderView.Stretch)
        self.library_view.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.resizeAllLibraryViewLinesToHeaderHeight()
        self.library_view.resizeColumnsToContents()
        self.layout.addWidget(self.library_view, 3, 0, 3, buttonColumnNumber + 1)

        self.hideNewsCheckbox = QCheckBox("Hide Newspapers", self)
        self.hideNewsCheckbox.clicked.connect(self.setHideNewspapers)
        self.hideNewsCheckbox.setChecked(prefs["hideNewspapers"])
        self.layout.addWidget(self.hideNewsCheckbox, 6, 0, 1, 3)

        self.hideBooksAlreadyInLibraryCheckbox = QCheckBox("Hide books already in library", self)
        self.hideBooksAlreadyInLibraryCheckbox.clicked.connect(self.setHideBooksAlreadyInLibrary)
        self.hideBooksAlreadyInLibraryCheckbox.setChecked(prefs["hideBooksAlreadyInLibrary"])
        self.layout.addWidget(self.hideBooksAlreadyInLibraryCheckbox, 7, 0, 1, 3)

        # Let the checkbox initial state control the filtering
        self.model.setFilterBooksThatAreNewspapers(self.hideNewsCheckbox.isChecked())
        self.model.setFilterBooksThatAreAlreadyInLibrary(
            self.hideBooksAlreadyInLibraryCheckbox.isChecked()
        )

        self.downloadButton = QPushButton("Download selected books", self)
        self.downloadButton.setAutoDefault(False)
        self.downloadButton.clicked.connect(self.downloadSelectedBooks)
        self.layout.addWidget(self.downloadButton, 6, buttonColumnNumber)
        buttonColumnWidths.append(
            self.layout.itemAtPosition(6, buttonColumnNumber).sizeHint().width()
        )

        self.fixTimestampButton = QPushButton("Fix timestamps of selection", self)
        self.fixTimestampButton.setAutoDefault(False)
        self.fixTimestampButton.clicked.connect(self.fixBookTimestamps)
        self.layout.addWidget(self.fixTimestampButton, 7, buttonColumnNumber)
        buttonColumnWidths.append(
            self.layout.itemAtPosition(7, buttonColumnNumber).sizeHint().width()
        )

        # Make all columns of the grid layout the same width as the button column
        buttonColumnWidth = max(buttonColumnWidths)
        for columnNumber in range(0, buttonColumnNumber):
            self.layout.setColumnMinimumWidth(columnNumber, buttonColumnWidth)

        # Make sure the first column isn't wider than the labels it holds
        labelColumnWidth = max(labelColumnWidths)
        self.layout.setColumnMinimumWidth(0, labelColumnWidth)

        self.resize(self.sizeHint())

    def opdsUrlEditorActivated(self, text):
        prefs["opds_url"] = config.saveOpdsUrlCombobox(self.opdsUrlEditor)
        catalogsTuple = self.model.downloadOpdsRootCatalog(
            self.gui, self.opdsUrlEditor.currentText(), True
        )
        firstCatalogTitle = catalogsTuple[0]
        self.currentOpdsCatalogs = catalogsTuple[1]  # A dictionary of title->feedURL
        self.opdsCatalogSelectorModel.setStringList(self.currentOpdsCatalogs.keys())
        self.opdsCatalogSelector.setCurrentText(firstCatalogTitle)

    def setHideNewspapers(self, checked):
        prefs["hideNewspapers"] = checked
        self.model.setFilterBooksThatAreNewspapers(checked)
        self.resizeAllLibraryViewLinesToHeaderHeight()

    def setHideBooksAlreadyInLibrary(self, checked):
        prefs["hideBooksAlreadyInLibrary"] = checked
        self.model.setFilterBooksThatAreAlreadyInLibrary(checked)
        self.resizeAllLibraryViewLinesToHeaderHeight()

    def searchBookList(self):
        searchString = self.searchEditor.text()
        print("starting book list search for: %s" % searchString)
        self.searchproxymodel.setFilterFixedString(searchString)

    def about(self):
        text = get_resources("about.txt")
        QMessageBox.about(self, "About the OPDS Client plugin", text.decode("utf-8"))

    def download_opds(self):
        opdsCatalogUrl = self.currentOpdsCatalogs.get(self.opdsCatalogSelector.currentText(), None)
        if opdsCatalogUrl is None:
            # Just give up quietly
            return
        self.model.downloadOpdsCatalog(self.gui, opdsCatalogUrl)
        if self.model.isCalibreOpdsServer():
            self.model.downloadMetadataUsingCalibreRestApi(self.opdsUrlEditor.currentText())
        self.library_view.horizontalHeader().setSectionResizeMode(0, QHeaderView.Stretch)
        self.library_view.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch)
        self.library_view.horizontalHeader().setSectionResizeMode(2, QHeaderView.Stretch)
        self.resizeAllLibraryViewLinesToHeaderHeight()
        self.resize(self.sizeHint())

    def config(self):
        self.do_user_config(parent=self)

    def downloadSelectedBooks(self):
        selectionmodel = self.library_view.selectionModel()
        if selectionmodel.hasSelection():
            rows = selectionmodel.selectedRows()
            for row in reversed(rows):
                book = row.data(Qt.UserRole)
                self.downloadBook(book)

    def downloadBook(self, book):
        if len(book.links) > 0:
            self.gui.download_ebook(book.links[0])

    def fixBookTimestamps(self):
        selectionmodel = self.library_view.selectionModel()
        if selectionmodel.hasSelection():
            rows = selectionmodel.selectedRows()
            for row in reversed(rows):
                book = row.data(Qt.UserRole)
                self.fixBookTimestamp(book)

    def fixBookTimestamp(self, book):
        bookTimestamp = book.timestamp
        identicalBookIds = self.findIdenticalBooksForBooksWithMultipleAuthors(book)
        bookIdToValMap = {}
        for identicalBookId in identicalBookIds:
            bookIdToValMap[identicalBookId] = bookTimestamp
        if len(bookIdToValMap) < 1:
            print("Failed to set timestamp of book: %s" % book)
        self.db.set_field("timestamp", bookIdToValMap)

    def findIdenticalBooksForBooksWithMultipleAuthors(self, book):
        authorsList = book.authors
        if len(authorsList) < 2:
            return self.db.find_identical_books(book)
        # Try matching the authors one by one
        identicalBookIds = set()
        for author in authorsList:
            singleAuthorBook = Metadata(book.title, [author])
            singleAuthorIdenticalBookIds = self.db.find_identical_books(singleAuthorBook)
            identicalBookIds = identicalBookIds.union(singleAuthorIdenticalBookIds)
        return identicalBookIds

    def dummy_books(self):
        dummy_author = " " * 40
        dummy_title = " " * 60
        books_list = []
        for line in range(1, 10):
            book = DynamicBook()
            book.author = dummy_author
            book.title = dummy_title
            book.updated = datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%S+00:00")
            book.id = ""
            books_list.append(book)
        return books_list

    def resizeAllLibraryViewLinesToHeaderHeight(self):
        rowHeight = self.library_view.horizontalHeader().height()
        for rowNumber in range(0, self.library_view.model().rowCount()):
            self.library_view.setRowHeight(rowNumber, rowHeight)
Пример #23
0
class Config(QDialog):
    '''
    Configuration dialog for single book conversion. If accepted, has the
    following important attributes

    output_format - Output format (without a leading .)
    input_format  - Input format (without a leading .)
    opf_path - Path to OPF file with user specified metadata
    cover_path - Path to user specified cover (can be None)
    recommendations - A pickled list of 3 tuples in the same format as the
    recommendations member of the Input/Output plugins.
    '''
    def __init__(self,
                 parent,
                 db,
                 book_id,
                 preferred_input_format=None,
                 preferred_output_format=None):
        QDialog.__init__(self, parent)
        self.setupUi()
        self.opt_individual_saved_settings.setVisible(False)
        self.db, self.book_id = db, book_id

        self.setup_input_output_formats(self.db, self.book_id,
                                        preferred_input_format,
                                        preferred_output_format)
        self.setup_pipeline()

        self.input_formats.currentIndexChanged[native_string_type].connect(
            self.setup_pipeline)
        self.output_formats.currentIndexChanged[native_string_type].connect(
            self.setup_pipeline)
        self.groups.setSpacing(5)
        self.groups.activated[(QModelIndex)].connect(self.show_pane)
        self.groups.clicked[(QModelIndex)].connect(self.show_pane)
        self.groups.entered[(QModelIndex)].connect(self.show_group_help)
        rb = self.buttonBox.button(self.buttonBox.RestoreDefaults)
        rb.setText(_('Restore &defaults'))
        rb.clicked.connect(self.restore_defaults)
        self.groups.setMouseTracking(True)
        geom = gprefs.get('convert_single_dialog_geom', None)
        if geom:
            self.restoreGeometry(geom)
        else:
            self.resize(self.sizeHint())

    def setupUi(self):
        self.setObjectName("Dialog")
        self.resize(1024, 700)
        self.setWindowIcon(QIcon(I('convert.png')))
        self.gridLayout = QGridLayout(self)
        self.gridLayout.setObjectName("gridLayout")
        self.horizontalLayout = QHBoxLayout()
        self.horizontalLayout.setObjectName("horizontalLayout")
        self.input_label = QLabel(self)
        self.input_label.setObjectName("input_label")
        self.horizontalLayout.addWidget(self.input_label)
        self.input_formats = QComboBox(self)
        self.input_formats.setSizeAdjustPolicy(
            QComboBox.AdjustToMinimumContentsLengthWithIcon)
        self.input_formats.setMinimumContentsLength(5)
        self.input_formats.setObjectName("input_formats")
        self.horizontalLayout.addWidget(self.input_formats)
        self.opt_individual_saved_settings = QCheckBox(self)
        self.opt_individual_saved_settings.setObjectName(
            "opt_individual_saved_settings")
        self.horizontalLayout.addWidget(self.opt_individual_saved_settings)
        spacerItem = QSpacerItem(40, 20, QSizePolicy.Expanding,
                                 QSizePolicy.Minimum)
        self.horizontalLayout.addItem(spacerItem)
        self.label_2 = QLabel(self)
        self.label_2.setObjectName("label_2")
        self.horizontalLayout.addWidget(self.label_2)
        self.output_formats = QComboBox(self)
        self.output_formats.setSizeAdjustPolicy(
            QComboBox.AdjustToMinimumContentsLengthWithIcon)
        self.output_formats.setMinimumContentsLength(5)
        self.output_formats.setObjectName("output_formats")
        self.horizontalLayout.addWidget(self.output_formats)
        self.gridLayout.addLayout(self.horizontalLayout, 0, 0, 1, 2)
        self.groups = QListView(self)
        sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        sizePolicy.setHorizontalStretch(1)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(
            self.groups.sizePolicy().hasHeightForWidth())
        self.groups.setSizePolicy(sizePolicy)
        self.groups.setTabKeyNavigation(True)
        self.groups.setIconSize(QSize(48, 48))
        self.groups.setWordWrap(True)
        self.groups.setObjectName("groups")
        self.gridLayout.addWidget(self.groups, 1, 0, 3, 1)
        self.scrollArea = QScrollArea(self)
        sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        sizePolicy.setHorizontalStretch(4)
        sizePolicy.setVerticalStretch(10)
        sizePolicy.setHeightForWidth(
            self.scrollArea.sizePolicy().hasHeightForWidth())
        self.scrollArea.setSizePolicy(sizePolicy)
        self.scrollArea.setFrameShape(QFrame.NoFrame)
        self.scrollArea.setLineWidth(0)
        self.scrollArea.setWidgetResizable(True)
        self.scrollArea.setObjectName("scrollArea")
        self.scrollAreaWidgetContents = QWidget()
        self.scrollAreaWidgetContents.setGeometry(QRect(0, 0, 810, 494))
        self.scrollAreaWidgetContents.setObjectName("scrollAreaWidgetContents")
        self.verticalLayout_3 = QVBoxLayout(self.scrollAreaWidgetContents)
        self.verticalLayout_3.setContentsMargins(0, 0, 0, 0)
        self.verticalLayout_3.setObjectName("verticalLayout_3")
        self.stack = QStackedWidget(self.scrollAreaWidgetContents)
        sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(
            self.stack.sizePolicy().hasHeightForWidth())
        self.stack.setSizePolicy(sizePolicy)
        self.stack.setObjectName("stack")
        self.page = QWidget()
        self.page.setObjectName("page")
        self.stack.addWidget(self.page)
        self.page_2 = QWidget()
        self.page_2.setObjectName("page_2")
        self.stack.addWidget(self.page_2)
        self.verticalLayout_3.addWidget(self.stack)
        self.scrollArea.setWidget(self.scrollAreaWidgetContents)
        self.gridLayout.addWidget(self.scrollArea, 1, 1, 1, 1)
        self.buttonBox = QDialogButtonBox(self)
        self.buttonBox.setOrientation(Qt.Horizontal)
        self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel
                                          | QDialogButtonBox.Ok
                                          | QDialogButtonBox.RestoreDefaults)
        self.buttonBox.setObjectName("buttonBox")
        self.gridLayout.addWidget(self.buttonBox, 3, 1, 1, 1)
        self.help = QTextEdit(self)
        sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(
            self.help.sizePolicy().hasHeightForWidth())
        self.help.setSizePolicy(sizePolicy)
        self.help.setMaximumHeight(80)
        self.help.setObjectName("help")
        self.gridLayout.addWidget(self.help, 2, 1, 1, 1)
        self.input_label.setBuddy(self.input_formats)
        self.label_2.setBuddy(self.output_formats)
        self.input_label.setText(_("&Input format:"))
        self.opt_individual_saved_settings.setText(
            _("Use &saved conversion settings for individual books"))
        self.label_2.setText(_("&Output format:"))

        self.buttonBox.accepted.connect(self.accept)
        self.buttonBox.rejected.connect(self.reject)

    def sizeHint(self):
        desktop = QCoreApplication.instance().desktop()
        geom = desktop.availableGeometry(self)
        nh, nw = max(300, geom.height() - 100), max(400, geom.width() - 70)
        return QSize(nw, nh)

    def restore_defaults(self):
        delete_specifics(self.db, self.book_id)
        self.setup_pipeline()

    @property
    def input_format(self):
        return unicode_type(self.input_formats.currentText()).lower()

    @property
    def output_format(self):
        return unicode_type(self.output_formats.currentText()).lower()

    @property
    def manually_fine_tune_toc(self):
        for i in range(self.stack.count()):
            w = self.stack.widget(i)
            if hasattr(w, 'manually_fine_tune_toc'):
                return w.manually_fine_tune_toc.isChecked()

    def setup_pipeline(self, *args):
        oidx = self.groups.currentIndex().row()
        input_format = self.input_format
        output_format = self.output_format
        self.plumber = create_dummy_plumber(input_format, output_format)

        def widget_factory(cls):
            return cls(self.stack, self.plumber.get_option_by_name,
                       self.plumber.get_option_help, self.db, self.book_id)

        self.mw = widget_factory(MetadataWidget)
        self.setWindowTitle(
            _('Convert') + ' ' + unicode_type(self.mw.title.text()))
        lf = widget_factory(LookAndFeelWidget)
        hw = widget_factory(HeuristicsWidget)
        sr = widget_factory(SearchAndReplaceWidget)
        ps = widget_factory(PageSetupWidget)
        sd = widget_factory(StructureDetectionWidget)
        toc = widget_factory(TOCWidget)
        from calibre.gui2.actions.toc_edit import SUPPORTED
        toc.manually_fine_tune_toc.setVisible(
            output_format.upper() in SUPPORTED)
        debug = widget_factory(DebugWidget)

        output_widget = self.plumber.output_plugin.gui_configuration_widget(
            self.stack, self.plumber.get_option_by_name,
            self.plumber.get_option_help, self.db, self.book_id)
        input_widget = self.plumber.input_plugin.gui_configuration_widget(
            self.stack, self.plumber.get_option_by_name,
            self.plumber.get_option_help, self.db, self.book_id)
        while True:
            c = self.stack.currentWidget()
            if not c:
                break
            self.stack.removeWidget(c)

        widgets = [self.mw, lf, hw, ps, sd, toc, sr]
        if input_widget is not None:
            widgets.append(input_widget)
        if output_widget is not None:
            widgets.append(output_widget)
        widgets.append(debug)
        for w in widgets:
            self.stack.addWidget(w)
            w.set_help_signal.connect(self.help.setPlainText)

        self._groups_model = GroupModel(widgets)
        self.groups.setModel(self._groups_model)

        idx = oidx if -1 < oidx < self._groups_model.rowCount() else 0
        self.groups.setCurrentIndex(self._groups_model.index(idx))
        self.stack.setCurrentIndex(idx)
        try:
            shutil.rmtree(self.plumber.archive_input_tdir, ignore_errors=True)
        except:
            pass

    def setup_input_output_formats(self, db, book_id, preferred_input_format,
                                   preferred_output_format):
        if preferred_output_format:
            preferred_output_format = preferred_output_format.upper()
        output_formats = get_output_formats(preferred_output_format)
        input_format, input_formats = get_input_format_for_book(
            db, book_id, preferred_input_format)
        preferred_output_format = preferred_output_format if \
            preferred_output_format in output_formats else \
            sort_formats_by_preference(output_formats,
                    [prefs['output_format']])[0]
        self.input_formats.addItems(
            (unicode_type(x.upper()) for x in input_formats))
        self.output_formats.addItems(
            (unicode_type(x.upper()) for x in output_formats))
        self.input_formats.setCurrentIndex(input_formats.index(input_format))
        self.output_formats.setCurrentIndex(
            output_formats.index(preferred_output_format))

    def show_pane(self, index):
        self.stack.setCurrentIndex(index.row())

    def accept(self):
        recs = GuiRecommendations()
        for w in self._groups_model.widgets:
            if not w.pre_commit_check():
                return
            x = w.commit(save_defaults=False)
            recs.update(x)
        self.opf_file, self.cover_file = self.mw.opf_file, self.mw.cover_file
        self._recommendations = recs
        if self.db is not None:
            recs['gui_preferred_input_format'] = self.input_format
            save_specifics(self.db, self.book_id, recs)
        self.break_cycles()
        QDialog.accept(self)

    def reject(self):
        self.break_cycles()
        QDialog.reject(self)

    def done(self, r):
        if self.isVisible():
            gprefs['convert_single_dialog_geom'] = \
                bytearray(self.saveGeometry())
        return QDialog.done(self, r)

    def break_cycles(self):
        for i in range(self.stack.count()):
            w = self.stack.widget(i)
            w.break_cycles()

    @property
    def recommendations(self):
        recs = [(k, v, OptionRecommendation.HIGH)
                for k, v in self._recommendations.items()]
        return recs

    def show_group_help(self, index):
        widget = self._groups_model.widgets[index.row()]
        self.help.setPlainText(widget.HELP)
class ConfigWidget(QWidget, Logger):
    '''
    Config dialog for Marvin Manager
    '''

    WIZARD_PROFILES = {
        'Annotations': {
            'label': 'mm_annotations',
            'datatype': 'comments',
            'display': {},
            'is_multiple': False
        },
        'Collections': {
            'label': 'mm_collections',
            'datatype': 'text',
            'display': {u'is_names': False},
            'is_multiple': True
        },
        'Last read': {
            'label': 'mm_date_read',
            'datatype': 'datetime',
            'display': {},
            'is_multiple': False
        },
        'Locked': {
            'label': 'mm_locked',
            'datatype': 'bool',
            'display': {},
            'is_multiple': False
        },
        'Progress': {
            'label': 'mm_progress',
            'datatype': 'float',
            'display': {u'number_format': u'{0:.0f}%'},
            'is_multiple': False
        },
        'Read': {
            'label': 'mm_read',
            'datatype': 'bool',
            'display': {},
            'is_multiple': False
        },
        'Reading list': {
            'label': 'mm_reading_list',
            'datatype': 'bool',
            'display': {},
            'is_multiple': False
        },
        'Word count': {
            'label': 'mm_word_count',
            'datatype': 'int',
            'display': {u'number_format': u'{0:n}'},
            'is_multiple': False
        }
    }

    def __init__(self, plugin_action):
        QWidget.__init__(self)
        self.parent = plugin_action

        self.gui = get_gui()
        self.icon = plugin_action.icon
        self.opts = plugin_action.opts
        self.prefs = plugin_prefs
        self.resources_path = plugin_action.resources_path
        self.verbose = plugin_action.verbose

        self.restart_required = False

        self._log_location()

        self.l = QGridLayout()
        self.setLayout(self.l)
        self.column1_layout = QVBoxLayout()
        self.l.addLayout(self.column1_layout, 0, 0)
        self.column2_layout = QVBoxLayout()
        self.l.addLayout(self.column2_layout, 0, 1)

        # ----------------------------- Column 1 -----------------------------
        # ~~~~~~~~ Create the Custom fields options group box ~~~~~~~~
        self.cfg_custom_fields_gb = QGroupBox(self)
        self.cfg_custom_fields_gb.setTitle('Custom column assignments')
        self.column1_layout.addWidget(self.cfg_custom_fields_gb)

        self.cfg_custom_fields_qgl = QGridLayout(self.cfg_custom_fields_gb)
        current_row = 0

        # ++++++++ Labels + HLine ++++++++
        self.marvin_source_label = QLabel("Marvin source")
        self.cfg_custom_fields_qgl.addWidget(self.marvin_source_label, current_row, 0)
        self.calibre_destination_label = QLabel("calibre destination")
        self.cfg_custom_fields_qgl.addWidget(self.calibre_destination_label, current_row, 1)
        current_row += 1
        self.sd_hl = QFrame(self.cfg_custom_fields_gb)
        self.sd_hl.setFrameShape(QFrame.HLine)
        self.sd_hl.setFrameShadow(QFrame.Raised)
        self.cfg_custom_fields_qgl.addWidget(self.sd_hl, current_row, 0, 1, 3)
        current_row += 1

        # ++++++++ Annotations ++++++++
        self.cfg_annotations_label = QLabel('Annotations')
        self.cfg_annotations_label.setAlignment(Qt.AlignLeft)
        self.cfg_custom_fields_qgl.addWidget(self.cfg_annotations_label, current_row, 0)

        self.annotations_field_comboBox = QComboBox(self.cfg_custom_fields_gb)
        self.annotations_field_comboBox.setObjectName('annotations_field_comboBox')
        self.annotations_field_comboBox.setToolTip('Select a custom column to store Marvin annotations')
        self.cfg_custom_fields_qgl.addWidget(self.annotations_field_comboBox, current_row, 1)

        self.cfg_highlights_wizard = QToolButton()
        self.cfg_highlights_wizard.setIcon(QIcon(I('wizard.png')))
        self.cfg_highlights_wizard.setToolTip("Create a custom column to store Marvin annotations")
        self.cfg_highlights_wizard.clicked.connect(partial(self.launch_cc_wizard, 'Annotations'))
        self.cfg_custom_fields_qgl.addWidget(self.cfg_highlights_wizard, current_row, 2)
        current_row += 1

        # ++++++++ Collections ++++++++
        self.cfg_collections_label = QLabel('Collections')
        self.cfg_collections_label.setAlignment(Qt.AlignLeft)
        self.cfg_custom_fields_qgl.addWidget(self.cfg_collections_label, current_row, 0)

        self.collection_field_comboBox = QComboBox(self.cfg_custom_fields_gb)
        self.collection_field_comboBox.setObjectName('collection_field_comboBox')
        self.collection_field_comboBox.setToolTip('Select a custom column to store Marvin collection assignments')
        self.cfg_custom_fields_qgl.addWidget(self.collection_field_comboBox, current_row, 1)

        self.cfg_collections_wizard = QToolButton()
        self.cfg_collections_wizard.setIcon(QIcon(I('wizard.png')))
        self.cfg_collections_wizard.setToolTip("Create a custom column for Marvin collection assignments")
        self.cfg_collections_wizard.clicked.connect(partial(self.launch_cc_wizard, 'Collections'))
        self.cfg_custom_fields_qgl.addWidget(self.cfg_collections_wizard, current_row, 2)
        current_row += 1

        # ++++++++ Last read ++++++++
        self.cfg_date_read_label = QLabel("Last read")
        self.cfg_date_read_label.setAlignment(Qt.AlignLeft)
        self.cfg_custom_fields_qgl.addWidget(self.cfg_date_read_label, current_row, 0)

        self.date_read_field_comboBox = QComboBox(self.cfg_custom_fields_gb)
        self.date_read_field_comboBox.setObjectName('date_read_field_comboBox')
        self.date_read_field_comboBox.setToolTip('Select a custom column to store Last read date')
        self.cfg_custom_fields_qgl.addWidget(self.date_read_field_comboBox, current_row, 1)

        self.cfg_collections_wizard = QToolButton()
        self.cfg_collections_wizard.setIcon(QIcon(I('wizard.png')))
        self.cfg_collections_wizard.setToolTip("Create a custom column to store Last read date")
        self.cfg_collections_wizard.clicked.connect(partial(self.launch_cc_wizard, 'Last read'))
        self.cfg_custom_fields_qgl.addWidget(self.cfg_collections_wizard, current_row, 2)
        current_row += 1

        # ++++++++ Locked ++++++++
        self.cfg_locked_label = QLabel("Locked")
        self.cfg_locked_label.setAlignment(Qt.AlignLeft)
        self.cfg_custom_fields_qgl.addWidget(self.cfg_locked_label, current_row, 0)

        self.locked_field_comboBox = QComboBox(self.cfg_custom_fields_gb)
        self.locked_field_comboBox.setObjectName('locked_field_comboBox')
        self.locked_field_comboBox.setToolTip('Select a custom column to store Locked status')
        self.cfg_custom_fields_qgl.addWidget(self.locked_field_comboBox, current_row, 1)

        self.cfg_locked_wizard = QToolButton()
        self.cfg_locked_wizard.setIcon(QIcon(I('wizard.png')))
        self.cfg_locked_wizard.setToolTip("Create a custom column to store Locked status")
        self.cfg_locked_wizard.clicked.connect(partial(self.launch_cc_wizard, 'Locked'))
        self.cfg_custom_fields_qgl.addWidget(self.cfg_locked_wizard, current_row, 2)
        current_row += 1

        # ++++++++ Progress ++++++++
        self.cfg_progress_label = QLabel('Progress')
        self.cfg_progress_label.setAlignment(Qt.AlignLeft)
        self.cfg_custom_fields_qgl.addWidget(self.cfg_progress_label, current_row, 0)

        self.progress_field_comboBox = QComboBox(self.cfg_custom_fields_gb)
        self.progress_field_comboBox.setObjectName('progress_field_comboBox')
        self.progress_field_comboBox.setToolTip('Select a custom column to store Marvin reading progress')
        self.cfg_custom_fields_qgl.addWidget(self.progress_field_comboBox, current_row, 1)

        self.cfg_progress_wizard = QToolButton()
        self.cfg_progress_wizard.setIcon(QIcon(I('wizard.png')))
        self.cfg_progress_wizard.setToolTip("Create a custom column to store Marvin reading progress")
        self.cfg_progress_wizard.clicked.connect(partial(self.launch_cc_wizard, 'Progress'))
        self.cfg_custom_fields_qgl.addWidget(self.cfg_progress_wizard, current_row, 2)
        current_row += 1

        # ++++++++ Read flag ++++++++
        self.cfg_read_label = QLabel('Read')
        self.cfg_read_label.setAlignment(Qt.AlignLeft)
        self.cfg_custom_fields_qgl.addWidget(self.cfg_read_label, current_row, 0)

        self.read_field_comboBox = QComboBox(self.cfg_custom_fields_gb)
        self.read_field_comboBox.setObjectName('read_field_comboBox')
        self.read_field_comboBox.setToolTip('Select a custom column to store Marvin Read status')
        self.cfg_custom_fields_qgl.addWidget(self.read_field_comboBox, current_row, 1)

        self.cfg_read_wizard = QToolButton()
        self.cfg_read_wizard.setIcon(QIcon(I('wizard.png')))
        self.cfg_read_wizard.setToolTip("Create a custom column to store Marvin Read status")
        self.cfg_read_wizard.clicked.connect(partial(self.launch_cc_wizard, 'Read'))
        self.cfg_custom_fields_qgl.addWidget(self.cfg_read_wizard, current_row, 2)
        current_row += 1

        # ++++++++ Reading list flag ++++++++
        self.cfg_reading_list_label = QLabel('Reading list')
        self.cfg_reading_list_label.setAlignment(Qt.AlignLeft)
        self.cfg_custom_fields_qgl.addWidget(self.cfg_reading_list_label, current_row, 0)

        self.reading_list_field_comboBox = QComboBox(self.cfg_custom_fields_gb)
        self.reading_list_field_comboBox.setObjectName('reading_list_field_comboBox')
        self.reading_list_field_comboBox.setToolTip('Select a custom column to store Marvin Reading list status')
        self.cfg_custom_fields_qgl.addWidget(self.reading_list_field_comboBox, current_row, 1)

        self.cfg_reading_list_wizard = QToolButton()
        self.cfg_reading_list_wizard.setIcon(QIcon(I('wizard.png')))
        self.cfg_reading_list_wizard.setToolTip("Create a custom column to store Marvin Reading list status")
        self.cfg_reading_list_wizard.clicked.connect(partial(self.launch_cc_wizard, 'Reading list'))
        self.cfg_custom_fields_qgl.addWidget(self.cfg_reading_list_wizard, current_row, 2)
        current_row += 1

        # ++++++++ Word count ++++++++
        self.cfg_word_count_label = QLabel('Word count')
        self.cfg_word_count_label.setAlignment(Qt.AlignLeft)
        self.cfg_custom_fields_qgl.addWidget(self.cfg_word_count_label, current_row, 0)

        self.word_count_field_comboBox = QComboBox(self.cfg_custom_fields_gb)
        self.word_count_field_comboBox.setObjectName('word_count_field_comboBox')
        self.word_count_field_comboBox.setToolTip('Select a custom column to store Marvin word counts')
        self.cfg_custom_fields_qgl.addWidget(self.word_count_field_comboBox, current_row, 1)

        self.cfg_word_count_wizard = QToolButton()
        self.cfg_word_count_wizard.setIcon(QIcon(I('wizard.png')))
        self.cfg_word_count_wizard.setToolTip("Create a custom column to store Marvin word counts")
        self.cfg_word_count_wizard.clicked.connect(partial(self.launch_cc_wizard, 'Word count'))
        self.cfg_custom_fields_qgl.addWidget(self.cfg_word_count_wizard, current_row, 2)
        current_row += 1

        self.spacerItem1 = QSpacerItem(20, 20, QSizePolicy.Minimum, QSizePolicy.Expanding)
        self.column1_layout.addItem(self.spacerItem1)

        # ----------------------------- Column 2 -----------------------------
        # ~~~~~~~~ Create the CSS group box ~~~~~~~~
        self.cfg_css_options_gb = QGroupBox(self)
        self.cfg_css_options_gb.setTitle('CSS')
        self.column2_layout.addWidget(self.cfg_css_options_gb)
        self.cfg_css_options_qgl = QGridLayout(self.cfg_css_options_gb)

        current_row = 0

        # ++++++++ Annotations appearance ++++++++
        self.annotations_icon = QIcon(os.path.join(self.resources_path, 'icons', 'annotations_hiliter.png'))
        self.cfg_annotations_appearance_toolbutton = QToolButton()
        self.cfg_annotations_appearance_toolbutton.setIcon(self.annotations_icon)
        self.cfg_annotations_appearance_toolbutton.clicked.connect(self.configure_appearance)
        self.cfg_css_options_qgl.addWidget(self.cfg_annotations_appearance_toolbutton, current_row, 0)
        self.cfg_annotations_label = ClickableQLabel("Book notes, Bookmark notes and Annotations")
        self.cfg_annotations_label.clicked.connect(self.configure_appearance)
        self.cfg_css_options_qgl.addWidget(self.cfg_annotations_label, current_row, 1)
        current_row += 1

        # ++++++++ Injected CSS ++++++++
        self.css_editor_icon = QIcon(I('format-text-heading.png'))
        self.cfg_css_editor_toolbutton = QToolButton()
        self.cfg_css_editor_toolbutton.setIcon(self.css_editor_icon)
        self.cfg_css_editor_toolbutton.clicked.connect(self.edit_css)
        self.cfg_css_options_qgl.addWidget(self.cfg_css_editor_toolbutton, current_row, 0)
        self.cfg_css_editor_label = ClickableQLabel("Articles, Vocabulary")
        self.cfg_css_editor_label.clicked.connect(self.edit_css)
        self.cfg_css_options_qgl.addWidget(self.cfg_css_editor_label, current_row, 1)


        """
        # ~~~~~~~~ Create the Dropbox syncing group box ~~~~~~~~
        self.cfg_dropbox_syncing_gb = QGroupBox(self)
        self.cfg_dropbox_syncing_gb.setTitle('Dropbox')
        self.column2_layout.addWidget(self.cfg_dropbox_syncing_gb)
        self.cfg_dropbox_syncing_qgl = QGridLayout(self.cfg_dropbox_syncing_gb)
        current_row = 0

        # ++++++++ Syncing enabled checkbox ++++++++
        self.dropbox_syncing_checkbox = QCheckBox('Enable Dropbox updates')
        self.dropbox_syncing_checkbox.setObjectName('dropbox_syncing')
        self.dropbox_syncing_checkbox.setToolTip('Refresh custom column content from Marvin metadata')
        self.cfg_dropbox_syncing_qgl.addWidget(self.dropbox_syncing_checkbox,
            current_row, 0, 1, 3)
        current_row += 1

        # ++++++++ Dropbox folder picker ++++++++
        self.dropbox_folder_icon = QIcon(os.path.join(self.resources_path, 'icons', 'dropbox.png'))
        self.cfg_dropbox_folder_toolbutton = QToolButton()
        self.cfg_dropbox_folder_toolbutton.setIcon(self.dropbox_folder_icon)
        self.cfg_dropbox_folder_toolbutton.setToolTip("Specify Dropbox folder location on your computer")
        self.cfg_dropbox_folder_toolbutton.clicked.connect(self.select_dropbox_folder)
        self.cfg_dropbox_syncing_qgl.addWidget(self.cfg_dropbox_folder_toolbutton,
            current_row, 1)

        # ++++++++ Dropbox location lineedit ++++++++
        self.dropbox_location_lineedit = QLineEdit()
        self.dropbox_location_lineedit.setPlaceholderText("Dropbox folder location")
        self.cfg_dropbox_syncing_qgl.addWidget(self.dropbox_location_lineedit,
            current_row, 2)
        """

        # ~~~~~~~~ Create the General options group box ~~~~~~~~
        self.cfg_runtime_options_gb = QGroupBox(self)
        self.cfg_runtime_options_gb.setTitle('General options')
        self.column2_layout.addWidget(self.cfg_runtime_options_gb)
        self.cfg_runtime_options_qvl = QVBoxLayout(self.cfg_runtime_options_gb)

        # ++++++++ Temporary markers: Duplicates ++++++++
        self.duplicate_markers_checkbox = QCheckBox('Apply temporary markers to duplicate books')
        self.duplicate_markers_checkbox.setObjectName('apply_markers_to_duplicates')
        self.duplicate_markers_checkbox.setToolTip('Books with identical content will be flagged in the Library window')
        self.cfg_runtime_options_qvl.addWidget(self.duplicate_markers_checkbox)

        # ++++++++ Temporary markers: Updated ++++++++
        self.updated_markers_checkbox = QCheckBox('Apply temporary markers to books with updated content')
        self.updated_markers_checkbox.setObjectName('apply_markers_to_updated')
        self.updated_markers_checkbox.setToolTip('Books with updated content will be flagged in the Library window')
        self.cfg_runtime_options_qvl.addWidget(self.updated_markers_checkbox)

        # ++++++++ Auto refresh checkbox ++++++++
        self.auto_refresh_checkbox = QCheckBox('Automatically refresh custom column content')
        self.auto_refresh_checkbox.setObjectName('auto_refresh_at_startup')
        self.auto_refresh_checkbox.setToolTip('Update calibre custom column when Marvin XD is opened')
        self.cfg_runtime_options_qvl.addWidget(self.auto_refresh_checkbox)

        # ++++++++ Progress as percentage checkbox ++++++++
        self.reading_progress_checkbox = QCheckBox('Show reading progress as percentage')
        self.reading_progress_checkbox.setObjectName('show_progress_as_percentage')
        self.reading_progress_checkbox.setToolTip('Display percentage in Progress column')
        self.cfg_runtime_options_qvl.addWidget(self.reading_progress_checkbox)

        # ~~~~~~~~ Create the Debug options group box ~~~~~~~~
        self.cfg_debug_options_gb = QGroupBox(self)
        self.cfg_debug_options_gb.setTitle('Debug options')
        self.column2_layout.addWidget(self.cfg_debug_options_gb)
        self.cfg_debug_options_qvl = QVBoxLayout(self.cfg_debug_options_gb)

        # ++++++++ Debug logging checkboxes ++++++++
        self.debug_plugin_checkbox = QCheckBox('Enable debug logging for Marvin XD')
        self.debug_plugin_checkbox.setObjectName('debug_plugin_checkbox')
        self.debug_plugin_checkbox.setToolTip('Print plugin diagnostic messages to console')
        self.cfg_debug_options_qvl.addWidget(self.debug_plugin_checkbox)

        self.debug_libimobiledevice_checkbox = QCheckBox('Enable debug logging for libiMobileDevice')
        self.debug_libimobiledevice_checkbox.setObjectName('debug_libimobiledevice_checkbox')
        self.debug_libimobiledevice_checkbox.setToolTip('Print libiMobileDevice diagnostic messages to console')
        self.cfg_debug_options_qvl.addWidget(self.debug_libimobiledevice_checkbox)

        self.spacerItem2 = QSpacerItem(20, 20, QSizePolicy.Minimum, QSizePolicy.Expanding)
        self.column2_layout.addItem(self.spacerItem2)

        # ~~~~~~~~ End of construction zone ~~~~~~~~
        self.resize(self.sizeHint())

        # ~~~~~~~~ Populate/restore config options ~~~~~~~~
        #  Annotations comboBox
        self.populate_annotations()
        self.populate_collections()
        self.populate_date_read()
        self.populate_locked()
        self.populate_progress()
        self.populate_read()
        self.populate_reading_list()
        self.populate_word_count()

        """
        # Restore Dropbox settings, hook changes
        dropbox_syncing = self.prefs.get('dropbox_syncing', False)
        self.dropbox_syncing_checkbox.setChecked(dropbox_syncing)
        self.set_dropbox_syncing(dropbox_syncing)
        self.dropbox_syncing_checkbox.clicked.connect(partial(self.set_dropbox_syncing))
        self.dropbox_location_lineedit.setText(self.prefs.get('dropbox_folder', ''))
        """

        # Restore general settings
        self.duplicate_markers_checkbox.setChecked(self.prefs.get('apply_markers_to_duplicates', True))
        self.updated_markers_checkbox.setChecked(self.prefs.get('apply_markers_to_updated', True))
        self.auto_refresh_checkbox.setChecked(self.prefs.get('auto_refresh_at_startup', False))
        self.reading_progress_checkbox.setChecked(self.prefs.get('show_progress_as_percentage', False))

        # Restore debug settings, hook changes
        self.debug_plugin_checkbox.setChecked(self.prefs.get('debug_plugin', False))
        self.debug_plugin_checkbox.stateChanged.connect(self.set_restart_required)
        self.debug_libimobiledevice_checkbox.setChecked(self.prefs.get('debug_libimobiledevice', False))
        self.debug_libimobiledevice_checkbox.stateChanged.connect(self.set_restart_required)

        # Hook changes to Annotations comboBox
#         self.annotations_field_comboBox.currentIndexChanged.connect(
#             partial(self.save_combobox_setting, 'annotations_field_comboBox'))
#        self.connect(self.annotations_field_comboBox,
#                     SIGNAL('currentIndexChanged(const QString &)'),
#                     self.annotations_destination_changed)
        self.annotations_field_comboBox.currentIndexChanged.connect(self.annotations_destination_changed)
        # Launch the annotated_books_scanner
        field = get_cc_mapping('annotations', 'field', None)
        self.annotated_books_scanner = InventoryAnnotatedBooks(self.gui, field)
        self.annotated_books_scanner.signal.connect(self.inventory_complete)
        QTimer.singleShot(1, self.start_inventory)

    def annotations_destination_changed(self, qs_new_destination_name):
        '''
        If the destination field changes, move all existing annotations from old to new
        '''
        self._log_location(str(qs_new_destination_name))
        self._log("self.eligible_annotations_fields: %s" % self.eligible_annotations_fields)

        old_destination_field = get_cc_mapping('annotations', 'field', None)
        old_destination_name = get_cc_mapping('annotations', 'combobox', None)

        self._log("old_destination_field: %s" % old_destination_field)
        self._log("old_destination_name: %s" % old_destination_name)

#        new_destination_name = unicode(qs_new_destination_name)
        # Signnls available have changed. Now receivin an indec rather than a name. Can get the name
        # from the combobox
        new_destination_name = unicode(self.annotations_field_comboBox.currentText())
        self._log("new_destination_name: %s" % repr(new_destination_name))

        if old_destination_name == new_destination_name:
            self._log_location("old_destination_name = new_destination_name, no changes")
            return

        if new_destination_name == '':
            self._log_location("annotations storage disabled")
            set_cc_mapping('annotations', field=None, combobox=new_destination_name)
            return

        new_destination_field = self.eligible_annotations_fields[new_destination_name]

        if existing_annotations(self.parent, old_destination_field):
            command = self.launch_new_destination_dialog(old_destination_name, new_destination_name)

            if command == 'move':
                set_cc_mapping('annotations', field=new_destination_field, combobox=new_destination_name)

                if self.annotated_books_scanner.isRunning():
                    self.annotated_books_scanner.wait()
                move_annotations(self, self.annotated_books_scanner.annotation_map,
                    old_destination_field, new_destination_field)

            elif command == 'change':
                # Keep the updated destination field, but don't move annotations
                pass

            elif command == 'cancel':
                # Restore previous destination
                self.annotations_field_comboBox.blockSignals(True)
                old_index = self.annotations_field_comboBox.findText(old_destination_name)
                self.annotations_field_comboBox.setCurrentIndex(old_index)
                self.annotations_field_comboBox.blockSignals(False)

        else:
            # No existing annotations, just update prefs
            self._log("no existing annotations, updating destination to '{0}'".format(new_destination_name))
            set_cc_mapping('annotations', field=new_destination_field, combobox=new_destination_name)

    def configure_appearance(self):
        '''
        '''
        self._log_location()
        appearance_settings = {
                                'appearance_css': default_elements,
                                'appearance_hr_checkbox': False,
                                'appearance_timestamp_format': default_timestamp
                              }

        # Save, hash the original settings
        original_settings = {}
        osh = hashlib.md5()
        for setting in appearance_settings:
            original_settings[setting] = plugin_prefs.get(setting, appearance_settings[setting])
            osh.update(repr(plugin_prefs.get(setting, appearance_settings[setting])))

        # Display the Annotations appearance dialog
        aa = AnnotationsAppearance(self, self.annotations_icon, plugin_prefs)
        cancelled = False
        if aa.exec_():
            # appearance_hr_checkbox and appearance_timestamp_format changed live to prefs during previews
            plugin_prefs.set('appearance_css', aa.elements_table.get_data())
            # Generate a new hash
            nsh = hashlib.md5()
            for setting in appearance_settings:
                nsh.update(repr(plugin_prefs.get(setting, appearance_settings[setting])))
        else:
            for setting in appearance_settings:
                plugin_prefs.set(setting, original_settings[setting])
            nsh = osh

        # If there were changes, and there are existing annotations,
        # and there is an active Annotations field, offer to re-render
        field = get_cc_mapping('annotations', 'field', None)
        if osh.digest() != nsh.digest() and existing_annotations(self.parent, field):
            title = 'Update annotations?'
            msg = '<p>Update existing annotations to new appearance settings?</p>'
            d = MessageBox(MessageBox.QUESTION,
                           title, msg,
                           show_copy_button=False)
            self._log_location("QUESTION: %s" % msg)
            if d.exec_():
                self._log_location("Updating existing annotations to modified appearance")

                # Wait for indexing to complete
                while not self.annotated_books_scanner.isFinished():
                    Application.processEvents()

                move_annotations(self, self.annotated_books_scanner.annotation_map,
                    field, field, window_title="Updating appearance")

    def edit_css(self):
        '''
        '''
        self._log_location()
        from calibre_plugins.marvin_manager.book_status import dialog_resources_path
        klass = os.path.join(dialog_resources_path, 'css_editor.py')
        if os.path.exists(klass):
            sys.path.insert(0, dialog_resources_path)
            this_dc = importlib.import_module('css_editor')
            sys.path.remove(dialog_resources_path)
            dlg = this_dc.CSSEditorDialog(self, 'css_editor')
            dlg.initialize(self)
            dlg.exec_()

    def get_eligible_custom_fields(self, eligible_types=[], is_multiple=None):
        '''
        Discover qualifying custom fields for eligible_types[]
        '''
        #self._log_location(eligible_types)

        eligible_custom_fields = {}
        for cf in self.gui.current_db.custom_field_keys():
            cft = self.gui.current_db.metadata_for_field(cf)['datatype']
            cfn = self.gui.current_db.metadata_for_field(cf)['name']
            cfim = self.gui.current_db.metadata_for_field(cf)['is_multiple']
            #self._log("cf: %s  cft: %s  cfn: %s cfim: %s" % (cf, cft, cfn, cfim))
            if cft in eligible_types:
                if is_multiple is not None:
                    if bool(cfim) == is_multiple:
                        eligible_custom_fields[cfn] = cf
                else:
                    eligible_custom_fields[cfn] = cf
        return eligible_custom_fields

    def inventory_complete(self, msg):
        self._log_location(msg)

    def launch_cc_wizard(self, column_type):
        '''
        '''
        def _update_combo_box(comboBox, destination, previous):
            '''
            '''
            cb = getattr(self, comboBox)
            cb.blockSignals(True)
            all_items = [str(cb.itemText(i))
                         for i in range(cb.count())]
            if previous and previous in all_items:
                all_items.remove(previous)
            all_items.append(destination)

            cb.clear()
            cb.addItems(sorted(all_items, key=lambda s: s.lower()))

            # Select the new destination in the comboBox
            idx = cb.findText(destination)
            if idx > -1:
                cb.setCurrentIndex(idx)

            cb.blockSignals(False)

        from calibre_plugins.marvin_manager.book_status import dialog_resources_path

        klass = os.path.join(dialog_resources_path, 'cc_wizard.py')
        if os.path.exists(klass):
            #self._log("importing CC Wizard dialog from '%s'" % klass)
            sys.path.insert(0, dialog_resources_path)
            this_dc = importlib.import_module('cc_wizard')
            sys.path.remove(dialog_resources_path)
            dlg = this_dc.CustomColumnWizard(self,
                                             column_type,
                                             self.WIZARD_PROFILES[column_type],
                                             verbose=True)
            dlg.exec_()

            if dlg.modified_column:
                self._log("modified_column: %s" % dlg.modified_column)

                self.restart_required = True

                destination = dlg.modified_column['destination']
                label = dlg.modified_column['label']
                previous = dlg.modified_column['previous']
                source = dlg.modified_column['source']

                if source == "Annotations":
                    _update_combo_box("annotations_field_comboBox", destination, previous)

                    # Add/update the new destination so save_settings() can find it
                    self.eligible_annotations_fields[destination] = label

                    # Save manually in case user cancels
                    set_cc_mapping('annotations', combobox=destination, field=label)

                elif source == 'Collections':
                    _update_combo_box("collection_field_comboBox", destination, previous)

                    # Add/update the new destination so save_settings() can find it
                    self.eligible_collection_fields[destination] = label

                    # Save manually in case user cancels
                    set_cc_mapping('collections', combobox=destination, field=label)

                elif source == 'Last read':
                    _update_combo_box("date_read_field_comboBox", destination, previous)

                    # Add/update the new destination so save_settings() can find it
                    self.eligible_date_read_fields[destination] = label

                    # Save manually in case user cancels
                    set_cc_mapping('date_read', combobox=destination, field=label)

                elif source == 'Locked':
                    _update_combo_box("locked_field_comboBox", destination, previous)

                    # Add/update the new destination so save_settings() can find it
                    self.eligible_locked_fields[destination] = label

                    # Save manually in case user cancels
                    set_cc_mapping('locked', combobox=destination, field=label)

                elif source == "Progress":
                    _update_combo_box("progress_field_comboBox", destination, previous)

                    # Add/update the new destination so save_settings() can find it
                    self.eligible_progress_fields[destination] = label

                    # Save manually in case user cancels
                    set_cc_mapping('progress', combobox=destination, field=label)

                elif source == "Read":
                    _update_combo_box("read_field_comboBox", destination, previous)

                    # Add/update the new destination so save_settings() can find it
                    self.eligible_read_fields[destination] = label

                    # Save manually in case user cancels
                    set_cc_mapping('read', combobox=destination, field=label)

                elif source == "Reading list":
                    _update_combo_box("reading_list_field_comboBox", destination, previous)

                    # Add/update the new destination so save_settings() can find it
                    self.eligible_reading_list_fields[destination] = label

                    # Save manually in case user cancels
                    set_cc_mapping('reading_list', combobox=destination, field=label)

                elif source == "Word count":
                    _update_combo_box("word_count_field_comboBox", destination, previous)

                    # Add/update the new destination so save_settings() can find it
                    self.eligible_word_count_fields[destination] = label

                    # Save manually in case user cancels
                    set_cc_mapping('word_count', combobox=destination, field=label)
        else:
            self._log("ERROR: Can't import from '%s'" % klass)

    def launch_new_destination_dialog(self, old, new):
        '''
        Return 'move', 'change' or 'cancel'
        '''
        from calibre_plugins.marvin_manager.book_status import dialog_resources_path
        self._log_location()
        klass = os.path.join(dialog_resources_path, 'new_destination.py')
        if os.path.exists(klass):
            self._log("importing new destination dialog from '%s'" % klass)
            sys.path.insert(0, dialog_resources_path)
            this_dc = importlib.import_module('new_destination')
            sys.path.remove(dialog_resources_path)
            dlg = this_dc.NewDestinationDialog(self, old, new)
            dlg.exec_()
            return dlg.command

    def populate_annotations(self):
        datatype = self.WIZARD_PROFILES['Annotations']['datatype']
        self.eligible_annotations_fields = self.get_eligible_custom_fields([datatype])
        self.annotations_field_comboBox.addItems([''])
        ecf = sorted(self.eligible_annotations_fields.keys(), key=lambda s: s.lower())
        self.annotations_field_comboBox.addItems(ecf)

        # Retrieve stored value
        existing = get_cc_mapping('annotations', 'combobox')
        if existing:
            ci = self.annotations_field_comboBox.findText(existing)
            self.annotations_field_comboBox.setCurrentIndex(ci)

    def populate_collections(self):
        datatype = self.WIZARD_PROFILES['Collections']['datatype']
        self.eligible_collection_fields = self.get_eligible_custom_fields([datatype],
                                                                          is_multiple=True)
        self.collection_field_comboBox.addItems([''])
        ecf = sorted(self.eligible_collection_fields.keys(), key=lambda s: s.lower())
        self.collection_field_comboBox.addItems(ecf)

        # Retrieve stored value
        existing = get_cc_mapping('collections', 'combobox')
        if existing:
            ci = self.collection_field_comboBox.findText(existing)
            self.collection_field_comboBox.setCurrentIndex(ci)

    def populate_date_read(self):
        #self.eligible_date_read_fields = self.get_eligible_custom_fields(['datetime'])
        datatype = self.WIZARD_PROFILES['Last read']['datatype']
        self.eligible_date_read_fields = self.get_eligible_custom_fields([datatype])
        self.date_read_field_comboBox.addItems([''])
        ecf = sorted(self.eligible_date_read_fields.keys(), key=lambda s: s.lower())
        self.date_read_field_comboBox.addItems(ecf)

        # Retrieve stored value
        existing = get_cc_mapping('date_read', 'combobox')
        if existing:
            ci = self.date_read_field_comboBox.findText(existing)
            self.date_read_field_comboBox.setCurrentIndex(ci)

    def populate_locked(self):
        datatype = self.WIZARD_PROFILES['Locked']['datatype']
        self.eligible_locked_fields = self.get_eligible_custom_fields([datatype])
        self.locked_field_comboBox.addItems([''])
        ecf = sorted(self.eligible_locked_fields.keys(), key=lambda s: s.lower())
        self.locked_field_comboBox.addItems(ecf)

        # Retrieve stored value
        existing = get_cc_mapping('locked', 'combobox')
        if existing:
            ci = self.locked_field_comboBox.findText(existing)
            self.locked_field_comboBox.setCurrentIndex(ci)

    def populate_progress(self):
        #self.eligible_progress_fields = self.get_eligible_custom_fields(['float'])
        datatype = self.WIZARD_PROFILES['Progress']['datatype']
        self.eligible_progress_fields = self.get_eligible_custom_fields([datatype])
        self.progress_field_comboBox.addItems([''])
        ecf = sorted(self.eligible_progress_fields.keys(), key=lambda s: s.lower())
        self.progress_field_comboBox.addItems(ecf)

        # Retrieve stored value
        existing = get_cc_mapping('progress', 'combobox')
        if existing:
            ci = self.progress_field_comboBox.findText(existing)
            self.progress_field_comboBox.setCurrentIndex(ci)

    def populate_read(self):
        datatype = self.WIZARD_PROFILES['Read']['datatype']
        self.eligible_read_fields = self.get_eligible_custom_fields([datatype])
        self.read_field_comboBox.addItems([''])
        ecf = sorted(self.eligible_read_fields.keys(), key=lambda s: s.lower())
        self.read_field_comboBox.addItems(ecf)

        # Retrieve stored value
        existing = get_cc_mapping('read', 'combobox')
        if existing:
            ci = self.read_field_comboBox.findText(existing)
            self.read_field_comboBox.setCurrentIndex(ci)

    def populate_reading_list(self):
        datatype = self.WIZARD_PROFILES['Reading list']['datatype']
        self.eligible_reading_list_fields = self.get_eligible_custom_fields([datatype])
        self.reading_list_field_comboBox.addItems([''])
        ecf = sorted(self.eligible_reading_list_fields.keys(), key=lambda s: s.lower())
        self.reading_list_field_comboBox.addItems(ecf)

        # Retrieve stored value
        existing = get_cc_mapping('reading_list', 'combobox')
        if existing:
            ci = self.reading_list_field_comboBox.findText(existing)
            self.reading_list_field_comboBox.setCurrentIndex(ci)

    def populate_word_count(self):
        #self.eligible_word_count_fields = self.get_eligible_custom_fields(['int'])
        datatype = self.WIZARD_PROFILES['Word count']['datatype']
        self.eligible_word_count_fields = self.get_eligible_custom_fields([datatype])
        self.word_count_field_comboBox.addItems([''])
        ecf = sorted(self.eligible_word_count_fields.keys(), key=lambda s: s.lower())
        self.word_count_field_comboBox.addItems(ecf)

        # Retrieve stored value
        existing = get_cc_mapping('word_count', 'combobox')
        if existing:
            ci = self.word_count_field_comboBox.findText(existing)
            self.word_count_field_comboBox.setCurrentIndex(ci)

    """
    def select_dropbox_folder(self):
        '''
        '''
        self._log_location()
        dropbox_location = QFileDialog.getExistingDirectory(
            self,
            "Dropbox folder",
            os.path.expanduser("~"),
            QFileDialog.ShowDirsOnly | QFileDialog.DontResolveSymlinks)
        self.dropbox_location_lineedit.setText(unicode(dropbox_location))

    def set_dropbox_syncing(self, state):
        '''
        Called when checkbox changes state, or when restoring state
        Set enabled state of Dropbox folder picker to match
        '''
        self.cfg_dropbox_folder_toolbutton.setEnabled(state)
        self.dropbox_location_lineedit.setEnabled(state)
    """

    def set_restart_required(self, state):
        '''
        Set restart_required flag to show show dialog when closing dialog
        '''
        self.restart_required = True

    """
    def save_combobox_setting(self, cb, index):
        '''
        Apply changes immediately
        '''
        cf = str(getattr(self, cb).currentText())
        self._log_location("%s => %s" % (cb, repr(cf)))

        if cb == 'annotations_field_comboBox':
            field = None
            if cf:
                field = self.eligible_annotations_fields[cf]
            set_cc_mapping('annotations', combobox=cf, field=field)
    """

    def save_settings(self):
        self._log_location()

        # Annotations field
        cf = unicode(self.annotations_field_comboBox.currentText())
        field = None
        if cf:
            field = self.eligible_annotations_fields[cf]
        set_cc_mapping('annotations', combobox=cf, field=field)

        # Collections field
        cf = unicode(self.collection_field_comboBox.currentText())
        field = None
        if cf:
            field = self.eligible_collection_fields[cf]
        set_cc_mapping('collections', combobox=cf, field=field)

        # Date read field
        cf = unicode(self.date_read_field_comboBox.currentText())
        field = None
        if cf:
            field = self.eligible_date_read_fields[cf]
        set_cc_mapping('date_read', combobox=cf, field=field)

        # Locked field
        cf = unicode(self.locked_field_comboBox.currentText())
        field = None
        if cf:
            field = self.eligible_locked_fields[cf]
        set_cc_mapping('locked', combobox=cf, field=field)

        # Progress field
        cf = unicode(self.progress_field_comboBox.currentText())
        field = None
        if cf:
            field = self.eligible_progress_fields[cf]
        set_cc_mapping('progress', combobox=cf, field=field)

        # Read field
        cf = unicode(self.read_field_comboBox.currentText())
        field = None
        if cf:
            field = self.eligible_read_fields[cf]
        set_cc_mapping('read', combobox=cf, field=field)

        # Reading list field
        cf = unicode(self.reading_list_field_comboBox.currentText())
        field = None
        if cf:
            field = self.eligible_reading_list_fields[cf]
        set_cc_mapping('reading_list', combobox=cf, field=field)

        # Word count field
        cf = unicode(self.word_count_field_comboBox.currentText())
        field = None
        if cf:
            field = self.eligible_word_count_fields[cf]
        set_cc_mapping('word_count', combobox=cf, field=field)

        '''
        # Save Dropbox settings
        self.prefs.set('dropbox_syncing', self.dropbox_syncing_checkbox.isChecked())
        self.prefs.set('dropbox_folder', unicode(self.dropbox_location_lineedit.text()))
        '''

        # Save general settings
        self.prefs.set('apply_markers_to_duplicates', self.duplicate_markers_checkbox.isChecked())
        self.prefs.set('apply_markers_to_updated', self.updated_markers_checkbox.isChecked())
        self.prefs.set('auto_refresh_at_startup', self.auto_refresh_checkbox.isChecked())
        self.prefs.set('show_progress_as_percentage', self.reading_progress_checkbox.isChecked())

        # Save debug settings
        self.prefs.set('debug_plugin', self.debug_plugin_checkbox.isChecked())
        self.prefs.set('debug_libimobiledevice', self.debug_libimobiledevice_checkbox.isChecked())

        # If restart needed, inform user
        if self.restart_required:
            do_restart = show_restart_warning('Restart calibre for the changes to be applied.',
                                              parent=self.gui)
            if do_restart:
                self.gui.quit(restart=True)

    def start_inventory(self):
        self._log_location()
        self.annotated_books_scanner.start()
Пример #25
0
class FindAnnotationsDialog(SizePersistedDialog, Logger):

    GENERIC_STYLE = 'Any style'
    GENERIC_READER = 'Any reader'

    def __init__(self, opts):
        self.matched_ids = set()
        self.opts = opts
        self.prefs = opts.prefs
        super(FindAnnotationsDialog, self).__init__(self.opts.gui, 'find_annotations_dialog')
        self.setWindowTitle(_('Find Annotations'))
        self.setWindowIcon(self.opts.icon)
        self.l = QVBoxLayout(self)
        self.setLayout(self.l)

        self.search_criteria_gb = QGroupBox(self)
        self.search_criteria_gb.setTitle(_("Search criteria"))
        self.scgl = QGridLayout(self.search_criteria_gb)
        self.l.addWidget(self.search_criteria_gb)
        # addWidget(widget, row, col, rowspan, colspan)

        row = 0
        # ~~~~~~~~ Create the Readers comboBox ~~~~~~~~
        self.reader_label = QLabel(_('Reader'))
        self.reader_label.setAlignment(Qt.AlignRight|Qt.AlignVCenter)
        self.scgl.addWidget(self.reader_label, row, 0, 1, 1)

        self.find_annotations_reader_comboBox = QComboBox()
        self.find_annotations_reader_comboBox.setObjectName('find_annotations_reader_comboBox')
        self.find_annotations_reader_comboBox.setToolTip(_('Reader annotations to search for'))

        self.find_annotations_reader_comboBox.addItem(self.GENERIC_READER)
        racs = ReaderApp.get_reader_app_classes()
        for ra in sorted(racs.keys()):
            self.find_annotations_reader_comboBox.addItem(ra)
        self.scgl.addWidget(self.find_annotations_reader_comboBox, row, 1, 1, 4)
        row += 1

        # ~~~~~~~~ Create the Styles comboBox ~~~~~~~~
        self.style_label = QLabel(_('Style'))
        self.style_label.setAlignment(Qt.AlignRight|Qt.AlignVCenter)
        self.scgl.addWidget(self.style_label, row, 0, 1, 1)

        self.find_annotations_color_comboBox = QComboBox()
        self.find_annotations_color_comboBox.setObjectName('find_annotations_color_comboBox')
        self.find_annotations_color_comboBox.setToolTip(_('Annotation style to search for'))

        self.find_annotations_color_comboBox.addItem(self.GENERIC_STYLE)
        all_colors = COLOR_MAP.keys()
        all_colors.remove('Default')
        for color in sorted(all_colors):
            self.find_annotations_color_comboBox.addItem(color)
        self.scgl.addWidget(self.find_annotations_color_comboBox, row, 1, 1, 4)
        row += 1

        # ~~~~~~~~ Create the Text LineEdit control ~~~~~~~~
        self.text_label = QLabel(_('Text'))
        self.text_label.setAlignment(Qt.AlignRight|Qt.AlignVCenter)
        self.scgl.addWidget(self.text_label, row, 0, 1, 1)
        self.find_annotations_text_lineEdit = MyLineEdit()
        self.find_annotations_text_lineEdit.setObjectName('find_annotations_text_lineEdit')
        self.scgl.addWidget(self.find_annotations_text_lineEdit, row, 1, 1, 3)
        self.reset_text_tb = QToolButton()
        self.reset_text_tb.setObjectName('reset_text_tb')
        self.reset_text_tb.setToolTip(_('Clear search criteria'))
        self.reset_text_tb.setIcon(QIcon(I('trash.png')))
        self.reset_text_tb.clicked.connect(self.clear_text_field)
        self.scgl.addWidget(self.reset_text_tb, row, 4, 1, 1)
        row += 1

        # ~~~~~~~~ Create the Note LineEdit control ~~~~~~~~
        self.note_label = QLabel(_('Note'))
        self.note_label.setAlignment(Qt.AlignRight|Qt.AlignVCenter)
        self.scgl.addWidget(self.note_label, row, 0, 1, 1)
        self.find_annotations_note_lineEdit = MyLineEdit()
        self.find_annotations_note_lineEdit.setObjectName('find_annotations_note_lineEdit')
        self.scgl.addWidget(self.find_annotations_note_lineEdit, row, 1, 1, 3)
        self.reset_note_tb = QToolButton()
        self.reset_note_tb.setObjectName('reset_note_tb')
        self.reset_note_tb.setToolTip(_('Clear search criteria'))
        self.reset_note_tb.setIcon(QIcon(I('trash.png')))
        self.reset_note_tb.clicked.connect(self.clear_note_field)
        self.scgl.addWidget(self.reset_note_tb, row, 4, 1, 1)
        row += 1

        # ~~~~~~~~ Create the Date range controls ~~~~~~~~
        self.date_range_label = QLabel(_('Date range'))
        self.date_range_label.setAlignment(Qt.AlignRight|Qt.AlignVCenter)
        self.scgl.addWidget(self.date_range_label, row, 0, 1, 1)

        # Date 'From'
        self.find_annotations_date_from_dateEdit = MyDateEdit(self, datetime(1970,1,1))
        self.find_annotations_date_from_dateEdit.setObjectName('find_annotations_date_from_dateEdit')
        #self.find_annotations_date_from_dateEdit.current_val = datetime(1970,1,1)
        self.find_annotations_date_from_dateEdit.clear_button.clicked.connect(self.find_annotations_date_from_dateEdit.reset_from_date)
        self.scgl.addWidget(self.find_annotations_date_from_dateEdit, row, 1, 1, 1)
        self.scgl.addWidget(self.find_annotations_date_from_dateEdit.clear_button, row, 2, 1, 1)

        # Date 'To'
        self.find_annotations_date_to_dateEdit = MyDateEdit(self, datetime.today())
        self.find_annotations_date_to_dateEdit.setObjectName('find_annotations_date_to_dateEdit')
        #self.find_annotations_date_to_dateEdit.current_val = datetime.today()
        self.find_annotations_date_to_dateEdit.clear_button.clicked.connect(self.find_annotations_date_to_dateEdit.reset_to_date)
        self.scgl.addWidget(self.find_annotations_date_to_dateEdit, row, 3, 1, 1)
        self.scgl.addWidget(self.find_annotations_date_to_dateEdit.clear_button, row, 4, 1, 1)
        row += 1

        # ~~~~~~~~ Create a horizontal line ~~~~~~~~
        self.hl = QFrame(self)
        self.hl.setGeometry(QRect(0, 0, 1, 3))
        self.hl.setFrameShape(QFrame.HLine)
        self.hl.setFrameShadow(QFrame.Raised)
        self.scgl.addWidget(self.hl, row, 0, 1, 5)
        row += 1

        # ~~~~~~~~ Create the results label field ~~~~~~~~
        self.result_label = QLabel('<p style="color:red">{0}</p>'.format(_('scanning…')))
        self.result_label.setAlignment(Qt.AlignCenter)
        self.result_label.setWordWrap(False)

        sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Maximum)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.result_label.sizePolicy().hasHeightForWidth())
        self.result_label.setSizePolicy(sizePolicy)
        self.result_label.setMinimumSize(QtCore.QSize(250, 0))
        self.scgl.addWidget(self.result_label, row, 0, 1, 5)
        row += 1

        # ~~~~~~~~ Create the ButtonBox ~~~~~~~~
        self.dialogButtonBox = QDialogButtonBox(self)
        self.dialogButtonBox.setOrientation(Qt.Horizontal)
        if False:
            self.update_button = QPushButton(_('Update results'))
            self.update_button.setDefault(True)
            self.update_button.setVisible(False)
            self.dialogButtonBox.addButton(self.update_button, QDialogButtonBox.ActionRole)

        self.cancel_button = self.dialogButtonBox.addButton(self.dialogButtonBox.Cancel)
        self.find_button = self.dialogButtonBox.addButton(self.dialogButtonBox.Ok)
        self.find_button.setText(_('Find Matching Books'))

        self.l.addWidget(self.dialogButtonBox)
        self.dialogButtonBox.clicked.connect(self.find_annotations_dialog_clicked)

        # ~~~~~~~~ Add a spacer ~~~~~~~~
        self.spacerItem = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
        self.l.addItem(self.spacerItem)

        # ~~~~~~~~ Restore previously saved settings ~~~~~~~~
        self.restore_settings()

        # ~~~~~~~~ Declare sizing ~~~~~~~~
        sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.sizePolicy().hasHeightForWidth())
        self.setSizePolicy(sizePolicy)
        self.resize_dialog()

        # ~~~~~~~~ Connect all signals ~~~~~~~~
        self.find_annotations_reader_comboBox.currentIndexChanged.connect(partial(self.update_results, 'reader'))
        self.find_annotations_color_comboBox.currentIndexChanged.connect(partial(self.update_results, 'color'))
        self.find_annotations_text_lineEdit.editingFinished.connect(partial(self.update_results, 'text'))
        self.find_annotations_note_lineEdit.editingFinished.connect(partial(self.update_results, 'note'))
#        self.connect(self.find_annotations_text_lineEdit, pyqtSignal("return_pressed"), self.return_pressed)
        self.find_annotations_text_lineEdit.return_pressed.connect(self.return_pressed)
#        self.connect(self.find_annotations_note_lineEdit, pyqtSignal("return_pressed"), self.return_pressed)
        self.find_annotations_note_lineEdit.return_pressed.connect(self.return_pressed)

        # Date range signals connected in inventory_available()

        # ~~~~~~~~ Allow dialog to render before doing inventory ~~~~~~~~
        #field = self.prefs.get('cfg_annotations_destination_field', None)
        field = get_cc_mapping('annotations', 'field', None)
        self.annotated_books_scanner = InventoryAnnotatedBooks(self.opts.gui, field, get_date_range=True)
        self.annotated_books_scanner.signal.connect(self.inventory_available)
        QTimer.singleShot(1, self.start_inventory_scan)

    def clear_note_field(self):
        if str(self.find_annotations_note_lineEdit.text()) > '':
            self.find_annotations_note_lineEdit.setText('')
            self.update_results('clear_note_field')

    def clear_text_field(self):
        if str(self.find_annotations_text_lineEdit.text()) > '':
            self.find_annotations_text_lineEdit.setText('')
            self.update_results('clear_text_field')

    def find_annotations_dialog_clicked(self, button):
        if self.dialogButtonBox.buttonRole(button) == QDialogButtonBox.AcceptRole:
            self.save_settings()
            self.accept()
        elif self.dialogButtonBox.buttonRole(button) == QDialogButtonBox.RejectRole:
            self.close()

    def inventory_available(self):
        '''
        Update the Date range widgets with the rounded oldest, newest dates
        Don't connect date signals until date range available
        '''
        self._log_location()

        # Reset the date range based on available annotations
        oldest = QDateTime(datetime.fromtimestamp(self.annotated_books_scanner.oldest_annotation))
        oldest_day = QDateTime(datetime.fromtimestamp(self.annotated_books_scanner.oldest_annotation).replace(hour=0, minute=0, second=0))
        newest = QDateTime(datetime.fromtimestamp(self.annotated_books_scanner.newest_annotation))
        newest_day = QDateTime(datetime.fromtimestamp(self.annotated_books_scanner.newest_annotation).replace(hour=23, minute=59, second=59))

        # Set 'From' date limits to inventory values
        self.find_annotations_date_from_dateEdit.setMinimumDateTime(oldest_day)
        self.find_annotations_date_from_dateEdit.current_val = oldest
        self.find_annotations_date_from_dateEdit.setMaximumDateTime(newest_day)

        # Set 'To' date limits to inventory values
        self.find_annotations_date_to_dateEdit.setMinimumDateTime(oldest_day)
        self.find_annotations_date_to_dateEdit.current_val = newest_day
        self.find_annotations_date_to_dateEdit.setMaximumDateTime(newest_day)

        # Connect the signals for date range changes
        self.find_annotations_date_from_dateEdit.dateTimeChanged.connect(partial(self.update_results, 'from_date'))
        self.find_annotations_date_to_dateEdit.dateTimeChanged.connect(partial(self.update_results, 'to_date'))

        self.update_results('inventory_available')

    def restore_settings(self):
        self.blockSignals(True)

        ra = self.prefs.get('find_annotations_reader_comboBox', self.GENERIC_READER)
        ra_index = self.find_annotations_reader_comboBox.findText(ra)
        self.find_annotations_reader_comboBox.setCurrentIndex(ra_index)

        color = self.prefs.get('find_annotations_color_comboBox', self.GENERIC_STYLE)
        color_index = self.find_annotations_color_comboBox.findText(color)
        self.find_annotations_color_comboBox.setCurrentIndex(color_index)

        text = self.prefs.get('find_annotations_text_lineEdit', '')
        self.find_annotations_text_lineEdit.setText(text)

        note = self.prefs.get('find_annotations_note_lineEdit', '')
        self.find_annotations_note_lineEdit.setText(note)

        if False:
            from_date = self.prefs.get('find_annotations_date_from_dateEdit', datetime(1970,1,1))
            self.find_annotations_date_from_dateEdit.current_val = from_date
            to_date = self.prefs.get('find_annotations_date_to_dateEdit', datetime.today())
            self.find_annotations_date_to_dateEdit.current_val = to_date

        self.blockSignals(False)

    def return_pressed(self):
        self.update_results("return_pressed")

    def save_settings(self):
        ra = str(self.find_annotations_reader_comboBox.currentText())
        self.prefs.set('find_annotations_reader_comboBox', ra)

        color = str(self.find_annotations_color_comboBox.currentText())
        self.prefs.set('find_annotations_color_comboBox', color)

        text = str(self.find_annotations_text_lineEdit.text())
        self.prefs.set('find_annotations_text_lineEdit', text)

        note = str(self.find_annotations_note_lineEdit.text())
        self.prefs.set('find_annotations_note_lineEdit', note)

        if False:
            from_date = self.find_annotations_date_from_dateEdit.current_val
            self.prefs.set('find_annotations_date_from_dateEdit', from_date)

            to_date = self.find_annotations_date_to_dateEdit.current_val
            self.prefs.set('find_annotations_date_to_dateEdit', to_date)

    def start_inventory_scan(self):
        self._log_location()
        self.annotated_books_scanner.start()

    def update_results(self, trigger):
        #self._log_location(trigger)
        reader_to_match = str(self.find_annotations_reader_comboBox.currentText())
        color_to_match = str(self.find_annotations_color_comboBox.currentText())
        text_to_match = str(self.find_annotations_text_lineEdit.text())
        note_to_match = str(self.find_annotations_note_lineEdit.text())

        from_date = self.find_annotations_date_from_dateEdit.dateTime().toTime_t()
        to_date = self.find_annotations_date_to_dateEdit.dateTime().toTime_t()

        annotation_map = self.annotated_books_scanner.annotation_map
        #field = self.prefs.get("cfg_annotations_destination_field", None)
        field = get_cc_mapping('annotations', 'field', None)

        db = self.opts.gui.current_db
        matched_titles = []
        self.matched_ids = set()

        for cid in annotation_map:
            mi = db.get_metadata(cid, index_is_id=True)
            soup = None
            if field == 'Comments':
                if mi.comments:
                    soup = BeautifulSoup(mi.comments)
            else:
                if mi.get_user_metadata(field, False)['#value#'] is not None:
                    soup = BeautifulSoup(mi.get_user_metadata(field, False)['#value#'])
            if soup:
                uas = soup.findAll('div', 'annotation')
                for ua in uas:
                    # Are we already logged?
                    if cid in self.matched_ids:
                        continue

                    # Check reader
                    if reader_to_match != self.GENERIC_READER:
                        this_reader = ua['reader']
                        if this_reader != reader_to_match:
                            continue

                    # Check color
                    if color_to_match != self.GENERIC_STYLE:
                        this_color = ua.find('table')['color']
                        if this_color != color_to_match:
                            continue

                    # Check date range, allow for mangled timestamp
                    try:
                        timestamp = float(ua.find('td', 'timestamp')['uts'])
                        if timestamp < from_date or timestamp > to_date:
                            continue
                    except:
                        continue

                    highlight_text = ''
                    try:
                        pels = ua.findAll('p', 'highlight')
                        highlight_text = '\n'.join([p.string for p in pels])
                    except:
                        pass
                    if text_to_match > '':
                        if not re.search(text_to_match, highlight_text, flags=re.IGNORECASE):
                            continue

                    note_text = ''
                    try:
                        nels = ua.findAll('p', 'note')
                        note_text = '\n'.join([n.string for n in nels])
                    except:
                        pass
                    if note_to_match > '':
                        if not re.search(note_to_match, note_text, flags=re.IGNORECASE):
                            continue

                    # If we made it this far, add the id to matched_ids
                    self.matched_ids.add(cid)
                    matched_titles.append(mi.title)

        # Update the results box
        matched_titles.sort()
        if len(annotation_map):
            if len(matched_titles):
                first_match = ("<i>%s</i>" % matched_titles[0])
                if len(matched_titles) == 1:
                    results = first_match
                else:
                    results = first_match + (_(" and {0} more.").format(len(matched_titles) - 1))
                self.result_label.setText('<p style="color:blue">{0}</p>'.format(results))
            else:
                self.result_label.setText('<p style="color:red">{0}</p>'.format(_('no matches')))
        else:
            self.result_label.setText('<p style="color:red">{0}</p>'.format(_('no annotated books in library')))

        self.resize_dialog()
Пример #26
0
class ConvertDialog(QDialog):

    hide_text = _('&Hide styles')
    show_text = _('&Show styles')
    prince_log = ''
    prince_file = ''
    prince_css = ''

    # GUI definition
    def __init__(self, mi, fmt, opf, oeb, icon):
        '''
        :param mi: The book metadata
        :param fmt: The source format used for conversion
        :param opf: The path to the OPF file
        :param oeb: An OEB object for the unpacked book
        :param icon: The window icon
        '''
        self.opf = opf
        self.oeb = oeb
        self.mi = mi
        # The unpacked book needs to be parsed before, to read the contents
        # of the prince-style file, if it exists
        self.parse()

        QDialog.__init__(self)

        self.setAttribute(Qt.WA_DeleteOnClose)

        self.setWindowTitle(_('Convert to PDF with Prince'))
        self.setWindowIcon(icon)

        self.l = QVBoxLayout()
        self.setLayout(self.l)

        self.title_label = QLabel(_('<b>Title:</b> %s') % self.mi.title)
        self.l.addWidget(self.title_label)

        self.format_label = QLabel(_('<b>Source format:</b> %s') % fmt)
        self.l.addWidget(self.format_label)

        self.add_book = QCheckBox(_('&Add PDF to the book record'))
        self.add_book.setToolTip(
            _('<qt>Add the converted PDF to the selected book record</qt>'))
        self.add_book.setChecked(prefs['add_book'])
        self.add_book.stateChanged.connect(self.set_add_book)
        self.l.addWidget(self.add_book)

        self.ll = QHBoxLayout()
        self.ll.setAlignment(Qt.AlignLeft)
        self.l.addLayout(self.ll)

        self.label_css = QLabel(_('&Custom style:'))
        self.ll.addWidget(self.label_css)

        self.css_list = QComboBox()
        self.css_list.setToolTip(
            _('<qt>Select one style to use. Additional styles can be created in the plugin configuration</qt>'
              ))
        for key in sorted(prefs['custom_CSS_list'], key=lambda x: x.lower()):
            self.css_list.addItem(key, key)
        self.css_list.setCurrentIndex(
            self.css_list.findText(prefs['default_CSS']))
        self.css_list.currentIndexChanged.connect(self.set_css)
        self.ll.addWidget(self.css_list)
        self.label_css.setBuddy(self.css_list)

        self.ll_ = QHBoxLayout()
        self.l.addLayout(self.ll_)

        self.label_args = QLabel(_('A&dditional command-line arguments:'))
        self.ll_.addWidget(self.label_args)

        self.args = QLineEdit(self)
        self.args.setText(prefs['custom_args_list'][prefs['default_CSS']])
        self.args.setToolTip(
            _('<qt>Specify additional command-line arguments for the conversion</qt>'
              ))
        self.ll_.addWidget(self.args)
        self.label_args.setBuddy(self.args)

        self.css = QTabWidget()
        self.l.addWidget(self.css)

        self.css1 = TextEditWithTooltip(self, expected_geometry=(80, 20))
        self.css1.setLineWrapMode(TextEditWithTooltip.NoWrap)
        self.css1.load_text(
            self.replace_templates(
                prefs['custom_CSS_list'][prefs['default_CSS']]), 'css')
        self.css1.setToolTip(
            _('<qt>This stylesheet can be modified<br/>The default can be configured</qt>'
              ))
        i = self.css.addTab(self.css1, _('C&ustom CSS'))
        self.css.setTabToolTip(
            i,
            _('<qt>Custom CSS stylesheet to be used for this conversion</qt>'))

        monofont = QFont('')
        monofont.setStyleHint(QFont.TypeWriter)

        if (self.prince_css):
            self.css2 = QPlainTextEdit()
            self.css2.setStyleSheet('* { font-family: monospace }')
            self.css2.setLineWrapMode(QPlainTextEdit.NoWrap)
            self.css2.setPlainText(self.prince_css)
            self.css2.setReadOnly(True)
            self.css2.setToolTip(
                _('<qt>This stylesheet cannot be modified</qt>'))
            i = self.css.addTab(self.css2, _('&Book CSS'))
            self.css.setTabToolTip(
                i,
                _('<qt>Book-specific CSS stylesheet included in the ebook file</qt>'
                  ))

        self.ll = QHBoxLayout()
        self.l.addLayout(self.ll)

        if (prefs['show_CSS']):
            self.toggle = QPushButton(self.hide_text, self)
        else:
            self.toggle = QPushButton(self.show_text, self)
        self.toggle.setToolTip(
            _('<qt>Show/hide the additional styles used for the conversion</qt>'
              ))
        self.toggle.clicked.connect(self.toggle_tabs)

        self.convert = QPushButton(_('Con&vert'), self)
        self.convert.setToolTip(_('<qt>Run the conversion with Prince</qt>'))
        self.convert.setDefault(True)

        self.buttons = QDialogButtonBox(QDialogButtonBox.Cancel)
        self.buttons.addButton(self.toggle, QDialogButtonBox.ResetRole)
        self.buttons.addButton(self.convert, QDialogButtonBox.AcceptRole)
        self.l.addWidget(self.buttons)
        self.buttons.accepted.connect(self.prince_convert)
        self.buttons.rejected.connect(self.reject)

        if (not prefs['show_CSS']):
            self.css.hide()
        self.adjustSize()

    def toggle_tabs(self):
        '''
        Enable/disable the CSS tabs, and store the setting
        '''
        if (self.css.isVisible()):
            self.css.hide()
            self.label_args.hide()
            self.args.hide()
            self.toggle.setText(self.show_text)
            self.adjustSize()
        else:
            self.css.show()
            self.label_args.show()
            self.args.show()
            self.toggle.setText(self.hide_text)
            self.adjustSize()
        prefs['show_CSS'] = self.css.isVisible()

    def set_add_book(self):
        '''
        Save the status of the add_book checkbox
        '''
        prefs['add_book'] = self.add_book.isChecked()

    def set_css(self):
        '''
        Fill the custom CSS text box with the selected stylesheet (and command-line arguments)
        '''
        style = unicode(self.css_list.currentText())
        self.css1.load_text(
            self.replace_templates(prefs['custom_CSS_list'][style]), 'css')
        self.args.setText(prefs['custom_args_list'][style])
        prefs['default_CSS'] = style

    def parse(self):
        '''
        Parse the unpacked OPF file to find and read the prince-style file
        '''
        from calibre.constants import DEBUG
        from os.path import dirname, join
        from lxml import etree
        import codecs

        if DEBUG: print(_('Parsing book...'))
        opf_dir = dirname(self.opf)
        root = etree.parse(self.opf).getroot()
        metadata = root.find('{*}metadata')
        for meta in metadata.findall("{*}meta[@name='prince-style']"):
            prince_id = meta.get('content')
            for item in self.oeb.manifest:
                if (item.id == prince_id):
                    self.prince_file = item.href
                    break
        if (self.prince_file):
            fl = codecs.open(join(opf_dir, self.prince_file), 'rb', 'utf-8')
            self.prince_css = fl.read()
            fl.close()

    def replace_templates(self, text):
        '''
        Replace templates (enclosed by '@{@', '@}@') in the input text
        '''
        import re
        import json
        from calibre.ebooks.metadata.book.formatter import SafeFormat
        from calibre.constants import DEBUG

        matches = list(re.finditer('@{@(.+?)@}@', text, re.DOTALL))
        results = {}
        for match in reversed(matches):
            result = SafeFormat().safe_format(match.group(1), self.mi,
                                              ('EXCEPTION: '), self.mi)
            # Escape quotes, backslashes and newlines
            result = re.sub(r'''['"\\]''', r'\\\g<0>', result)
            result = re.sub('\n', r'\\A ', result)
            results[match.group(1)] = result
            text = text[:match.start(0)] + result + text[match.end(0):]
        if DEBUG:
            print(_('Replacing templates'))
            for match in matches:
                print(
                    _('Found: %s (%d-%d)') %
                    (match.group(1), match.start(0), match.end(0)))
                print(_('Replace with: %s') % results[match.group(1)])
        return text

    def prince_convert(self):
        '''
        Call the actual Prince command to convert to PDF
        '''
        from os import makedirs
        from os.path import dirname, join, exists
        from calibre.ptempfile import PersistentTemporaryFile
        from calibre.constants import DEBUG
        from shlex import split as shsplit

        # All files are relative to the OPF location
        opf_dir = dirname(self.opf)
        base_dir = dirname(self.pdf_file)
        base_dir = join(opf_dir, base_dir)
        try:
            makedirs(base_dir)
        except BaseException:
            if not exists(base_dir): raise

        # Create a temporary CSS file with the box contents
        custom_CSS = PersistentTemporaryFile(mode='w+')
        custom_CSS.write(unicode(self.css1.toPlainText()))
        custom_CSS.close()
        # Create a temporary file with the list of input files
        file_list = PersistentTemporaryFile(mode='w+')
        for item in self.oeb.spine:
            file_list.write(item.href + "\n")
        file_list.close()
        # Build the command line
        command = prefs['prince_exe']
        args = ['-v']
        if self.prince_file:
            args.append('-s')
            args.append(self.prince_file)
        args.append('-s')
        args.append(custom_CSS.name)
        args.append('-l')
        args.append(file_list.name)
        args.append('-o')
        args.append(self.pdf_file)
        # Additional command-line arguments
        args.extend(shsplit(self.args.text()))

        # Hide the convert button and show a busy indicator
        self.convert.setEnabled(False)
        self.progress_bar = QProgressBar()
        self.progress_bar.setRange(0, 0)
        self.progress_bar.setValue(0)
        self.l.addWidget(self.progress_bar)

        # Run the command and return the path to the PDF file
        if DEBUG: print(_('Converting book...'))
        process = QProcess(self)
        process.setWorkingDirectory(opf_dir)
        process.setProcessChannelMode(QProcess.MergedChannels)
        process.error.connect(self.error)
        process.finished.connect(self.end)
        self.process = process
        if DEBUG:
            from subprocess import list2cmdline
            line = list2cmdline([command] + args)
            print(_('Command line: %s') % line)
        process.start(command, args)

    def error(self, rc):
        '''
        Show a message when there is an error in the command
        :param rc: The error code
        '''
        from calibre.gui2 import error_dialog

        # Remove the progress bar while the error message is displayed
        self.progress_bar.hide()
        self.progress_bar.deleteLater()
        error_dialog(
            self,
            _('Process error'),
            _('<p>Error code: %s'
              '<p>make sure Prince (<a href="http://www.princexml.com">www.princexml.com</a>) is installed '
              'and the correct command-line-interface executable is set in the configuration of this plugin, '
              'which is usually:'
              '<ul><li>In Windows: <code><i>Prince_folder</i>\\Engine\\bin\\prince.exe</code>'
              '    <li>In Linux: <code>prince</code>'
              '</ul>') % rc,
            show=True)
        self.pdf_file = None
        self.accept()

    def end(self, rc):
        '''
        Close and return the filename when the process ends
        :param rc: The return code (0 if successful)
        '''
        from os.path import join

        self.prince_log = unicode(self.process.readAllStandardOutput().data())
        opf_dir = unicode(self.process.workingDirectory())
        if (rc == 0):
            self.pdf_file = join(opf_dir, self.pdf_file)
        else:
            self.pdf_file = None
        self.accept()
Пример #27
0
class ConfigWidget(QWidget):

    def __init__(self, plugin_action):
        QWidget.__init__(self)
        self.plugin_action = plugin_action
        layout = QVBoxLayout(self)
        self.setLayout(layout)

        # --- Directory Options ---
        directory_group_box = QGroupBox(_('Default Unpack Directory:'), self)
        layout.addWidget(directory_group_box)
        directory_group_box_layout = QVBoxLayout()
        directory_group_box.setLayout(directory_group_box_layout)

        # Directory path Textbox
        # Load the textbox with the current preference setting
        self.directory_txtBox = QLineEdit(plugin_prefs['Unpack_Folder'], self)
        self.directory_txtBox.setToolTip(_('<p>Default directory to extract files to'))
        directory_group_box_layout.addWidget(self.directory_txtBox)
        self.directory_txtBox.setReadOnly(True)

        # Folder select button
        directory_button = QPushButton(_('Select/Change Unpack Directory'), self)
        directory_button.setToolTip(_('<p>Select/Change directory to extract files to.'))
        # Connect button to the getDirectory function
        directory_button.clicked.connect(self.getDirectory)
        directory_group_box_layout.addWidget(directory_button)
        self.default_folder_check = QCheckBox(_('Always use the Default Unpack Directory'), self)
        self.default_folder_check.setToolTip(_('<p>When unchecked... you will be prompted to select a destination '+
                                                                                'directory for the extracted content each time you use Mobiunpack.'))
        directory_group_box_layout.addWidget(self.default_folder_check)
        # Load the checkbox with the current preference setting
        self.default_folder_check.setChecked(plugin_prefs['Always_Use_Unpack_Folder'])

        misc_group_box = QGroupBox(_('Default settings:'), self)
        layout.addWidget(misc_group_box)
        misc_group_box_layout = QVBoxLayout()
        misc_group_box.setLayout(misc_group_box_layout)

        self.use_hd_images = QCheckBox(_('Always use HD images if present'), self)
        self.use_hd_images.setToolTip(_('<p>When checked... any HD images present in the kindlebook '+
                                                                                'will be used for creating the ePub.'))
        misc_group_box_layout.addWidget(self.use_hd_images)
        # Load the checkbox with the current preference setting
        self.use_hd_images.setChecked(plugin_prefs['Use_HD_Images'])

        combo_label = QLabel('Select epub version output:', self)
        misc_group_box_layout.addWidget(combo_label)
        self.epub_version_combobox = QComboBox()
        self.epub_version_combobox.setToolTip(_('<p>Select the type of OPF file to create.'))
        misc_group_box_layout.addWidget(self.epub_version_combobox)
        self.epub_version_combobox.addItems(['Auto-detect', 'ePub2', 'ePub3'])
        if plugin_prefs['Epub_Version'] == 'A':
            self.epub_version_combobox.setCurrentIndex(0)
        else:
            self.epub_version_combobox.setCurrentIndex(int(plugin_prefs['Epub_Version'])-1)

    def save_settings(self):
        # Save current dialog sttings back to JSON config file
            plugin_prefs['Unpack_Folder'] = unicode(self.directory_txtBox.displayText())
            plugin_prefs['Always_Use_Unpack_Folder'] = self.default_folder_check.isChecked()
            plugin_prefs['Use_HD_Images'] = self.use_hd_images.isChecked()
            if unicode(self.epub_version_combobox.currentText()) == 'Auto-detect':
                plugin_prefs['Epub_Version'] = 'A'
            else:
                plugin_prefs['Epub_Version'] = unicode(self.epub_version_combobox.currentText())[4:]

    def getDirectory(self):
        c = choose_dir(self, _(PLUGIN_NAME + 'dir_chooser'),
                _('Select Default Directory To Unpack Kindle Book/Mobi To'))
        if c:
            self.directory_txtBox.setReadOnly(False)
            self.directory_txtBox.setText(c)
            self.directory_txtBox.setReadOnly(True)

    def validate(self):
        # This is just to catch the situation where somone might
        # manually enter a non-existent path in the Default path textbox.
        # Shouldn't be possible at this point.
        if not os.path.exists(self.directory_txtBox.text()):
            errmsg = '<p>The path specified for the Default Unpack folder does not exist.</p>' \
                        '<p>Your latest preference changes will <b>NOT</b> be saved!</p>' + \
                        '<p>You should configure again and make sure your settings are correct.'
            error_dialog(None, _(PLUGIN_NAME + ' v' + PLUGIN_VERSION),
                                    _(errmsg), show=True)
            return False
        return True
class FindAnnotationsDialog(SizePersistedDialog, Logger):

    GENERIC_STYLE = 'Any style'
    GENERIC_READER = 'Any reader'

    def __init__(self, opts):
        self.matched_ids = set()
        self.opts = opts
        self.prefs = opts.prefs
        super(FindAnnotationsDialog, self).__init__(self.opts.gui, 'find_annotations_dialog')
        self.setWindowTitle('Find Annotations')
        self.setWindowIcon(self.opts.icon)
        self.l = QVBoxLayout(self)
        self.setLayout(self.l)

        self.search_criteria_gb = QGroupBox(self)
        self.search_criteria_gb.setTitle("Search criteria")
        self.scgl = QGridLayout(self.search_criteria_gb)
        self.l.addWidget(self.search_criteria_gb)
        # addWidget(widget, row, col, rowspan, colspan)

        row = 0
        # ~~~~~~~~ Create the Readers comboBox ~~~~~~~~
        self.reader_label = QLabel('Reader')
        self.reader_label.setAlignment(Qt.AlignRight|Qt.AlignVCenter)
        self.scgl.addWidget(self.reader_label, row, 0, 1, 1)

        self.find_annotations_reader_comboBox = QComboBox()
        self.find_annotations_reader_comboBox.setObjectName('find_annotations_reader_comboBox')
        self.find_annotations_reader_comboBox.setToolTip('Reader annotations to search for')

        self.find_annotations_reader_comboBox.addItem(self.GENERIC_READER)
        racs = ReaderApp.get_reader_app_classes()
        for ra in sorted(racs.keys()):
            self.find_annotations_reader_comboBox.addItem(ra)
        self.scgl.addWidget(self.find_annotations_reader_comboBox, row, 1, 1, 4)
        row += 1

        # ~~~~~~~~ Create the Styles comboBox ~~~~~~~~
        self.style_label = QLabel('Style')
        self.style_label.setAlignment(Qt.AlignRight|Qt.AlignVCenter)
        self.scgl.addWidget(self.style_label, row, 0, 1, 1)

        self.find_annotations_color_comboBox = QComboBox()
        self.find_annotations_color_comboBox.setObjectName('find_annotations_color_comboBox')
        self.find_annotations_color_comboBox.setToolTip('Annotation style to search for')

        self.find_annotations_color_comboBox.addItem(self.GENERIC_STYLE)
        all_colors = COLOR_MAP.keys()
        all_colors.remove('Default')
        for color in sorted(all_colors):
            self.find_annotations_color_comboBox.addItem(color)
        self.scgl.addWidget(self.find_annotations_color_comboBox, row, 1, 1, 4)
        row += 1

        # ~~~~~~~~ Create the Text LineEdit control ~~~~~~~~
        self.text_label = QLabel('Text')
        self.text_label.setAlignment(Qt.AlignRight|Qt.AlignVCenter)
        self.scgl.addWidget(self.text_label, row, 0, 1, 1)
        self.find_annotations_text_lineEdit = MyLineEdit()
        self.find_annotations_text_lineEdit.setObjectName('find_annotations_text_lineEdit')
        self.scgl.addWidget(self.find_annotations_text_lineEdit, row, 1, 1, 3)
        self.reset_text_tb = QToolButton()
        self.reset_text_tb.setObjectName('reset_text_tb')
        self.reset_text_tb.setToolTip('Clear search criteria')
        self.reset_text_tb.setIcon(QIcon(I('trash.png')))
        self.reset_text_tb.clicked.connect(self.clear_text_field)
        self.scgl.addWidget(self.reset_text_tb, row, 4, 1, 1)
        row += 1

        # ~~~~~~~~ Create the Note LineEdit control ~~~~~~~~
        self.note_label = QLabel('Note')
        self.note_label.setAlignment(Qt.AlignRight|Qt.AlignVCenter)
        self.scgl.addWidget(self.note_label, row, 0, 1, 1)
        self.find_annotations_note_lineEdit = MyLineEdit()
        self.find_annotations_note_lineEdit.setObjectName('find_annotations_note_lineEdit')
        self.scgl.addWidget(self.find_annotations_note_lineEdit, row, 1, 1, 3)
        self.reset_note_tb = QToolButton()
        self.reset_note_tb.setObjectName('reset_note_tb')
        self.reset_note_tb.setToolTip('Clear search criteria')
        self.reset_note_tb.setIcon(QIcon(I('trash.png')))
        self.reset_note_tb.clicked.connect(self.clear_note_field)
        self.scgl.addWidget(self.reset_note_tb, row, 4, 1, 1)
        row += 1

        # ~~~~~~~~ Create the Date range controls ~~~~~~~~
        self.date_range_label = QLabel('Date range')
        self.date_range_label.setAlignment(Qt.AlignRight|Qt.AlignVCenter)
        self.scgl.addWidget(self.date_range_label, row, 0, 1, 1)

        # Date 'From'
        self.find_annotations_date_from_dateEdit = MyDateEdit(self, datetime(1970,1,1))
        self.find_annotations_date_from_dateEdit.setObjectName('find_annotations_date_from_dateEdit')
        #self.find_annotations_date_from_dateEdit.current_val = datetime(1970,1,1)
        self.find_annotations_date_from_dateEdit.clear_button.clicked.connect(self.find_annotations_date_from_dateEdit.reset_from_date)
        self.scgl.addWidget(self.find_annotations_date_from_dateEdit, row, 1, 1, 1)
        self.scgl.addWidget(self.find_annotations_date_from_dateEdit.clear_button, row, 2, 1, 1)

        # Date 'To'
        self.find_annotations_date_to_dateEdit = MyDateEdit(self, datetime.today())
        self.find_annotations_date_to_dateEdit.setObjectName('find_annotations_date_to_dateEdit')
        #self.find_annotations_date_to_dateEdit.current_val = datetime.today()
        self.find_annotations_date_to_dateEdit.clear_button.clicked.connect(self.find_annotations_date_to_dateEdit.reset_to_date)
        self.scgl.addWidget(self.find_annotations_date_to_dateEdit, row, 3, 1, 1)
        self.scgl.addWidget(self.find_annotations_date_to_dateEdit.clear_button, row, 4, 1, 1)
        row += 1

        # ~~~~~~~~ Create a horizontal line ~~~~~~~~
        self.hl = QFrame(self)
        self.hl.setGeometry(QRect(0, 0, 1, 3))
        self.hl.setFrameShape(QFrame.HLine)
        self.hl.setFrameShadow(QFrame.Raised)
        self.scgl.addWidget(self.hl, row, 0, 1, 5)
        row += 1

        # ~~~~~~~~ Create the results label field ~~~~~~~~
        self.result_label = QLabel('<p style="color:red">scanning…</p>')
        self.result_label.setAlignment(Qt.AlignCenter)
        self.result_label.setWordWrap(False)

        sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Maximum)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.result_label.sizePolicy().hasHeightForWidth())
        self.result_label.setSizePolicy(sizePolicy)
        self.result_label.setMinimumSize(QtCore.QSize(250, 0))
        self.scgl.addWidget(self.result_label, row, 0, 1, 5)
        row += 1

        # ~~~~~~~~ Create the ButtonBox ~~~~~~~~
        self.dialogButtonBox = QDialogButtonBox(self)
        self.dialogButtonBox.setOrientation(Qt.Horizontal)
        if False:
            self.update_button = QPushButton('Update results')
            self.update_button.setDefault(True)
            self.update_button.setVisible(False)
            self.dialogButtonBox.addButton(self.update_button, QDialogButtonBox.ActionRole)

        self.cancel_button = self.dialogButtonBox.addButton(self.dialogButtonBox.Cancel)
        self.find_button = self.dialogButtonBox.addButton(self.dialogButtonBox.Ok)
        self.find_button.setText('Find Matching Books')

        self.l.addWidget(self.dialogButtonBox)
        self.dialogButtonBox.clicked.connect(self.find_annotations_dialog_clicked)

        # ~~~~~~~~ Add a spacer ~~~~~~~~
        self.spacerItem = QtGui.QSpacerItem(20, 40, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
        self.l.addItem(self.spacerItem)

        # ~~~~~~~~ Restore previously saved settings ~~~~~~~~
        self.restore_settings()

        # ~~~~~~~~ Declare sizing ~~~~~~~~
        sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.sizePolicy().hasHeightForWidth())
        self.setSizePolicy(sizePolicy)
        self.resize_dialog()

        # ~~~~~~~~ Connect all signals ~~~~~~~~
        self.find_annotations_reader_comboBox.currentIndexChanged.connect(partial(self.update_results, 'reader'))
        self.find_annotations_color_comboBox.currentIndexChanged.connect(partial(self.update_results, 'color'))
        self.find_annotations_text_lineEdit.editingFinished.connect(partial(self.update_results, 'text'))
        self.find_annotations_note_lineEdit.editingFinished.connect(partial(self.update_results, 'note'))
#        self.connect(self.find_annotations_text_lineEdit, pyqtSignal("return_pressed"), self.return_pressed)
        self.find_annotations_text_lineEdit.return_pressed.connect(self.return_pressed)
#        self.connect(self.find_annotations_note_lineEdit, pyqtSignal("return_pressed"), self.return_pressed)
        self.find_annotations_note_lineEdit.return_pressed.connect(self.return_pressed)

        # Date range signals connected in inventory_available()

        # ~~~~~~~~ Allow dialog to render before doing inventory ~~~~~~~~
        #field = self.prefs.get('cfg_annotations_destination_field', None)
        field = get_cc_mapping('annotations', 'field', None)
        self.annotated_books_scanner = InventoryAnnotatedBooks(self.opts.gui, field, get_date_range=True)
        self.annotated_books_scanner.signal.connect(self.inventory_available)
        QTimer.singleShot(1, self.start_inventory_scan)

    def clear_note_field(self):
        if str(self.find_annotations_note_lineEdit.text()) > '':
            self.find_annotations_note_lineEdit.setText('')
            self.update_results('clear_note_field')

    def clear_text_field(self):
        if str(self.find_annotations_text_lineEdit.text()) > '':
            self.find_annotations_text_lineEdit.setText('')
            self.update_results('clear_text_field')

    def find_annotations_dialog_clicked(self, button):
        if self.dialogButtonBox.buttonRole(button) == QDialogButtonBox.AcceptRole:
            self.save_settings()
            self.accept()
        elif self.dialogButtonBox.buttonRole(button) == QDialogButtonBox.RejectRole:
            self.close()

    def inventory_available(self):
        '''
        Update the Date range widgets with the rounded oldest, newest dates
        Don't connect date signals until date range available
        '''
        self._log_location()

        # Reset the date range based on available annotations
        oldest = QDateTime(datetime.fromtimestamp(self.annotated_books_scanner.oldest_annotation))
        oldest_day = QDateTime(datetime.fromtimestamp(self.annotated_books_scanner.oldest_annotation).replace(hour=0, minute=0, second=0))
        newest = QDateTime(datetime.fromtimestamp(self.annotated_books_scanner.newest_annotation))
        newest_day = QDateTime(datetime.fromtimestamp(self.annotated_books_scanner.newest_annotation).replace(hour=23, minute=59, second=59))

        # Set 'From' date limits to inventory values
        self.find_annotations_date_from_dateEdit.setMinimumDateTime(oldest_day)
        self.find_annotations_date_from_dateEdit.current_val = oldest
        self.find_annotations_date_from_dateEdit.setMaximumDateTime(newest_day)

        # Set 'To' date limits to inventory values
        self.find_annotations_date_to_dateEdit.setMinimumDateTime(oldest_day)
        self.find_annotations_date_to_dateEdit.current_val = newest_day
        self.find_annotations_date_to_dateEdit.setMaximumDateTime(newest_day)

        # Connect the signals for date range changes
        self.find_annotations_date_from_dateEdit.dateTimeChanged.connect(partial(self.update_results, 'from_date'))
        self.find_annotations_date_to_dateEdit.dateTimeChanged.connect(partial(self.update_results, 'to_date'))

        self.update_results('inventory_available')

    def restore_settings(self):
        self.blockSignals(True)

        ra = self.prefs.get('find_annotations_reader_comboBox', self.GENERIC_READER)
        ra_index = self.find_annotations_reader_comboBox.findText(ra)
        self.find_annotations_reader_comboBox.setCurrentIndex(ra_index)

        color = self.prefs.get('find_annotations_color_comboBox', self.GENERIC_STYLE)
        color_index = self.find_annotations_color_comboBox.findText(color)
        self.find_annotations_color_comboBox.setCurrentIndex(color_index)

        text = self.prefs.get('find_annotations_text_lineEdit', '')
        self.find_annotations_text_lineEdit.setText(text)

        note = self.prefs.get('find_annotations_note_lineEdit', '')
        self.find_annotations_note_lineEdit.setText(note)

        if False:
            from_date = self.prefs.get('find_annotations_date_from_dateEdit', datetime(1970,1,1))
            self.find_annotations_date_from_dateEdit.current_val = from_date
            to_date = self.prefs.get('find_annotations_date_to_dateEdit', datetime.today())
            self.find_annotations_date_to_dateEdit.current_val = to_date

        self.blockSignals(False)

    def return_pressed(self):
        self.update_results("return_pressed")

    def save_settings(self):
        ra = str(self.find_annotations_reader_comboBox.currentText())
        self.prefs.set('find_annotations_reader_comboBox', ra)

        color = str(self.find_annotations_color_comboBox.currentText())
        self.prefs.set('find_annotations_color_comboBox', color)

        text = str(self.find_annotations_text_lineEdit.text())
        self.prefs.set('find_annotations_text_lineEdit', text)

        note = str(self.find_annotations_note_lineEdit.text())
        self.prefs.set('find_annotations_note_lineEdit', note)

        if False:
            from_date = self.find_annotations_date_from_dateEdit.current_val
            self.prefs.set('find_annotations_date_from_dateEdit', from_date)

            to_date = self.find_annotations_date_to_dateEdit.current_val
            self.prefs.set('find_annotations_date_to_dateEdit', to_date)

    def start_inventory_scan(self):
        self._log_location()
        self.annotated_books_scanner.start()

    def update_results(self, trigger):
        #self._log_location(trigger)
        reader_to_match = str(self.find_annotations_reader_comboBox.currentText())
        color_to_match = str(self.find_annotations_color_comboBox.currentText())
        text_to_match = str(self.find_annotations_text_lineEdit.text())
        note_to_match = str(self.find_annotations_note_lineEdit.text())

        from_date = self.find_annotations_date_from_dateEdit.dateTime().toTime_t()
        to_date = self.find_annotations_date_to_dateEdit.dateTime().toTime_t()

        annotation_map = self.annotated_books_scanner.annotation_map
        #field = self.prefs.get("cfg_annotations_destination_field", None)
        field = get_cc_mapping('annotations', 'field', None)

        db = self.opts.gui.current_db
        matched_titles = []
        self.matched_ids = set()

        for cid in annotation_map:
            mi = db.get_metadata(cid, index_is_id=True)
            soup = None
            if field == 'Comments':
                if mi.comments:
                    soup = BeautifulSoup(mi.comments)
            else:
                if mi.get_user_metadata(field, False)['#value#'] is not None:
                    soup = BeautifulSoup(mi.get_user_metadata(field, False)['#value#'])
            if soup:
                uas = soup.findAll('div', 'annotation')
                for ua in uas:
                    # Are we already logged?
                    if cid in self.matched_ids:
                        continue

                    # Check reader
                    if reader_to_match != self.GENERIC_READER:
                        this_reader = ua['reader']
                        if this_reader != reader_to_match:
                            continue

                    # Check color
                    if color_to_match != self.GENERIC_STYLE:
                        this_color = ua.find('table')['color']
                        if this_color != color_to_match:
                            continue

                    # Check date range, allow for mangled timestamp
                    try:
                        timestamp = float(ua.find('td', 'timestamp')['uts'])
                        if timestamp < from_date or timestamp > to_date:
                            continue
                    except:
                        continue

                    highlight_text = ''
                    try:
                        pels = ua.findAll('p', 'highlight')
                        for pel in pels:
                            highlight_text += pel.string + '\n'
                    except:
                        pass
                    if text_to_match > '':
                        if not re.search(text_to_match, highlight_text, flags=re.IGNORECASE):
                            continue

                    note_text = ''
                    try:
                        nels = ua.findAll('p', 'note')
                        for nel in nels:
                            note_text += nel.string + '\n'
                    except:
                        pass
                    if note_to_match > '':
                        if not re.search(note_to_match, note_text, flags=re.IGNORECASE):
                            continue

                    # If we made it this far, add the id to matched_ids
                    self.matched_ids.add(cid)
                    matched_titles.append(mi.title)

        # Update the results box
        matched_titles.sort()
        if len(annotation_map):
            if len(matched_titles):
                first_match = ("<i>%s</i>" % matched_titles[0])
                if len(matched_titles) == 1:
                    results = first_match
                else:
                    results = first_match + (" and %d more." % (len(matched_titles) - 1))
                self.result_label.setText('<p style="color:blue">{0}</p>'.format(results))
            else:
                self.result_label.setText('<p style="color:red">no matches</p>')
        else:
            self.result_label.setText('<p style="color:red">no annotated books in library</p>')

        self.resize_dialog()
Пример #29
0
class ConfigWidget(QWidget):
    def __init__(self):
        QWidget.__init__(self)
        self.l = QVBoxLayout()
        self.l.setContentsMargins(0, 0, 0, 0)
        self.setLayout(self.l)

        self.help_msg = QLabel('''
            <h2 style="text-align: center">Get Started</h2>
            <p>
                To start syncing your library you will need to create an account to retrieve<br>
                your API key.
            </p>
        ''')
        self.l.addWidget(self.help_msg)

        self.form = QFormLayout()
        self.form.setFieldGrowthPolicy(QFormLayout.ExpandingFieldsGrow)
        self.l.addLayout(self.form)

        self.link = QLabel('<a href="{0}">{0}</a>'.format(prefs['api_base'] + '/api-key'))
        self.link.setOpenExternalLinks(True)
        self.form.addRow('Visit:', self.link)

        self.api_key = QLineEdit(self)
        self.api_key.setText(prefs['api_key'])
        self.form.addRow('API Key:', self.api_key)

        self.debug = QCheckBox(self)
        self.debug.setChecked(prefs['debug'])
        self.form.addRow('Debug Logging:', self.debug)

        self.update_metadata_layout = QHBoxLayout()
        self.update_metadata_layout.setContentsMargins(0, 0, 0, 0)

        self.update_metadata = QCheckBox(self)
        self.update_metadata.setChecked(prefs['update_metadata'])
        self.update_metadata_layout.addWidget(self.update_metadata)

        self.update_metadata_hint = QLabel('(sync all metadata changes made)')
        self.update_metadata_layout.addWidget(self.update_metadata_hint)

        self.form.addRow('Update Metadata:', self.update_metadata_layout)

        self.threads = QComboBox(self)
        for n in range(3):
            self.threads.addItem(str(pow(2, n)))
        self.threads.setCurrentText(str(prefs['threads']))
        self.form.addRow('Sync Threads:', self.threads)

        self.bookshelves_custom_column = QComboBox(self)
        self.bookshelves_custom_column.addItem('')
        for key, meta in get_current_db().new_api.field_metadata.custom_iteritems():
            if meta['datatype'] == 'text':
                self.bookshelves_custom_column.addItem(key)
        self.bookshelves_custom_column.setCurrentText(prefs['bookshelves_custom_column'])
        self.form.addRow('Bookshelves Column:', self.bookshelves_custom_column)

    def save_settings(self):
        prefs['api_key'] = unicode(self.api_key.text())
        prefs['debug'] = self.debug.isChecked()
        prefs['update_metadata'] = self.update_metadata.isChecked()
        prefs['threads'] = int(self.threads.currentText())
        prefs['bookshelves_custom_column'] = unicode(self.bookshelves_custom_column.currentText())
Пример #30
0
class HistoChart (QGraphicsView):
    def __init__(self,parent,size=QSize(400,400)):
        super(HistoChart,self).__init__(parent)
        self.my_scene = QGraphicsScene()
        #self.my_scene.setSceneRect(self.sceneRect())
        self.setScene(self.my_scene)
        #self.setBackgroundBrush(QBrush(QtCore.Qt.black, QtCore.Qt.SolidPattern));
        
        
        self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        self.size_text_width = -1
        self.margin = {'top':10,'bottom':10,'left':10,'right':10}        
        self.initialize = False
        self.max_length_text = 10
        self.histo_items = []


    
    def setData (self,list_items):
        size = min(self.frameSize().width()/2.0,self.frameSize().height())
        self.size_pie = QSize(size,size)
        self.data = list_items
        if self.initialize == False:
            self.c_box = QComboBox()
            self.c_box.addItem("warrior")
            self.c_box.addItem("rank")
            self.c_box.addItem("power")
            self.c_box.currentIndexChanged.connect(self.update)
            proxy = self.my_scene.addWidget(self.c_box)
            
            proxy.setPos(QPointF(0.0,self.margin['top']))    
            self.margin['top']+=self.c_box.height()
            self.initialize = True
        self.update()

    def update(self):

        
        i = 0
        #self.my_scene.clear()
        for item in self.histo_items :
            self.my_scene.removeItem(item)
        self.histo_items = []
        self.scene().setSceneRect(QRectF(0,0,self.frameSize().width(),self.frameSize().height()))

        max = -1
        for value in self.data.values():
            if value[self.c_box.currentText()] > max:
                max = value[self.c_box.currentText()]


        size_text_number = QGraphicsTextItem(str(max)).boundingRect().width()+10
        interval= self.parent().size().height()-self.margin['top']-self.margin['bottom']
        interval = interval /len(self.data)
        

        temp = "aaaaaaaaaaaaaaaaa"
        if len(temp)> self.max_length_text:
            data = temp[:self.max_length_text]+"."
        else :
            data = temp
        self.size_text_width = QGraphicsTextItem(data).boundingRect().width()+10
        #print ('width:',self.size_text_width)
        horizontal_size = self.parent().size().width()- self.margin['left']- self.margin['right']-self.size_text_width- size_text_number
        try:
            ratio = horizontal_size/ max
        except ZeroDivisionError :
            ratio = 0


        i = 0
        for groupe,value in zip(self.data.keys(),self.data.values()) :

            if self.c_box.currentText() == "warrior":
                title_str = 'Nombre de Heros'
                bar_all = QGraphicsRectItem(0,self.margin['top'],value['warrior']*ratio,interval*0.8)
                bar_all.setPos(self.size_text_width,interval*0.2+(i*interval))
                gradient = QLinearGradient(QPointF(bar_all.rect().width()/2,0),QPointF(bar_all.rect().width()/2,bar_all.rect().height()+self.margin['top']))
                gradient.setColorAt(0,QColor('white'))
                gradient.setColorAt(1,QColor('red'))
                brush = QBrush(gradient)            
                #brush.setTexture(QPixmap(":/textures/"+groupe.attribs['color']))
                bar_all.setBrush(brush)
                self.my_scene.addItem(bar_all)
                self.histo_items.append(bar_all)
    
                bar_alive = QGraphicsRectItem(0,self.margin['top'],value['alive']*ratio,interval*0.8)
                bar_alive.setPos(self.size_text_width,interval*0.2+(i*interval))
                gradient = QLinearGradient(QPointF(bar_alive.rect().width()/2,0),QPointF(bar_alive.rect().width()/2,bar_alive.rect().height()+self.margin['top']))
    #             gradient.setStart(QPointF(0.5,0))
    #             gradient.setStop(QPointF(0.5,1))
                gradient.setColorAt(0,QColor('white'))
                gradient.setColorAt(1,QColor('green'))
                brush = QBrush(gradient)
                
                bar_alive.setBrush(brush)
                self.my_scene.addItem(bar_alive)
                self.histo_items.append(bar_alive)
    
                text_nb_warriors= QGraphicsTextItem(str(value['warrior']))
                text_nb_warriors.setDefaultTextColor(QColor('green'))
                trans = QTransform().translate(bar_all.pos().x()+bar_all.rect().width()+10,interval*0.2+(i*interval)+self.margin['top'])
                pts = trans.map(QPointF(0,0.0))
                text_nb_warriors.setPos(pts.x(),pts.y())
                self.my_scene.addItem(text_nb_warriors)            
                self.histo_items.append(text_nb_warriors)


            #bar ranl
            elif self.c_box.currentText()== "rank":
                title_str = "Rank Moyen"
                bar_rank = QGraphicsRectItem(0,self.margin['top'],value['rank']*ratio,interval*0.8)
                bar_rank.setPos(self.size_text_width, interval*0.2+(i*interval))
                gradient = QLinearGradient(QPointF(bar_rank.rect().width()/2,0),QPointF(bar_rank.rect().width()/2,bar_rank.rect().height()+self.margin['top']))
    #             gradient.setStart(QPointF(0.5,0))
    #             gradient.setStop(QPointF(0.5,1))
                gradient.setColorAt(0,QColor('white'))
                gradient.setColorAt(1,QColor('red'))
                brush = QBrush(gradient)
                
                bar_rank.setBrush(brush)
                self.my_scene.addItem(bar_rank)
                self.histo_items.append(bar_rank)
    
                # value
                text_rank = QGraphicsTextItem("{0:1.1f}".format(value['rank']))
                text_rank.setDefaultTextColor(QColor('red'))
                trans = QTransform().translate(bar_rank.pos().x()+bar_rank.rect().width()+10,interval*0.2+(i*interval)+self.margin['top'])
                pts = trans.map(QPointF(0,0.0))
                text_rank.setPos(pts.x(),pts.y())
                self.my_scene.addItem(text_rank)            
                self.histo_items.append(text_rank)
    

            else:
                title_str = "Puissance"
                bar_rank = QGraphicsRectItem(0,self.margin['top'],value['power']*ratio,interval*0.8)
                bar_rank.setPos(self.size_text_width, interval*0.2+(i*interval))
                gradient = QLinearGradient(QPointF(bar_rank.rect().width()/2,0),QPointF(bar_rank.rect().width()/2,bar_rank.rect().height()+self.margin['top']))
    #             gradient.setStart(QPointF(0.5,0))
    #             gradient.setStop(QPointF(0.5,1))
                gradient.setColorAt(0,QColor('white'))
                gradient.setColorAt(1,QColor('blue'))
                brush = QBrush(gradient)
                
                bar_rank.setBrush(brush)
                self.my_scene.addItem(bar_rank)
                self.histo_items.append(bar_rank)
    
                # value
                try :
                    valeur = (value['power']/max)*100
                except ZeroDivisionError :
                    valeur = 0
                text_rank = QGraphicsTextItem("{0:1.1f}".format(valeur))
                text_rank.setDefaultTextColor(QColor('blue'))
                trans = QTransform().translate(bar_rank.pos().x()+bar_rank.rect().width()+10,interval*0.2+(i*interval)+self.margin['top'])
                pts = trans.map(QPointF(0,0.0))
                text_rank.setPos(pts.x(),pts.y())
                self.my_scene.addItem(text_rank)            
                self.histo_items.append(text_rank)


            #dessin du titre
            title = QGraphicsTextItem(title_str)
            title.setPos(self.margin['left']+self.size_text_width+horizontal_size/2.0,self.c_box.pos().y())
            self.my_scene.addItem(title)
            self.histo_items.append(title)
    
            #affichage des label colonne de gauche
            if len(groupe.name)> self.max_length_text:
                data = groupe.name[:self.max_length_text]+".."
            else :
                data = groupe.name
            text = QGraphicsTextItem(data)
            #text.setTextWidth(20)

            trans = QTransform().translate(self.margin['left'],interval*0.2+(i*interval)+self.margin['top'])
            pts = trans.map(QPointF(0,0.0))
            text.setPos(pts.x(),pts.y())
            self.my_scene.addItem(text)            
            self.histo_items.append(text)

            i +=1

     #   self.fitInView(self.scene.sceneRect())
Пример #31
0
 def currentText(self):
     if self.currentIndex() == 0:
         return ''
     return QComboBox.currentText(self)
Пример #32
0
class UArmUI(object):
    def __init__(self, ui, layout):
        self.main_ui = ui
        self.layout = layout
        super(UArmUI, self).__init__()
        self.handler = UArmHandler(self)
        self.lang = self.main_ui.lang
        self.status = 0
        self.set_ui()
        self.set_disable(True)

    def set_ui(self):
        self._set_common_top_ui()
        self._set_tab()
        self._set_common_down_ui()
        self.connect_slot()

    def _set_common_top_ui(self):
        top_frame = QFrame()
        top_frame.setMaximumHeight(60)
        top_layout = QVBoxLayout(top_frame)
        self.layout.addWidget(top_frame)

        common_top_frame = QFrame()
        common_top_frame.setMinimumHeight(50)
        common_top_frame.setMaximumHeight(50)
        common_top_layout = QHBoxLayout(common_top_frame)
        top_layout.addWidget(common_top_frame)
        common_top_layout.addStretch(0)

        label = QLabel(i18n[self.lang]['Type'] + ':')
        self.label_type = QLabel('')
        self.label_type.setStyleSheet('''color: gray;font:bold;''')
        common_top_layout.addWidget(label)
        common_top_layout.addWidget(self.label_type)

        label = QLabel(i18n[self.lang]['Mode'] + ':')
        self.label_mode = QLabel('')
        self.label_mode.setStyleSheet('''color: gray;font:bold;''')
        common_top_layout.addWidget(label)
        common_top_layout.addWidget(self.label_mode)

        label = QLabel(i18n[self.lang]['HardwareVersion'] + ':')
        self.label_hard_version = QLabel('')
        self.label_hard_version.setStyleSheet('''color: gray;font:bold;''')
        common_top_layout.addWidget(label)
        common_top_layout.addWidget(self.label_hard_version)

        label = QLabel(i18n[self.lang]['FirmwareVersion'] + ':')
        self.label_firm_version = QLabel('')
        self.label_firm_version.setStyleSheet('''color: gray;font:bold;''')
        common_top_layout.addWidget(label)
        common_top_layout.addWidget(self.label_firm_version)

        label_1 = QLabel(i18n[self.lang]['Connected'] + ':')
        self.label_connected = QLabel()
        img = QImage()
        self.label_connected.setMaximumHeight(20)
        self.label_connected.setMaximumWidth(20)
        self.label_connected.setScaledContents(True)
        if img.load(disconnect_icon_path):
            self.label_connected.setPixmap(QPixmap.fromImage(img))

        self.lnt_addr = QLineEdit('COM12')
        self.lnt_addr.setMaximumWidth(50)
        self.lnt_addr.setMinimumWidth(30)
        self.btn_connect = QPushButton(i18n[self.lang]['Connect'])
        # self.btn_connect.setMaximumWidth(50)

        # common_top_layout.addStretch(0)
        common_top_layout.setSpacing(10)
        common_top_layout.addWidget(label_1)
        common_top_layout.addWidget(self.label_connected)
        common_top_layout.addWidget(self.lnt_addr)
        common_top_layout.addWidget(self.btn_connect)

    def _set_common_down_ui(self):
        slider_frame = QFrame()
        slider_layout = QGridLayout(slider_frame)
        self.layout.addWidget(slider_frame)

        label = QLabel(i18n[self.lang]['Speed'] + ':')
        self.slider_speed = QSlider(Qt.Horizontal)
        self.spinbox_speed = QSpinBox()
        self.slider_speed.setMinimum(1)
        self.slider_speed.setMaximum(100000)
        self.slider_speed.setValue(10000)
        self.spinbox_speed.setSingleStep(1)
        self.spinbox_speed.setMinimum(1)
        self.spinbox_speed.setMaximum(100000)
        self.spinbox_speed.setValue(10000)
        slider_layout.addWidget(label, 0, 0)
        slider_layout.addWidget(self.slider_speed, 0, 1)
        slider_layout.addWidget(self.spinbox_speed, 0, 2)

        # label = QLabel(i18n[self.lang]['Acc'] + ':')
        # self.slider_acc = QSlider(Qt.Horizontal)
        # self.spinbox_acc = QSpinBox()
        # self.slider_acc.setMinimum(1)
        # self.slider_acc.setMaximum(100000)
        # self.slider_acc.setValue(5000)
        # self.spinbox_acc.setSingleStep(1)
        # self.spinbox_acc.setMinimum(1)
        # self.spinbox_acc.setMaximum(100000)
        # self.spinbox_acc.setValue(5000)
        # slider_layout.addWidget(label, 0, 3)
        # slider_layout.addWidget(self.slider_acc, 0, 4)
        # slider_layout.addWidget(self.spinbox_acc, 0, 5)

        btn_frame = QFrame()
        btn_layout = QGridLayout(btn_frame)
        self.layout.addWidget(btn_frame)

        self.btn_get_device_info = QPushButton(i18n[self.lang]['GetDeviceInfo'])
        self.btn_get_position = QPushButton(i18n[self.lang]['GetPosition'])
        self.btn_get_polar = QPushButton(i18n[self.lang]['GetPolar'])
        self.btn_get_servo_angle = QPushButton(i18n[self.lang]['GetServoAngle'])
        self.btn_get_mode = QPushButton(i18n[self.lang]['GetMode'])

        btn_layout.addWidget(self.btn_get_device_info, 0, 0)
        btn_layout.addWidget(self.btn_get_position, 0, 1)
        btn_layout.addWidget(self.btn_get_polar, 0, 2)
        btn_layout.addWidget(self.btn_get_servo_angle, 0, 3)
        btn_layout.addWidget(self.btn_get_mode, 0, 4)

        self.combobox_servo = QComboBox()
        self.combobox_servo.setStyleSheet('''color: blue;''')
        for item in ['axis-1', 'axis-2', 'axis-3', 'axis-all']:
            self.combobox_servo.addItem(item)
        self.combobox_servo.setCurrentIndex(0)
        btn_layout.addWidget(self.combobox_servo, 0, 0)

        self.btn_servo_attach = QPushButton(i18n[self.lang]['ServoAttach'])
        self.btn_servo_detach = QPushButton(i18n[self.lang]['ServoDetach'])
        self.combobox_mode = QComboBox()
        self.combobox_mode.setStyleSheet('''color: blue;''')
        for item in ['normal', 'laser', '3D', 'pen']:
            self.combobox_mode.addItem(item)
            self.combobox_mode.setCurrentIndex(0)
        self.btn_set_mode = QPushButton(i18n[self.lang]['SetMode'])

        btn_layout.addWidget(self.combobox_servo, 1, 0)
        btn_layout.addWidget(self.btn_servo_attach, 1, 1)
        btn_layout.addWidget(self.btn_servo_detach, 1, 2)
        btn_layout.addWidget(self.combobox_mode, 1, 3)
        btn_layout.addWidget(self.btn_set_mode, 1, 4)

        self.btn_reset = QPushButton(i18n[self.lang]['Reset'])
        self.btn_pump_on = QPushButton(i18n[self.lang]['PumpOn'])
        self.btn_pump_off = QPushButton(i18n[self.lang]['PumpOff'])
        self.btn_gripper_catch = QPushButton(i18n[self.lang]['GripperCatch'])
        self.btn_gripper_release = QPushButton(i18n[self.lang]['GripperRelease'])
        btn_layout.addWidget(self.btn_reset, 2, 0)
        btn_layout.addWidget(self.btn_pump_on, 2, 1)
        btn_layout.addWidget(self.btn_pump_off, 2, 2)
        btn_layout.addWidget(self.btn_gripper_catch, 2, 3)
        btn_layout.addWidget(self.btn_gripper_release, 2, 4)

    @staticmethod
    def slider_spinbox_related(value, master=None, slave=None, scale=1):
        try:
            slave.setValue(value * scale)
        except Exception as e:
            print(e)

    def connect_slot(self):
        self.btn_connect.clicked.connect(self.connect)
        self.slider_speed.valueChanged.connect(
            functools.partial(self.slider_spinbox_related, slave=self.spinbox_speed, scale=1))
        self.spinbox_speed.valueChanged.connect(
            functools.partial(self.slider_spinbox_related, slave=self.slider_speed, scale=1))
        # self.slider_acc.valueChanged.connect(
        #     functools.partial(self.slider_spinbox_related, slave=self.spinbox_acc, scale=1))
        # self.spinbox_acc.valueChanged.connect(
        #     functools.partial(self.slider_spinbox_related, slave=self.slider_acc, scale=1))
        self.btn_get_device_info.clicked.connect(self.get_device_info)
        self.btn_get_position.clicked.connect(self.get_position)
        self.btn_get_polar.clicked.connect(self.get_polar)
        self.btn_get_servo_angle.clicked.connect(self.get_servo_angle)
        self.btn_get_mode.clicked.connect(self.get_mode)

        self.btn_servo_attach.clicked.connect(self.set_servo_attach)
        self.btn_servo_detach.clicked.connect(self.set_servo_detach)
        self.btn_set_mode.clicked.connect(self.set_mode)

        self.btn_reset.clicked.connect(self.reset)
        self.btn_pump_on.clicked.connect(self.pump_on)
        self.btn_pump_off.clicked.connect(self.pump_off)
        self.btn_gripper_catch.clicked.connect(self.gripper_catch)
        self.btn_gripper_release.clicked.connect(self.gripper_release)

    def reset_flag(self):
        self.cartesian_ui.reset_flag()
        self.axis_ui.reset_flag()

    def update_connect_status(self, item):
        try:
            img = QImage()
            if item and self.status != 1:
                self.status = 1
                logger.info('connect to {} success'.format(self.handler.port))
                if img.load(connect_icon_path):
                    self.label_connected.setPixmap(QPixmap.fromImage(img))
                    self.btn_connect.setText(i18n[self.lang]['Disconnect'])
                    self.btn_connect.setStyleSheet('''color: red;font:bold;''')
                self.set_disable(False)
            elif not item and self.status != 0:
                self.status = 0
                logger.info('disconnect from {0} or failed connect {0}'.format(self.handler.port))
                self.handler.cmd_que.queue.clear()
                if img.load(disconnect_icon_path):
                    self.label_connected.setPixmap(QPixmap.fromImage(img))
                    self.btn_connect.setText(i18n[self.lang]['Connect'])
                    self.btn_connect.setStyleSheet('''color: green;font:bold;''')
                self.set_disable(True)
        except Exception as e:
            print(e)

    def connect(self):
        try:
            if str(self.btn_connect.text()) == i18n[self.lang]['Connect']:
                addr = self.lnt_addr.text().strip()
                if addr == 'auto':
                    addr = None
                self.btn_connect.setText('Connecting')
                self.status = 2
                self.btn_connect.setStyleSheet('''color: orange;font:bold;''')
                self.handler.connect(addr)
                # if self.window.connect(addr, report_type=report_type):
                #     self.btn_connect.setText(self.disconnect_label)
                #     self.btn_connect.setStyleSheet('''color: red;font:bold;''')
            elif str(self.btn_connect.text()) == i18n[self.lang]['Disconnect']:
                self.handler.disconnect()
                self.btn_connect.setText(i18n[self.lang]['Connect'])
                self.btn_connect.setStyleSheet('''color: green;font:bold;''')
        except Exception as e:
            print(e)

    def get_device_info(self, event):
        try:
            item = {
                'cmd': 'get_device_info',
            }
            self.handler.put_cmd_que(item)
        except Exception as e:
            print(e)

    def get_mode(self, event):
        try:
            item = {
                'cmd': 'get_mode',
            }
            self.handler.put_cmd_que(item)
        except Exception as e:
            print(e)

    def get_position(self, event):
        try:
            item = {
                'cmd': 'get_position',
            }
            self.handler.put_cmd_que(item)
        except Exception as e:
            print(e)

    def get_polar(self, event):
        try:
            item = {
                'cmd': 'get_polar',
            }
            self.handler.put_cmd_que(item)
        except Exception as e:
            print(e)

    def get_servo_angle(self, event):
        try:
            item = {
                'cmd': 'get_servo_angle',
            }
            self.handler.put_cmd_que(item)
        except Exception as e:
            print(e)

    def set_servo_attach(self, event):
        try:
            text = self.combobox_servo.currentText()
            if text == 'axis-all':
                servo_id = None
            else:
                servo_id = int(self.combobox_servo.currentIndex())
            item = {
                'cmd': 'set_servo_attach',
                'kwargs': {
                    'servo_id': servo_id
                }
            }
            self.handler.put_cmd_que(item)
        except Exception as e:
            print(e)

    def set_servo_detach(self, event):
        try:
            text = self.combobox_servo.currentText()
            if text == 'axis-all':
                servo_id = None
            else:
                servo_id = int(self.combobox_servo.currentIndex())
            item = {
                'cmd': 'set_servo_detach',
                'kwargs': {
                    'servo_id': servo_id
                }
            }
            self.handler.put_cmd_que(item)
        except Exception as e:
            print(e)

    def pump_on(self, event):
        try:
            item = {
                'cmd': 'set_pump',
                'kwargs': {
                    'on': True
                }
            }
            self.handler.put_cmd_que(item)
        except Exception as e:
            print(e)

    def pump_off(self, event):
        try:
            item = {
                'cmd': 'set_pump',
                'kwargs': {
                    'on': False
                }
            }
            self.handler.put_cmd_que(item)
        except Exception as e:
            print(e)

    def gripper_catch(self, event):
        try:
            item = {
                'cmd': 'set_gripper',
                'kwargs': {
                    'catch': True
                }
            }
            self.handler.put_cmd_que(item)
        except Exception as e:
            print(e)

    def gripper_release(self, event):
        try:
            item = {
                'cmd': 'set_gripper',
                'kwargs': {
                    'catch': False
                }
            }
            self.handler.put_cmd_que(item)
        except Exception as e:
            print(e)

    def set_mode(self, event):
        try:
            mode = self.combobox_mode.currentIndex()
            item = {
                'cmd': 'set_mode',
                'kwargs': {
                    'mode': int(mode)
                }
            }
            self.handler.put_cmd_que(item)
        except Exception as e:
            print(e)

    def reset(self, event):
        try:
            self.handler.cmd_que.queue.clear()
            item = {
                'cmd': 'reset',
                'kwargs': {
                    'speed': self.spinbox_speed.value(),
                    'wait': False,
                }
            }
            self.handler.put_cmd_que(item)
            self.reset_flag()
        except Exception as e:
            print(e)

    @staticmethod
    def slider_spinbox_related(value, master=None, slave=None, scale=1):
        try:
            slave.setValue(value * scale)
        except Exception as e:
            print(e)

    def _set_tab(self):
        tab_widget = QTabWidget()
        tab_widget.setMaximumHeight(self.main_ui.window.geometry().height() // 2)
        self.layout.addWidget(tab_widget)

        toolBox1 = QToolBox()
        toolBox2 = QToolBox()

        groupBox1 = QGroupBox()
        groupBox2 = QGroupBox()

        toolBox1.addItem(groupBox1, "")
        toolBox2.addItem(groupBox2, "")

        tab_widget.addTab(toolBox1, i18n[self.lang]['Angle'])
        tab_widget.addTab(toolBox2, i18n[self.lang]['Coordinate'])

        joint_layout = QVBoxLayout(groupBox1)
        cartesian_layout = QVBoxLayout(groupBox2)

        self.cartesian_ui = CoordinateUI(self, cartesian_layout)
        self.axis_ui = AngleUI(self, joint_layout)

    def update_device_info(self, device_info):
        if device_info is not None:
            self.label_type.setText(str(device_info.get('device_type', None)))
            self.label_hard_version.setText(str(device_info.get('hardware_version', None)))
            self.label_firm_version.setText(str(device_info.get('firmware_version', None)))

    def update_mode(self, mode):
        if mode is not None:
            if mode == 0:
                mode_str = 'Normal'
            elif mode == 1:
                mode_str = 'Laser'
            elif mode == 2:
                mode_str = '3D'
            elif mode == 3:
                mode_str = 'Pen'
            else:
                mode_str = self.label_mode.text()
            self.label_mode.setText(mode_str)

    def set_disable(self, disable):
        try:
            self.btn_get_device_info.setDisabled(disable)
            self.btn_get_position.setDisabled(disable)
            self.btn_get_polar.setDisabled(disable)
            self.btn_get_servo_angle.setDisabled(disable)
            self.btn_get_mode.setDisabled(disable)
            self.combobox_servo.setDisabled(disable)
            self.combobox_mode.setDisabled(disable)
            self.btn_servo_attach.setDisabled(disable)
            self.btn_servo_detach.setDisabled(disable)
            self.btn_reset.setDisabled(disable)
            self.btn_pump_on.setDisabled(disable)
            self.btn_pump_off.setDisabled(disable)
            self.btn_gripper_catch.setDisabled(disable)
            self.btn_gripper_release.setDisabled(disable)

            self.slider_speed.setDisabled(disable)
            self.spinbox_speed.setDisabled(disable)
            # self.slider_acc.setDisabled(disable)
            # self.spinbox_acc.setDisabled(disable)

            self.axis_ui.set_disable(disable)
            self.cartesian_ui.set_disable(disable)
        except Exception as e:
            print(e)
class HotkeyWidget(QWidget):
    def __init__(self, prefs, configName, title):
        QWidget.__init__(self)
        self.l = QVBoxLayout()
        self.setLayout(self.l)
        
        self.configName = configName
    
        self.hotkeyLayout = QHBoxLayout()
        self.l.addLayout(self.hotkeyLayout)
        
        enabledLabel = QLabel(title)
        self.hotkeyLayout.addWidget(enabledLabel)
        
        self.enabledBox = QCheckBox()
        self.hotkeyLayout.addWidget(self.enabledBox)
        self.enabledBox.setChecked(prefs[configName + '_hotkey_enabled'])
        
        hotkeyLayout2 = QHBoxLayout()
        self.l.addLayout(hotkeyLayout2)
        
        ctrlLabel = QLabel("Ctrl")
        self.ctrlBox = QCheckBox()
        self.ctrlBox.setChecked(prefs[configName + '_hotkey_ctrl'])
        
        ctrlLabel.setBuddy(self.ctrlBox)
        hotkeyLayout2.addWidget(ctrlLabel)
        hotkeyLayout2.addWidget(self.ctrlBox)
        
        altLabel = QLabel("Alt")
        self.altBox = QCheckBox()
        self.altBox.setChecked(prefs[configName + '_hotkey_alt'])
        
        altLabel.setBuddy(self.altBox)
        hotkeyLayout2.addWidget(altLabel)
        hotkeyLayout2.addWidget(self.altBox)
        
        shiftLabel = QLabel("Shift")
        self.shiftBox = QCheckBox()
        self.shiftBox.setChecked(prefs[configName + '_hotkey_shift'])
        
        shiftLabel.setBuddy(self.shiftBox)
        hotkeyLayout2.addWidget(shiftLabel)
        hotkeyLayout2.addWidget(self.shiftBox)
        
        self.keycodeBox = QComboBox()
        for key, value in keycodes.iteritems():
            self.keycodeBox.addItem(key, value)
            
        index = self.keycodeBox.findData(prefs[configName + '_hotkey_keycode'])
        if index != -1:
            self.keycodeBox.setCurrentIndex(index)
            
        hotkeyLayout2.addWidget(self.keycodeBox)
        
    def save_settings(self, prefs):
        prefs[self.configName + '_hotkey_enabled'] = self.enabledBox.isChecked()
        prefs[self.configName + '_hotkey_ctrl'] = self.ctrlBox.isChecked()
        prefs[self.configName + '_hotkey_alt'] = self.altBox.isChecked()
        prefs[self.configName + '_hotkey_shift'] = self.shiftBox.isChecked()
        prefs[self.configName + '_hotkey_keycode'] = keycodes[unicode(self.keycodeBox.currentText())]
        
Пример #34
0
class EKWindow(QDialog):
    """
        Class which is responisble for running this entire application
    """

    def __init__(self):
        """
            Constructor for this class
        """
        super(EKWindow, self).__init__()
        self.engine = Engine("tables/Tamil-bamini.txt.in")

        # Settings file initialization
        self.settingsFilePath = os.getenv("APPDATA") + "\\" + qApp.applicationName() + "\eksettings.ini"
        self.init_settings()    # Function to check whether the settings file is or not.
        self.iniSettings = QSettings(self.settingsFilePath, QSettings.IniFormat)

        # Variable Initialization
        self.registrySettings = QSettings("HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run", QSettings.NativeFormat)
        self.shortcutModifierKey = self.iniSettings.value("shortcut_modifier")
        self.shortcutKey = self.iniSettings.value("shortcut")
        self.selectedKeyboard = self.iniSettings.value("selected_keyboard")
        self.keyboardStatus = False
        self.fileName = ""

        # Ui variable Initialization
        self.iconGroupBox = QGroupBox("Keyboards")
        self.iconLabel = QLabel("Keyboard:")
        self.iconComboBox = QComboBox(self)
        self.shortcutGroupBox = QGroupBox("Shortcut Setting")
        self.shortcutComboBox1 = QComboBox(self)
        self.shortcutComboBox2 = QComboBox(self)
        self.otherSettingsGroupBox = QGroupBox("Other Settings")
        self.checkboxStartWithWindows = QCheckBox()
        self.minimizeAction = QAction("Minimize", self)
        self.maximizeAction = QAction("Maximize", self)
        self.settingsAction = QAction("Settings", self)
        self.aboutAction = QAction("About", self)
        self.quitAction = QAction("Quit", self)
        self.trayIconMenu = QMenu(self)
        self.trayIcon = QSystemTrayIcon(self)
        self.mainLayout = QVBoxLayout()
        self.mainLayout.addWidget(self.iconGroupBox)
        self.mainLayout.addWidget(self.shortcutGroupBox)
        self.mainLayout.addWidget(self.otherSettingsGroupBox)
        self.setLayout(self.mainLayout)

        # UI constructor and connectors
        self.create_settings_group_boxes()
        self.create_actions()
        self.create_tray_icon()

        # Signal connectors
        self.iconComboBox.currentIndexChanged.connect(self.change_keyboard)
        self.shortcutComboBox1.currentIndexChanged.connect(self.set_shortcut_modifier)
        self.shortcutComboBox2.currentIndexChanged.connect(self.set_shortcut_key)
        self.trayIcon.activated.connect(self.icon_activated)
        self.checkboxStartWithWindows.stateChanged.connect(self.checkbox_start_with_windows_ticked)

        if self.keyboardStatus:
            self.iconComboBox.setCurrentIndex(self.selectedKeyBoard)
        else:
            self.change_keyboard(0)
            self.iconComboBox.setCurrentIndex(0)

        self.trayIcon.show()
        self.set_shortcut_key()
        self.setWindowTitle(qApp.applicationName() + " " + qApp.applicationVersion())

    def init_settings(self):
        """
            Function to check whether the settings file is there or not. If there is no file, then it will create with
            default settings.
        """
        if not os.path.exists(self.settingsFilePath):
            settings_dir = os.getenv("APPDATA") + "\\" + qApp.applicationName()
            if not os.path.exists(settings_dir):
                os.makedirs(settings_dir)
            setting_path = ""
            if getattr(sys, 'frozen', False):
                setting_path = os.path.dirname(sys.executable)
            elif __file__:
                setting_path = os.path.dirname(__file__)
            shutil.copyfile(os.path.join(setting_path, "resources\eksettings.ini"), self.settingsFilePath)
        return

    def create_settings_group_boxes(self):
        """
            UI generator function.
        """
        self.iconComboBox.addItem("No Keyboard")
        self.iconComboBox.addItem("Tamil99")
        self.iconComboBox.addItem("Phonetic")
        self.iconComboBox.addItem("Typewriter")
        self.iconComboBox.addItem("Bamini")
        self.iconComboBox.addItem("Inscript")
        icon_layout = QHBoxLayout(self)
        icon_layout.addWidget(self.iconLabel)
        icon_layout.addWidget(self.iconComboBox)
        icon_layout.addStretch()
        self.iconGroupBox.setLayout(icon_layout)

        shortcut_label_1 = QLabel("Modifier Key:")
        shortcut_label_2 = QLabel("Shortcut Key:")

        self.shortcutComboBox1.addItem("NONE")
        self.shortcutComboBox1.addItem("CTRL")
        self.shortcutComboBox1.addItem("ALT")

        modifier_index = self.shortcutComboBox1.findText(self.shortcutModifierKey)
        self.shortcutComboBox1.setCurrentIndex(modifier_index)

        self.shortcutComboBox2.setMinimumContentsLength(3)

        if modifier_index == 0:
            self.shortcutComboBox2.addItem("F1")
            self.shortcutComboBox2.addItem("ESC")
            self.shortcutComboBox2.addItem("F2")
            self.shortcutComboBox2.addItem("F3")
            self.shortcutComboBox2.addItem("F4")
            self.shortcutComboBox2.addItem("F5")
            self.shortcutComboBox2.addItem("F6")
            self.shortcutComboBox2.addItem("F7")
            self.shortcutComboBox2.addItem("F8")
            self.shortcutComboBox2.addItem("F9")
            self.shortcutComboBox2.addItem("F10")
        else:
            self.shortcutComboBox2.addItem("1")
            self.shortcutComboBox2.addItem("2")
            self.shortcutComboBox2.addItem("3")
            self.shortcutComboBox2.addItem("4")
            self.shortcutComboBox2.addItem("5")
            self.shortcutComboBox2.addItem("6")
            self.shortcutComboBox2.addItem("7")
            self.shortcutComboBox2.addItem("8")
            self.shortcutComboBox2.addItem("9")
            self.shortcutComboBox2.addItem("0")

        key_index = self.shortcutComboBox2.findText(self.shortcutKey)
        self.shortcutComboBox2.setCurrentIndex(key_index)

        shortcut_layout = QHBoxLayout(self)
        shortcut_layout.addWidget(shortcut_label_1)
        shortcut_layout.addWidget(self.shortcutComboBox1)
        shortcut_layout.addWidget(shortcut_label_2)
        shortcut_layout.addWidget(self.shortcutComboBox2)
        shortcut_layout.addStretch()
        self.shortcutGroupBox.setLayout(shortcut_layout)

        checkbox_start_with_windows_label = QLabel("Start eKalappai whenever windows starts")

        # if registry entry for auto start with windows for the current user exists, then check the checkbox
        if self.registrySettings.contains(qApp.applicationName()):
            self.checkboxStartWithWindows.setChecked(True)
        else:
            self.checkboxStartWithWindows.setChecked(False)

        other_settings_layout = QHBoxLayout(self)
        other_settings_layout.addWidget(checkbox_start_with_windows_label)
        other_settings_layout.addWidget(self.checkboxStartWithWindows)
        other_settings_layout.addStretch()
        self.otherSettingsGroupBox.setLayout(other_settings_layout)

    def set_shortcut_key(self):
        """
            Function to change the shortcut key when its changed.
        """
        self.shortcutKey = self.shortcutComboBox2.currentText()
        self.iniSettings.setValue("shortcut", self.shortcutKey)
        self.register_shortcut_listener()
        if self.shortcutKey == "ESC":
            self.shortcutKeyHex = 0x1B
        elif self.shortcutKey == "F1":
            self.shortcutKeyHex = 0x70
        elif self.shortcutKey == "F2":
            self.shortcutKeyHex = 0x71
        elif self.shortcutKey == "F3":
            self.shortcutKeyHex = 0x72
        elif self.shortcutKey == "F4":
            self.shortcutKeyHex = 0x73
        elif self.shortcutKey == "F5":
            self.shortcutKeyHex = 0x74
        elif self.shortcutKey == "F6":
            self.shortcutKeyHex = 0x75
        elif self.shortcutKey == "F7":
            self.shortcutKeyHex = 0x76
        elif self.shortcutKey == "F8":
            self.shortcutKeyHex = 0x77
        elif self.shortcutKey == "F9":
            self.shortcutKeyHex = 0x78
        elif self.shortcutKey == "F10":
            self.shortcutKeyHex = 0x79
        elif self.shortcutKey == "1":
            self.shortcutKeyHex = 0x31
        elif self.shortcutKey == "2":
            self.shortcutKeyHex = 0x32
        elif self.shortcutKey == "3":
            self.shortcutKeyHex = 0x33
        elif self.shortcutKey == "4":
            self.shortcutKeyHex = 0x34
        elif self.shortcutKey == "5":
            self.shortcutKeyHex = 0x35
        elif self.shortcutKey == "6":
            self.shortcutKeyHex = 0x36
        elif self.shortcutKey == "7":
            self.shortcutKeyHex = 0x37
        elif self.shortcutKey == "8":
            self.shortcutKeyHex = 0x38
        elif self.shortcutKey == "9":
            self.shortcutKeyHex = 0x39
        elif self.shortcutKey == "0":
            self.shortcutKeyHex = 0x30

    def create_actions(self):
        """
            Slot connectors for all right clicking and other actions.
        """
        self.minimizeAction.triggered.connect(self.hide)
        self.maximizeAction.triggered.connect(self.showMaximized)
        self.settingsAction.triggered.connect(self.showNormal)
        self.aboutAction.triggered.connect(self.show_about)
        self.quitAction.triggered.connect(self.quit)

    def quit(self):
        self.engine.un_hook()
        exit(0)

    def create_tray_icon(self):
        """
            Tray icon creator and corresponding connectors
        """
        self.trayIconMenu.addAction(self.settingsAction)
        self.trayIconMenu.addSeparator()
        self.trayIconMenu.addAction(self.aboutAction)
        self.trayIconMenu.addSeparator()
        self.trayIconMenu.addAction(self.quitAction)
        self.trayIcon.setContextMenu(self.trayIconMenu)

    def setVisible(self, visible):
        self.settingsAction.setEnabled(self.isMaximized() or not visible)
        super(EKWindow, self).setVisible(visible)

    def closeEvent(self, event):
        if self.trayIcon.isVisible():
            self.hide()
            event.ignore()

    def load_keyboard(self):
        """
            Mapping file loading function
        """
        if self.selectedKeyboard == 1:
            self.fileName = "tables/Tamil-tamil99.txt.in"
        elif self.selectedKeyboard == 2:
            self.fileName = "tables/Tamil-phonetic.txt.in"
        elif self.selectedKeyboard == 3:
            self.fileName = "tables/Tamil-typewriter.txt.in"
        elif self.selectedKeyboard == 4:
            self.fileName = "tables/Tamil-bamini.txt.in"
        elif self.selectedKeyboard == 5:
            self.fileName = "tables/Tamil-inscript.txt.in"
        else:
            pass

    def getPath(self, index):
            if index == 1:
                self.path = "tables/Tamil-tamil99.txt.in"
            elif index == 2:
                self.path = "tables/Tamil-phonetic.txt.in"
            elif index == 3:
                self.path = "tables/Tamil-typewriter.txt.in"
            elif index == 4:
                self.path = "tables/Tamil-bamini.txt.in"
            elif index == 5:
                self.path = "tables/Tamil-inscript.txt.in"
            else:
                pass

    def change_keyboard(self, index):
        """
            Function to change the keyboard based on the index which was sent as a param
        """
        if int(index) != 0:
            self.iniSettings.setValue("selected_keyboard", index)
            self.selectedKeyboard = index
        self.iconComboBox.setCurrentIndex(int(index))
        icon = self.iconComboBox.itemIcon(int(index))
        self.trayIcon.setIcon(icon)
        self.setWindowIcon(icon)
        self.trayIcon.setToolTip(self.iconComboBox.itemText(int(index)))
        self.show_tray_message(index)
        self.load_keyboard()
        if int(index) != 0:
            self.getPath(int(index))
            self.engine.file_name = self.path
            self.engine.initialize()
            self.engine.conv_state = True
        else:
            try:
                self.engine.conv_state = False
            except:
                pass

    def icon_activated(self, reason):
        """
            Function to toggle the state when the icon is clicked or shortcut key is pressed
        """
        if reason == QSystemTrayIcon.DoubleClick:
            pass
        elif reason == QSystemTrayIcon.Trigger:
            if self.keyboardStatus:
                self.keyboardStatus = False
            else:
                self.keyboardStatus = True
            if self.keyboardStatus:
                self.change_keyboard(self.selectedKeyboard)
            else:
                self.change_keyboard(0)
        elif reason == QSystemTrayIcon.MiddleClick:
            pass
        else:
            pass

    def show_tray_message(self, index):
        """
            Tray message generator when there is change in keyboard state
        """
        icon = QSystemTrayIcon.MessageIcon(0)
        message = self.iconComboBox.itemText(int(index)) + " set"
        self.trayIcon.showMessage(qApp.applicationName() + " " + qApp.applicationVersion(), message, icon, 100)

    def checkbox_start_with_windows_ticked(self):
        """
            Function to add or disable registry entry to auto start ekalappai with windows for the current users
        """
        if self.checkboxStartWithWindows.isChecked():
            self.registrySettings.setValue(qApp.applicationName(), qApp.applicationFilePath())
        else:
            self.registrySettings.remove(qApp.applicationName())

    def show_about(self):
        pass

    def set_shortcut_modifier(self, index):
        """
            Function to set the shortcut modifier when its changed.
        """
        self.iniSettings.setValue("shortcut_modifier", self.shortcutComboBox1.currentText())
        self.shortcutModifierKey = self.iniSettings.value("shortcut_modifier")
        # if none is selected, the allowed single key shortcuts should change
        if index == 0:
            self.shortcutComboBox2.clear()
            self.shortcutComboBox2.addItem("ESC")
            self.shortcutComboBox2.addItem("F1")
            self.shortcutComboBox2.addItem("F2")
            self.shortcutComboBox2.addItem("F3")
            self.shortcutComboBox2.addItem("F4")
            self.shortcutComboBox2.addItem("F5")
            self.shortcutComboBox2.addItem("F6")
            self.shortcutComboBox2.addItem("F7")
            self.shortcutComboBox2.addItem("F8")
            self.shortcutComboBox2.addItem("F9")
            self.shortcutComboBox2.addItem("F10")
        else:
            self.shortcutComboBox2.clear()
            self.shortcutComboBox2.addItem("1")
            self.shortcutComboBox2.addItem("2")
            self.shortcutComboBox2.addItem("3")
            self.shortcutComboBox2.addItem("4")
            self.shortcutComboBox2.addItem("5")
            self.shortcutComboBox2.addItem("6")
            self.shortcutComboBox2.addItem("7")
            self.shortcutComboBox2.addItem("8")
            self.shortcutComboBox2.addItem("9")
            self.shortcutComboBox2.addItem("0")
        self.register_shortcut_listener()

    def register_shortcut_listener(self):
        self.engine.event_queue.remove_all()
        if self.iniSettings.value("shortcut_modifier") == "NONE":
            self.engine.event_queue.register_event([[self.shortcutKey], self.icon_activated, QSystemTrayIcon.Trigger])
        elif self.iniSettings.value("shortcut_modifier") == "CTRL":
            self.engine.event_queue.register_event([['Lcontrol', self.shortcutKey], self.icon_activated, QSystemTrayIcon.Trigger])
            self.engine.event_queue.register_event([['Rcontrol', self.shortcutKey], self.icon_activated, QSystemTrayIcon.Trigger])
        elif self.iniSettings.value("shortcut_modifier") == "ALT":
            self.engine.event_queue.register_event([['LMenu', self.shortcutKey], self.icon_activated, QSystemTrayIcon.Trigger])
            self.engine.event_queue.register_event([['RMenu', self.shortcutKey], self.icon_activated, QSystemTrayIcon.Trigger])
        return True
Пример #35
0
class CreateVirtualLibrary(QDialog):  # {{{

    def __init__(self, gui, existing_names, editing=None):
        QDialog.__init__(self, gui)

        self.gui = gui
        self.existing_names = existing_names

        if editing:
            self.setWindowTitle(_('Edit virtual library'))
        else:
            self.setWindowTitle(_('Create virtual library'))
        self.setWindowIcon(QIcon(I('lt.png')))

        gl = QGridLayout()
        self.setLayout(gl)
        self.la1 = la1 = QLabel(_('Virtual library &name:'))
        gl.addWidget(la1, 0, 0)
        self.vl_name = QComboBox()
        self.vl_name.setEditable(True)
        self.vl_name.lineEdit().setMaxLength(MAX_VIRTUAL_LIBRARY_NAME_LENGTH)
        la1.setBuddy(self.vl_name)
        gl.addWidget(self.vl_name, 0, 1)
        self.editing = editing

        self.saved_searches_label = QLabel('')
        self.saved_searches_label.setTextInteractionFlags(Qt.TextSelectableByMouse)
        gl.addWidget(self.saved_searches_label, 2, 0, 1, 2)

        self.la2 = la2 = QLabel(_('&Search expression:'))
        gl.addWidget(la2, 1, 0)
        self.vl_text = QLineEdit()
        self.vl_text.textChanged.connect(self.search_text_changed)
        la2.setBuddy(self.vl_text)
        gl.addWidget(self.vl_text, 1, 1)
        self.vl_text.setText(_build_full_search_string(self.gui))

        self.sl = sl = QLabel('<p>'+_('Create a virtual library based on: ')+
            ('<a href="author.{0}">{0}</a>, '
            '<a href="tag.{1}">{1}</a>, '
            '<a href="publisher.{2}">{2}</a>, '
            '<a href="series.{3}">{3}</a>, '
            '<a href="search.{4}">{4}</a>.').format(_('Authors'), _('Tags'),
                                            _('Publishers'), _('Series'), _('Saved Searches')))
        sl.setWordWrap(True)
        sl.setTextInteractionFlags(Qt.LinksAccessibleByMouse)
        sl.linkActivated.connect(self.link_activated)
        gl.addWidget(sl, 3, 0, 1, 2)
        gl.setRowStretch(3,10)

        self.hl = hl = QLabel(_('''
            <h2>Virtual Libraries</h2>

            <p>Using <i>virtual libraries</i> you can restrict calibre to only show
            you books that match a search. When a virtual library is in effect, calibre
            behaves as though the library contains only the matched books. The Tag Browser
            display only the tags/authors/series/etc. that belong to the matched books and any searches
            you do will only search within the books in the virtual library. This
            is a good way to partition your large library into smaller and easier to work with subsets.</p>

            <p>For example you can use a Virtual Library to only show you books with the Tag <i>"Unread"</i>
            or only books by <i>"My Favorite Author"</i> or only books in a particular series.</p>

            <p>More information and examples are available in the
            <a href="%s">User Manual</a>.</p>
            ''') % localize_user_manual_link('http://manual.calibre-ebook.com/virtual_libraries.html'))
        hl.setWordWrap(True)
        hl.setOpenExternalLinks(True)
        hl.setFrameStyle(hl.StyledPanel)
        gl.addWidget(hl, 0, 3, 4, 1)

        bb = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
        bb.accepted.connect(self.accept)
        bb.rejected.connect(self.reject)
        gl.addWidget(bb, 4, 0, 1, 0)

        if editing:
            db = self.gui.current_db
            virt_libs = db.prefs.get('virtual_libraries', {})
            for dex,vl in enumerate(sorted(virt_libs.keys(), key=sort_key)):
                self.vl_name.addItem(vl, virt_libs.get(vl, ''))
                if vl == editing:
                    self.vl_name.setCurrentIndex(dex)
                    self.original_index = dex
            self.original_search = virt_libs.get(editing, '')
            self.vl_text.setText(self.original_search)
            self.new_name = editing
            self.vl_name.currentIndexChanged[int].connect(self.name_index_changed)
            self.vl_name.lineEdit().textEdited.connect(self.name_text_edited)

        self.resize(self.sizeHint()+QSize(150, 25))

    def search_text_changed(self, txt):
        db = self.gui.current_db
        searches = [_('Saved searches recognized in the expression:')]
        txt = unicode(txt)
        while txt:
            p = txt.partition('search:')
            if p[1]:  # found 'search:'
                possible_search = p[2]
                if possible_search:  # something follows the 'search:'
                    if possible_search[0] == '"':  # strip any quotes
                        possible_search = possible_search[1:].partition('"')
                    else:  # find end of the search name. Is EOL, space, rparen
                        sp = possible_search.find(' ')
                        pp = possible_search.find(')')
                        if pp < 0 or (sp > 0 and sp <= pp):
                            # space in string before rparen, or neither found
                            possible_search = possible_search.partition(' ')
                        else:
                            # rparen in string before space
                            possible_search = possible_search.partition(')')
                    txt = possible_search[2]  # grab remainder of the string
                    search_name = possible_search[0]
                    if search_name.startswith('='):
                        search_name = search_name[1:]
                    if search_name in db.saved_search_names():
                        searches.append(search_name + '=' +
                                        db.saved_search_lookup(search_name))
                else:
                    txt = ''
            else:
                txt = ''
        if len(searches) > 1:
            self.saved_searches_label.setText('\n'.join(searches))
        else:
            self.saved_searches_label.setText('')

    def name_text_edited(self, new_name):
        self.new_name = unicode(new_name)

    def name_index_changed(self, dex):
        if self.editing and (self.vl_text.text() != self.original_search or
                             self.new_name != self.editing):
            if not question_dialog(self.gui, _('Search text changed'),
                         _('The virtual library name or the search text has changed. '
                           'Do you want to discard these changes?'),
                         default_yes=False):
                self.vl_name.blockSignals(True)
                self.vl_name.setCurrentIndex(self.original_index)
                self.vl_name.lineEdit().setText(self.new_name)
                self.vl_name.blockSignals(False)
                return
        self.new_name = self.editing = self.vl_name.currentText()
        self.original_index = dex
        self.original_search = unicode(self.vl_name.itemData(dex) or '')
        self.vl_text.setText(self.original_search)

    def link_activated(self, url):
        db = self.gui.current_db
        f, txt = unicode(url).partition('.')[0::2]
        if f == 'search':
            names = db.saved_search_names()
        else:
            names = getattr(db, 'all_%s_names'%f)()
        d = SelectNames(names, txt, parent=self)
        if d.exec_() == d.Accepted:
            prefix = f+'s' if f in {'tag', 'author'} else f
            if f == 'search':
                search = ['(%s)'%(db.saved_search_lookup(x)) for x in d.names]
            else:
                search = ['%s:"=%s"'%(prefix, x.replace('"', '\\"')) for x in d.names]
            if search:
                if not self.editing:
                    self.vl_name.lineEdit().setText(d.names.next())
                    self.vl_name.lineEdit().setCursorPosition(0)
                self.vl_text.setText(d.match_type.join(search))
                self.vl_text.setCursorPosition(0)

    def accept(self):
        n = unicode(self.vl_name.currentText()).strip()
        if not n:
            error_dialog(self.gui, _('No name'),
                         _('You must provide a name for the new virtual library'),
                         show=True)
            return

        if n.startswith('*'):
            error_dialog(self.gui, _('Invalid name'),
                         _('A virtual library name cannot begin with "*"'),
                         show=True)
            return

        if n in self.existing_names and n != self.editing:
            if not question_dialog(self.gui, _('Name already in use'),
                         _('That name is already in use. Do you want to replace it '
                           'with the new search?'),
                            default_yes=False):
                return

        v = unicode(self.vl_text.text()).strip()
        if not v:
            error_dialog(self.gui, _('No search string'),
                         _('You must provide a search to define the new virtual library'),
                         show=True)
            return

        try:
            db = self.gui.library_view.model().db
            recs = db.data.search_getting_ids('', v, use_virtual_library=False, sort_results=False)
        except ParseException as e:
            error_dialog(self.gui, _('Invalid search'),
                         _('The search in the search box is not valid'),
                         det_msg=e.msg, show=True)
            return

        if not recs and not question_dialog(
                self.gui, _('Search found no books'),
                _('The search found no books, so the virtual library '
                'will be empty. Do you really want to use that search?'),
                default_yes=False):
                return

        self.library_name = n
        self.library_search = v
        QDialog.accept(self)
Пример #36
0
class _BuilderTab(QWidget):
    """Handles the creation of launchers."""
    def __init__(self):
        super().__init__()

        self._layout = QVBoxLayout()

        host_label = QLabel("Server host (where EvilOSX will connect to):")
        self._host_field = QLineEdit()

        self._layout.addWidget(host_label)
        self._layout.addWidget(self._host_field)

        port_label = QLabel("Server port:")
        self._port_field = QLineEdit()

        self._layout.addWidget(port_label)
        self._layout.addWidget(self._port_field)

        live_label = QLabel(
            "Where should EvilOSX live? (Leave empty for ~/Library/Containers/.<RANDOM>): "
        )
        self._live_field = QLineEdit()

        self._layout.addWidget(live_label)
        self._layout.addWidget(self._live_field)

        launcher_label = QLabel("Launcher name:")
        self._launcher_combobox = QComboBox()

        for launcher_name in launchers.get_names():
            self._launcher_combobox.addItem(launcher_name)

        self._layout.addWidget(launcher_label)
        self._layout.addWidget(self._launcher_combobox)

        loader_label = QLabel("Loader name:")
        loader_combobox = QComboBox()
        self._loader_layout = QVBoxLayout()

        for loader_name in loaders.get_names():
            loader_combobox.addItem(loader_name)

        self._layout.addWidget(loader_label)
        self._layout.addWidget(loader_combobox)
        loader_combobox.currentTextChanged.connect(self._set_on_loader_change)

        # Dynamically loaded loader layout
        self._layout.addLayout(self._loader_layout)
        self._set_on_loader_change(loader_combobox.currentText())

        self._layout.setContentsMargins(10, 10, 10, 0)
        self._layout.setAlignment(Qt.AlignTop)
        self.setLayout(self._layout)

    def _set_on_loader_change(self, new_text: str):
        """Handles the loader combobox change event."""
        while self._loader_layout.count():
            child = self._loader_layout.takeAt(0)

            if child.widget():
                child.widget().deleteLater()

        input_fields = []

        for message in loaders.get_option_messages(new_text):
            input_field = QLineEdit()

            self._loader_layout.addWidget(QLabel(message))
            self._loader_layout.addWidget(input_field)
            input_fields.append(input_field)

        create_button = QPushButton("Create launcher")
        create_button.setMaximumWidth(250)
        create_button.setMinimumHeight(30)
        create_button.pressed.connect(lambda: self._on_create_launcher(
            self._host_field.text(), self._port_field.text(),
            self._live_field.text(), new_text,
            self._launcher_combobox.currentText(), input_fields))

        self._loader_layout.addWidget(QLabel(""))
        self._loader_layout.addWidget(create_button)

    @staticmethod
    def display_error(text: str):
        """Displays an error message to the user."""
        message = QMessageBox()

        message.setIcon(QMessageBox.Critical)
        message.setWindowTitle("Error")
        message.setText(text)
        message.setStandardButtons(QMessageBox.Ok)
        message.exec_()

    @staticmethod
    def display_info(text: str):
        message = QMessageBox()

        message.setIcon(QMessageBox.Information)
        message.setWindowTitle("Information")
        message.setText(text)
        message.setStandardButtons(QMessageBox.Ok)
        message.exec_()

    def _on_create_launcher(self, server_host, server_port, program_directory,
                            loader_name: str, launcher_name: str,
                            input_fields: list):
        """Creates the launcher and outputs it to the builds directory."""
        if not self._host_field.text():
            self.display_error("Invalid host specified.")
        elif not str(self._port_field.text()).isdigit():
            self.display_error("Invalid port specified.")
        else:
            set_options = []

            for field in input_fields:
                set_options.append(field.text())

            loader_options = loaders.get_options(loader_name, set_options)
            loader_options["program_directory"] = program_directory

            stager = launchers.create_stager(server_host, server_port,
                                             loader_options)

            launcher_extension, launcher = launchers.generate(
                launcher_name, stager)
            launcher_path = path.realpath(
                path.join(
                    path.dirname(__file__), path.pardir, path.pardir, "data",
                    "builds", "Launcher-{}.{}".format(
                        str(uuid4())[:6], launcher_extension)))

            with open(launcher_path, "w") as output_file:
                output_file.write(launcher)

            self.display_info(
                "Launcher written to: \n{}".format(launcher_path))
class App(QWidget):

    USs = []  # { F# , name , description , Acceptance Criteria }
    USNames = []  # user stories names
    TCs = []  # { TC name , TC description }

    def __init__(self):
        super().__init__()
        self.title = 'NLP project.'
        self.left = 50
        self.top = 50
        self.width = 1500
        self.height = 800
        self.initUI()

    def initUI(self):
        self.setWindowTitle(self.title)
        self.setGeometry(self.left, self.top, self.width, self.height)
        app.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling, True)

        # Create Label
        label = QLabel('Select User Story by name:', self)
        label.move(50, 0)

        # Create Label
        label2 = QLabel('US Description + Acceptance Criteria:', self)
        label2.move(50, 125)

        # Create Label
        label3 = QLabel('Choose Algorithm:', self)
        label3.move(50, 600)

        # Create Label
        label4 = QLabel('Results:', self)
        label4.move(1000, 50)

        # Create textbox user story
        self.textbox = QPlainTextEdit(self)
        self.textbox.move(50, 175)
        self.textbox.resize(500, 300)
        """
        # Create textbox results
        self.textbox3 = QLineEdit(self)
        self.textbox3.move(1000, 75)
        self.textbox3.resize(400,600)
        self.textbox3.setDisabled(True)
        """

        self.tableWidget = QTableWidget()

        #self.tableWidget.resize(400,600)

        # Create textbox tokenize

        self.textbox4 = QLineEdit(self)
        self.textbox4.move(50, 700)
        self.textbox4.resize(280, 40)
        #self.textbox4.setDisabled(True)
        """
        self.scrollArea = QScrollArea(self)
        self.scrollArea.setWidgetResizable(True)
        self.scrollArea.move(50,400)
        self.scrollArea.resize(280,40)
        self.scrollArea.setWidget(self.textbox4)
        """
        button = QPushButton('Start', self)
        button.move(150, 750)
        button.clicked.connect(self.on_click_start)

        button2 = QPushButton('Tokenize', self)
        button2.move(50, 625)
        button2.clicked.connect(self.on_click_tokenize)

        button3 = QPushButton('Load excel', self)
        button3.move(50, 750)
        button3.clicked.connect(self.on_click_load)

        button4 = QPushButton('Choose US', self)
        button4.move(550, 29)
        button4.clicked.connect(self.on_click_choose_US)

        button5 = QPushButton('Choose AC', self)
        button5.move(550, 149)
        button5.clicked.connect(self.on_click_choose_AC)

        button6 = QPushButton('Clear', self)
        button6.move(250, 750)
        button6.clicked.connect(self.on_click_clear)

        # algorithm combobox
        self.comboBox = QComboBox(self)
        algorithms = ['cosine', 'other']
        self.comboBox.addItems(algorithms)
        self.comboBox.move(150, 625)

        # US combobox
        self.comboBox2 = QComboBox(self)
        self.comboBox2.move(50, 25)
        self.comboBox2.resize(500, 20)
        #action_relect = lambda: self.on_click_reselect_us(self)
        self.comboBox2.currentIndexChanged.connect(self.on_click_reselect_us)

        # AC combobox
        self.comboBox3 = QComboBox(self)
        self.comboBox3.move(50, 150)
        self.comboBox3.resize(500, 20)

        self.show()

    @pyqtSlot()
    def on_click_reselect_us(self):

        self.textbox.clear()
        self.comboBox3.clear()

    @pyqtSlot()
    def on_click_clear(self):

        self.textbox.clear()
        self.textbox3.clear()
        self.textbox4.clear()
        self.comboBox.clear()
        self.comboBox2.clear()
        self.comboBox3.clear()

    @pyqtSlot()
    def on_click_start(self):

        us = self.textbox2.text()
        results = []
        for tc in self.TCs:
            tmp = tc[0] + tc[1]
            results.append({
                'match_sentence': tmp,
                'score': compare_sens(us, tmp)
            })

        i = 0
        self.tableWidget.setSortingEnabled(True)
        self.tableWidget.setRowCount(100)
        self.tableWidget.setColumnCount(2)
        results = sorted(results, key=itemgetter('score'), reverse=True)

        for res in results:
            #self.tableWidget.insertRow(str(res)+str1)
            #self.tableWidget.setItem(i,0, QTableWidgetItem(str(res[1])))
            self.tableWidget.setItem(i, 0, QTableWidgetItem(str(res)))
            i += 1
            #self.textbox3.insert(str(res)+str1
        self.tableWidget.move(1000, 75)
        self.tableWidget.show()

    @pyqtSlot()
    def on_click_choose_US(self):

        # set User Story over textbox so we can edit if needed
        text = self.comboBox2.currentText()
        self.textbox.setPlainText(text)
        self.textbox.show()

        #  fill AC combobox
        descriptions = []
        ACs = []
        tmp = []
        us = self.comboBox2.currentText()

        for a, b, c, d in self.USs:
            if b == us:
                descriptions.append(c)
                ACs.append(d)
                tmp.append(c + d)
        self.comboBox3.addItems(tmp)

    @pyqtSlot()
    def on_click_choose_AC(self):

        # set AC over textbox so we can edit if needed
        text = self.comboBox3.currentText()
        self.textbox.paste()
        self.textbox.show()

    @pyqtSlot()
    def on_click_tokenize(self):

        text = self.textbox.text()
        tok = str(tokenize(text))
        self.textbox4.setText(tok)

    @pyqtSlot()
    def on_click_load(self):

        # Assign spreadsheet filename to `file`
        file = self.openFileNameDialog()

        # Load in the workbook
        wb = load_workbook(file)

        # Get sheet names
        data_sheets_names = wb.sheetnames
        print(wb.sheetnames)

        # Get all the sheet names that hold User stories and Test Cases
        USs_sheets_names = []
        TCs_Sheets_names = []

        for sheet_name in data_sheets_names:
            if sheet_name.find("TCs") != -1:
                TCs_Sheets_names.append(sheet_name)
            if sheet_name.find(" F") != -1:
                USs_sheets_names.append(sheet_name)

        # get NLP data sheets
        for name in USs_sheets_names:
            sheet = wb[name]
            for i in range(2, sheet.max_row + 1):
                self.USs.append(
                    ((sheet.cell(i, 1).value), (sheet.cell(i, 2).value),
                     (sheet.cell(i, 3).value), (sheet.cell(i, 4).value)))
            self.USNames.append((sheet.cell(2, 2).value))

        for name in TCs_Sheets_names:
            sheet = wb[name]
            for i in range(2, sheet.max_row + 1):
                self.TCs.append(
                    (name, (sheet.cell(i, 1).value), (sheet.cell(i, 2).value)))

        #remove doubles
        self.USNames = list(set(self.USNames))
        self.comboBox2.addItems(self.USNames)

        print(self.USNames)

    def openFileNameDialog(self):
        options = QFileDialog.Options()
        options |= QFileDialog.DontUseNativeDialog
        fileName, _ = QFileDialog.getOpenFileName(
            self,
            "QFileDialog.getOpenFileName()",
            "",
            "All Files (*);;Excel Files (*.xlsx)",
            options=options)
        if fileName:
            print(fileName)
        return fileName
Пример #38
0
class Window(QWidget):

    ext_list = ['mkv', 'mp4', 'avi']

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

        self.v_box = QVBoxLayout()
        self.v_dir = QVBoxLayout()
        self.v_ext = QVBoxLayout()
        self.v_av = QVBoxLayout()
        self.H_default_save_dir = QHBoxLayout()
        self.H_default_ext = QHBoxLayout()
        self.H_av = QHBoxLayout()
        self.H_save = QHBoxLayout()

        self.settings = xml.ParseXMLSettings()

        self.txt_default_dir = QLineEdit(self)
        self.drpdwn_av = QComboBox(self)
        self.drpdwn_ext = QComboBox(self)

        self.shortcut = QShortcut(QKeySequence('Ctrl+W'), self)
        self.shortcut.activated.connect(self.close)

        self.initUI()

        self.v_box.addSpacing(12)
        self.H_av.addSpacing(12)
        self.H_default_ext.addSpacing(12)
        self.H_default_save_dir.addSpacing(12)
        self.H_save.addSpacing(12)

        self.setLayout(self.v_box)

        self.setGeometry(200, 200, 250, 150)
        self.setWindowTitle('Global Settings')

    def initUI(self):

        self.txt_default_dir.setText(self.settings.settings_list['save_dir'])
        self.txt_default_dir.setEnabled(False)
        lbl_default_dir = QLabel(self)
        lbl_default_dir.setText('Default Directory: ')
        btn_default_dir = QPushButton(self)
        btn_default_dir.setText('Change...')

        self.drpdwn_av.addItem(self.settings.settings_list['av'])
        lbl_av = QLabel(self)
        lbl_av.setText('Record Audio and Video? ')

        self.drpdwn_ext.addItem(self.settings.settings_list['ext'])
        lbl_ext = QLabel(self)
        lbl_ext.setText('Video Extension: ')

        btn_save = QPushButton(self)
        btn_save.setText('Save changes...')

        btn_default_dir.clicked.connect(lambda: self.change_dir())
        btn_save.clicked.connect(self.save)

        if self.settings.settings_list['av'] == 'True':
            self.drpdwn_av.addItem('False')
        else:
            self.drpdwn_av.addItem('True')

        for ext in self.ext_list:
            if ext != self.settings.settings_list['ext']:
                self.drpdwn_ext.addItem(ext)

        self.v_dir.addWidget(lbl_default_dir)
        self.H_default_save_dir.addWidget(self.txt_default_dir)
        self.H_default_save_dir.addWidget(btn_default_dir)
        self.v_dir.addLayout(self.H_default_save_dir)
        self.v_av.addWidget(lbl_av)
        self.H_av.addWidget(self.drpdwn_av)
        self.v_av.addLayout(self.H_av)
        self.v_ext.addWidget(lbl_ext)
        self.H_default_ext.addWidget(self.drpdwn_ext)
        self.v_ext.addLayout(self.H_default_ext)
        self.H_save.addWidget(btn_save)

        self.v_ext.addStretch(1)
        self.v_av.addStretch(1)
        self.v_dir.addStretch(1)

        self.v_box.addLayout(self.v_dir)
        self.v_box.addLayout(self.v_av)
        self.v_box.addLayout(self.v_ext)
        self.v_box.addLayout(self.H_save)

        self.v_box.addStretch(1)

    def save(self):
        if self.drpdwn_ext.currentText(
        ) != self.settings.settings_list['ext'] or self.drpdwn_av.currentText(
        ) != self.settings.settings_list['av']:
            dlg = dialog.QuestionDialog('Are you ready to save changes?')
            if dlg.clickedButton() == dlg.yes:
                if self.drpdwn_ext.currentText(
                ) != self.settings.settings_list['ext']:
                    self.settings.edit('ext', self.drpdwn_ext.currentText())
                    SETTINGS.settings_list[
                        'ext'] = self.drpdwn_ext.currentText()
                if self.drpdwn_av.currentText(
                ) != self.settings.settings_list['av']:
                    self.settings.edit('av', self.drpdwn_av.currentText())
                    SETTINGS.settings_list['av'] = self.drpdwn_av.currentText()
        else:
            pass

    def change_dir(self):
        dlg = str(QFileDialog.getExistingDirectory(self, "Select Directory"))

        if dlg != "":
            self.settings.edit('save_dir', dlg)
            self.txt_default_dir.setText(dlg)
            SETTINGS.settings_list['save_dir'] = dlg
Пример #39
0
class SpotlightLibreDialog(QDialog):

    def __init__(self, gui, icon, do_user_config):
        QDialog.__init__(self, gui)
        self.gui = gui
        self.do_user_config = do_user_config

        # The current database shown in the GUI > class LibraryDatabase2 
        self.db = gui.current_db

        self.l = QVBoxLayout()
        self.setLayout(self.l)
        


        # Label
        self.labelText = QLabel('Use "and" and "or" for the search.')
        self.l.addWidget(self.labelText)

        # Title
        self.setWindowTitle('SpotlightLibre Full Text Search')
        self.setWindowIcon(icon)

        # Search window
        self.searchTextWindow = QComboBox()
        self.searchTextWindow.setEditable(True)
        self.l.addWidget(self.searchTextWindow)
        self.searchTextWindow.setFocus()
        self.searchTextWindow.setInsertPolicy(QComboBox.NoInsert)
        self.searchTextWindow.setDuplicatesEnabled(False)
        
        #Completer for the seach window
        self.completer = QCompleter()
        self.completer.setCompletionMode( QCompleter.UnfilteredPopupCompletion )
        self.searchTextWindow.setCompleter(self.completer)
        
        # output window
        self.outputWindow = QLabel()
        self.l.addWidget(self.outputWindow)
        
        # search button 1
        self.doSearchButton = QPushButton('Search and replace the filter', self)
        self.doSearchButton.clicked.connect(self.spotlightSearchNew)
        self.l.addWidget(self.doSearchButton)
        self.doSearchButton.setDefault(True)
                
        # search button 2
        self.doSearchButton = QPushButton('Search and add to filter', self)
        self.doSearchButton.clicked.connect(self.spotlightSearchAdd)
        self.l.addWidget(self.doSearchButton)
    

        # about button
        self.aboutButton = QPushButton('About', self)
        self.aboutButton.clicked.connect(self.about)
        self.l.addWidget(self.aboutButton)

        self.resize(self.sizeHint())
        #self.resize(500, self.height())
        

    def about(self):
        # Get the about text from a file inside the plugin zip file
        # The get_resources function is a builtin function defined for all your
        # plugin code. It loads files from the plugin zip file. It returns
        # the bytes from the specified file.
        
        text = get_resources('about.txt')
        #box = QMessageBox()
        #box.about(self, 'About the SpotlightLibre Full Text Search \t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t',text.decode('utf-8'))
        #self.resize(600, self.height())
        
        self.box = AboutWindow()
        self.box.setWindowTitle("About the SpotlightLibre Full Text Search Plugin")
        self.box.textWindow.setText(text)
        self.box.textWindow.setReadOnly(True)
        self.box.resize(600, 500)
        self.box.show()
        



    def spotlightSearchNew(self):
        self.searchAdd = False
        self.spotlightSearch()
    
    def spotlightSearchAdd(self):
        self.searchAdd = True
        self.spotlightSearch()

    def spotlightSearch(self):
        self.searchText = str(self.searchTextWindow.currentText())# search text from the plugin gui
        self.searchTextWindow.insertItem(0, self.searchText)
        # self.cmd = 'mdfind -onlyin '+ prefs['pathToLibrary']+' '  # saved to allow for checkbox option later?

        print(self.db.library_path)
        self.cmd = 'mdfind -onlyin "'+self.db.library_path+'" '
								
        self.cmdString = self.cmd + '"'+self.searchText+'"'

        print(self.cmdString)

        self.p = Popen(self.cmdString,  shell=True, stdin=PIPE, stdout=PIPE, stderr=STDOUT, close_fds=True)
        self.output = self.p.stdout.read()# output from the spotlight search
				
        print(self.output)
        self.found = ''
        self.found = re.findall(r" \((\d+)\)\/", self.output)# regex to find the calibre ids in the folder names
        
        
        self.wholeString = ''
        if len(self.found) == 0 :
            self.outputWindow.setText('no books found' + ' for ' + self.searchText)
        else :
            self.wholeString = '#cid:'
            for elem in self.found:
                self.wholeString += '=' + elem + ' or '
            self.wholeString = self.wholeString[:-4]
            self.outputWindow.setText(str(len(self.found)) + ' books found' + ' for ' + self.searchText)
        
        if self.searchAdd == True :
            self.oldFilter = self.gui.search.text()
            self.wholeString = self.oldFilter + ' and (' + self.wholeString + ')'
        
        self.searchTextWindow.clearEditText()
        self.gui.search.setEditText(self.wholeString) # set calibre search to the string found by spotlight
        self.gui.search.do_search()

    def config(self):
        self.do_user_config(parent=self)
class RecollFulltextSearchDialog(QDialog):

    def __init__(self, gui, icon, do_user_config):
        QDialog.__init__(self, gui)
        self.gui = gui
        self.do_user_config = do_user_config

        # The current database shown in the GUI
        # db is an instance of the class LibraryDatabase2 from database.py
        # This class has many, many methods that allow you to do a lot of
        # things.
        self.db = gui.current_db

        self.l = QVBoxLayout()
        self.setLayout(self.l)
        


        # Label
        self.labelText = QLabel('Use "and" and "or" for the search.')
        self.l.addWidget(self.labelText)

        # Title
        self.setWindowTitle('Recoll Full Text Search')
        self.setWindowIcon(icon)

        # Search window
        self.searchTextWindow = QComboBox()
        self.searchTextWindow.setEditable(True)
        self.l.addWidget(self.searchTextWindow)
        self.searchTextWindow.setFocus()
        self.searchTextWindow.setInsertPolicy(QComboBox.NoInsert)
        self.searchTextWindow.setDuplicatesEnabled(False)
        
        #Completer for the seach window
        self.completer = QCompleter()
        self.completer.setCompletionMode( QCompleter.UnfilteredPopupCompletion )
        self.searchTextWindow.setCompleter(self.completer)
        
        # output window
        self.outputWindow = QLabel()
        self.l.addWidget(self.outputWindow)
        
        # search button 1
        self.doSearchButton = QPushButton('Search and replace the filter', self)
        self.doSearchButton.clicked.connect(self.recollSearchNew)
        self.l.addWidget(self.doSearchButton)
        self.doSearchButton.setDefault(True)
                
        # search button 2
        self.doSearchButton = QPushButton('Search and add to filter', self)
        self.doSearchButton.clicked.connect(self.recollSearchAdd)
        self.l.addWidget(self.doSearchButton)
    
        # update database button 1
        self.updateDatabaseButton = QPushButton('Update recoll database', self)
        self.updateDatabaseButton.clicked.connect(self.updateDatabase)
        self.l.addWidget(self.updateDatabaseButton)
        
        # update database button 2
        self.newDatabaseButton = QPushButton('Make new recoll database', self)
        self.newDatabaseButton.clicked.connect(self.newDatabase)
        self.l.addWidget(self.newDatabaseButton)
        
        # config button
        self.configButton = QPushButton('Configure this plugin', self)
        self.configButton.clicked.connect(self.config)
        self.l.addWidget(self.configButton)
        
        # about button
        self.aboutButton = QPushButton('About', self)
        self.aboutButton.clicked.connect(self.about)
        self.l.addWidget(self.aboutButton)

        self.resize(self.sizeHint())
        #self.resize(500, self.height())
        

    def about(self):
        # Get the about text from a file inside the plugin zip file
        # The get_resources function is a builtin function defined for all your
        # plugin code. It loads files from the plugin zip file. It returns
        # the bytes from the specified file.
        
        text = get_resources('about.txt')
        #box = QMessageBox()
        #box.about(self, 'About the Recoll Full Text Search \t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t',text.decode('utf-8'))
        #self.resize(600, self.height())
        
        self.box = AboutWindow()
        self.box.setWindowTitle("About the Recoll Full Text Search Plugin")
        self.box.textWindow.setText(text)
        self.box.textWindow.setReadOnly(True)
        self.box.resize(600, 500)
        self.box.show()
        

    def updateDatabase(self):
        self.replaceDatabase =False
        self.makeDatabase()

    def newDatabase(self):
        self.replaceDatabase = True
        self.makeDatabase()

    def recollSearchNew(self):
        self.searchAdd = False
        self.recollSearch()
    
    def recollSearchAdd(self):
        self.searchAdd = True
        self.recollSearch()

    def makeDatabase(self):
        '''Runs recollindex outside calibre like in a terminal. 
        Look for recollindex for more information about the flags and options'''
        self.cmd = [prefs['pathToRecoll'] + '/recollindex', '-c', prefs['pathToCofig'] + '/plugins/recollFullTextSearchPlugin']
        #TODO: Fix for Linux
        #self.cmd = 'LD_LIBRARY_PATH="" ' + prefs['pathToRecoll'] + '/recollindex -c ' + prefs['pathToCofig'] + '/plugins/recollFullTextSearchPlugin'
        if self.replaceDatabase == True :
            self.cmd += [' -z']
        self.p = Popen(self.cmd,  shell=False)
        # TODO: Was close_fds nessesary? check it on linux
        #self.p = Popen(self.cmd,  shell=True, stdin=PIPE, stdout=PIPE, stderr=STDOUT, close_fds=True)

        box = QMessageBox()
        box.about(self, 'Please read! \t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t','Depending on you library size this operation can take a lot of time.\nThe process runs outside calibre so you can use or close it, but do not use this plugin.\nFor now there is no information about when recoll finishs,\nso look up, whether a recoll of recollindex process is running on you system.')

    def recollSearch(self):
        '''Runs recoll outside calibre like in a terminal. 
        Look for recollindex for more information about the flags and options'''
        self.searchText = str(self.searchTextWindow.currentText())# search text from the plugin gui
        self.searchTextWindow.insertItem(0, self.searchText)
        #TODO: Fix Linux
        #self.cmd = 'LD_LIBRARY_PATH="" ' + prefs['pathToRecoll'] + '/recoll -c ' + prefs['pathToCofig'] + '/plugins/recollFullTextSearchPlugin -b -t '
        self.cmd = [prefs['pathToRecoll'] + '/recoll', '-c', prefs['pathToCofig'] + '/plugins/recollFullTextSearchPlugin', '-b', '-t']
        self.cmdString = self.cmd + [self.searchText]
        # TODO: Was close_fds nessesary? check it on linux
        #self.p = Popen(self.cmdString,  shell=True, stdin=PIPE, stdout=PIPE, stderr=STDOUT, close_fds=True)
        self.p = Popen(self.cmdString,  shell=True, stdin=PIPE, stdout=PIPE, stderr=STDOUT)
        self.output = self.p.stdout.read()# output from the recoll search
     
        self.found = list(set(re.findall(r" \((\d+)\)\/[^/]*", self.output)))# regex to find the calibre ids in the folder names
     
        self.wholeString = ''
        if len(self.found) == 0 :
            self.outputWindow.setText('no books found' + ' for ' + self.searchText)
        else :
            for elem in self.found[:400]:
                self.wholeString += 'id:=' + elem + ' or '
            self.wholeString = self.wholeString[:-4]
            if len(self.found) > 400 :
                self.outputWindow.setText(str(len(self.found)) + ' books found' + ' for ' + self.searchText+ '. Only the first 400 books are shown')
            else :
                self.outputWindow.setText(str(len(self.found)) + ' books found' + ' for ' + self.searchText)

        if self.searchAdd == True :
            self.oldFilter = self.gui.search.text()
            self.wholeString = self.oldFilter + ' and (' + self.wholeString + ')'
        
        self.searchTextWindow.clearEditText()
        self.gui.search.setEditText(self.wholeString) # set calibre search to the string found by recoll
        self.gui.search.do_search()

    def config(self):
        self.do_user_config(parent=self)
Пример #41
0
class PieChart (QGraphicsView):
    def __init__(self,parent,total_value):
        super(PieChart,self).__init__(parent)
        self.my_scene = QGraphicsScene()
        #self.my_scene.setSceneRect(self.sceneRect())
        self.setScene(self.my_scene)
        #self.setBackgroundBrush(QBrush(QtCore.Qt.black, QtCore.Qt.SolidPattern));
        self.total_value = total_value

        self.initialize = False
        self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        self.margin = {'top':10,'bottom':10,'left':10,'right':10}        

        self.space = 30


        self.pie_items = []

    def setTotal (self, total):
        self.total_value = total
#     def setData (self,list_item):
# 
#         rect = QGraphicsRectItem(self.sceneRect())
#         rect.setBrush(QColor("red"))
#         self.scene().addItem(rect)
    
    def setData (self,list_items):
        size = min(self.frameSize().width()/2.0,self.frameSize().height())
        self.size_pie = QSize(size,size)
        #print ('size pie ',self.size_pie)
        if self.initialize == False:
            self.c_box = QComboBox()
            self.c_box.addItem("all")
            self.c_box.addItem("alive")
            self.c_box.addItem("dead")
            self.c_box.currentIndexChanged.connect(self.update)
            proxy = self.my_scene.addWidget(self.c_box)
            x = self.size_pie.width()+ self.space
            proxy.setPos(QPointF(x,self.margin['top']))    
            self.initialize = True

        self.data = list_items
        self.update()

    def update(self):

        restant = 360
        i = 0
        #self.my_scene.clear()
        for item in self.pie_items :
            self.my_scene.removeItem(item)
        self.pie_items = []
        self.scene().setSceneRect(QRectF(0,0,self.frameSize().width(),self.frameSize().height()))
        #print ('size',self.scene().sceneRect())
        #proxy = QGraphicsProxyWidget ()

        for item in self.data :
            if (i==len(self.data)-1 ):
                angle = restant
            else:
                try : 
                    angle = int(360*item[self.c_box.currentText()]/self.total_value[self.c_box.currentText()])
                except ZeroDivisionError:
                    angle = 0 
                    
            ellipse = Section(0,0,self.size_pie.width(),self.size_pie.height())

            y = (self.parent().size().height()-self.size_pie.height())/2.0
            x_pie = ((self.parent().size().width()/2.0)-self.size_pie.height())/2.0
            ellipse.setPos(x_pie,y)
 
            ellipse.setStartAngle(16*(360-restant))
            ellipse.setSpanAngle(angle*16)
            ellipse.setBrush(item['color'])
            self.my_scene.addItem(ellipse)
            self.pie_items.append(ellipse)
            # text pourcentage a afficher dans les portions de disque
            try :
                v = (item[self.c_box.currentText()]/self.total_value[self.c_box.currentText()])*100
            except ZeroDivisionError :
                v = 0
            text = QGraphicsSimpleTextItem("{0:5.2f}".format(v)+"%")
            trans = QTransform().translate(x_pie+self.size_pie.width()/2.0,y+self.size_pie.height()/2.0).rotate(((360-restant)+angle/2.0)*-1)
            pts = trans.map(QPointF(self.size_pie.width()/3.0,0))
            text.setPos(pts.x(),pts.y())
            self.my_scene.addItem(text)            
            self.pie_items.append(text)
            
            #libelle 
            rect = QGraphicsRectItem(0,0,10,10)
            x = x_pie + self.size_pie.width()+ self.space 
            interval_height = (self.parent().size().height()-self.margin['top']-self.margin['bottom'])/(len(self.data)+1)
            rect.setPos(QPointF(x,self.margin['top']+((i+1)*interval_height)))
            rect.setBrush(item['color'])
            self.my_scene.addItem(rect)
            self.pie_items.append(rect)
            text = QGraphicsSimpleTextItem(item['label']+ " ("+str(int(item[self.c_box.currentText()]))+")")
            pts = rect.pos()
            transform = QTransform().translate(30, 0)
            pts = transform.map(pts)
            text.setPos(pts)            
            self.my_scene.addItem(text)
            self.pie_items.append(text)
            restant = restant - angle
            i +=1

     #   self.fitInView(self.scene.sceneRect())
Пример #42
0
class RemoveDialog(Dialog):
    def __init__(self, parent):
        from calibre_plugins.diaps_toolbag.span_div_config import plugin_prefs as prefs
        self.criteria = None
        self.prefs = prefs
        self.parent = parent
        self.help_file_name = '{0}_span_div_help.html'.format(PLUGIN_SAFE_NAME)
        self.taglist = TAGLIST
        Dialog.__init__(self, _('Edit Spans & Divs'), 'toolbag_spans_divs_dialog', parent)

    def setup_ui(self):
        DELETE_STR = _('Delete')
        MODIFY_STR = _('Modify')
        NO_ATTRIB_STR = _('No attributes ("naked" tag)')
        self.NO_CHANGE_STR = _('No change')

        layout = QVBoxLayout(self)
        self.setLayout(layout)

        help_layout = QHBoxLayout()
        layout.addLayout(help_layout)
        # Add hyperlink to a help file at the right. We will replace the correct name when it is clicked.
        help_label = QLabel('<a href="http://www.foo.com/">Plugin Help</a>', self)
        help_label.setTextInteractionFlags(Qt.LinksAccessibleByMouse | Qt.LinksAccessibleByKeyboard)
        help_label.setAlignment(Qt.AlignRight)
        help_label.linkActivated.connect(self.help_link_activated)
        help_layout.addWidget(help_label)

        action_layout = QHBoxLayout()
        layout.addLayout(action_layout)
        label = QLabel(_('Action type:'), self)
        action_layout.addWidget(label)
        self.action_combo = QComboBox()
        action_layout.addWidget(self.action_combo)
        self.action_combo.addItems([DELETE_STR, MODIFY_STR])
        self.action_combo.currentIndexChanged.connect(self.update_gui)

        tag_layout = QHBoxLayout()
        layout.addLayout(tag_layout)
        label = QLabel(_('Tag name:'), self)
        tag_layout.addWidget(label)
        self.tag_combo = QComboBox()
        tag_layout.addWidget(self.tag_combo)
        self.tag_combo.addItems(self.taglist)
        self.tag_combo.currentIndexChanged.connect(self.update_gui)

        attr_layout = QHBoxLayout()
        layout.addLayout(attr_layout)
        label = QLabel(_('Having the attribute:'), self)
        attr_layout.addWidget(label)
        self.attr_combo = QComboBox()
        attr_layout.addWidget(self.attr_combo)
        self.attr_combo.addItems(self.prefs['attrs'])
        self.attr_combo.addItem(NO_ATTRIB_STR)
        self.attr_combo.currentIndexChanged.connect(self.update_gui)

        srch_layout = QHBoxLayout()
        layout.addLayout(srch_layout)
        label = QLabel(_("Whose value is (no quotes):"), self)
        srch_layout.addWidget(label)
        self.srch_txt = QLineEdit('', self)
        srch_layout.addWidget(self.srch_txt)
        self.srch_method = QCheckBox(_('Regex'), self)
        srch_layout.addWidget(self.srch_method)

        newtag_layout = QHBoxLayout()
        layout.addLayout(newtag_layout)
        label = QLabel(_('Change tag to:'), self)
        newtag_layout.addWidget(label)
        self.newtag_combo = QComboBox()
        newtag_layout.addWidget(self.newtag_combo)

        self.newtag_combo.addItem(self.NO_CHANGE_STR)
        self.newtag_combo.addItems(self.prefs['{}_changes'.format(unicode(self.tag_combo.currentText()))])

        if self.action_combo.currentIndex() == 0:
            self.newtag_combo.setDisabled(True)

        newattr_layout = QVBoxLayout()
        layout.addLayout(newattr_layout)
        label = QLabel(_('New attribute string to insert (entire):'), self)
        newattr_layout.addWidget(label)
        self.newattr_txt = QLineEdit('', self)
        newattr_layout.addWidget(self.newattr_txt)
        self.copy_attr = QCheckBox(_('Copy existing attribute string'), self)
        self.copy_attr.stateChanged.connect(self.update_txt_box)
        newattr_layout.addWidget(self.copy_attr)
        if self.action_combo.currentIndex() == 0:
            self.copy_attr.setDisabled(True)
            self.newattr_txt.setDisabled(True)

        layout.addSpacing(10)
        button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
        button_box.accepted.connect(self._ok_clicked)
        button_box.rejected.connect(self.reject)
        layout.addWidget(button_box)

    def update_gui(self):
        if self.attr_combo.currentIndex() == self.attr_combo.count()-1:
            self.srch_txt.clear()
            self.srch_txt.setDisabled(True)
            self.srch_method.setChecked(False)
            self.srch_method.setDisabled(True)
        else:
            self.srch_txt.setDisabled(False)
            self.srch_method.setDisabled(False)

        self.newtag_combo.clear()
        self.newtag_combo.addItem(self.NO_CHANGE_STR)
        self.newtag_combo.addItems(self.prefs['{}_changes'.format(unicode(self.tag_combo.currentText()))])

        if self.action_combo.currentIndex() == 0:
            self.newtag_combo.setCurrentIndex(0)
            self.newtag_combo.setDisabled(True)
            self.newattr_txt.clear()
            self.newattr_txt.setDisabled(True)
            self.copy_attr.setChecked(False)
            self.copy_attr.setDisabled(True)
        else:
            self.newtag_combo.setDisabled(False)
            self.newattr_txt.setDisabled(False)
            self.copy_attr.setDisabled(False)

    def update_txt_box(self):
        if self.copy_attr.isChecked():
            self.newattr_txt.clear()
            self.newattr_txt.setDisabled(True)
        else:
            self.newattr_txt.setDisabled(False)

    def _ok_clicked(self):
        if self.action_combo.currentIndex() == 0:
            action = 'delete'
        else:
            action = 'modify'
        if self.attr_combo.currentIndex() == self.attr_combo.count()-1:
            attribute = None
        else:
            attribute = unicode(self.attr_combo.currentText())
        srch_str = unicode(self.srch_txt.displayText())
        if not len(srch_str):
            srch_str = None
        if srch_str is None and attribute is not None:
            return error_dialog(self.parent, _('Error'), '<p>{0}'.format(
                    _('Must enter a value for the attribute selected')), det_msg='', show=True)
        srch_method = 'normal'
        if self.srch_method.isChecked():
            srch_method = 'regex'
        if self.newtag_combo.currentIndex() == 0:
            newtag = None
        else:
            newtag = unicode(self.newtag_combo.currentText())
        if action == 'modify' and newtag is None and self.copy_attr.isChecked():
            return error_dialog(self.parent, _('Error'), '<p>{0}'.format(
                    _('What--exactly--would that achieve?')), det_msg='', show=True)
        new_str = unicode(self.newattr_txt.displayText())
        copy_attr = False
        if self.copy_attr.isChecked():
            copy_attr = True
        if not len(new_str):
            new_str = ''

        self.criteria = (srch_str, srch_method, unicode(self.tag_combo.currentText()), attribute, action, newtag, new_str, copy_attr)
        self.accept()

    def getCriteria(self):
        return self.criteria

    def help_link_activated(self, url):
        def get_help_file_resource():
            # Copy the HTML helpfile to the plugin directory each time the
            # link is clicked in case the helpfile is updated in newer plugins.
            file_path = os.path.join(config_dir, 'plugins', self.help_file_name)
            with open(file_path,'w') as f:
                f.write(load_resource('resources/{}'.format(self.help_file_name)))
            return file_path
        url = 'file:///' + get_help_file_resource()
        open_url(QUrl(url))
Пример #43
0
 def currentText(self):
     if self.currentIndex() == 0:
         return ''
     return QComboBox.currentText(self)
Пример #44
0
class MainWin(QMainWindow):
    def __init__(self, fileName=None, logName=None, parent=None):
        super(MainWin, self).__init__(parent)

        #self.setWindowIcon(QIcon(':/images/logo.png'))
        self.setToolButtonStyle(Qt.ToolButtonFollowStyle)
        self.setupFileActions()
        self.setupEditActions()
        self.setupTextActions()
        self.setupRunActions()
        self.initializeSettings()
        self.populateRunSettings()  # FIXME put in initializeSettings()?
        
        settingsMenu = QMenu('Settings', self)
        self.menuBar().addMenu(settingsMenu)
        settingsMenu.addAction('Configure...', self.configure)

        helpMenu = QMenu("Help", self)
        self.menuBar().addMenu(helpMenu)
        helpMenu.addAction("About", self.about)
        helpMenu.addAction("About &Qt", QApplication.instance().aboutQt)
 
        self.splitter = QSplitter(self)
        self.splitter.setOrientation(Qt.Vertical)
        self.textPane = TextPane()
        self.logPane = LogPane()
        self.logBox = QGroupBox()
        self.logBox.setFlat(True)
        vbox = QVBoxLayout()
        vbox.addWidget(self.logPane)
        self.logBox.setLayout(vbox)
        self.splitter.addWidget(self.textPane)
        self.splitter.addWidget(QLabel()) # spacer
        self.splitter.addWidget(self.logBox)
        self.setCentralWidget(self.splitter)

        self.loadSrc(fileName)
        self.loadLog(logName)
        #if logName and (-1 == self.comboLogFile.findText(logName)):
            #self.comboLogFile.addItem(logName)

        self.logPane.setFocus()
        self.fontChanged(self.textPane.font())
        self.textPane.document().modificationChanged.connect(self.actionSave.setEnabled)
        self.textPane.document().modificationChanged.connect(self.setWindowModified)
        self.textPane.document().undoAvailable.connect(self.actionUndo.setEnabled)
        self.textPane.document().redoAvailable.connect( self.actionRedo.setEnabled)
        self.setWindowModified(self.textPane.document().isModified())
        self.actionSave.setEnabled(self.textPane.document().isModified())
        self.actionUndo.setEnabled(self.textPane.document().isUndoAvailable())
        self.actionRedo.setEnabled(self.textPane.document().isRedoAvailable())
        self.actionUndo.triggered.connect(self.textPane.undo)
        self.actionRedo.triggered.connect(self.textPane.redo)
        self.actionCut.setEnabled(False)
        self.actionCopy.setEnabled(False)
        self.actionCut.triggered.connect(self.textPane.cut)
        self.actionCopy.triggered.connect(self.textPane.copy)
        self.actionPaste.triggered.connect(self.textPane.paste)
        self.textPane.copyAvailable.connect(self.actionCut.setEnabled)
        self.textPane.copyAvailable.connect(self.actionCopy.setEnabled)
        QApplication.clipboard().dataChanged.connect(self.clipboardDataChanged)
        
        self.actionRun.triggered.connect(self.scannoCheck)       
        self.logPane.lineMatchChanged.connect(self.logLineMatchChanged)

    def closeEvent(self, e):
        if self.maybeSave():
            e.accept()
        else:
            e.ignore()

    def setupFileActions(self):
        tb = QToolBar(self)
        tb.setWindowTitle("File Actions")
        self.addToolBar(tb)

        menu = QMenu("&File", self)
        self.menuBar().addMenu(menu)

        self.actionNew = QAction("&New", self, priority=QAction.LowPriority,
                shortcut=QKeySequence.New, triggered=self.fileNew)
        tb.addAction(self.actionNew)
        menu.addAction(self.actionNew)

        self.actionOpen = QAction("&Open...", self, shortcut=QKeySequence.Open,
                triggered=self.fileOpen)
        tb.addAction(self.actionOpen)
        menu.addAction(self.actionOpen)
        menu.addSeparator()

        self.actionSave = QAction("&Save", self, shortcut=QKeySequence.Save,
                triggered=self.fileSave, enabled=False)
        tb.addAction(self.actionSave)
        menu.addAction(self.actionSave)

        self.actionSaveAs = QAction("Save &As...", self, priority=QAction.LowPriority,
                shortcut=Qt.CTRL + Qt.SHIFT + Qt.Key_S, triggered=self.fileSaveAs)
        menu.addAction(self.actionSaveAs)
        menu.addSeparator()
 
        self.actionQuit = QAction("&Quit", self, shortcut=QKeySequence.Quit, triggered=self.close)
        menu.addAction(self.actionQuit)

    def setupEditActions(self):
        tb = QToolBar(self)
        tb.setWindowTitle("Edit Actions")
        self.addToolBar(tb)

        menu = QMenu("&Edit", self)
        self.menuBar().addMenu(menu)

        self.actionUndo = QAction("&Undo", self, shortcut=QKeySequence.Undo)
        tb.addAction(self.actionUndo)
        menu.addAction(self.actionUndo)

        self.actionRedo = QAction("&Redo", self, priority=QAction.LowPriority, shortcut=QKeySequence.Redo)
        tb.addAction(self.actionRedo)
        menu.addAction(self.actionRedo)
        menu.addSeparator()

        self.actionCut = QAction("Cu&t", self, priority=QAction.LowPriority, shortcut=QKeySequence.Cut)
        tb.addAction(self.actionCut)
        menu.addAction(self.actionCut)

        self.actionCopy = QAction("&Copy", self, priority=QAction.LowPriority, shortcut=QKeySequence.Copy)
        tb.addAction(self.actionCopy)
        menu.addAction(self.actionCopy)

        self.actionPaste = QAction("&Paste", self, priority=QAction.LowPriority,
                shortcut=QKeySequence.Paste, enabled=(len(QApplication.clipboard().text()) != 0))
        tb.addAction(self.actionPaste)
        menu.addAction(self.actionPaste)

    def setupTextActions(self):
        tb = QToolBar(self)
        tb.setWindowTitle("Format Actions")
        self.addToolBar(tb)

        tb = QToolBar(self)
        tb.setAllowedAreas(Qt.TopToolBarArea | Qt.BottomToolBarArea)
        tb.setWindowTitle("Format Actions")
        self.addToolBarBreak(Qt.TopToolBarArea)
        self.addToolBar(tb)

        self.comboFont = QFontComboBox(tb)
        tb.addWidget(self.comboFont)
        self.comboFont.activated[str].connect(self.textFamily)

        self.comboSize = QComboBox(tb)
        self.comboSize.setObjectName("comboSize")
        tb.addWidget(self.comboSize)
        self.comboSize.setEditable(True)

        db = QFontDatabase()
        for size in db.standardSizes():
            self.comboSize.addItem('{}'.format(size))

        self.comboSize.activated[str].connect(self.textSize)
        self.comboSize.setCurrentIndex(self.comboSize.findText('{}'.format(QApplication.font().pointSize())))
        
    def setupRunActions(self):
        tb = QToolBar(self)
        tb.setWindowTitle("Run Actions")
        self.addToolBar(tb)

        menu = QMenu("Run", self)
        self.menuBar().addMenu(menu)

        self.actionRun = QAction(
                "&Run", self, shortcut=Qt.CTRL + Qt.Key_R)
        tb.addAction(self.actionRun)
        menu.addAction(self.actionRun)

        self.comboScannoFile = QComboBox(tb)
        self.comboScannoFile.setObjectName("comboScannoFile")
        tb.addWidget(self.comboScannoFile)
        self.comboScannoFile.setEditable(True)
        
        self.comboLogFile= QComboBox(tb)
        self.comboLogFile.setObjectName("comboLogFile")
        self.comboLogFile.setEditable(True)
        tb.addWidget(self.comboLogFile)
    
    def populateRunSettings(self):
        for f in self.scannoFiles():
            self.comboScannoFile.addItem(f)
        if self.defaultScannoFile:
            idx = self.comboScannoFile.findText(self.defaultScannoFile)
            self.comboScannoFile.setCurrentIndex(idx)

        self.comboLogFile.addItem('plog.txt')

    def loadSrc(self, src):
        if src:
            if not self.textPane.load(src):
                return False
        self.setCurrentFileName(src)
        return True

    def loadLog(self, log):
        if log:
            if not self.logPane.load(log):
                return False
            self.comboLogFile.clear()
            self.comboLogFile.addItem(log)
        else:
            self.logPane.clear()
            self.logPane.setEnabled(False)
            self.logBox.setTitle(self.tr('No log file loaded.'))
        return True
        
    def maybeSave(self):
        if not self.textPane.document().isModified():
            return True

        ret = QMessageBox.warning(self, 'GuiScannos',
                'The document has been modified.\n'
                'Do you want to save your changes?',
                QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel)

        if ret == QMessageBox.Save:
            return self.fileSave()

        if ret == QMessageBox.Cancel:
            return False

        return True

    def setCurrentFileName(self, fileName=''):
        self.fileName = fileName
        self.textPane.document().setModified(False)

        if not fileName:
            shownName = 'untitled.txt'
            self.actionRun.setEnabled(False)
        else:
            shownName = QFileInfo(fileName).fileName()
            self.actionRun.setEnabled(True)

        self.setWindowTitle(self.tr('{}[*] - {}'.format(shownName, 'GUI Scannos')))
        self.setWindowModified(False)

    def fileNew(self):
        if self.maybeSave():
            self.textPane.clear()
            self.loadLog(None)  # clears logPane, logBox title, etc
            self.setCurrentFileName()

    def fileOpen(self):
        fn, _ = QFileDialog.getOpenFileName(self, 'Open File...', None, 'Text Files (*.txt);;All Files (*)')
        if fn:
            self.loadSrc(fn)
            self.loadLog(None)  # clears logPane, logBox title, etc

    def fileSave(self):
        if not self.fileName:
            return self.fileSaveAs()

        return self.textpane.save(self.fileName)

    def fileSaveAs(self):
        fn, _ = QFileDialog.getSaveFileName(self, "Save as...", None, "text files (*.txt);;All Files (*)")

        if not fn:
            return False

        self.setCurrentFileName(fn)
        return self.fileSave()

    def logLineMatchChanged(self):
        linenum = self.logPane.srcLineNum()
        col = self.logPane.srcColNum()
        s = self.logPane.srcScanno()
        self.textPane.setSelection(linenum, col, len(s))
    
    def textFamily(self, family):
        """Set font family for text and log panes."""
        
        self.textPane.setFontFamily(family)
        self.logPane.setFontFamily(family)

    def textSize(self, pointSize):
        """Set font size for text and log panes."""
        
        self.textPane.setFontPointSize(pointSize)
        self.logPane.setFontPointSize(pointSize)

    def clipboardDataChanged(self):
        self.actionPaste.setEnabled(len(QApplication.clipboard().text()) != 0)

    def about(self):
        QMessageBox.about(self, 'About', 'GUI for ppscannos.')

    def fontChanged(self, font):
        self.comboFont.setCurrentIndex(self.comboFont.findText(QFontInfo(font).family()))
        self.comboSize.setCurrentIndex(self.comboSize.findText('{}'.format(font.pointSize())))
        
    def scannoCheck(self):
        """Run ppscannos."""
        
        scannodir = os.path.dirname(self.ppscannos)
        cmd = sys.executable
        assert(cmd)
        scannoFile = self.comboScannoFile.currentText()
        if not scannoFile:
            scannoFile = self.defaultScannoFile
        scannoFile = scannodir + '/' + scannoFile
        src = self.fileName
        log = self.comboLogFile.currentText()
        if not log:
            log = './plog.txt'
        subprocess.call([cmd, self.ppscannos, '-s' + scannoFile, '-o' + log, '-i' + src])
        self.loadLog(log)
        self.logPane.setEnabled(True)
        
    def configure(self):
        """Configure application settings by way of a dialog."""
        
        dlg = ConfigDialog()
        if dlg.exec():
            self.setPPScannos(dlg.lineEditPPScannos.text())
            self.setDefaultScannoFile(dlg.comboScannoFiles.currentText())
            settings = QSettings(QApplication.organizationName(), QApplication.applicationName())
            settings.setValue('ppscannos', self.ppscannos)
            settings.setValue('defaultScannoFile', self.defaultScannoFile)

    def setPPScannos(self, s):
        self.ppscannos = s
        self.actionRun.setEnabled(self.ppscannos and os.path.exists(self.ppscannos))
        
    def scannoFiles(self):
        """Return list of .rc filenames (without path) that are in ppscannos directory."""
        
        if not self.ppscannos:
            return []
        return getRCFilesForDir(os.path.dirname(self.ppscannos))
        
    def setDefaultScannoFile(self, s):
        self.defaultScannoFile = s
        valid = False
        if self.defaultScannoFile and self.ppscannos and os.path.exists(self.ppscannos):
            if os.path.exists(os.path.dirname(self.ppscannos) + '/' + self.defaultScannoFile):
                valid = True
        self.actionRun.setEnabled(valid)
        
    def initializeSettings(self):
        """Load persistent config settings."""
        
        settings = QSettings()
        s = settings.value('ppscannos', type=str)
        if not s:
            # try the default
            s = os.path.expanduser('~') + '/ppscannos1/ppscannos1.py'
            #s = os.environ['HOME'] + '/ppscannos1/ppscannos1.py'
        self.setPPScannos(s)
        
        s = settings.value('defaultScannoFile', type=str)
        if (not s) and self.ppscannos:
            # try the default
            lst = getRCFilesForDir(os.path.dirname(self.ppscannos))
            if len(lst):
                # prefer 'regex.rc'; otherwise use the first one
                s = lst[0]
                for f in lst:
                    if f == 'regex.rc':
                        s = f
                        break
        self.setDefaultScannoFile(s)