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_()
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
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)
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)
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
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
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))
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_()
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 = []
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()