Example #1
1
class PrincePDFDialog(QDialog):
    prince_log = ''

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

        self.setAttribute(Qt.WA_DeleteOnClose)

        self.setWindowTitle(_('Prince PDF'))
        self.setWindowIcon(icon)

        self.l = QGridLayout()
        self.setLayout(self.l)

        self.image = QLabel()
        self.image.setPixmap(icon.pixmap(120, 120))
        self.l.addWidget(self.image, 1, 1, 4, 1, Qt.AlignVCenter)

        self.convert_to_PDF_button = QPushButton(_('Convert to &PDF'), self)
        self.convert_to_PDF_button.clicked.connect(self.convert_to_PDF)
        self.convert_to_PDF_button.setDefault(True)
        self.convert_to_PDF_button.setToolTip(_('<qt>Start the conversion process</qt>'))
        self.l.addWidget(self.convert_to_PDF_button, 1, 2, Qt.AlignVCenter)

        self.view_log = QPushButton(_('&View log'), self)
        self.view_log.clicked.connect(self.show_log)
        self.view_log.setToolTip(_('<qt>Display the log from the last Prince run</qt>'))
        self.l.addWidget(self.view_log, 2, 2, Qt.AlignVCenter)
        self.view_log.hide()

        self.conf_button = QPushButton(_('Con&figure'), self)
        self.conf_button.clicked.connect(self.config)
        self.conf_button.setToolTip(_('<qt>Configure this plugin</qt>'))
        self.l.addWidget(self.conf_button, 4, 2, Qt.AlignVCenter)

        self.info = QLabel()
        self.l.addWidget(self.info, 5, 1, 1, -1)

        self.suggestion = QLabel()
        self.suggestion.setAlignment(Qt.AlignCenter)
        self.l.addWidget(self.suggestion, 6, 1, 1, -1)

        self.l.setColumnStretch(1, 1)
        self.l.setColumnStretch(2, 10)
        self.l.setRowStretch(1, 1)
        self.l.setRowStretch(2, 1)
        self.l.setRowStretch(3, 10)
        self.l.setRowStretch(4, 1)
        self.l.setRowStretch(5, 1)
        self.l.setRowStretch(6, 1)
        self.l.setRowMinimumHeight(1, 1)
        self.l.setRowMinimumHeight(2, 1)
        self.l.setRowMinimumHeight(3, 1)
        self.l.setRowMinimumHeight(4, 1)
        self.l.setRowMinimumHeight(5, 1)
        self.l.setRowMinimumHeight(6, 1)

        self.buttons = QDialogButtonBox(QDialogButtonBox.Close | QDialogButtonBox.Help)
        self.l.addWidget(self.buttons, 7, 1, 1, -1)
        self.buttons.rejected.connect(self.reject)
        self.buttons.helpRequested.connect(self.about)

        self.adjustSize()

        # Update the info now and every time the selection or the data changes
        self.gui.library_view.model().dataChanged.connect(self.update_info)
        self.gui.library_view.selectionModel().selectionChanged.connect(self.update_info)
        self.update_info()

    def update_info(self):
        '''
        Update the info on top of the window
        '''

        self.db = self.gui.current_db
        # Get selected rows
        rows = self.gui.library_view.selectionModel().selectedRows()
        if not rows or len(rows) == 0:
            self.info.setText(_('<b>No books selected</b>'))
            self.info.setAlignment(Qt.AlignCenter)
            self.suggestion.setText(_('Select one single book'))
            self.selected = None
            self.convert_to_PDF_button.setEnabled(False)
        elif len(rows) > 1:
            self.info.setText(_('<b>Many books selected</b>'))
            self.info.setAlignment(Qt.AlignCenter)
            self.suggestion.setText(_('Select one single book'))
            self.selected = None
            self.convert_to_PDF_button.setEnabled(False)
        else:
            # If there is only one selected book, enable conversion
            # and show list of available formats (formats in prefs are bold)
            book_id = self.gui.library_view.model().id(rows[0])
            fmts = self.db.formats(book_id, index_is_id=True)
            if (fmts):
                fmts = fmts.split(',')
            else: 
                fmts = [_('<i>none</i>')]
            available = False
            for i,fmt in enumerate(fmts):
                fmts[i] = fmt.lower()
                if fmts[i] in prefs['formats']:
                    fmts[i] = '<b>%s</b>' % fmts[i]
                    available = True
            self.info.setText(_('Available formats: %s') % ', '.join(fmts))
            self.info.setAlignment(Qt.AlignLeft)
            # Conversion enabled only if some "preferred" format is found
            if (available):
                self.suggestion.setText(_('Ready'))
                self.selected = book_id
                self.convert_to_PDF_button.setEnabled(True)
            else:
                self.suggestion.setText(_('No preferred format available'))
                self.selected = None
                self.convert_to_PDF_button.setEnabled(False)

    def about(self):
        '''
        Display a short help message
        '''
        from os.path import join
        from calibre.ptempfile import TemporaryDirectory
        from calibre.gui2.dialogs.message_box import MessageBox
        from calibre_plugins.prince_pdf.help import help_txt, license_txt
        from calibre_plugins.prince_pdf import PrincePDFPlugin
        from calibre_plugins.prince_pdf import __license__

        author = PrincePDFPlugin.author
        version = "%i.%i.%i" % PrincePDFPlugin.version
        license = __license__
        with TemporaryDirectory('xxx') as tdir:
          for x in ('prince_icon.png', 'small_icon.png'):
            with open(join(tdir, x),'w') as f:
              f.write(get_resources('images/' + x))
          help_box = MessageBox(type_ = MessageBox.INFO, \
                                title = _('About the Prince PDF Plugin'), \
                                msg = help_txt % {'author':author, 'version':version, 'license':license, 'dir':tdir, 'code':'style="font-family:monospace ; font-weight:bold"'}, \
                                det_msg = 'Copyright \u00a9 %s\n%s' % (__copyright__, license_txt), \
                                q_icon = self.icon, \
                                show_copy_button = False)
          #help_box.gridLayout.addWidget(help_box.icon_widget,0,0,Qt.AlignTop)
          help_box.gridLayout.setAlignment(help_box.icon_widget,Qt.AlignTop)
          help_box.exec_()

    def convert_to_PDF(self):
        '''
        Unpack and convert the currently selected book to PDF
        '''
        from calibre.gui2 import error_dialog
        from calibre.constants import DEBUG

        # Get available formats
        book_id = self.selected
        fmts = self.db.formats(book_id, index_is_id=True)
        fmts = fmts.lower().split(',')
        # Process only the first format matching the 'formats' configuration option
        for fmt in prefs['formats']:
            fmt = fmt.lower()
            if (not fmt in fmts): continue
            mi = self.db.get_metadata(book_id, index_is_id=True, get_cover=False)
            pdf_base_file = self.get_filename(book_id, mi)

            # This is the actual code:
            if DEBUG: print('===========')
            # Unpack the book and call the conversion dialog
            (opf, oeb) = self.unpack(book_id, fmt)
            if (opf == None or oeb == None):
               return error_dialog(self.gui, _('Cannot convert to PDF'), _('Format not supported: %s') % fmt, show=True)
            convert_dialog = ConvertDialog(mi, fmt, opf, oeb, self.icon)
            convert_dialog.pdf_file = pdf_base_file
            pdf_file = ''
            if (convert_dialog.exec_()):
                pdf_file = convert_dialog.pdf_file
            self.prince_log = convert_dialog.prince_log
            # After the dialog returns, pdf_file has the output file path,
            # and prince_log has the Prince console output
            if DEBUG: print(_('PDF file: %s') % pdf_file)
            # If there is any log, enable the View log button
            if (self.prince_log):
                self.view_log.show()
                log_msg = _(' Check the log.')
            else:
                self.view_log.hide()
                log_msg = ''
            # If the conversion failed, pdf_file will be None,
            if (pdf_file == None):
                error_dialog(self.gui, _('Could not convert to PDF'), _('The conversion failed.') + log_msg , show=True)
            # If the user cancelled the dialog, pdf_file will be ''
            if (pdf_file):
                # Set the metadata in the PDF and add it or save it
                try:
                    self.set_pdf_metadata(mi, pdf_file)
                except:
                    error_dialog(self.gui, _('Could not convert to PDF'), _("Error reading or writing the PDF file:\n%s" % pdf_file), show=True)
                    return
                if (prefs['add_book']):
                    self.add_pdf(book_id, pdf_file, ('pdf' in fmts))
                else:
                    self.save_pdf(pdf_file, pdf_base_file)
	    if DEBUG: print('===========')
	    return
        # No matching format in the book
        return error_dialog(self.gui, _('Cannot convert to PDF'), _('No supported format available'), show=True)

    def show_log(self):
        '''
        Display the Prince log dialog
        '''
        msg = LogDialog(self.prince_log, self.icon)
        msg.exec_()

    def config(self):
        '''
        Display the configuration dialog
        '''
        self.do_user_config(parent=self)

    def get_filename(self, book_id, mi):
        '''
        Obtain a filename from the save_to_disk template
        :param book_id: The book identifier
        :param mi: The book metadata
        '''
        from os.path import join
        from calibre.library.save_to_disk import get_components, config
        from calibre import sanitize_file_name_unicode
        from calibre.utils.filenames import ascii_filename

        opts = config().parse()
        components = get_components(opts.template, mi, book_id, opts.timefmt,
                                    sanitize_func=(ascii_filename if opts.asciiize else sanitize_file_name_unicode),
                                    to_lowercase=opts.to_lowercase, replace_whitespace=opts.replace_whitespace)
        base_path = join(*components)
        return '%s.pdf' % base_path

    def unpack(self, book_id, fmt):
        '''
        Unpack the book in a temporary directory
        :param book_id: The book identifier
        :param fmt: The format to unpack
        '''
        from calibre.constants import DEBUG
        from calibre.ptempfile import PersistentTemporaryDirectory
        from calibre.ebooks.tweak import get_tools
        from calibre.ebooks.oeb.base import OEBBook
        from calibre.ebooks.oeb.reader import OEBReader
        from calibre.utils.logging import default_log
        from calibre_plugins.prince_pdf.dummy_preprocessor import dummy_preprocessor

        book_file = self.db.format(book_id, fmt, index_is_id=True, as_path=True)
        if DEBUG: print(_('Unpacking book...'))
        tdir = PersistentTemporaryDirectory('_unpack')
        exploder = get_tools(fmt)[0]
        if (exploder == None): return (None, None)
        opf = exploder(book_file, tdir)
        html_preprocessor = dummy_preprocessor()
        css_preprocessor = dummy_preprocessor()
        oeb = OEBBook(default_log, html_preprocessor, css_preprocessor)
        OEBReader()(oeb, opf)
        return (opf, oeb)

    def set_pdf_metadata(self, mi, pdf_file):
        '''
        Set the metadata in the PDF file
        :param mi: The book metadata
        :param pdf_file: The path to the PDF file
        '''
        from calibre.constants import DEBUG
        from calibre.ebooks.metadata.pdf import set_metadata

        if DEBUG: print(_('Setting metadata...'))
        pdf_stream = open(pdf_file, 'r+b')
        set_metadata(pdf_stream, mi)
        pdf_stream.close()

    def add_pdf(self, book_id, pdf_file, exists):
        '''
        Add the PDF file to the book record, asking for replacement
        :param book_id: The book identifier
        :param pdf_file: The path to the PDF file
        :param exists: True if there is already a PDF in the book
        '''
        from calibre.constants import DEBUG
        from calibre.gui2.dialogs.message_box import MessageBox

        add_it = True
        if (exists):
            msg = MessageBox(MessageBox.QUESTION, _('Existing format'),
                             _('The selected book already contains a PDF format. Are you sure you want to replace it?'),
                             _("The temporary file can be found in:\n%s") % pdf_file)
            msg.toggle_det_msg()
            add_it = (msg.exec_())
        if (add_it):
            if DEBUG: print(_('Adding PDF...'))
            self.db.new_api.add_format(book_id, 'pdf', pdf_file)
            self.gui.library_view.model().refresh_ids([book_id])
            self.gui.library_view.refresh_book_details()
            self.gui.tags_view.recount()

    def save_pdf(self, pdf_file, pdf_base_file):
        '''
        Save the PDF file in the final location
        :param pdf_file: The path to the PDF file
        :param pdf_base_file: The desired file name and relative path
        '''
        from os import rename, makedirs
        from os.path import basename, dirname, join, exists
        from calibre.constants import DEBUG
        from calibre.gui2 import choose_dir
        from calibre.gui2.dialogs.message_box import MessageBox
        from calibre.gui2 import error_dialog

        path = choose_dir(self.gui, 'save to disk dialog', _('Choose destination directory'))
        if not path:
            return
        save_file = join(path, pdf_base_file)
        base_dir = dirname(save_file)
        try:
            makedirs(base_dir)
        except BaseException:
            if not exists(base_dir): raise
        try:
            rename(pdf_file, save_file)
        except:
            error_dialog(self.gui, _('Could not save PDF'), _("Error writing the PDF file:\n%s" % save_file), show=True)
            return
        if DEBUG: print(save_file)
        MessageBox(MessageBox.INFO, _('File saved'), _("PDF file saved in:\n%s") % save_file).exec_()
Example #2
0
    def __init__(self, plugin):
        DefaultConfigWidget.__init__(self, plugin)
        c = plugin_prefs[STORE_NAME]

        other_group_box = QGroupBox('Other options', self)
        self.l.addWidget(other_group_box, self.l.rowCount(), 0, 1, 2)
        other_group_box_layout = QGridLayout()
        other_group_box.setLayout(other_group_box_layout)

        # Maximum # of title/author searches to review.
        max_label = QLabel(
            'Maximum title/author search matches to evaluate (1 = fastest):',
            self)
        max_label.setToolTip(
            'ISFDB doesn\'t always have links to large covers for every ISBN\n'
            'of the same book. Increasing this value will take effect when doing\n'
            'title/author searches to consider more ISBN editions.\n\n'
            'This will increase the potential likelihood of getting a larger cover,\n'
            'though does not guarantee it.')
        other_group_box_layout.addWidget(max_label, 0, 0, 1, 1)
        self.max_downloads_spin = QtGui.QSpinBox(self)
        self.max_downloads_spin.setMinimum(1)
        self.max_downloads_spin.setMaximum(5)
        self.max_downloads_spin.setProperty(
            'value',
            c.get(KEY_MAX_DOWNLOADS, DEFAULT_STORE_VALUES[KEY_MAX_DOWNLOADS]))
        other_group_box_layout.addWidget(self.max_downloads_spin, 0, 1, 1, 1)
        other_group_box_layout.setColumnStretch(2, 1)

        # Contents field, if possible.
        self.contents_checkbox = QCheckBox(
            'Append Contents if available to comments', self)
        self.contents_checkbox.setToolTip(
            'Choosing this option will write the Contents section to the comments\n'
            'field, if such a section exists.')
        self.contents_checkbox.setChecked(
            c.get(KEY_APPEND_CONTENTS,
                  DEFAULT_STORE_VALUES[KEY_APPEND_CONTENTS]))
        other_group_box_layout.addWidget(self.contents_checkbox, 2, 0, 1, 3)
def populate_metadata_page(layout, db, book_id, bulk=False, two_column=False, parent=None):
    def widget_factory(typ, key):
        if bulk:
            w = bulk_widgets[typ](db, key, parent)
        else:
            w = widgets[typ](db, key, parent)
        if book_id is not None:
            w.initialize(book_id)
        return w
    fm = db.field_metadata

    # Get list of all non-composite custom fields. We must make widgets for these
    fields = fm.custom_field_keys(include_composites=False)
    cols_to_display = fields
    cols_to_display.sort(key=partial(field_sort_key, fm=fm))

    # This will contain the fields in the order to display them
    cols = []

    # The fields named here must be first in the widget list
    tweak_cols = tweaks['metadata_edit_custom_column_order']
    comments_in_tweak = 0
    for key in (tweak_cols or ()):
        # Add the key if it really exists in the database
        if key in cols_to_display:
            cols.append(key)
            if fm[key]['datatype'] == 'comments' and fm[key].get('display', {}).get('interpret_as') != 'short-text':
                comments_in_tweak += 1

    # Add all the remaining fields
    comments_not_in_tweak = 0
    for key in cols_to_display:
        if key not in cols:
            cols.append(key)
            if fm[key]['datatype'] == 'comments' and fm[key].get('display', {}).get('interpret_as') != 'short-text':
                comments_not_in_tweak += 1

    count = len(cols)
    layout_rows_for_comments = 9
    if two_column:
        turnover_point = ((count-comments_not_in_tweak+1) +
                          comments_in_tweak*(layout_rows_for_comments-1))/2
    else:
        # Avoid problems with multi-line widgets
        turnover_point = count + 1000
    ans = []
    column = row = base_row = max_row = 0
    for key in cols:
        if not fm[key]['is_editable']:
            continue  # this almost never happens
        dt = fm[key]['datatype']
        if dt == 'composite' or (bulk and dt == 'comments'):
            continue
        is_comments = dt == 'comments' and fm[key].get('display', {}).get('interpret_as') != 'short-text'
        w = widget_factory(dt, fm[key]['colnum'])
        ans.append(w)
        if two_column and is_comments:
            # Here for compatibility with old layout. Comments always started
            # in the left column
            comments_in_tweak -= 1
            # no special processing if the comment field was named in the tweak
            if comments_in_tweak < 0 and comments_not_in_tweak > 0:
                # Force a turnover, adding comments widgets below max_row.
                # Save the row to return to if we turn over again
                column = 0
                row = max_row
                base_row = row
                turnover_point = row + (comments_not_in_tweak * layout_rows_for_comments)/2
                comments_not_in_tweak = 0

        l = QGridLayout()
        if is_comments:
            layout.addLayout(l, row, column, layout_rows_for_comments, 1)
            layout.setColumnStretch(column, 100)
            row += layout_rows_for_comments
        else:
            layout.addLayout(l, row, column, 1, 1)
            layout.setColumnStretch(column, 100)
            row += 1
        for c in range(0, len(w.widgets), 2):
            if not is_comments:
                w.widgets[c].setWordWrap(True)
                w.widgets[c].setBuddy(w.widgets[c+1])
                l.addWidget(w.widgets[c], c, 0)
                l.addWidget(w.widgets[c+1], c, 1)
                l.setColumnStretch(1, 10000)
            else:
                l.addWidget(w.widgets[0], 0, 0, 1, 2)
        l.addItem(QSpacerItem(0, 0, vPolicy=QSizePolicy.Expanding), c, 0, 1, 1)
        max_row = max(max_row, row)
        if row >= turnover_point:
            column = 1
            turnover_point = count + 1000
            row = base_row

    items = []
    if len(ans) > 0:
        items.append(QSpacerItem(10, 10, QSizePolicy.Minimum,
            QSizePolicy.Expanding))
        layout.addItem(items[-1], layout.rowCount(), 0, 1, 1)
        layout.setRowStretch(layout.rowCount()-1, 100)
    return ans, items
Example #4
0
    def __init__(self, window, plugin, keystore, device_id):
        title = _("%s Settings") % plugin.device
        super(SettingsDialog, self).__init__(window, title)
        self.setMaximumWidth(540)

        devmgr = plugin.device_manager()
        config = devmgr.config
        handler = keystore.handler
        thread = keystore.thread
        hs_rows, hs_cols = (64, 128)

        def invoke_client(method, *args, **kw_args):
            unpair_after = kw_args.pop('unpair_after', False)

            def task():
                client = devmgr.client_by_id(device_id)
                if not client:
                    raise RuntimeError("Device not connected")
                if method:
                    getattr(client, method)(*args, **kw_args)
                if unpair_after:
                    devmgr.unpair_id(device_id)
                return client.features

            thread.add(task, on_success=update)

        def update(features):
            self.features = features
            set_label_enabled()
            bl_hash = bh2u(features.bootloader_hash)
            bl_hash = "\n".join([bl_hash[:32], bl_hash[32:]])
            noyes = [_("No"), _("Yes")]
            endis = [_("Enable Passphrases"), _("Disable Passphrases")]
            disen = [_("Disabled"), _("Enabled")]
            setchange = [_("Set a PIN"), _("Change PIN")]

            version = "%d.%d.%d" % (features.major_version,
                                    features.minor_version,
                                    features.patch_version)
            coins = ", ".join(coin.coin_name for coin in features.coins)

            device_label.setText(features.label)
            pin_set_label.setText(noyes[features.pin_protection])
            passphrases_label.setText(disen[features.passphrase_protection])
            bl_hash_label.setText(bl_hash)
            label_edit.setText(features.label)
            device_id_label.setText(features.device_id)
            initialized_label.setText(noyes[features.initialized])
            version_label.setText(version)
            coins_label.setText(coins)
            clear_pin_button.setVisible(features.pin_protection)
            clear_pin_warning.setVisible(features.pin_protection)
            pin_button.setText(setchange[features.pin_protection])
            pin_msg.setVisible(not features.pin_protection)
            passphrase_button.setText(endis[features.passphrase_protection])
            language_label.setText(features.language)

        def set_label_enabled():
            label_apply.setEnabled(label_edit.text() != self.features.label)

        def rename():
            invoke_client('change_label', label_edit.text())

        def toggle_passphrase():
            title = _("Confirm Toggle Passphrase Protection")
            currently_enabled = self.features.passphrase_protection
            if currently_enabled:
                msg = _("After disabling passphrases, you can only pair this "
                        "Electrum-DASH wallet if it had an empty passphrase.  "
                        "If its passphrase was not empty, you will need to "
                        "create a new wallet with the install wizard.  You "
                        "can use this wallet again at any time by re-enabling "
                        "passphrases and entering its passphrase.")
            else:
                msg = _(
                    "Your current Electrum-DASH wallet can only be used with "
                    "an empty passphrase.  You must create a separate "
                    "wallet with the install wizard for other passphrases "
                    "as each one generates a new set of addresses.")
            msg += "\n\n" + _("Are you sure you want to proceed?")
            if not self.question(msg, title=title):
                return
            invoke_client('toggle_passphrase', unpair_after=currently_enabled)

        def change_homescreen():
            from PIL import Image  # FIXME
            dialog = QFileDialog(self, _("Choose Homescreen"))
            filename, __ = dialog.getOpenFileName()
            if filename:
                im = Image.open(str(filename))
                if im.size != (hs_cols, hs_rows):
                    raise Exception('Image must be 64 x 128 pixels')
                im = im.convert('1')
                pix = im.load()
                img = ''
                for j in range(hs_rows):
                    for i in range(hs_cols):
                        img += '1' if pix[i, j] else '0'
                img = ''.join(
                    chr(int(img[i:i + 8], 2)) for i in range(0, len(img), 8))
                invoke_client('change_homescreen', img)

        def clear_homescreen():
            invoke_client('change_homescreen', '\x00')

        def set_pin():
            invoke_client('set_pin', remove=False)

        def clear_pin():
            invoke_client('set_pin', remove=True)

        def wipe_device():
            wallet = window.wallet
            if wallet and sum(wallet.get_balance()):
                title = _("Confirm Device Wipe")
                msg = _("Are you SURE you want to wipe the device?\n"
                        "Your wallet still has Dash coins in it!")
                if not self.question(
                        msg, title=title, icon=QMessageBox.Critical):
                    return
            invoke_client('wipe_device', unpair_after=True)

        def slider_moved():
            mins = timeout_slider.sliderPosition()
            timeout_minutes.setText(_("%2d minutes") % mins)

        def slider_released():
            config.set_session_timeout(timeout_slider.sliderPosition() * 60)

        # Information tab
        info_tab = QWidget()
        info_layout = QVBoxLayout(info_tab)
        info_glayout = QGridLayout()
        info_glayout.setColumnStretch(2, 1)
        device_label = QLabel()
        pin_set_label = QLabel()
        passphrases_label = QLabel()
        version_label = QLabel()
        device_id_label = QLabel()
        bl_hash_label = QLabel()
        bl_hash_label.setWordWrap(True)
        coins_label = QLabel()
        coins_label.setWordWrap(True)
        language_label = QLabel()
        initialized_label = QLabel()
        rows = [
            (_("Device Label"), device_label),
            (_("PIN set"), pin_set_label),
            (_("Passphrases"), passphrases_label),
            (_("Firmware Version"), version_label),
            (_("Device ID"), device_id_label),
            (_("Bootloader Hash"), bl_hash_label),
            (_("Supported Coins"), coins_label),
            (_("Language"), language_label),
            (_("Initialized"), initialized_label),
        ]
        for row_num, (label, widget) in enumerate(rows):
            info_glayout.addWidget(QLabel(label), row_num, 0)
            info_glayout.addWidget(widget, row_num, 1)
        info_layout.addLayout(info_glayout)

        # Settings tab
        settings_tab = QWidget()
        settings_layout = QVBoxLayout(settings_tab)
        settings_glayout = QGridLayout()

        # Settings tab - Label
        label_msg = QLabel(
            _("Name this %s.  If you have mutiple devices "
              "their labels help distinguish them.") % plugin.device)
        label_msg.setWordWrap(True)
        label_label = QLabel(_("Device Label"))
        label_edit = QLineEdit()
        label_edit.setMinimumWidth(150)
        label_edit.setMaxLength(plugin.MAX_LABEL_LEN)
        label_apply = QPushButton(_("Apply"))
        label_apply.clicked.connect(rename)
        label_edit.textChanged.connect(set_label_enabled)
        settings_glayout.addWidget(label_label, 0, 0)
        settings_glayout.addWidget(label_edit, 0, 1, 1, 2)
        settings_glayout.addWidget(label_apply, 0, 3)
        settings_glayout.addWidget(label_msg, 1, 1, 1, -1)

        # Settings tab - PIN
        pin_label = QLabel(_("PIN Protection"))
        pin_button = QPushButton()
        pin_button.clicked.connect(set_pin)
        settings_glayout.addWidget(pin_label, 2, 0)
        settings_glayout.addWidget(pin_button, 2, 1)
        pin_msg = QLabel(
            _("PIN protection is strongly recommended.  "
              "A PIN is your only protection against someone "
              "stealing your Dash coins if they obtain physical "
              "access to your %s.") % plugin.device)
        pin_msg.setWordWrap(True)
        pin_msg.setStyleSheet("color: red")
        settings_glayout.addWidget(pin_msg, 3, 1, 1, -1)

        # Settings tab - Homescreen
        if plugin.device != 'KeepKey':  # Not yet supported by KK firmware
            homescreen_layout = QHBoxLayout()
            homescreen_label = QLabel(_("Homescreen"))
            homescreen_change_button = QPushButton(_("Change..."))
            homescreen_clear_button = QPushButton(_("Reset"))
            homescreen_change_button.clicked.connect(change_homescreen)
            homescreen_clear_button.clicked.connect(clear_homescreen)
            homescreen_msg = QLabel(
                _("You can set the homescreen on your "
                  "device to personalize it.  You must "
                  "choose a %d x %d monochrome black and "
                  "white image.") % (hs_rows, hs_cols))
            homescreen_msg.setWordWrap(True)
            settings_glayout.addWidget(homescreen_label, 4, 0)
            settings_glayout.addWidget(homescreen_change_button, 4, 1)
            settings_glayout.addWidget(homescreen_clear_button, 4, 2)
            settings_glayout.addWidget(homescreen_msg, 5, 1, 1, -1)

        # Settings tab - Session Timeout
        timeout_label = QLabel(_("Session Timeout"))
        timeout_minutes = QLabel()
        timeout_slider = QSlider(Qt.Horizontal)
        timeout_slider.setRange(1, 60)
        timeout_slider.setSingleStep(1)
        timeout_slider.setTickInterval(5)
        timeout_slider.setTickPosition(QSlider.TicksBelow)
        timeout_slider.setTracking(True)
        timeout_msg = QLabel(
            _("Clear the session after the specified period "
              "of inactivity.  Once a session has timed out, "
              "your PIN and passphrase (if enabled) must be "
              "re-entered to use the device."))
        timeout_msg.setWordWrap(True)
        timeout_slider.setSliderPosition(config.get_session_timeout() // 60)
        slider_moved()
        timeout_slider.valueChanged.connect(slider_moved)
        timeout_slider.sliderReleased.connect(slider_released)
        settings_glayout.addWidget(timeout_label, 6, 0)
        settings_glayout.addWidget(timeout_slider, 6, 1, 1, 3)
        settings_glayout.addWidget(timeout_minutes, 6, 4)
        settings_glayout.addWidget(timeout_msg, 7, 1, 1, -1)
        settings_layout.addLayout(settings_glayout)
        settings_layout.addStretch(1)

        # Advanced tab
        advanced_tab = QWidget()
        advanced_layout = QVBoxLayout(advanced_tab)
        advanced_glayout = QGridLayout()

        # Advanced tab - clear PIN
        clear_pin_button = QPushButton(_("Disable PIN"))
        clear_pin_button.clicked.connect(clear_pin)
        clear_pin_warning = QLabel(
            _("If you disable your PIN, anyone with physical access to your "
              "%s device can spend your Dash coins.") % plugin.device)
        clear_pin_warning.setWordWrap(True)
        clear_pin_warning.setStyleSheet("color: red")
        advanced_glayout.addWidget(clear_pin_button, 0, 2)
        advanced_glayout.addWidget(clear_pin_warning, 1, 0, 1, 5)

        # Advanced tab - toggle passphrase protection
        passphrase_button = QPushButton()
        passphrase_button.clicked.connect(toggle_passphrase)
        passphrase_msg = WWLabel(PASSPHRASE_HELP)
        passphrase_warning = WWLabel(PASSPHRASE_NOT_PIN)
        passphrase_warning.setStyleSheet("color: red")
        advanced_glayout.addWidget(passphrase_button, 3, 2)
        advanced_glayout.addWidget(passphrase_msg, 4, 0, 1, 5)
        advanced_glayout.addWidget(passphrase_warning, 5, 0, 1, 5)

        # Advanced tab - wipe device
        wipe_device_button = QPushButton(_("Wipe Device"))
        wipe_device_button.clicked.connect(wipe_device)
        wipe_device_msg = QLabel(
            _("Wipe the device, removing all data from it.  The firmware "
              "is left unchanged."))
        wipe_device_msg.setWordWrap(True)
        wipe_device_warning = QLabel(
            _("Only wipe a device if you have the recovery seed written down "
              "and the device wallet(s) are empty, otherwise the Dash coins "
              "will be lost forever."))
        wipe_device_warning.setWordWrap(True)
        wipe_device_warning.setStyleSheet("color: red")
        advanced_glayout.addWidget(wipe_device_button, 6, 2)
        advanced_glayout.addWidget(wipe_device_msg, 7, 0, 1, 5)
        advanced_glayout.addWidget(wipe_device_warning, 8, 0, 1, 5)
        advanced_layout.addLayout(advanced_glayout)
        advanced_layout.addStretch(1)

        tabs = QTabWidget(self)
        tabs.addTab(info_tab, _("Information"))
        tabs.addTab(settings_tab, _("Settings"))
        tabs.addTab(advanced_tab, _("Advanced"))
        dialog_vbox = QVBoxLayout(self)
        dialog_vbox.addWidget(tabs)
        dialog_vbox.addLayout(Buttons(CloseButton(self)))

        # Update information
        invoke_client(None)
Example #5
0
    def __init__(self, window, plugin, keystore, device_id):
        title = _("{} Settings").format(plugin.device)
        super(SettingsDialog, self).__init__(window, title)
        self.setMaximumWidth(540)

        devmgr = plugin.device_manager()
        config = devmgr.config
        handler = keystore.handler
        thread = keystore.thread
        hs_rows, hs_cols = (64, 128)

        def invoke_client(method, *args, **kw_args):
            unpair_after = kw_args.pop('unpair_after', False)

            def task():
                client = devmgr.client_by_id(device_id)
                if not client:
                    raise RuntimeError("Device not connected")
                if method:
                    getattr(client, method)(*args, **kw_args)
                if unpair_after:
                    devmgr.unpair_id(device_id)
                return client.features

            thread.add(task, on_success=update)

        def update(features):
            self.features = features
            set_label_enabled()
            if features.bootloader_hash:
                bl_hash = bh2u(features.bootloader_hash)
                bl_hash = "\n".join([bl_hash[:32], bl_hash[32:]])
            else:
                bl_hash = "N/A"
            noyes = [_("No"), _("Yes")]
            endis = [_("Enable Passphrases"), _("Disable Passphrases")]
            disen = [_("Disabled"), _("Enabled")]
            setchange = [_("Set a PIN"), _("Change PIN")]

            version = "%d.%d.%d" % (features.major_version,
                                    features.minor_version,
                                    features.patch_version)

            device_label.setText(features.label)
            pin_set_label.setText(noyes[features.pin_protection])
            passphrases_label.setText(disen[features.passphrase_protection])
            bl_hash_label.setText(bl_hash)
            label_edit.setText(features.label)
            device_id_label.setText(features.device_id)
            initialized_label.setText(noyes[features.initialized])
            version_label.setText(version)
            clear_pin_button.setVisible(features.pin_protection)
            clear_pin_warning.setVisible(features.pin_protection)
            pin_button.setText(setchange[features.pin_protection])
            pin_msg.setVisible(not features.pin_protection)
            passphrase_button.setText(endis[features.passphrase_protection])
            language_label.setText(features.language)

        def set_label_enabled():
            label_apply.setEnabled(label_edit.text() != self.features.label)

        def rename():
            invoke_client('change_label', label_edit.text())

        def toggle_passphrase():
            title = _("Confirm Toggle Passphrase Protection")
            currently_enabled = self.features.passphrase_protection
            if currently_enabled:
                msg = _("After disabling passphrases, you can only pair this "
                        "Electrum wallet if it had an empty passphrase.  "
                        "If its passphrase was not empty, you will need to "
                        "create a new wallet with the install wizard.  You "
                        "can use this wallet again at any time by re-enabling "
                        "passphrases and entering its passphrase.")
            else:
                msg = _("Your current Electrum wallet can only be used with "
                        "an empty passphrase.  You must create a separate "
                        "wallet with the install wizard for other passphrases "
                        "as each one generates a new set of addresses.")
            msg += "\n\n" + _("Are you sure you want to proceed?")
            if not self.question(msg, title=title):
                return
            invoke_client('toggle_passphrase', unpair_after=currently_enabled)

        def change_homescreen():
            dialog = QFileDialog(self, _("Choose Homescreen"))
            filename, __ = dialog.getOpenFileName()
            if not filename:
                return  # user cancelled

            if filename.endswith('.toif'):
                img = open(filename, 'rb').read()
                if img[:8] != b'TOIf\x90\x00\x90\x00':
                    handler.show_error('File is not a TOIF file with size of 144x144')
                    return
            else:
                from PIL import Image # FIXME
                im = Image.open(filename)
                if im.size != (128, 64):
                    handler.show_error('Image must be 128 x 64 pixels')
                    return
                im = im.convert('1')
                pix = im.load()
                img = bytearray(1024)
                for j in range(64):
                    for i in range(128):
                        if pix[i, j]:
                            o = (i + j * 128)
                            img[o // 8] |= (1 << (7 - o % 8))
                img = bytes(img)
            invoke_client('change_homescreen', img)

        def clear_homescreen():
            invoke_client('change_homescreen', b'\x00')

        def set_pin():
            invoke_client('set_pin', remove=False)

        def clear_pin():
            invoke_client('set_pin', remove=True)

        def wipe_device():
            wallet = window.wallet
            if wallet and sum(wallet.get_balance()):
                title = _("Confirm Device Wipe")
                msg = _("Are you SURE you want to wipe the device?\n"
                        "Your wallet still has bitcoins in it!")
                if not self.question(msg, title=title,
                                     icon=QMessageBox.Critical):
                    return
            invoke_client('wipe_device', unpair_after=True)

        def slider_moved():
            mins = timeout_slider.sliderPosition()
            timeout_minutes.setText(_("%2d minutes") % mins)

        def slider_released():
            config.set_session_timeout(timeout_slider.sliderPosition() * 60)

        # Information tab
        info_tab = QWidget()
        info_layout = QVBoxLayout(info_tab)
        info_glayout = QGridLayout()
        info_glayout.setColumnStretch(2, 1)
        device_label = QLabel()
        pin_set_label = QLabel()
        passphrases_label = QLabel()
        version_label = QLabel()
        device_id_label = QLabel()
        bl_hash_label = QLabel()
        bl_hash_label.setWordWrap(True)
        language_label = QLabel()
        initialized_label = QLabel()
        rows = [
            (_("Device Label"), device_label),
            (_("PIN set"), pin_set_label),
            (_("Passphrases"), passphrases_label),
            (_("Firmware Version"), version_label),
            (_("Device ID"), device_id_label),
            (_("Bootloader Hash"), bl_hash_label),
            (_("Language"), language_label),
            (_("Initialized"), initialized_label),
        ]
        for row_num, (label, widget) in enumerate(rows):
            info_glayout.addWidget(QLabel(label), row_num, 0)
            info_glayout.addWidget(widget, row_num, 1)
        info_layout.addLayout(info_glayout)

        # Settings tab
        settings_tab = QWidget()
        settings_layout = QVBoxLayout(settings_tab)
        settings_glayout = QGridLayout()

        # Settings tab - Label
        label_msg = QLabel(_("Name this {}.  If you have multiple devices "
                             "their labels help distinguish them.")
                           .format(plugin.device))
        label_msg.setWordWrap(True)
        label_label = QLabel(_("Device Label"))
        label_edit = QLineEdit()
        label_edit.setMinimumWidth(150)
        label_edit.setMaxLength(plugin.MAX_LABEL_LEN)
        label_apply = QPushButton(_("Apply"))
        label_apply.clicked.connect(rename)
        label_edit.textChanged.connect(set_label_enabled)
        settings_glayout.addWidget(label_label, 0, 0)
        settings_glayout.addWidget(label_edit, 0, 1, 1, 2)
        settings_glayout.addWidget(label_apply, 0, 3)
        settings_glayout.addWidget(label_msg, 1, 1, 1, -1)

        # Settings tab - PIN
        pin_label = QLabel(_("PIN Protection"))
        pin_button = QPushButton()
        pin_button.clicked.connect(set_pin)
        settings_glayout.addWidget(pin_label, 2, 0)
        settings_glayout.addWidget(pin_button, 2, 1)
        pin_msg = QLabel(_("PIN protection is strongly recommended.  "
                           "A PIN is your only protection against someone "
                           "stealing your bitcoins if they obtain physical "
                           "access to your {}.").format(plugin.device))
        pin_msg.setWordWrap(True)
        pin_msg.setStyleSheet("color: red")
        settings_glayout.addWidget(pin_msg, 3, 1, 1, -1)

        # Settings tab - Homescreen
        homescreen_label = QLabel(_("Homescreen"))
        homescreen_change_button = QPushButton(_("Change..."))
        homescreen_clear_button = QPushButton(_("Reset"))
        homescreen_change_button.clicked.connect(change_homescreen)
        try:
            import PIL
        except ImportError:
            homescreen_change_button.setDisabled(True)
            homescreen_change_button.setToolTip(
                _("Required package 'PIL' is not available - Please install it or use the Trezor website instead.")
            )
        homescreen_clear_button.clicked.connect(clear_homescreen)
        homescreen_msg = QLabel(_("You can set the homescreen on your "
                                  "device to personalize it.  You must "
                                  "choose a {} x {} monochrome black and "
                                  "white image.").format(hs_rows, hs_cols))
        homescreen_msg.setWordWrap(True)
        settings_glayout.addWidget(homescreen_label, 4, 0)
        settings_glayout.addWidget(homescreen_change_button, 4, 1)
        settings_glayout.addWidget(homescreen_clear_button, 4, 2)
        settings_glayout.addWidget(homescreen_msg, 5, 1, 1, -1)

        # Settings tab - Session Timeout
        timeout_label = QLabel(_("Session Timeout"))
        timeout_minutes = QLabel()
        timeout_slider = QSlider(Qt.Horizontal)
        timeout_slider.setRange(1, 60)
        timeout_slider.setSingleStep(1)
        timeout_slider.setTickInterval(5)
        timeout_slider.setTickPosition(QSlider.TicksBelow)
        timeout_slider.setTracking(True)
        timeout_msg = QLabel(
            _("Clear the session after the specified period "
              "of inactivity.  Once a session has timed out, "
              "your PIN and passphrase (if enabled) must be "
              "re-entered to use the device."))
        timeout_msg.setWordWrap(True)
        timeout_slider.setSliderPosition(config.get_session_timeout() // 60)
        slider_moved()
        timeout_slider.valueChanged.connect(slider_moved)
        timeout_slider.sliderReleased.connect(slider_released)
        settings_glayout.addWidget(timeout_label, 6, 0)
        settings_glayout.addWidget(timeout_slider, 6, 1, 1, 3)
        settings_glayout.addWidget(timeout_minutes, 6, 4)
        settings_glayout.addWidget(timeout_msg, 7, 1, 1, -1)
        settings_layout.addLayout(settings_glayout)
        settings_layout.addStretch(1)

        # Advanced tab
        advanced_tab = QWidget()
        advanced_layout = QVBoxLayout(advanced_tab)
        advanced_glayout = QGridLayout()

        # Advanced tab - clear PIN
        clear_pin_button = QPushButton(_("Disable PIN"))
        clear_pin_button.clicked.connect(clear_pin)
        clear_pin_warning = QLabel(
            _("If you disable your PIN, anyone with physical access to your "
              "{} device can spend your bitcoins.").format(plugin.device))
        clear_pin_warning.setWordWrap(True)
        clear_pin_warning.setStyleSheet("color: red")
        advanced_glayout.addWidget(clear_pin_button, 0, 2)
        advanced_glayout.addWidget(clear_pin_warning, 1, 0, 1, 5)

        # Advanced tab - toggle passphrase protection
        passphrase_button = QPushButton()
        passphrase_button.clicked.connect(toggle_passphrase)
        passphrase_msg = WWLabel(PASSPHRASE_HELP)
        passphrase_warning = WWLabel(PASSPHRASE_NOT_PIN)
        passphrase_warning.setStyleSheet("color: red")
        advanced_glayout.addWidget(passphrase_button, 3, 2)
        advanced_glayout.addWidget(passphrase_msg, 4, 0, 1, 5)
        advanced_glayout.addWidget(passphrase_warning, 5, 0, 1, 5)

        # Advanced tab - wipe device
        wipe_device_button = QPushButton(_("Wipe Device"))
        wipe_device_button.clicked.connect(wipe_device)
        wipe_device_msg = QLabel(
            _("Wipe the device, removing all data from it.  The firmware "
              "is left unchanged."))
        wipe_device_msg.setWordWrap(True)
        wipe_device_warning = QLabel(
            _("Only wipe a device if you have the recovery seed written down "
              "and the device wallet(s) are empty, otherwise the bitcoins "
              "will be lost forever."))
        wipe_device_warning.setWordWrap(True)
        wipe_device_warning.setStyleSheet("color: red")
        advanced_glayout.addWidget(wipe_device_button, 6, 2)
        advanced_glayout.addWidget(wipe_device_msg, 7, 0, 1, 5)
        advanced_glayout.addWidget(wipe_device_warning, 8, 0, 1, 5)
        advanced_layout.addLayout(advanced_glayout)
        advanced_layout.addStretch(1)

        tabs = QTabWidget(self)
        tabs.addTab(info_tab, _("Information"))
        tabs.addTab(settings_tab, _("Settings"))
        tabs.addTab(advanced_tab, _("Advanced"))
        dialog_vbox = QVBoxLayout(self)
        dialog_vbox.addWidget(tabs)
        dialog_vbox.addLayout(Buttons(CloseButton(self)))

        # Update information
        invoke_client(None)
Example #6
0
def populate_metadata_page(layout,
                           db,
                           book_id,
                           bulk=False,
                           two_column=False,
                           parent=None):
    def widget_factory(typ, key):
        if bulk:
            w = bulk_widgets[typ](db, key, parent)
        else:
            w = widgets[typ](db, key, parent)
        if book_id is not None:
            w.initialize(book_id)
        return w

    fm = db.field_metadata

    # Get list of all non-composite custom fields. We must make widgets for these
    fields = fm.custom_field_keys(include_composites=False)
    cols_to_display = fields
    cols_to_display.sort(key=partial(field_sort_key, fm=fm))

    # This will contain the fields in the order to display them
    cols = []

    # The fields named here must be first in the widget list
    tweak_cols = tweaks['metadata_edit_custom_column_order']
    comments_in_tweak = 0
    for key in (tweak_cols or ()):
        # Add the key if it really exists in the database
        if key in cols_to_display:
            cols.append(key)
            if fm[key]['datatype'] == 'comments':
                comments_in_tweak += 1

    # Add all the remaining fields
    comments_not_in_tweak = 0
    for key in cols_to_display:
        if key not in cols:
            cols.append(key)
            if fm[key]['datatype'] == 'comments':
                comments_not_in_tweak += 1

    count = len(cols)
    layout_rows_for_comments = 9
    if two_column:
        turnover_point = (
            (count - comments_not_in_tweak + 1) + comments_in_tweak *
            (layout_rows_for_comments - 1)) / 2
    else:
        # Avoid problems with multi-line widgets
        turnover_point = count + 1000
    ans = []
    column = row = base_row = max_row = 0
    for key in cols:
        if not fm[key]['is_editable']:
            continue  # this almost never happens
        dt = fm[key]['datatype']
        if dt == 'composite' or (bulk and dt == 'comments'):
            continue
        w = widget_factory(dt, fm[key]['colnum'])
        ans.append(w)
        if two_column and dt == 'comments':
            # Here for compatibility with old layout. Comments always started
            # in the left column
            comments_in_tweak -= 1
            # no special processing if the comment field was named in the tweak
            if comments_in_tweak < 0 and comments_not_in_tweak > 0:
                # Force a turnover, adding comments widgets below max_row.
                # Save the row to return to if we turn over again
                column = 0
                row = max_row
                base_row = row
                turnover_point = row + (comments_not_in_tweak *
                                        layout_rows_for_comments) / 2
                comments_not_in_tweak = 0

        l = QGridLayout()
        if dt == 'comments':
            layout.addLayout(l, row, column, layout_rows_for_comments, 1)
            layout.setColumnStretch(column, 100)
            row += layout_rows_for_comments
        else:
            layout.addLayout(l, row, column, 1, 1)
            layout.setColumnStretch(column, 100)
            row += 1
        for c in range(0, len(w.widgets), 2):
            if dt != 'comments':
                w.widgets[c].setWordWrap(True)
                w.widgets[c].setBuddy(w.widgets[c + 1])
                l.addWidget(w.widgets[c], c, 0)
                l.addWidget(w.widgets[c + 1], c, 1)
                l.setColumnStretch(1, 10000)
            else:
                l.addWidget(w.widgets[0], 0, 0, 1, 2)
        l.addItem(QSpacerItem(0, 0, vPolicy=QSizePolicy.Expanding), c, 0, 1, 1)
        max_row = max(max_row, row)
        if row >= turnover_point:
            column = 1
            turnover_point = count + 1000
            row = base_row

    items = []
    if len(ans) > 0:
        items.append(
            QSpacerItem(10, 10, QSizePolicy.Minimum, QSizePolicy.Expanding))
        layout.addItem(items[-1], layout.rowCount(), 0, 1, 1)
        layout.setRowStretch(layout.rowCount() - 1, 100)
    return ans, items
Example #7
0
class _ExecuteTab(QTabWidget):
    """Tab used to execute modules or shell commands on the selected bot."""
    def __init__(self, responses_tab: _ResponsesTab, model):
        super().__init__()

        self._model = model
        self._current_layout = None
        self._current_bot = None

        self._layout = QGridLayout()
        self._sub_layout = QVBoxLayout()
        self._module_view = ModuleView(responses_tab)

        self._layout.setAlignment(Qt.AlignTop)
        self.setLayout(self._layout)
        self.set_empty_layout()

    def set_current_bot(self, bot: Bot):
        """Sets the connected bot this tab will interact with."""
        self._current_bot = bot

    def _clear_layout(self):
        while self._layout.count():
            child = self._layout.takeAt(0)

            if child.widget():
                child.widget().deleteLater()
        while self._sub_layout.count():
            child = self._sub_layout.takeAt(0)

            if child.widget():
                child.widget().deleteLater()

    def set_empty_layout(self):
        """Default layout shown when the user has not yet selected a row."""
        self._current_layout = "Empty"
        self._clear_layout()

        self._layout.addWidget(
            QLabel("Please select a bot in the table above."), 0, 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())

    def set_shell_layout(self):
        """Sets the layout which can execute shell commands."""
        self._current_layout = "Shell"
        self._clear_layout()

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

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

        command_label = QLabel("Command:")
        command_input = QLineEdit()

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

        command_type_combobox.currentTextChanged.connect(
            self._on_command_type_change)
        run_button.pressed.connect(lambda: self._on_command_run(command_input))

        self._layout.addWidget(command_type_label, 0, 0)
        self._layout.addWidget(command_type_combobox, 0, 1)
        self._layout.addWidget(command_label, 1, 0)
        self._layout.addWidget(command_input, 1, 1)

        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)

    def _on_command_type_change(self, text: str):
        """Handles the command type combobox change event."""
        if text == "Module":
            self.set_module_layout()
        else:
            self.set_shell_layout()

    def _on_module_change(self, module_name: str):
        """Handles module combobox changes."""
        while self._sub_layout.count():
            child = self._sub_layout.takeAt(0)

            if child.widget():
                child.widget().deleteLater()

        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()
            input_fields.append(input_field)

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

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

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

        self._sub_layout.addWidget(QLabel(""))
        self._sub_layout.addWidget(run_button)
        self._sub_layout.setContentsMargins(0, 15, 0, 0)

    def display_info(self, text: str):
        message_box = QMessageBox()

        message_box.setIcon(QMessageBox.Information)
        message_box.setWindowTitle("Information")
        message_box.setText(text)
        message_box.setStandardButtons(QMessageBox.Ok)
        message_box.exec_()

    def _on_module_run(self, module_name: str, input_fields: list):
        """Handles running modules."""
        set_options = []

        for input_field in input_fields:
            set_options.append(input_field.text())

        module = modules.get_module(module_name)

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

        successful, options = module.setup(set_options)

        if successful:
            if module_name == "remove_bot":
                code = loaders.get_remove_code(self._current_bot.loader_name)
            elif module_name == "update_bot":
                code = loaders.get_update_code(self._current_bot.loader_name)
            else:
                code = modules.get_code(module_name)

            if not options:
                options = {}

            options["module_name"] = module_name

            self._model.add_command(self._current_bot.uid,
                                    Command(CommandType.MODULE, code, options))

            self.display_info("Module added to the queue of:\n {}@{}".format(
                self._current_bot.username, self._current_bot.hostname))

    def _on_command_run(self, command_input: QLineEdit):
        """Handles running commands."""
        if command_input.text().strip() == "":
            return

        self._model.add_command(
            self._current_bot.uid,
            Command(CommandType.SHELL,
                    command_input.text().encode()))

        command_input.clear()
        self.display_info("Command added to the queue of:\n {}@{}".format(
            self._current_bot.username, self._current_bot.hostname))
class PlotToolbarOptions(QWidget):
    def __init__(self,
                 parent,
                 series_style,
                 theme_manager,
                 plot,
                 options=None,
                 legend_control=None,
                 right_padding=0.0,
                 has_extra_tools=False):
        QWidget.__init__(self, parent)
        self._theme_manager = theme_manager
        self._plot = plot
        self._toolbar_container = ToolbarContainer(plot)

        self._background_color_qt = _to_qt_color(
            series_style.get_color_from_key('axes_background'))
        self._foreground_color_qt = _to_qt_color(
            series_style.get_color_from_key('axes_foreground'))
        interpolation = interpolate_rgb(self._background_color_qt,
                                        self._foreground_color_qt, 3)
        self._icon_hover_color = interpolation[1]

        self._toolbar = PlotToolbarWidget(self._toolbar_container, plot,
                                          self._foreground_color_qt,
                                          self._icon_hover_color)
        self._toolbar.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
        self._layout = QGridLayout(self)
        self._layout.setContentsMargins(0, 0, 0, 0)
        self._layout.setSpacing(0)
        self._layout.addWidget(plot, 0, 0, 1, 3)
        self._background_opacity = 0.8
        plot.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        if options is not None:
            if isinstance(options, PlotOptionsView):
                plot.setOptionsView(options)
            self._options = self._add_popout(ToolType.options, options)
        else:
            self._options = None

        if legend_control is not None:
            if isinstance(legend_control, LegendControlView):
                plot.setLegendControl(legend_control)
            self._legend_control = self._add_popout(ToolType.legend,
                                                    legend_control)
            legend_control.hasHiddenSeriesChanged.connect(
                self._handle_has_hidden_series_changed)
        else:
            self._legend_control = None

        self._toolbar_layout = QVBoxLayout(self._toolbar_container)
        self._toolbar_layout.addWidget(self._toolbar, Qt.AlignTop)

        self._layout.addWidget(self._toolbar_container, 0, 1,
                               Qt.AlignRight | Qt.AlignTop)
        self._layout.setColumnStretch(0, 1)
        self._layout.setColumnStretch(1, 0)
        if right_padding > 0:
            self._padding_widget = QWidget(self)
            self._padding_widget.setVisible(False)
            self._layout.addWidget(self._padding_widget, 0, 2)
            self._layout.setColumnMinimumWidth(2, right_padding)
        else:
            self._padding_widget = None

        if not has_extra_tools:
            self._toolbar_layout.addStretch()

    def _handle_has_hidden_series_changed(self, has_hidden):
        color = None if not has_hidden else self._theme_manager.get_color(
            'highlight')
        self._toolbar.setColor(ToolType.legend, color)

    def _handle_tool_activated(self, tool_type, view):
        def _(tool, active):
            if tool == tool_type:
                view.setVisible(active)
                if self._padding_widget:
                    self._padding_widget.setVisible(active)
                if active:
                    self._toolbar_container.setStyleSheet(
                        "ToolbarContainer {{ background-color: {} }}".format(
                            format_color(self._background_color_qt,
                                         ColorFormat.rgba_string_256,
                                         self._background_opacity)))
                    self._layout.setAlignment(self._toolbar_container,
                                              Qt.AlignRight)
                    if self._padding_widget:
                        self._padding_widget.setStyleSheet(
                            "QWidget {{ background-color: {} }}".format(
                                format_color(self._background_color_qt,
                                             ColorFormat.rgba_string_256,
                                             self._background_opacity)))
                else:
                    self._layout.setAlignment(self._toolbar_container,
                                              Qt.AlignRight | Qt.AlignTop)
                    self._toolbar_container.setStyleSheet("")
                    if self._padding_widget:
                        self._padding_widget.setStyleSheet("")

        return _

    def addTool(self, tool_widget):
        self._toolbar_layout.addWidget(HLine(self._plot), Qt.AlignTop)
        self._toolbar_layout.addWidget(tool_widget,
                                       Qt.AlignTop | Qt.AlignCenter)
        self._toolbar_layout.addStretch()

    @property
    def icon_color(self):
        return self._foreground_color_qt

    @property
    def icon_hover_color(self):
        return self._icon_hover_color

    @property
    def toolbar(self):
        return self._toolbar

    def _add_popout(self, tool_type, view):
        popout = PopoutWidget(self, view, self._background_color_qt,
                              self._foreground_color_qt,
                              self._background_opacity)
        popout.setVisible(False)
        self._layout.addWidget(popout, 0, 0, Qt.AlignRight)
        self._toolbar.toolActivated.connect(
            self._handle_tool_activated(tool_type, popout))
        return popout
Example #9
0
    def __init__(self, window, plugin, keystore, device_id):
        title = _("{} Settings").format(plugin.device)
        super(SettingsDialog, self).__init__(window, title)
        self.setMaximumWidth(540)

        devmgr = plugin.device_manager()
        config = devmgr.config
        handler = keystore.handler
        thread = keystore.thread
        hs_rows, hs_cols = (64, 128)

        def invoke_client(method, *args, **kw_args):
            unpair_after = kw_args.pop('unpair_after', False)

            def task():
                client = devmgr.client_by_id(device_id)
                if not client:
                    raise RuntimeError("Device not connected")
                if method:
                    getattr(client, method)(*args, **kw_args)
                if unpair_after:
                    devmgr.unpair_id(device_id)
                return client.features

            thread.add(task, on_success=update)

        def update(features):
            self.features = features
            set_label_enabled()
            bl_hash = bh2u(features.bootloader_hash)
            bl_hash = "\n".join([bl_hash[:32], bl_hash[32:]])
            noyes = [_("No"), _("Yes")]
            endis = [_("Enable Passphrases"), _("Disable Passphrases")]
            disen = [_("Disabled"), _("Enabled")]
            setchange = [_("Set a PIN"), _("Change PIN")]

            version = "%d.%d.%d" % (features.major_version,
                                    features.minor_version,
                                    features.patch_version)
            coins = ", ".join(coin.coin_name for coin in features.coins)

            device_label.setText(features.label)
            pin_set_label.setText(noyes[features.pin_protection])
            passphrases_label.setText(disen[features.passphrase_protection])
            bl_hash_label.setText(bl_hash)
            label_edit.setText(features.label)
            device_id_label.setText(features.device_id)
            initialized_label.setText(noyes[features.initialized])
            version_label.setText(version)
            coins_label.setText(coins)
            clear_pin_button.setVisible(features.pin_protection)
            clear_pin_warning.setVisible(features.pin_protection)
            pin_button.setText(setchange[features.pin_protection])
            pin_msg.setVisible(not features.pin_protection)
            passphrase_button.setText(endis[features.passphrase_protection])
            language_label.setText(features.language)

        def set_label_enabled():
            label_apply.setEnabled(label_edit.text() != self.features.label)

        def rename():
            invoke_client('change_label', label_edit.text())

        def toggle_passphrase():
            title = _("Confirm Toggle Passphrase Protection")
            currently_enabled = self.features.passphrase_protection
            if currently_enabled:
                msg = _("After disabling passphrases, you can only pair this "
                        "Electrum wallet if it had an empty passphrase.  "
                        "If its passphrase was not empty, you will need to "
                        "create a new wallet with the install wizard.  You "
                        "can use this wallet again at any time by re-enabling "
                        "passphrases and entering its passphrase.")
            else:
                msg = _("Your current Electrum wallet can only be used with "
                        "an empty passphrase.  You must create a separate "
                        "wallet with the install wizard for other passphrases "
                        "as each one generates a new set of addresses.")
            msg += "\n\n" + _("Are you sure you want to proceed?")
            if not self.question(msg, title=title):
                return
            invoke_client('toggle_passphrase', unpair_after=currently_enabled)

        def change_homescreen():
            from PIL import Image  # FIXME
            dialog = QFileDialog(self, _("Choose Homescreen"))
            filename, __ = dialog.getOpenFileName()
            if filename:
                im = Image.open(str(filename))
                if im.size != (hs_cols, hs_rows):
                    raise Exception('Image must be 64 x 128 pixels')
                im = im.convert('1')
                pix = im.load()
                img = ''
                for j in range(hs_rows):
                    for i in range(hs_cols):
                        img += '1' if pix[i, j] else '0'
                img = ''.join(chr(int(img[i:i + 8], 2))
                              for i in range(0, len(img), 8))
                invoke_client('change_homescreen', img)

        def clear_homescreen():
            invoke_client('change_homescreen', '\x00')

        def set_pin():
            invoke_client('set_pin', remove=False)

        def clear_pin():
            invoke_client('set_pin', remove=True)

        def wipe_device():
            wallet = window.wallet
            if wallet and sum(wallet.get_balance()):
                title = _("Confirm Device Wipe")
                msg = _("Are you SURE you want to wipe the device?\n"
                        "Your wallet still has viacoins in it!")
                if not self.question(msg, title=title,
                                     icon=QMessageBox.Critical):
                    return
            invoke_client('wipe_device', unpair_after=True)

        def slider_moved():
            mins = timeout_slider.sliderPosition()
            timeout_minutes.setText(_("%2d minutes") % mins)

        def slider_released():
            config.set_session_timeout(timeout_slider.sliderPosition() * 60)

        # Information tab
        info_tab = QWidget()
        info_layout = QVBoxLayout(info_tab)
        info_glayout = QGridLayout()
        info_glayout.setColumnStretch(2, 1)
        device_label = QLabel()
        pin_set_label = QLabel()
        passphrases_label = QLabel()
        version_label = QLabel()
        device_id_label = QLabel()
        bl_hash_label = QLabel()
        bl_hash_label.setWordWrap(True)
        coins_label = QLabel()
        coins_label.setWordWrap(True)
        language_label = QLabel()
        initialized_label = QLabel()
        rows = [
            (_("Device Label"), device_label),
            (_("PIN set"), pin_set_label),
            (_("Passphrases"), passphrases_label),
            (_("Firmware Version"), version_label),
            (_("Device ID"), device_id_label),
            (_("Bootloader Hash"), bl_hash_label),
            (_("Supported Coins"), coins_label),
            (_("Language"), language_label),
            (_("Initialized"), initialized_label),
        ]
        for row_num, (label, widget) in enumerate(rows):
            info_glayout.addWidget(QLabel(label), row_num, 0)
            info_glayout.addWidget(widget, row_num, 1)
        info_layout.addLayout(info_glayout)

        # Settings tab
        settings_tab = QWidget()
        settings_layout = QVBoxLayout(settings_tab)
        settings_glayout = QGridLayout()

        # Settings tab - Label
        label_msg = QLabel(_("Name this {}.  If you have multiple devices "
                             "their labels help distinguish them.")
                           .format(plugin.device))
        label_msg.setWordWrap(True)
        label_label = QLabel(_("Device Label"))
        label_edit = QLineEdit()
        label_edit.setMinimumWidth(150)
        label_edit.setMaxLength(plugin.MAX_LABEL_LEN)
        label_apply = QPushButton(_("Apply"))
        label_apply.clicked.connect(rename)
        label_edit.textChanged.connect(set_label_enabled)
        settings_glayout.addWidget(label_label, 0, 0)
        settings_glayout.addWidget(label_edit, 0, 1, 1, 2)
        settings_glayout.addWidget(label_apply, 0, 3)
        settings_glayout.addWidget(label_msg, 1, 1, 1, -1)

        # Settings tab - PIN
        pin_label = QLabel(_("PIN Protection"))
        pin_button = QPushButton()
        pin_button.clicked.connect(set_pin)
        settings_glayout.addWidget(pin_label, 2, 0)
        settings_glayout.addWidget(pin_button, 2, 1)
        pin_msg = QLabel(_("PIN protection is strongly recommended.  "
                           "A PIN is your only protection against someone "
                           "stealing your viacoins if they obtain physical "
                           "access to your %s.") % plugin.device)
                           "stealing your viacoins if they obtain physical "
                           "access to your {}.").format(plugin.device))
Example #10
0
class PrincePDFDialog(QDialog):
    prince_log = ''

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

        self.setAttribute(Qt.WA_DeleteOnClose)

        self.setWindowTitle(_('Prince PDF'))
        self.setWindowIcon(icon)

        self.l = QGridLayout()
        self.setLayout(self.l)

        self.image = QLabel()
        self.image.setPixmap(icon.pixmap(120, 120))
        self.l.addWidget(self.image, 1, 1, 4, 1, Qt.AlignVCenter)

        self.convert_to_PDF_button = QPushButton(_('Convert to &PDF'), self)
        self.convert_to_PDF_button.clicked.connect(self.convert_to_PDF)
        self.convert_to_PDF_button.setDefault(True)
        self.convert_to_PDF_button.setToolTip(
            _('<qt>Start the conversion process</qt>'))
        self.l.addWidget(self.convert_to_PDF_button, 1, 2, Qt.AlignVCenter)

        self.view_log = QPushButton(_('&View log'), self)
        self.view_log.clicked.connect(self.show_log)
        self.view_log.setToolTip(
            _('<qt>Display the log from the last Prince run</qt>'))
        self.l.addWidget(self.view_log, 2, 2, Qt.AlignVCenter)
        self.view_log.hide()

        self.conf_button = QPushButton(_('Con&figure'), self)
        self.conf_button.clicked.connect(self.config)
        self.conf_button.setToolTip(_('<qt>Configure this plugin</qt>'))
        self.l.addWidget(self.conf_button, 4, 2, Qt.AlignVCenter)

        self.info = QLabel()
        self.l.addWidget(self.info, 5, 1, 1, -1)

        self.suggestion = QLabel()
        self.suggestion.setAlignment(Qt.AlignCenter)
        self.l.addWidget(self.suggestion, 6, 1, 1, -1)

        self.l.setColumnStretch(1, 1)
        self.l.setColumnStretch(2, 10)
        self.l.setRowStretch(1, 1)
        self.l.setRowStretch(2, 1)
        self.l.setRowStretch(3, 10)
        self.l.setRowStretch(4, 1)
        self.l.setRowStretch(5, 1)
        self.l.setRowStretch(6, 1)
        self.l.setRowMinimumHeight(1, 1)
        self.l.setRowMinimumHeight(2, 1)
        self.l.setRowMinimumHeight(3, 1)
        self.l.setRowMinimumHeight(4, 1)
        self.l.setRowMinimumHeight(5, 1)
        self.l.setRowMinimumHeight(6, 1)

        self.buttons = QDialogButtonBox(QDialogButtonBox.Close
                                        | QDialogButtonBox.Help)
        self.l.addWidget(self.buttons, 7, 1, 1, -1)
        self.buttons.rejected.connect(self.reject)
        self.buttons.helpRequested.connect(self.about)

        self.adjustSize()

        # Update the info now and every time the selection or the data changes
        self.gui.library_view.model().dataChanged.connect(self.update_info)
        self.gui.library_view.selectionModel().selectionChanged.connect(
            self.update_info)
        self.update_info()

    def update_info(self):
        '''
        Update the info on top of the window
        '''

        self.db = self.gui.current_db
        # Get selected rows
        rows = self.gui.library_view.selectionModel().selectedRows()
        if not rows or len(rows) == 0:
            self.info.setText(_('<b>No books selected</b>'))
            self.info.setAlignment(Qt.AlignCenter)
            self.suggestion.setText(_('Select one single book'))
            self.selected = None
            self.convert_to_PDF_button.setEnabled(False)
        elif len(rows) > 1:
            self.info.setText(_('<b>Many books selected</b>'))
            self.info.setAlignment(Qt.AlignCenter)
            self.suggestion.setText(_('Select one single book'))
            self.selected = None
            self.convert_to_PDF_button.setEnabled(False)
        else:
            # If there is only one selected book, enable conversion
            # and show list of available formats (formats in prefs are bold)
            book_id = self.gui.library_view.model().id(rows[0])
            fmts = self.db.formats(book_id, index_is_id=True)
            if (fmts):
                fmts = fmts.split(',')
            else:
                fmts = [_('<i>none</i>')]
            available = False
            for i, fmt in enumerate(fmts):
                fmts[i] = fmt.lower()
                if fmts[i] in prefs['formats']:
                    fmts[i] = '<b>%s</b>' % fmts[i]
                    available = True
            self.info.setText(_('Available formats: %s') % ', '.join(fmts))
            self.info.setAlignment(Qt.AlignLeft)
            # Conversion enabled only if some "preferred" format is found
            if (available):
                self.suggestion.setText(_('Ready'))
                self.selected = book_id
                self.convert_to_PDF_button.setEnabled(True)
            else:
                self.suggestion.setText(_('No preferred format available'))
                self.selected = None
                self.convert_to_PDF_button.setEnabled(False)

    def about(self):
        '''
        Display a short help message
        '''
        from os.path import join
        from calibre.ptempfile import TemporaryDirectory
        from calibre.gui2.dialogs.message_box import MessageBox
        from calibre_plugins.prince_pdf.help import help_txt, license_txt
        from calibre_plugins.prince_pdf import PrincePDFPlugin
        from calibre_plugins.prince_pdf import __license__

        author = PrincePDFPlugin.author
        version = "%i.%i.%i" % PrincePDFPlugin.version
        license = __license__
        with TemporaryDirectory('xxx') as tdir:
            for x in ('prince_icon.png', 'small_icon.png'):
                with open(join(tdir, x), 'w') as f:
                    f.write(get_resources('images/' + x))
            help_box = MessageBox(type_ = MessageBox.INFO, \
                                  title = _('About the Prince PDF Plugin'), \
                                  msg = help_txt % {'author':author, 'version':version, 'license':license, 'dir':tdir, 'code':'style="font-family:monospace ; font-weight:bold"'}, \
                                  det_msg = 'Copyright \u00a9 %s\n%s' % (__copyright__, license_txt), \
                                  q_icon = self.icon, \
                                  show_copy_button = False)
            #help_box.gridLayout.addWidget(help_box.icon_widget,0,0,Qt.AlignTop)
            help_box.gridLayout.setAlignment(help_box.icon_widget, Qt.AlignTop)
            help_box.exec_()

    def convert_to_PDF(self):
        '''
        Unpack and convert the currently selected book to PDF
        '''
        from calibre.gui2 import error_dialog
        from calibre.constants import DEBUG

        # Get available formats
        book_id = self.selected
        fmts = self.db.formats(book_id, index_is_id=True)
        fmts = fmts.lower().split(',')
        # Process only the first format matching the 'formats' configuration option
        for fmt in prefs['formats']:
            fmt = fmt.lower()
            if (not fmt in fmts): continue
            mi = self.db.get_metadata(book_id,
                                      index_is_id=True,
                                      get_cover=False)
            pdf_base_file = self.get_filename(book_id, mi)

            # This is the actual code:
            if DEBUG: print('===========')
            # Unpack the book and call the conversion dialog
            (opf, oeb) = self.unpack(book_id, fmt)
            if (opf == None or oeb == None):
                return error_dialog(self.gui,
                                    _('Cannot convert to PDF'),
                                    _('Format not supported: %s') % fmt,
                                    show=True)
            convert_dialog = ConvertDialog(mi, fmt, opf, oeb, self.icon)
            convert_dialog.pdf_file = pdf_base_file
            pdf_file = ''
            if (convert_dialog.exec_()):
                pdf_file = convert_dialog.pdf_file
            self.prince_log = convert_dialog.prince_log
            # After the dialog returns, pdf_file has the output file path,
            # and prince_log has the Prince console output
            if DEBUG: print(_('PDF file: %s') % pdf_file)
            # If there is any log, enable the View log button
            if (self.prince_log):
                self.view_log.show()
                log_msg = _(' Check the log.')
            else:
                self.view_log.hide()
                log_msg = ''
            # If the conversion failed, pdf_file will be None,
            if (pdf_file == None):
                error_dialog(self.gui,
                             _('Could not convert to PDF'),
                             _('The conversion failed.') + log_msg,
                             show=True)
            # If the user cancelled the dialog, pdf_file will be ''
            if (pdf_file):
                # Set the metadata in the PDF and add it or save it
                try:
                    self.set_pdf_metadata(mi, pdf_file)
                except:
                    error_dialog(
                        self.gui,
                        _('Could not convert to PDF'),
                        _("Error reading or writing the PDF file:\n%s" %
                          pdf_file),
                        show=True)
                    return
                if (prefs['add_book']):
                    self.add_pdf(book_id, pdf_file, ('pdf' in fmts))
                else:
                    self.save_pdf(pdf_file, pdf_base_file)
            if DEBUG: print('===========')
            return
        # No matching format in the book
        return error_dialog(self.gui,
                            _('Cannot convert to PDF'),
                            _('No supported format available'),
                            show=True)

    def show_log(self):
        '''
        Display the Prince log dialog
        '''
        msg = LogDialog(self.prince_log, self.icon)
        msg.exec_()

    def config(self):
        '''
        Display the configuration dialog
        '''
        self.do_user_config(parent=self)

    def get_filename(self, book_id, mi):
        '''
        Obtain a filename from the save_to_disk template
        :param book_id: The book identifier
        :param mi: The book metadata
        '''
        from os.path import join
        from calibre.library.save_to_disk import get_components, config
        from calibre import sanitize_file_name_unicode
        from calibre.utils.filenames import ascii_filename

        opts = config().parse()
        components = get_components(
            opts.template,
            mi,
            book_id,
            opts.timefmt,
            sanitize_func=(ascii_filename
                           if opts.asciiize else sanitize_file_name_unicode),
            to_lowercase=opts.to_lowercase,
            replace_whitespace=opts.replace_whitespace)
        base_path = join(*components)
        return '%s.pdf' % base_path

    def unpack(self, book_id, fmt):
        '''
        Unpack the book in a temporary directory
        :param book_id: The book identifier
        :param fmt: The format to unpack
        '''
        from calibre.constants import DEBUG
        from calibre.ptempfile import PersistentTemporaryDirectory
        from calibre.ebooks.tweak import get_tools
        from calibre.ebooks.oeb.base import OEBBook
        from calibre.ebooks.oeb.reader import OEBReader
        from calibre.utils.logging import default_log
        from calibre_plugins.prince_pdf.dummy_preprocessor import dummy_preprocessor

        book_file = self.db.format(book_id,
                                   fmt,
                                   index_is_id=True,
                                   as_path=True)
        if DEBUG: print(_('Unpacking book...'))
        tdir = PersistentTemporaryDirectory('_unpack')
        exploder = get_tools(fmt)[0]
        if (exploder == None): return (None, None)
        opf = exploder(book_file, tdir)
        html_preprocessor = dummy_preprocessor()
        css_preprocessor = dummy_preprocessor()
        oeb = OEBBook(default_log, html_preprocessor, css_preprocessor)
        OEBReader()(oeb, opf)
        return (opf, oeb)

    def set_pdf_metadata(self, mi, pdf_file):
        '''
        Set the metadata in the PDF file
        :param mi: The book metadata
        :param pdf_file: The path to the PDF file
        '''
        from calibre.constants import DEBUG
        from calibre.ebooks.metadata.pdf import set_metadata

        if DEBUG: print(_('Setting metadata...'))
        pdf_stream = open(pdf_file, 'r+b')
        set_metadata(pdf_stream, mi)
        pdf_stream.close()

    def add_pdf(self, book_id, pdf_file, exists):
        '''
        Add the PDF file to the book record, asking for replacement
        :param book_id: The book identifier
        :param pdf_file: The path to the PDF file
        :param exists: True if there is already a PDF in the book
        '''
        from calibre.constants import DEBUG
        from calibre.gui2.dialogs.message_box import MessageBox

        add_it = True
        if (exists):
            msg = MessageBox(
                MessageBox.QUESTION, _('Existing format'),
                _('The selected book already contains a PDF format. Are you sure you want to replace it?'
                  ),
                _("The temporary file can be found in:\n%s") % pdf_file)
            msg.toggle_det_msg()
            add_it = (msg.exec_())
        if (add_it):
            if DEBUG: print(_('Adding PDF...'))
            self.db.new_api.add_format(book_id, 'pdf', pdf_file)
            self.gui.library_view.model().refresh_ids([book_id])
            self.gui.library_view.refresh_book_details()
            self.gui.tags_view.recount()

    def save_pdf(self, pdf_file, pdf_base_file):
        '''
        Save the PDF file in the final location
        :param pdf_file: The path to the PDF file
        :param pdf_base_file: The desired file name and relative path
        '''
        from os import makedirs
        from os.path import basename, dirname, join, exists
        from shutil import move
        from calibre.constants import DEBUG
        from calibre.gui2 import choose_dir
        from calibre.gui2.dialogs.message_box import MessageBox
        from calibre.gui2 import error_dialog

        path = choose_dir(self.gui, 'save to disk dialog',
                          _('Choose destination directory'))
        if not path:
            return
        save_file = join(path, pdf_base_file)
        base_dir = dirname(save_file)
        try:
            makedirs(base_dir)
        except BaseException:
            if not exists(base_dir): raise
        try:
            move(pdf_file, save_file)
        except:
            error_dialog(self.gui,
                         _('Could not save PDF'),
                         _("Error writing the PDF file:\n%s" % save_file),
                         show=True)
            return
        if DEBUG: print(save_file)
        MessageBox(MessageBox.INFO, _('File saved'),
                   _("PDF file saved in:\n%s") % save_file).exec_()
Example #11
0
class LLT_ConjAdd(QMainWindow):
    def __init__(self):
        super(LLT_ConjAdd, self).__init__()
        self.w = QWidget()
        self.setCentralWidget(self.w)

        #construct GUI
        self.setWindowTitle("Verb Conjugations")
        self.setGeometry(0, 0, 900, 600)

        self.conjDicList = []
        self.wordList = []
        self.newDic = {'INF': '', 'IND': '', 'SUB': '', 'IMP': ''}

        self.headingLab = QLabel("Add New Conjugation Table")
        self.wordInfLab = QLabel("Infinitive")
        self.wordInfLab.setAlignment(Qt.AlignRight)
        self.infEntry = QLineEdit()

        #INDICATIVE FORM TABLE
        self.indLab = QLabel("Indicative")
        self.indYoLab = QLabel("Yo")
        self.indYoLab.setAlignment(Qt.AlignRight)
        self.indTuLab = QLabel("Tú")
        self.indTuLab.setAlignment(Qt.AlignRight)
        self.indUstLab = QLabel("Él/la, Ud")
        self.indUstLab.setAlignment(Qt.AlignRight)
        self.indNosLab = QLabel("Nosotros")
        self.indNosLab.setAlignment(Qt.AlignRight)
        self.indUstdsLab = QLabel("Ellos/as, Uds")
        self.indUstdsLab.setAlignment(Qt.AlignRight)

        self.indPresLab = QLabel("Present")
        self.indPretLab = QLabel("Preterite")
        self.indImpLab = QLabel("Imperfect")
        self.indFutLab = QLabel("Future")
        self.indCondLab = QLabel("Conditional")

        self.entryGridInd = QGridLayout()
        for i in range(5):
            for j in range(5):
                self.entry = QLineEdit()
                self.entryGridInd.addWidget(self.entry, i, j)

        #SUBJUNCTIVE FORM TABLE
        self.subLab = QLabel("Subjunctive")
        self.subYoLab = QLabel("Yo")
        self.subYoLab.setAlignment(Qt.AlignRight)
        self.subTuLab = QLabel("Tú")
        self.subTuLab.setAlignment(Qt.AlignRight)
        self.subUstLab = QLabel("Él/la, Ud")
        self.subUstLab.setAlignment(Qt.AlignRight)
        self.subNosLab = QLabel("Nosotros/as")
        self.subNosLab.setAlignment(Qt.AlignRight)
        self.subUstdsLab = QLabel("Ellos/as, Uds")
        self.subUstdsLab.setAlignment(Qt.AlignRight)

        self.subPresLab = QLabel("Present")
        self.subImpLab = QLabel("Imperfect")
        self.subFutLab = QLabel("Future")

        self.entryGridSub = QGridLayout()
        for i in range(5):
            for j in range(3):
                self.entry = QLineEdit()
                self.entryGridSub.addWidget(self.entry, i, j)

        #IMPERATIVE FORM TABLE
        self.impvLab = QLabel("Imperative")
        self.impvTu = QLabel("Tú")
        self.impvTu.setAlignment(Qt.AlignRight)
        self.impvUd = QLabel("Usted")
        self.impvUd.setAlignment(Qt.AlignRight)
        self.impvNos = QLabel("Nosotros/as")
        self.impvNos.setAlignment(Qt.AlignRight)
        self.impvUdes = QLabel("Ustedes")
        self.impvUdes.setAlignment(Qt.AlignRight)

        self.impvAffLab = QLabel("Affirmative")
        self.impvNegLab = QLabel("Negative")

        self.entryGridImpv = QGridLayout()
        for i in range(4):
            for j in range(2):
                self.entry = QLineEdit()
                self.entryGridImpv.addWidget(self.entry, i, j)
        self.checkBut = QPushButton("Check")
        self.checkBut.clicked.connect(self.check)
        self.saveBut = QPushButton("Save")
        self.saveBut.clicked.connect(self.save)
        self.newBut = QPushButton("New Word")
        self.newBut.clicked.connect(self.new)
        self.clearBut = QPushButton("Clear")
        self.clearBut.clicked.connect(self.clear)
        self.quitBut = QPushButton("Quit")
        self.quitBut.clicked.connect(self.quit)

        self.theGrid = QGridLayout()

        self.theGrid.addWidget(self.headingLab, 0, 0)
        self.theGrid.addWidget(self.wordInfLab, 1, 0)
        self.theGrid.addWidget(self.infEntry, 1, 1)

        self.theGrid.addWidget(self.indLab, 3, 0)
        self.theGrid.addWidget(self.indPresLab, 3, 1)
        self.theGrid.addWidget(self.indPretLab, 3, 2)
        self.theGrid.addWidget(self.indImpLab, 3, 3)
        self.theGrid.addWidget(self.indFutLab, 3, 4)
        self.theGrid.addWidget(self.indCondLab, 3, 5)
        self.theGrid.addWidget(self.indYoLab, 4, 0)
        self.theGrid.addWidget(self.indTuLab, 5, 0)
        self.theGrid.addWidget(self.indUstLab, 6, 0)
        self.theGrid.addWidget(self.indNosLab, 7, 0)
        self.theGrid.addWidget(self.indUstdsLab, 8, 0)
        self.theGrid.addLayout(self.entryGridInd, 4, 1, 5, 5)

        self.theGrid.addWidget(self.subLab, 11, 0)
        self.theGrid.addWidget(self.subPresLab, 11, 1)
        self.theGrid.addWidget(self.subImpLab, 11, 2)
        self.theGrid.addWidget(self.subFutLab, 11, 3)
        self.theGrid.addWidget(self.subYoLab, 12, 0)
        self.theGrid.addWidget(self.subTuLab, 13, 0)
        self.theGrid.addWidget(self.subUstLab, 14, 0)
        self.theGrid.addWidget(self.subNosLab, 15, 0)
        self.theGrid.addWidget(self.subUstdsLab, 16, 0)
        self.theGrid.addLayout(self.entryGridSub, 12, 1, 5, 3)

        self.theGrid.addWidget(self.impvLab, 19, 0)
        self.theGrid.addWidget(self.impvAffLab, 19, 1)
        self.theGrid.addWidget(self.impvNegLab, 19, 2)
        self.theGrid.addWidget(self.impvTu, 20, 0)
        self.theGrid.addWidget(self.impvUd, 21, 0)
        self.theGrid.addWidget(self.impvNos, 22, 0)
        self.theGrid.addWidget(self.impvUdes, 23, 0)
        self.theGrid.addLayout(self.entryGridImpv, 20, 1, 4, 2)

        self.theGrid.addWidget(self.checkBut, 19, 5)
        self.theGrid.addWidget(self.saveBut, 20, 5)
        self.theGrid.addWidget(self.clearBut, 21, 5)
        self.theGrid.addWidget(self.newBut, 22, 5)
        self.theGrid.addWidget(self.quitBut, 23, 5)

        for i in range(24):
            self.theGrid.setRowStretch(i, 1)
        for j in range(6):
            self.theGrid.setColumnStretch(j, 1)
        self.w.setLayout(self.theGrid)
        self.getDic()

    def check(self):
        word = self.infEntry.text().upper()
        if word in self.wordList:
            msgBox = QMessageBox()
            msgBox.setText(word + ' already in dictionary')
            msgBox.exec_()
        else:
            msgBox = QMessageBox()
            msgBox.setText(word + ' not in dictionary yet')
            msgBox.exec_()

    def save(self):
        infinitive = self.infEntry.text().upper()
        if infinitive in self.wordList:
            msgBox = QMessageBox()
            msgBox.setText(infinitive + ' already in dictionary')
            msgBox.exec_()
        else:
            self.wordList.append(infinitive)
            indList = []
            subList = []
            impList = []
            for i in range(self.entryGridInd.count()):
                item = self.entryGridInd.itemAt(i)
                child = item.widget()
                indList.append(child.text().upper())

            for i in range(self.entryGridSub.count()):
                item = self.entryGridSub.itemAt(i)
                child = item.widget()
                subList.append(child.text().upper())

            for i in range(self.entryGridImpv.count()):
                item = self.entryGridImpv.itemAt(i)
                child = item.widget()
                impList.append(child.text().upper())

                self.newDic['INF'] = infinitive
                self.newDic['IND'] = indList
                self.newDic['SUB'] = subList
                self.newDic['IMP'] = impList

            self.conjDicList.append(self.newDic)
            c = open('conj.txt', 'w')
            json.dump(self.conjDicList, c)
            c.close()
            msgBox = QMessageBox()
            msgBox.setText(infinitive + ' has been saved')
            msgBox.exec_()
            self.newDic = {'INF': '', 'IND': '', 'SUB': '', 'IMP': ''}

    def new(self):
        confirm = QMessageBox.question(
            self.w, 'New Word',
            'Are you sure you want to clear all entries \nand start a new word?',
            QMessageBox.Yes | QMessageBox.No)
        if confirm == QMessageBox.Yes:
            self.infEntry.clear()
            for i in range(self.entryGridInd.count()):
                item = self.entryGridInd.itemAt(i)
                child = item.widget()
                child.clear()
            for i in range(self.entryGridSub.count()):
                item = self.entryGridSub.itemAt(i)
                child = item.widget()
                child.clear()
            for i in range(self.entryGridImpv.count()):
                item = self.entryGridImpv.itemAt(i)
                child = item.widget()
                child.clear()
        else:
            pass

    def clear(self):
        confirm = QMessageBox.question(
            self.w, 'Clear', 'Are you sure you want to clear all entries?',
            QMessageBox.Yes | QMessageBox.No)
        if confirm == QMessageBox.Yes:
            self.infEntry.clear()
            for i in range(self.entryGridInd.count()):
                item = self.entryGridInd.itemAt(i)
                child = item.widget()
                child.clear()
            for i in range(self.entryGridSub.count()):
                item = self.entryGridSub.itemAt(i)
                child = item.widget()
                child.clear()
            for i in range(self.entryGridImpv.count()):
                item = self.entryGridImpv.itemAt(i)
                child = item.widget()
                child.clear()
        else:
            pass

    def quit(self):
        confirm = QMessageBox.question(self.w, 'Quit',
                                       'Are you sure you want to exit?',
                                       QMessageBox.Yes | QMessageBox.No)
        if confirm == QMessageBox.Yes:
            self.close()
        else:
            pass

    def getDic(self):
        try:
            c = open('conj.txt', 'r')
            self.conjDicList = json.load(c)
            c.close()
            for item in self.conjDicList:
                self.wordList.append(item['INF'])

        except:
            self.conjDicList = []
Example #12
0
class CoversGroupBox(DeviceOptionsGroupBox):

    def __init__(self, parent, device):
        super(CoversGroupBox, self).__init__(parent, device)
        self.setTitle(_("Upload covers"))

        self.options_layout = QGridLayout()
        self.options_layout.setObjectName("options_layout")
        self.setLayout(self.options_layout)

        self.setCheckable(True)
        self.setChecked(device.get_pref('upload_covers'))
        self.setToolTip(wrap_msg(_('Upload cover images from the calibre library when sending books to the device.')))

        self.upload_grayscale_checkbox = create_checkbox(
                             _('Upload black and white covers'),
                             _('Convert covers to grayscale when uploading.'),
                             device.get_pref('upload_grayscale')
                             )

        self.dithered_covers_checkbox = create_checkbox(
                             _('Upload dithered covers'),
                             _('Dither cover images to the appropriate 16c grayscale palette for an eInk screen.'
                               ' This usually ensures greater accuracy and avoids banding, making sleep covers look better.'
                               ' On FW >= 4.11, Nickel itself may sometimes do a decent job of it.'
                               ' Has no effect without "Upload black and white covers"!'),
                             device.get_pref('dithered_covers')
                             )
        # Make it visually depend on B&W being enabled!
        # c.f., https://stackoverflow.com/q/36281103
        self.dithered_covers_checkbox.setEnabled(device.get_pref('upload_grayscale'))
        self.upload_grayscale_checkbox.toggled.connect(self.dithered_covers_checkbox.setEnabled)
        self.upload_grayscale_checkbox.toggled.connect(
            lambda checked: not checked and self.dithered_covers_checkbox.setChecked(False))

        self.keep_cover_aspect_checkbox = create_checkbox(
                             _('Keep cover aspect ratio'),
                             _('When uploading covers, do not change the aspect ratio when resizing for the device.'
                               ' This is for firmware versions 2.3.1 and later.'),
                             device.get_pref('keep_cover_aspect'))

        self.letterbox_fs_covers_checkbox = create_checkbox(
                             _('Letterbox full-screen covers'),
                             _('Do it on our end, instead of letting Nickel handle it.'
                               ' Provides pixel-perfect results on devices where Nickel does not do extra processing.'
                               ' Obviously has no effect without "Keep cover aspect ratio".'
                               ' This is probably undesirable if you disable the "Show book covers full screen"'
                               ' setting on your device.'),
                             device.get_pref('letterbox_fs_covers'))

        self.letterbox_fs_covers_color_button = ColorButton(self.options_layout)
        self.letterbox_fs_covers_color_button.setToolTip(_('Choose the color to use when letterboxing the cover.'
                                                           ' The default color is black (#000000)'
                                                           )
        )
        self.letterbox_fs_covers_color_button.color = device.get_pref('letterbox_fs_covers_color')

        # Make it visually depend on AR being enabled!
        self.letterbox_fs_covers_checkbox.setEnabled(device.get_pref('keep_cover_aspect'))
        self.letterbox_fs_covers_color_button.setEnabled(device.get_pref('keep_cover_aspect') and device.get_pref('letterbox_fs_covers'))
        self.keep_cover_aspect_checkbox.toggled.connect(self.letterbox_fs_covers_checkbox.setEnabled)
        self.keep_cover_aspect_checkbox.toggled.connect(
            lambda checked: not checked and self.letterbox_fs_covers_checkbox.setChecked(False))
        self.letterbox_fs_covers_checkbox.toggled.connect(self.letterbox_fs_covers_color_button.setEnabled)

        self.png_covers_checkbox = create_checkbox(
                             _('Save covers as PNG'),
                             _('Use the PNG image format instead of JPG.'
                               ' Higher quality, especially with "Upload dithered covers" enabled,'
                               ' which will also help generate potentially smaller files.'
                               ' Behavior completely unknown on old (< 3.x) Kobo firmwares,'
                               ' known to behave on FW >= 4.8.'
                               ' Has no effect without "Upload black and white covers"!'),
                             device.get_pref('png_covers'))
        # Make it visually depend on B&W being enabled, to avoid storing ridiculously large color PNGs.
        self.png_covers_checkbox.setEnabled(device.get_pref('upload_grayscale'))
        self.upload_grayscale_checkbox.toggled.connect(self.png_covers_checkbox.setEnabled)
        self.upload_grayscale_checkbox.toggled.connect(
            lambda checked: not checked and self.png_covers_checkbox.setChecked(False))

        self.options_layout.addWidget(self.keep_cover_aspect_checkbox,          0, 0, 1, 1)
        self.options_layout.addWidget(self.letterbox_fs_covers_checkbox,        0, 1, 1, 2)
        self.options_layout.addWidget(self.letterbox_fs_covers_color_button,    1, 1, 1, 1)
        self.options_layout.addWidget(self.upload_grayscale_checkbox,           2, 0, 1, 1)
        self.options_layout.addWidget(self.dithered_covers_checkbox,            2, 1, 1, 2)
        self.options_layout.addWidget(self.png_covers_checkbox,                 3, 1, 1, 2)
        self.options_layout.setColumnStretch(0, 0)
        self.options_layout.setColumnStretch(1, 0)
        self.options_layout.setColumnStretch(2, 1)

    @property
    def upload_covers(self):
        return self.isChecked()

    @property
    def upload_grayscale(self):
        return self.upload_grayscale_checkbox.isChecked()

    @property
    def dithered_covers(self):
        return self.dithered_covers_checkbox.isChecked()

    @property
    def keep_cover_aspect(self):
        return self.keep_cover_aspect_checkbox.isChecked()

    @property
    def letterbox_fs_covers(self):
        return self.letterbox_fs_covers_checkbox.isChecked()

    @property
    def letterbox_fs_covers_color(self):
        return self.letterbox_fs_covers_color_button.color

    @property
    def png_covers(self):
        return self.png_covers_checkbox.isChecked()