class MainWindowMixin(object): # {{{ def __init__(self, *args, **kwargs): pass def init_main_window_mixin(self, db): self.setObjectName('MainWindow') self.setWindowIcon(QIcon(I('lt.png'))) self.setWindowTitle(__appname__) self.setContextMenuPolicy(Qt.NoContextMenu) self.centralwidget = QWidget(self) self.setCentralWidget(self.centralwidget) self._central_widget_layout = QVBoxLayout() self.centralwidget.setLayout(self._central_widget_layout) self.resize(1012, 740) self.donate_button = ThrobbingButton() self.location_manager = LocationManager(self) self.iactions['Fetch News'].init_scheduler(db) self.search_bar = SearchBar(self) self.bars_manager = BarsManager(self.donate_button, self.location_manager, self) for bar in self.bars_manager.main_bars: self.addToolBar(Qt.TopToolBarArea, bar) for bar in self.bars_manager.child_bars: self.addToolBar(Qt.BottomToolBarArea, bar) self.bars_manager.update_bars() # This is disabled because it introduces various toolbar related bugs # The width of the toolbar becomes the sum of both toolbars if tweaks['unified_title_toolbar_on_osx']: self.setUnifiedTitleAndToolBarOnMac(True) l = self.centralwidget.layout() l.addWidget(self.search_bar)
class MainWindowMixin(object): # {{{ def __init__(self, *args, **kwargs): pass def init_main_window_mixin(self, db): self.setObjectName('MainWindow') self.setWindowIcon(QIcon(I('lt.png'))) self.setWindowTitle(__appname__) self.setContextMenuPolicy(Qt.NoContextMenu) self.centralwidget = QWidget(self) self.setCentralWidget(self.centralwidget) self._central_widget_layout = QVBoxLayout() self.centralwidget.setLayout(self._central_widget_layout) self.resize(1012, 740) self.donate_button = ThrobbingButton() self.location_manager = LocationManager(self) self.iactions['Fetch News'].init_scheduler(db) self.search_bar = SearchBar(self) self.bars_manager = BarsManager(self.donate_button, self.location_manager, self) for bar in self.bars_manager.main_bars: self.addToolBar(Qt.TopToolBarArea, bar) for bar in self.bars_manager.child_bars: self.addToolBar(Qt.BottomToolBarArea, bar) self.bars_manager.update_bars() # This is disabled because it introduces various toolbar related bugs # The width of the toolbar becomes the sum of both toolbars if tweaks['unified_title_toolbar_on_osx']: try: self.setUnifiedTitleAndToolBarOnMac(True) except AttributeError: pass # PyQt5 seems to be missing this property l = self.centralwidget.layout() # And now, start adding the real widgets l.addWidget(self.search_bar) # Add in the widget for the shutdown messages. It is invisible until a # message is shown smw = self.shutdown_message_widget = QLabel(self) smw.setAlignment(Qt.AlignCenter) smw.setVisible(False) smw.setAutoFillBackground(True) smw.setStyleSheet( 'QLabel { background-color: rgba(200, 200, 200, 200); color: black }' ) def show_shutdown_message(self, message=''): smw = self.shutdown_message_widget smw.setGeometry(0, 0, self.width(), self.height()) smw.setVisible(True) smw.raise_() smw.setText(_('<h2>Shutting down</h2><div>') + message) # Force processing the events needed to show the message QCoreApplication.processEvents()
class MainWindowMixin(object): # {{{ def __init__(self, *args, **kwargs): pass def init_main_window_mixin(self, db): self.setObjectName('MainWindow') self.setWindowIcon(QIcon(I('lt.png'))) self.setWindowTitle(__appname__) self.setContextMenuPolicy(Qt.NoContextMenu) self.centralwidget = QWidget(self) self.setCentralWidget(self.centralwidget) self._central_widget_layout = QVBoxLayout() self.centralwidget.setLayout(self._central_widget_layout) self.resize(1012, 740) self.donate_button = ThrobbingButton() self.location_manager = LocationManager(self) self.iactions['Fetch News'].init_scheduler(db) self.search_bar = SearchBar(self) self.bars_manager = BarsManager(self.donate_button, self.location_manager, self) for bar in self.bars_manager.main_bars: self.addToolBar(Qt.TopToolBarArea, bar) for bar in self.bars_manager.child_bars: self.addToolBar(Qt.BottomToolBarArea, bar) self.bars_manager.update_bars() # This is disabled because it introduces various toolbar related bugs # The width of the toolbar becomes the sum of both toolbars if tweaks['unified_title_toolbar_on_osx']: try: self.setUnifiedTitleAndToolBarOnMac(True) except AttributeError: pass # PyQt5 seems to be missing this property l = self.centralwidget.layout() # Add in the widget for the shutdown messages. It is invisible until a # message is shown smw = self.shutdown_message_widget = QLabel('') smw.setMinimumHeight(200) smw.setAlignment(Qt.AlignCenter) self.shutdown_message_widget.setVisible(False) l.addWidget(smw) # And now, start adding the real widgets l.addWidget(self.search_bar) def show_shutdown_message(self, message): smw = self.shutdown_message_widget smw.setVisible(True) txt = smw.text() txt += '\n' + message smw.setText(txt) # Force processing the events needed to show the message QCoreApplication.processEvents()
def create_donate_widget(button): w = QWidget() w.setLayout(QVBoxLayout()) w.layout().addWidget(button) if isosx: w.setStyleSheet('QWidget, QToolButton {background-color: none; border: none; }') w.layout().setContentsMargins(0,0,0,0) w.setContentsMargins(0,0,0,0) w.filler = QLabel(u'\u00a0') w.layout().addWidget(w.filler) return w
def create_donate_widget(button): w = QWidget() w.setLayout(QVBoxLayout()) w.layout().addWidget(button) if isosx: w.setStyleSheet( 'QWidget, QToolButton {background-color: none; border: none; }') w.layout().setContentsMargins(0, 0, 0, 0) w.setContentsMargins(0, 0, 0, 0) w.filler = QLabel(u'\u00a0') w.layout().addWidget(w.filler) return w
class MainWindowMixin(object): # {{{ def __init__(self, *args, **kwargs): pass def init_main_window_mixin(self, db): self.setObjectName('MainWindow') self.setWindowIcon(QIcon(I('lt.png'))) self.setWindowTitle(__appname__) self.setContextMenuPolicy(Qt.NoContextMenu) self.centralwidget = QWidget(self) self.setCentralWidget(self.centralwidget) self._central_widget_layout = QVBoxLayout() self.centralwidget.setLayout(self._central_widget_layout) self.resize(1012, 740) self.donate_button = ThrobbingButton() self.location_manager = LocationManager(self) # self.iactions['Fetch News'].init_scheduler(db) self.search_bar = SearchBar(self) self.bars_manager = BarsManager(self.donate_button, self.location_manager, self) for bar in self.bars_manager.main_bars: self.addToolBar(Qt.TopToolBarArea, bar) for bar in self.bars_manager.child_bars: self.addToolBar(Qt.BottomToolBarArea, bar) self.bars_manager.update_bars() # This is disabled because it introduces various toolbar related bugs # The width of the toolbar becomes the sum of both toolbars if tweaks['unified_title_toolbar_on_osx']: try: self.setUnifiedTitleAndToolBarOnMac(True) except AttributeError: pass # PyQt5 seems to be missing this property l = self.centralwidget.layout() l.addWidget(self.search_bar)
class RuleEditor(QDialog): # {{{ @property def doing_multiple(self): return hasattr(self, 'multiple_icon_cb') and self.multiple_icon_cb.isChecked() def __init__(self, fm, pref_name, parent=None): QDialog.__init__(self, parent) self.fm = fm if pref_name == 'column_color_rules': self.rule_kind = 'color' rule_text = _('column coloring') elif pref_name == 'column_icon_rules': self.rule_kind = 'icon' rule_text = _('column icon') elif pref_name == 'cover_grid_icon_rules': self.rule_kind = 'emblem' rule_text = _('Cover grid emblem') self.setWindowIcon(QIcon(I('format-fill-color.png'))) self.setWindowTitle(_('Create/edit a {0} rule').format(rule_text)) self.l = l = QGridLayout(self) self.setLayout(l) self.l1 = l1 = QLabel(_('Create a {0} rule by' ' filling in the boxes below').format(rule_text)) l.addWidget(l1, 0, 0, 1, 8) self.f1 = QFrame(self) self.f1.setFrameShape(QFrame.HLine) l.addWidget(self.f1, 1, 0, 1, 8) self.l2 = l2 = QLabel(_('Add the emblem:') if self.rule_kind == 'emblem' else _('Set the')) l.addWidget(l2, 2, 0) if self.rule_kind == 'color': l.addWidget(QLabel(_('color'))) elif self.rule_kind == 'icon': self.kind_box = QComboBox(self) for tt, t in icon_rule_kinds: self.kind_box.addItem(tt, t) l.addWidget(self.kind_box, 2, 1) self.kind_box.setToolTip(textwrap.fill(_( 'If you choose composed icons and multiple rules match, then all the' ' matching icons will be combined, otherwise the icon from the' ' first rule to match will be used.'))) else: pass self.l3 = l3 = QLabel(_('of the column:')) l.addWidget(l3, 2, 2) self.column_box = QComboBox(self) l.addWidget(self.column_box, 2, 3) self.l4 = l4 = QLabel(_('to')) l.addWidget(l4, 2, 4) if self.rule_kind == 'emblem': l3.setVisible(False), self.column_box.setVisible(False), l4.setVisible(False) def create_filename_box(): self.filename_box = f = QComboBox() self.filenamebox_view = v = QListView() v.setIconSize(QSize(32, 32)) self.filename_box.setView(v) self.orig_filenamebox_view = f.view() f.setMinimumContentsLength(20), f.setSizeAdjustPolicy(f.AdjustToMinimumContentsLengthWithIcon) self.populate_icon_filenames() if self.rule_kind == 'color': self.color_box = ColorButton(parent=self) self.color_label = QLabel('Sample text Sample text') self.color_label.setTextFormat(Qt.RichText) l.addWidget(self.color_box, 2, 5) l.addWidget(self.color_label, 2, 6) l.addItem(QSpacerItem(10, 10, QSizePolicy.Expanding), 2, 7) elif self.rule_kind == 'emblem': create_filename_box() self.update_filename_box() self.filename_button = QPushButton(QIcon(I('document_open.png')), _('&Add new image')) l.addWidget(self.filename_box) l.addWidget(self.filename_button, 2, 6) l.addWidget(QLabel(_('(Images should be square-ish)')), 2, 7) l.setColumnStretch(7, 10) else: create_filename_box() vb = QVBoxLayout() self.multiple_icon_cb = QCheckBox(_('Choose &more than one icon')) vb.addWidget(self.multiple_icon_cb) self.update_filename_box() self.multiple_icon_cb.clicked.connect(self.multiple_box_clicked) vb.addWidget(self.filename_box) l.addLayout(vb, 2, 5) self.filename_button = QPushButton(QIcon(I('document_open.png')), _('&Add icon')) l.addWidget(self.filename_button, 2, 6) l.addWidget(QLabel(_('Icons should be square or landscape')), 2, 7) l.setColumnStretch(7, 10) self.l5 = l5 = QLabel( _('Only if the following conditions are all satisfied:')) l.addWidget(l5, 3, 0, 1, 7) self.scroll_area = sa = QScrollArea(self) sa.setMinimumHeight(300) sa.setMinimumWidth(950) sa.setWidgetResizable(True) l.addWidget(sa, 4, 0, 1, 8) self.add_button = b = QPushButton(QIcon(I('plus.png')), _('Add &another condition')) l.addWidget(b, 5, 0, 1, 8) b.clicked.connect(self.add_blank_condition) self.l6 = l6 = QLabel(_('You can disable a condition by' ' blanking all of its boxes')) l.addWidget(l6, 6, 0, 1, 8) self.bb = bb = QDialogButtonBox( QDialogButtonBox.Ok|QDialogButtonBox.Cancel) bb.accepted.connect(self.accept) bb.rejected.connect(self.reject) l.addWidget(bb, 7, 0, 1, 8) if self.rule_kind != 'color': self.remove_button = b = bb.addButton(_('&Remove icon'), bb.ActionRole) b.setIcon(QIcon(I('minus.png'))) b.setMenu(QMenu()) b.setToolTip('<p>' + _('Remove a previously added icon. Note that doing so will cause rules that use it to stop working.')) self.update_remove_button() self.conditions_widget = QWidget(self) sa.setWidget(self.conditions_widget) self.conditions_widget.setLayout(QVBoxLayout()) self.conditions_widget.layout().setAlignment(Qt.AlignTop) self.conditions = [] if self.rule_kind == 'color': for b in (self.column_box, ): b.setSizeAdjustPolicy(b.AdjustToMinimumContentsLengthWithIcon) b.setMinimumContentsLength(15) for key in sorted(displayable_columns(fm), key=lambda k: sort_key(fm[k]['name']) if k != color_row_key else 0): if key == color_row_key and self.rule_kind != 'color': continue name = all_columns_string if key == color_row_key else fm[key]['name'] if name: self.column_box.addItem(name, key) self.column_box.setCurrentIndex(0) if self.rule_kind == 'color': self.color_box.color = '#000' self.update_color_label() self.color_box.color_changed.connect(self.update_color_label) else: self.rule_icon_files = [] self.filename_button.clicked.connect(self.filename_button_clicked) self.resize(self.sizeHint()) def multiple_box_clicked(self): self.update_filename_box() self.update_icon_filenames_in_box() @property def icon_folder(self): return os.path.join(config_dir, 'cc_icons') def populate_icon_filenames(self): d = self.icon_folder self.icon_file_names = [] if os.path.exists(d): for icon_file in os.listdir(d): icon_file = lower(icon_file) if os.path.exists(os.path.join(d, icon_file)) and icon_file.endswith('.png'): self.icon_file_names.append(icon_file) self.icon_file_names.sort(key=sort_key) def update_filename_box(self): doing_multiple = self.doing_multiple model = QStandardItemModel() self.filename_box.setModel(model) self.icon_file_names.sort(key=sort_key) if doing_multiple: item = QStandardItem(_('Open to see checkboxes')) item.setIcon(QIcon(I('blank.png'))) else: item = QStandardItem('') item.setFlags(Qt.ItemFlag(0)) model.appendRow(item) for i,filename in enumerate(self.icon_file_names): item = QStandardItem(filename) if doing_multiple: item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled) item.setData(Qt.Unchecked, Qt.CheckStateRole) else: item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable) icon = QIcon(os.path.join(self.icon_folder, filename)) item.setIcon(icon) model.appendRow(item) def update_color_label(self): pal = QApplication.palette() bg1 = unicode_type(pal.color(pal.Base).name()) bg2 = unicode_type(pal.color(pal.AlternateBase).name()) c = self.color_box.color self.color_label.setText(''' <span style="color: {c}; background-color: {bg1}"> {st} </span> <span style="color: {c}; background-color: {bg2}"> {st} </span> '''.format(c=c, bg1=bg1, bg2=bg2, st=_('Sample text'))) def sanitize_icon_file_name(self, icon_path): n = lower(sanitize_file_name_unicode( os.path.splitext( os.path.basename(icon_path))[0]+'.png')) return n.replace("'", '_') def filename_button_clicked(self): try: path = choose_files(self, 'choose_category_icon', _('Select Icon'), filters=[ (_('Images'), ['png', 'gif', 'jpg', 'jpeg'])], all_files=False, select_only_single_file=True) if path: icon_path = path[0] icon_name = self.sanitize_icon_file_name(icon_path) if icon_name not in self.icon_file_names: self.icon_file_names.append(icon_name) try: p = QIcon(icon_path).pixmap(QSize(128, 128)) d = self.icon_folder if not os.path.exists(os.path.join(d, icon_name)): if not os.path.exists(d): os.makedirs(d) with open(os.path.join(d, icon_name), 'wb') as f: f.write(pixmap_to_data(p, format='PNG')) except: import traceback traceback.print_exc() self.update_filename_box() self.update_remove_button() if self.doing_multiple: if icon_name not in self.rule_icon_files: self.rule_icon_files.append(icon_name) self.update_icon_filenames_in_box() else: self.filename_box.setCurrentIndex(self.filename_box.findText(icon_name)) self.filename_box.adjustSize() except: import traceback traceback.print_exc() return def get_filenames_from_box(self): if self.doing_multiple: model = self.filename_box.model() fnames = [] for i in range(1, model.rowCount()): item = model.item(i, 0) if item.checkState() == Qt.Checked: fnames.append(lower(unicode_type(item.text()))) fname = ' : '.join(fnames) else: fname = lower(unicode_type(self.filename_box.currentText())) return fname def update_icon_filenames_in_box(self): if self.rule_icon_files: if not self.doing_multiple: idx = self.filename_box.findText(self.rule_icon_files[0]) if idx >= 0: self.filename_box.setCurrentIndex(idx) else: self.filename_box.setCurrentIndex(0) else: model = self.filename_box.model() for icon in self.rule_icon_files: idx = self.filename_box.findText(icon) if idx >= 0: item = model.item(idx) item.setCheckState(Qt.Checked) def update_remove_button(self): m = self.remove_button.menu() m.clear() for name in self.icon_file_names: ac = m.addAction(QIcon(os.path.join(self.icon_folder, name)), name) connect_lambda(ac.triggered, self, lambda self: self.remove_image(self.sender().text())) def remove_image(self, name): try: os.remove(os.path.join(self.icon_folder, name)) except EnvironmentError: pass else: self.populate_icon_filenames() self.update_remove_button() self.update_filename_box() self.update_icon_filenames_in_box() def add_blank_condition(self): c = ConditionEditor(self.fm, parent=self.conditions_widget) self.conditions.append(c) self.conditions_widget.layout().addWidget(c) def apply_rule(self, kind, col, rule): if kind == 'color': if rule.color: self.color_box.color = rule.color else: if self.rule_kind == 'icon': for i, tup in enumerate(icon_rule_kinds): if kind == tup[1]: self.kind_box.setCurrentIndex(i) break self.rule_icon_files = [ic.strip() for ic in rule.color.split(':')] if len(self.rule_icon_files) > 1: self.multiple_icon_cb.setChecked(True) self.update_filename_box() self.update_icon_filenames_in_box() for i in range(self.column_box.count()): c = unicode_type(self.column_box.itemData(i) or '') if col == c: self.column_box.setCurrentIndex(i) break for c in rule.conditions: ce = ConditionEditor(self.fm, parent=self.conditions_widget) self.conditions.append(ce) self.conditions_widget.layout().addWidget(ce) try: ce.condition = c except: import traceback traceback.print_exc() def accept(self): if self.rule_kind != 'color': fname = self.get_filenames_from_box() if not fname: error_dialog(self, _('No icon selected'), _('You must choose an icon for this rule'), show=True) return if self.validate(): QDialog.accept(self) def validate(self): r = Rule(self.fm) for c in self.conditions: condition = c.condition if condition is not None: try: r.add_condition(*condition) except Exception as e: import traceback error_dialog(self, _('Invalid condition'), _('One of the conditions for this rule is' ' invalid: <b>%s</b>')%e, det_msg=traceback.format_exc(), show=True) return False if len(r.conditions) < 1: error_dialog(self, _('No conditions'), _('You must specify at least one non-empty condition' ' for this rule'), show=True) return False return True @property def rule(self): r = Rule(self.fm) if self.rule_kind != 'color': r.color = self.get_filenames_from_box() else: r.color = self.color_box.color idx = self.column_box.currentIndex() col = unicode_type(self.column_box.itemData(idx) or '') for c in self.conditions: condition = c.condition if condition is not None: r.add_condition(*condition) if self.rule_kind == 'icon': kind = unicode_type(self.kind_box.itemData( self.kind_box.currentIndex()) or '') else: kind = self.rule_kind return kind, col, r
class RuleEditor(QDialog): # {{{ @property def doing_multiple(self): return hasattr(self, 'multiple_icon_cb') and self.multiple_icon_cb.isChecked() def __init__(self, fm, pref_name, parent=None): QDialog.__init__(self, parent) self.fm = fm if pref_name == 'column_color_rules': self.rule_kind = 'color' rule_text = _('column coloring') elif pref_name == 'column_icon_rules': self.rule_kind = 'icon' rule_text = _('column icon') elif pref_name == 'cover_grid_icon_rules': self.rule_kind = 'emblem' rule_text = _('Cover grid emblem') self.setWindowIcon(QIcon(I('format-fill-color.png'))) self.setWindowTitle(_('Create/edit a {0} rule').format(rule_text)) self.l = l = QGridLayout(self) self.setLayout(l) self.l1 = l1 = QLabel(_('Create a {0} rule by' ' filling in the boxes below').format(rule_text)) l.addWidget(l1, 0, 0, 1, 8) self.f1 = QFrame(self) self.f1.setFrameShape(QFrame.HLine) l.addWidget(self.f1, 1, 0, 1, 8) self.l2 = l2 = QLabel(_('Add the emblem:') if self.rule_kind == 'emblem' else _('Set the')) l.addWidget(l2, 2, 0) if self.rule_kind == 'color': l.addWidget(QLabel(_('color'))) elif self.rule_kind == 'icon': self.kind_box = QComboBox(self) for tt, t in icon_rule_kinds: self.kind_box.addItem(tt, t) l.addWidget(self.kind_box, 2, 1) self.kind_box.setToolTip(textwrap.fill(_( 'If you choose composed icons and multiple rules match, then all the' ' matching icons will be combined, otherwise the icon from the' ' first rule to match will be used.'))) else: pass self.l3 = l3 = QLabel(_('of the column:')) l.addWidget(l3, 2, 2) self.column_box = QComboBox(self) l.addWidget(self.column_box, 2, 3) self.l4 = l4 = QLabel(_('to')) l.addWidget(l4, 2, 4) if self.rule_kind == 'emblem': l3.setVisible(False), self.column_box.setVisible(False), l4.setVisible(False) def create_filename_box(): self.filename_box = f = QComboBox() self.filenamebox_view = v = QListView() v.setIconSize(QSize(32, 32)) self.filename_box.setView(v) self.orig_filenamebox_view = f.view() f.setMinimumContentsLength(20), f.setSizeAdjustPolicy(f.AdjustToMinimumContentsLengthWithIcon) self.populate_icon_filenames() if self.rule_kind == 'color': self.color_box = ColorButton(parent=self) self.color_label = QLabel('Sample text Sample text') self.color_label.setTextFormat(Qt.RichText) l.addWidget(self.color_box, 2, 5) l.addWidget(self.color_label, 2, 6) l.addItem(QSpacerItem(10, 10, QSizePolicy.Expanding), 2, 7) elif self.rule_kind == 'emblem': create_filename_box() self.update_filename_box() self.filename_button = QPushButton(QIcon(I('document_open.png')), _('&Add new image')) l.addWidget(self.filename_box) l.addWidget(self.filename_button, 2, 6) l.addWidget(QLabel(_('(Images should be square-ish)')), 2, 7) l.setColumnStretch(7, 10) else: create_filename_box() vb = QVBoxLayout() self.multiple_icon_cb = QCheckBox(_('Choose &more than one icon')) vb.addWidget(self.multiple_icon_cb) self.update_filename_box() self.multiple_icon_cb.clicked.connect(self.multiple_box_clicked) vb.addWidget(self.filename_box) l.addLayout(vb, 2, 5) self.filename_button = QPushButton(QIcon(I('document_open.png')), _('&Add icon')) l.addWidget(self.filename_button, 2, 6) l.addWidget(QLabel(_('Icons should be square or landscape')), 2, 7) l.setColumnStretch(7, 10) self.l5 = l5 = QLabel( _('Only if the following conditions are all satisfied:')) l.addWidget(l5, 3, 0, 1, 7) self.scroll_area = sa = QScrollArea(self) sa.setMinimumHeight(300) sa.setMinimumWidth(950) sa.setWidgetResizable(True) l.addWidget(sa, 4, 0, 1, 8) self.add_button = b = QPushButton(QIcon(I('plus.png')), _('Add &another condition')) l.addWidget(b, 5, 0, 1, 8) b.clicked.connect(self.add_blank_condition) self.l6 = l6 = QLabel(_('You can disable a condition by' ' blanking all of its boxes')) l.addWidget(l6, 6, 0, 1, 8) self.bb = bb = QDialogButtonBox( QDialogButtonBox.Ok|QDialogButtonBox.Cancel) bb.accepted.connect(self.accept) bb.rejected.connect(self.reject) l.addWidget(bb, 7, 0, 1, 8) if self.rule_kind != 'color': self.remove_button = b = bb.addButton(_('&Remove icon'), bb.ActionRole) b.setIcon(QIcon(I('minus.png'))) b.setMenu(QMenu()) b.setToolTip('<p>' + _('Remove a previously added icon. Note that doing so will cause rules that use it to stop working.')) self.update_remove_button() self.conditions_widget = QWidget(self) sa.setWidget(self.conditions_widget) self.conditions_widget.setLayout(QVBoxLayout()) self.conditions_widget.layout().setAlignment(Qt.AlignTop) self.conditions = [] if self.rule_kind == 'color': for b in (self.column_box, ): b.setSizeAdjustPolicy(b.AdjustToMinimumContentsLengthWithIcon) b.setMinimumContentsLength(15) for key in sorted(displayable_columns(fm), key=lambda(k): sort_key(fm[k]['name']) if k != color_row_key else 0): if key == color_row_key and self.rule_kind != 'color': continue name = all_columns_string if key == color_row_key else fm[key]['name'] if name: self.column_box.addItem(name, key) self.column_box.setCurrentIndex(0) if self.rule_kind == 'color': self.color_box.color = '#000' self.update_color_label() self.color_box.color_changed.connect(self.update_color_label) else: self.rule_icon_files = [] self.filename_button.clicked.connect(self.filename_button_clicked) self.resize(self.sizeHint()) def multiple_box_clicked(self): self.update_filename_box() self.update_icon_filenames_in_box() @property def icon_folder(self): return os.path.join(config_dir, 'cc_icons') def populate_icon_filenames(self): d = self.icon_folder self.icon_file_names = [] if os.path.exists(d): for icon_file in os.listdir(d): icon_file = lower(icon_file) if os.path.exists(os.path.join(d, icon_file)) and icon_file.endswith('.png'): self.icon_file_names.append(icon_file) self.icon_file_names.sort(key=sort_key) def update_filename_box(self): doing_multiple = self.doing_multiple model = QStandardItemModel() self.filename_box.setModel(model) self.icon_file_names.sort(key=sort_key) if doing_multiple: item = QStandardItem(_('Open to see checkboxes')) item.setIcon(QIcon(I('blank.png'))) else: item = QStandardItem('') item.setFlags(Qt.ItemFlag(0)) model.appendRow(item) for i,filename in enumerate(self.icon_file_names): item = QStandardItem(filename) if doing_multiple: item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled) item.setData(Qt.Unchecked, Qt.CheckStateRole) else: item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable) icon = QIcon(os.path.join(self.icon_folder, filename)) item.setIcon(icon) model.appendRow(item) def update_color_label(self): pal = QApplication.palette() bg1 = unicode(pal.color(pal.Base).name()) bg2 = unicode(pal.color(pal.AlternateBase).name()) c = self.color_box.color self.color_label.setText(''' <span style="color: {c}; background-color: {bg1}"> {st} </span> <span style="color: {c}; background-color: {bg2}"> {st} </span> '''.format(c=c, bg1=bg1, bg2=bg2, st=_('Sample text'))) def sanitize_icon_file_name(self, icon_path): n = lower(sanitize_file_name_unicode( os.path.splitext( os.path.basename(icon_path))[0]+'.png')) return n.replace("'", '_') def filename_button_clicked(self): try: path = choose_files(self, 'choose_category_icon', _('Select Icon'), filters=[ (_('Images'), ['png', 'gif', 'jpg', 'jpeg'])], all_files=False, select_only_single_file=True) if path: icon_path = path[0] icon_name = self.sanitize_icon_file_name(icon_path) if icon_name not in self.icon_file_names: self.icon_file_names.append(icon_name) try: p = QIcon(icon_path).pixmap(QSize(128, 128)) d = self.icon_folder if not os.path.exists(os.path.join(d, icon_name)): if not os.path.exists(d): os.makedirs(d) with open(os.path.join(d, icon_name), 'wb') as f: f.write(pixmap_to_data(p, format='PNG')) except: import traceback traceback.print_exc() self.update_filename_box() self.update_remove_button() if self.doing_multiple: if icon_name not in self.rule_icon_files: self.rule_icon_files.append(icon_name) self.update_icon_filenames_in_box() else: self.filename_box.setCurrentIndex(self.filename_box.findText(icon_name)) self.filename_box.adjustSize() except: import traceback traceback.print_exc() return def get_filenames_from_box(self): if self.doing_multiple: model = self.filename_box.model() fnames = [] for i in range(1, model.rowCount()): item = model.item(i, 0) if item.checkState() == Qt.Checked: fnames.append(lower(unicode(item.text()))) fname = ' : '.join(fnames) else: fname = lower(unicode(self.filename_box.currentText())) return fname def update_icon_filenames_in_box(self): if self.rule_icon_files: if not self.doing_multiple: idx = self.filename_box.findText(self.rule_icon_files[0]) if idx >= 0: self.filename_box.setCurrentIndex(idx) else: self.filename_box.setCurrentIndex(0) else: model = self.filename_box.model() for icon in self.rule_icon_files: idx = self.filename_box.findText(icon) if idx >= 0: item = model.item(idx) item.setCheckState(Qt.Checked) def update_remove_button(self): m = self.remove_button.menu() m.clear() for name in self.icon_file_names: m.addAction(QIcon(os.path.join(self.icon_folder, name)), name).triggered.connect(partial( self.remove_image, name)) def remove_image(self, name): try: os.remove(os.path.join(self.icon_folder, name)) except EnvironmentError: pass else: self.populate_icon_filenames() self.update_remove_button() self.update_filename_box() self.update_icon_filenames_in_box() def add_blank_condition(self): c = ConditionEditor(self.fm, parent=self.conditions_widget) self.conditions.append(c) self.conditions_widget.layout().addWidget(c) def apply_rule(self, kind, col, rule): if kind == 'color': if rule.color: self.color_box.color = rule.color else: if self.rule_kind == 'icon': for i, tup in enumerate(icon_rule_kinds): if kind == tup[1]: self.kind_box.setCurrentIndex(i) break self.rule_icon_files = [ic.strip() for ic in rule.color.split(':')] if len(self.rule_icon_files) > 1: self.multiple_icon_cb.setChecked(True) self.update_filename_box() self.update_icon_filenames_in_box() for i in range(self.column_box.count()): c = unicode(self.column_box.itemData(i) or '') if col == c: self.column_box.setCurrentIndex(i) break for c in rule.conditions: ce = ConditionEditor(self.fm, parent=self.conditions_widget) self.conditions.append(ce) self.conditions_widget.layout().addWidget(ce) try: ce.condition = c except: import traceback traceback.print_exc() def accept(self): if self.rule_kind != 'color': fname = self.get_filenames_from_box() if not fname: error_dialog(self, _('No icon selected'), _('You must choose an icon for this rule'), show=True) return if self.validate(): QDialog.accept(self) def validate(self): r = Rule(self.fm) for c in self.conditions: condition = c.condition if condition is not None: try: r.add_condition(*condition) except Exception as e: import traceback error_dialog(self, _('Invalid condition'), _('One of the conditions for this rule is' ' invalid: <b>%s</b>')%e, det_msg=traceback.format_exc(), show=True) return False if len(r.conditions) < 1: error_dialog(self, _('No conditions'), _('You must specify at least one non-empty condition' ' for this rule'), show=True) return False return True @property def rule(self): r = Rule(self.fm) if self.rule_kind != 'color': r.color = self.get_filenames_from_box() else: r.color = self.color_box.color idx = self.column_box.currentIndex() col = unicode(self.column_box.itemData(idx) or '') for c in self.conditions: condition = c.condition if condition is not None: r.add_condition(*condition) if self.rule_kind == 'icon': kind = unicode(self.kind_box.itemData( self.kind_box.currentIndex()) or '') else: kind = self.rule_kind return kind, col, r
self.animation.setEndValue(size) QMetaObject.invokeMethod(self.animation, 'start', Qt.QueuedConnection) def stop_animation(self): self.animation.stop() self.animation_finished() def paintEvent(self, ev): size = self._icon_size if self._icon_size > 10 else self.iconSize().width() p = QPainter(self) opt = QStyleOptionToolButton() self.initStyleOption(opt) s = self.style() opt.iconSize = QSize(size, size) s.drawComplexControl(s.CC_ToolButton, opt, p, self) if __name__ == '__main__': from PyQt5.Qt import QApplication, QHBoxLayout app = QApplication([]) w = QWidget() w.setLayout(QHBoxLayout()) b = ThrobbingButton() b.setIcon(QIcon(I('donate.png'))) w.layout().addWidget(b) w.show() b.set_normal_icon_size(64, 64) b.start_animation() app.exec_()
self.animation.setStartValue(smaller) self.animation.setEndValue(size) QMetaObject.invokeMethod(self.animation, 'start', Qt.QueuedConnection) def stop_animation(self): self.animation.stop() self.animation_finished() def paintEvent(self, ev): size = self._icon_size if self._icon_size > 10 else self.iconSize().width() p = QPainter(self) opt = QStyleOptionToolButton() self.initStyleOption(opt) s = self.style() opt.iconSize = QSize(size, size) s.drawComplexControl(s.CC_ToolButton, opt, p, self) if __name__ == '__main__': from PyQt5.Qt import QApplication, QHBoxLayout app = QApplication([]) w = QWidget() w.setLayout(QHBoxLayout()) b = ThrobbingButton() b.setIcon(QIcon(I('donate.png'))) w.layout().addWidget(b) w.show() b.set_normal_icon_size(64, 64) b.start_animation() app.exec_()
class Preferences(QMainWindow): run_wizard_requested = pyqtSignal() def __init__(self, gui, initial_plugin=None, close_after_initial=False): QMainWindow.__init__(self, gui) self.gui = gui self.must_restart = False self.committed = False self.close_after_initial = close_after_initial self.resize(930, 720) nh, nw = min_available_height() - 25, available_width() - 10 if nh < 0: nh = 800 if nw < 0: nw = 600 nh = min(self.height(), nh) nw = min(self.width(), nw) self.resize(nw, nh) self.esc_action = QAction(self) self.addAction(self.esc_action) self.esc_action.setShortcut(QKeySequence(Qt.Key_Escape)) self.esc_action.triggered.connect(self.esc) geom = gprefs.get('preferences_window_geometry', None) if geom is not None: self.restoreGeometry(geom) # Center if islinux: self.move(gui.rect().center() - self.rect().center()) self.setWindowModality(Qt.WindowModal) self.setWindowTitle(__appname__ + ' - ' + _('Preferences')) self.setWindowIcon(QIcon(I('config.png'))) self.status_bar = StatusBar(self) self.setStatusBar(self.status_bar) self.stack = QStackedWidget(self) self.cw = QWidget(self) self.cw.setLayout(QVBoxLayout()) self.cw.layout().addWidget(self.stack) self.bb = QDialogButtonBox(QDialogButtonBox.Close) self.wizard_button = self.bb.addButton(_('Run welcome wizard'), self.bb.ActionRole) self.wizard_button.setIcon(QIcon(I('wizard.png'))) self.wizard_button.clicked.connect(self.run_wizard, type=Qt.QueuedConnection) self.cw.layout().addWidget(self.bb) self.bb.button(self.bb.Close).setDefault(True) self.bb.rejected.connect(self.close, type=Qt.QueuedConnection) self.setCentralWidget(self.cw) self.browser = Browser(self) self.browser.show_plugin.connect(self.show_plugin) self.stack.addWidget(self.browser) self.scroll_area = QScrollArea(self) self.stack.addWidget(self.scroll_area) self.scroll_area.setWidgetResizable(True) self.setContextMenuPolicy(Qt.NoContextMenu) self.bar = QToolBar(self) self.addToolBar(self.bar) self.bar.setVisible(False) self.bar.setIconSize(QSize(ICON_SIZE, ICON_SIZE)) self.bar.setMovable(False) self.bar.setFloatable(False) self.bar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.apply_action = self.bar.addAction(QIcon(I('ok.png')), _('&Apply'), self.commit) self.cancel_action = self.bar.addAction(QIcon(I('window-close.png')), _('&Cancel'), self.cancel) self.bar_title = BarTitle(self.bar) self.bar.addWidget(self.bar_title) self.restore_action = self.bar.addAction(QIcon(I('clear_left.png')), _('Restore &defaults'), self.restore_defaults) for ac, tt in [('apply', _('Save changes')), ('cancel', _('Cancel and return to overview'))]: ac = getattr(self, ac + '_action') ac.setToolTip(tt) ac.setWhatsThis(tt) ac.setStatusTip(tt) for ch in self.bar.children(): if isinstance(ch, QToolButton): ch.setCursor(Qt.PointingHandCursor) ch.setAutoRaise(True) self.stack.setCurrentIndex(0) if initial_plugin is not None: category, name = initial_plugin plugin = get_plugin(category, name) if plugin is not None: self.show_plugin(plugin) def event(self, ev): if ev.type() == ev.StatusTip: msg = re.sub(r'</?[a-z1-6]+>', ' ', ev.tip()) ev = QStatusTipEvent(msg) return QMainWindow.event(self, ev) def run_wizard(self): self.close() self.run_wizard_requested.emit() def set_tooltips_for_labels(self): def process_child(child): for g in child.children(): if isinstance(g, QLabel): buddy = g.buddy() if buddy is not None and hasattr(buddy, 'toolTip'): htext = unicode(buddy.toolTip()).strip() etext = unicode(g.toolTip()).strip() if htext and not etext: g.setToolTip(htext) g.setWhatsThis(htext) else: process_child(g) process_child(self.showing_widget) def show_plugin(self, plugin): self.showing_widget = plugin.create_widget(self.scroll_area) self.showing_widget.genesis(self.gui) self.showing_widget.initialize() self.set_tooltips_for_labels() self.scroll_area.setWidget(self.showing_widget) self.stack.setCurrentIndex(1) self.showing_widget.show() self.setWindowTitle(__appname__ + ' - ' + _('Preferences') + ' - ' + plugin.gui_name) self.apply_action.setEnabled(False) self.showing_widget.changed_signal.connect( lambda: self.apply_action.setEnabled(True)) self.showing_widget.restart_now.connect(self.restart_now) self.restore_action.setEnabled( self.showing_widget.supports_restoring_to_defaults) tt = self.showing_widget.restore_defaults_desc if not self.restore_action.isEnabled(): tt = _('Restoring to defaults not supported for') + ' ' + \ plugin.gui_name self.restore_action.setToolTip(textwrap.fill(tt)) self.restore_action.setWhatsThis(textwrap.fill(tt)) self.restore_action.setStatusTip(tt) self.bar_title.show_plugin(plugin) self.setWindowIcon(QIcon(plugin.icon)) self.bar.setVisible(True) self.bb.setVisible(False) def hide_plugin(self): self.showing_widget = QWidget(self.scroll_area) self.scroll_area.setWidget(self.showing_widget) self.setWindowTitle(__appname__ + ' - ' + _('Preferences')) self.bar.setVisible(False) self.stack.setCurrentIndex(0) self.setWindowIcon(QIcon(I('config.png'))) self.bb.setVisible(True) def esc(self, *args): if self.stack.currentIndex() == 1: self.cancel() elif self.stack.currentIndex() == 0: self.close() def restart_now(self): try: self.showing_widget.commit() except AbortCommit: return self.hide_plugin() self.close() self.gui.quit(restart=True) def commit(self, *args): try: must_restart = self.showing_widget.commit() except AbortCommit: return rc = self.showing_widget.restart_critical self.committed = True do_restart = False if must_restart: self.must_restart = True msg = _('Some of the changes you made require a restart.' ' Please restart calibre as soon as possible.') if rc: msg = _('The changes you have made require calibre be ' 'restarted immediately. You will not be allowed to ' 'set any more preferences, until you restart.') do_restart = show_restart_warning(msg, parent=self) self.showing_widget.refresh_gui(self.gui) self.hide_plugin() if self.close_after_initial or (must_restart and rc) or do_restart: self.close() if do_restart: self.gui.quit(restart=True) def cancel(self, *args): if self.close_after_initial: self.close() else: self.hide_plugin() def restore_defaults(self, *args): self.showing_widget.restore_defaults() def closeEvent(self, *args): gprefs.set('preferences_window_geometry', bytearray(self.saveGeometry())) if self.committed: self.gui.must_restart_before_config = self.must_restart self.gui.tags_view.recount() self.gui.create_device_menu() self.gui.set_device_menu_items_state( bool(self.gui.device_connected)) self.gui.bars_manager.apply_settings() self.gui.bars_manager.update_bars() self.gui.build_context_menus() return QMainWindow.closeEvent(self, *args)
class Preferences(QMainWindow): run_wizard_requested = pyqtSignal() def __init__(self, gui, initial_plugin=None, close_after_initial=False): QMainWindow.__init__(self, gui) self.gui = gui self.must_restart = False self.committed = False self.close_after_initial = close_after_initial self.resize(930, 720) nh, nw = min_available_height()-25, available_width()-10 if nh < 0: nh = 800 if nw < 0: nw = 600 nh = min(self.height(), nh) nw = min(self.width(), nw) self.resize(nw, nh) self.esc_action = QAction(self) self.addAction(self.esc_action) self.esc_action.setShortcut(QKeySequence(Qt.Key_Escape)) self.esc_action.triggered.connect(self.esc) geom = gprefs.get('preferences_window_geometry', None) if geom is not None: self.restoreGeometry(geom) # Center if islinux: self.move(gui.rect().center() - self.rect().center()) self.setWindowModality(Qt.WindowModal) self.setWindowTitle(__appname__ + ' - ' + _('Preferences')) self.setWindowIcon(QIcon(I('config.png'))) self.status_bar = StatusBar(self) self.setStatusBar(self.status_bar) self.stack = QStackedWidget(self) self.cw = QWidget(self) self.cw.setLayout(QVBoxLayout()) self.cw.layout().addWidget(self.stack) self.bb = QDialogButtonBox(QDialogButtonBox.Close) self.wizard_button = self.bb.addButton(_('Run welcome wizard'), self.bb.ActionRole) self.wizard_button.setIcon(QIcon(I('wizard.png'))) self.wizard_button.clicked.connect(self.run_wizard, type=Qt.QueuedConnection) self.cw.layout().addWidget(self.bb) self.bb.button(self.bb.Close).setDefault(True) self.bb.rejected.connect(self.close, type=Qt.QueuedConnection) self.setCentralWidget(self.cw) self.browser = Browser(self) self.browser.show_plugin.connect(self.show_plugin) self.stack.addWidget(self.browser) self.scroll_area = QScrollArea(self) self.stack.addWidget(self.scroll_area) self.scroll_area.setWidgetResizable(True) self.setContextMenuPolicy(Qt.NoContextMenu) self.bar = QToolBar(self) self.addToolBar(self.bar) self.bar.setVisible(False) self.bar.setIconSize(QSize(ICON_SIZE, ICON_SIZE)) self.bar.setMovable(False) self.bar.setFloatable(False) self.bar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) self.apply_action = self.bar.addAction(QIcon(I('ok.png')), _('&Apply'), self.commit) self.cancel_action = self.bar.addAction(QIcon(I('window-close.png')), _('&Cancel'), self.cancel) self.bar_title = BarTitle(self.bar) self.bar.addWidget(self.bar_title) self.restore_action = self.bar.addAction(QIcon(I('clear_left.png')), _('Restore &defaults'), self.restore_defaults) for ac, tt in [('apply', _('Save changes')), ('cancel', _('Cancel and return to overview'))]: ac = getattr(self, ac+'_action') ac.setToolTip(tt) ac.setWhatsThis(tt) ac.setStatusTip(tt) for ch in self.bar.children(): if isinstance(ch, QToolButton): ch.setCursor(Qt.PointingHandCursor) ch.setAutoRaise(True) self.stack.setCurrentIndex(0) if initial_plugin is not None: category, name = initial_plugin plugin = get_plugin(category, name) if plugin is not None: self.show_plugin(plugin) def run_wizard(self): self.close() self.run_wizard_requested.emit() def set_tooltips_for_labels(self): def process_child(child): for g in child.children(): if isinstance(g, QLabel): buddy = g.buddy() if buddy is not None and hasattr(buddy, 'toolTip'): htext = unicode(buddy.toolTip()).strip() etext = unicode(g.toolTip()).strip() if htext and not etext: g.setToolTip(htext) g.setWhatsThis(htext) else: process_child(g) process_child(self.showing_widget) def show_plugin(self, plugin): self.showing_widget = plugin.create_widget(self.scroll_area) self.showing_widget.genesis(self.gui) self.showing_widget.initialize() self.set_tooltips_for_labels() self.scroll_area.setWidget(self.showing_widget) self.stack.setCurrentIndex(1) self.showing_widget.show() self.setWindowTitle(__appname__ + ' - ' + _('Preferences') + ' - ' + plugin.gui_name) self.apply_action.setEnabled(False) self.showing_widget.changed_signal.connect(lambda : self.apply_action.setEnabled(True)) self.showing_widget.restart_now.connect(self.restart_now) self.restore_action.setEnabled(self.showing_widget.supports_restoring_to_defaults) tt = self.showing_widget.restore_defaults_desc if not self.restore_action.isEnabled(): tt = _('Restoring to defaults not supported for') + ' ' + \ plugin.gui_name self.restore_action.setToolTip(textwrap.fill(tt)) self.restore_action.setWhatsThis(textwrap.fill(tt)) self.restore_action.setStatusTip(tt) self.bar_title.show_plugin(plugin) self.setWindowIcon(QIcon(plugin.icon)) self.bar.setVisible(True) self.bb.setVisible(False) def hide_plugin(self): self.showing_widget = QWidget(self.scroll_area) self.scroll_area.setWidget(self.showing_widget) self.setWindowTitle(__appname__ + ' - ' + _('Preferences')) self.bar.setVisible(False) self.stack.setCurrentIndex(0) self.setWindowIcon(QIcon(I('config.png'))) self.bb.setVisible(True) def esc(self, *args): if self.stack.currentIndex() == 1: self.cancel() elif self.stack.currentIndex() == 0: self.close() def restart_now(self): try: self.showing_widget.commit() except AbortCommit: return self.hide_plugin() self.close() self.gui.quit(restart=True) def commit(self, *args): try: must_restart = self.showing_widget.commit() except AbortCommit: return rc = self.showing_widget.restart_critical self.committed = True do_restart = False if must_restart: self.must_restart = True msg = _('Some of the changes you made require a restart.' ' Please restart calibre as soon as possible.') if rc: msg = _('The changes you have made require calibre be ' 'restarted immediately. You will not be allowed to ' 'set any more preferences, until you restart.') do_restart = show_restart_warning(msg, parent=self) self.showing_widget.refresh_gui(self.gui) self.hide_plugin() if self.close_after_initial or (must_restart and rc) or do_restart: self.close() if do_restart: self.gui.quit(restart=True) def cancel(self, *args): if self.close_after_initial: self.close() else: self.hide_plugin() def restore_defaults(self, *args): self.showing_widget.restore_defaults() def closeEvent(self, *args): gprefs.set('preferences_window_geometry', bytearray(self.saveGeometry())) if self.committed: self.gui.must_restart_before_config = self.must_restart self.gui.tags_view.recount() self.gui.create_device_menu() self.gui.set_device_menu_items_state(bool(self.gui.device_connected)) self.gui.bars_manager.apply_settings() self.gui.bars_manager.update_bars() self.gui.build_context_menus() return QMainWindow.closeEvent(self, *args)