Ejemplo n.º 1
0
class Shortcuts(preferences.Page):
    def __init__(self, dialog):
        super(Shortcuts, self).__init__(dialog)
        
        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        self.setLayout(layout)
        
        self.scheme = SchemeSelector(self)
        layout.addWidget(self.scheme)
        self.tree = QTreeWidget(self)
        self.tree.setHeaderLabels([_("Command"), _("Shortcut")])
        self.tree.setRootIsDecorated(False)
        self.tree.setColumnCount(2)
        self.tree.setAllColumnsShowFocus(True)
        self.tree.setAnimated(True)
        layout.addWidget(self.tree)
        
        self.edit = QPushButton(icons.get("preferences-desktop-keyboard-shortcuts"), '')
        layout.addWidget(self.edit)
        
        # signals
        self.scheme.currentChanged.connect(self.slotSchemeChanged)
        self.scheme.changed.connect(self.changed)
        self.tree.currentItemChanged.connect(self.slotCurrentItemChanged)
        self.tree.itemDoubleClicked.connect(self.editCurrentItem)
        self.edit.clicked.connect(self.editCurrentItem)
        
        # make a dict of all actions with the actions as key and the names as
        # value, with the collection prepended (for loading/saving)
        win = dialog.parent()
        allactions = {}
        for collection in actioncollectionmanager.manager(win).actionCollections():
            for name, action in collection.actions().items():
                allactions[action] = (collection, name)
        
        # keep a list of actions not in the menu structure
        left = allactions.keys()
        
        def add_actions(menuitem, actions):
            """Add actions to a QTreeWidgetItem."""
            for a in actions:
                if a.menu():
                    item = build_menu_item(a)
                    if item.childCount():
                        menuitem.addChild(item)
                elif a in left:
                    left.remove(a)
                    menuitem.addChild(ShortcutItem(a, *allactions[a]))
            menuitem.setFlags(Qt.ItemIsEnabled) # disable selection
            
        def build_menu_item(action):
            """Return a QTreeWidgetItem with children for all the actions in the submenu."""
            menuitem = QTreeWidgetItem()
            text = qutil.removeAccelelator(action.text())
            menuitem.setText(0, _("Menu {name}").format(name=text))
            add_actions(menuitem, action.menu().actions())
            return menuitem
        
        # present the actions nicely ordered as in the menus
        for a in win.menuBar().actions():
            menuitem = build_menu_item(a)
            if menuitem.childCount():
                self.tree.addTopLevelItem(menuitem)
        
        # sort leftover actions
        left.sort(key=lambda i: i.text())
        
        # show actions that are left, grouped by collection
        titlegroups = {}
        for a in left[:]: # copy
            collection, name = allactions[a]
            if collection.title():
                titlegroups.setdefault(collection.title(), []).append(a)
                left.remove(a)
        for title in sorted(titlegroups):
            item = QTreeWidgetItem(["{0}:".format(title)])
            for a in titlegroups[title]:
                item.addChild(ShortcutItem(a, *allactions[a]))
            self.tree.addTopLevelItem(item)
            item.setFlags(Qt.ItemIsEnabled) # disable selection
            
        # show other actions that were not in the menus
        item = QTreeWidgetItem([_("Other commands:")])
        for a in left:
            if a.text() and not a.menu():
                item.addChild(ShortcutItem(a, *allactions[a]))
        if item.childCount():
            self.tree.addTopLevelItem(item)
            item.setFlags(Qt.ItemIsEnabled) # disable selection
        
        self.tree.expandAll()
        
        item = self.tree.topLevelItem(0).child(0)
        if _lastaction:
            # find the previously selected item
            for i in self.items():
                if i.name == _lastaction:
                    item = i
                    break
        self.tree.setCurrentItem(item)
        self.tree.resizeColumnToContents(0)
        
    def items(self):
        """Yield all the items in the actions tree."""
        def children(item):
            for i in range(item.childCount()):
                c = item.child(i)
                if c.childCount():
                    for c1 in children(c):
                        yield c1
                else:
                    yield c
        for c in children(self.tree.invisibleRootItem()):
            yield c
    
    def saveSettings(self):
        self.scheme.saveSettings("shortcut_scheme", "shortcut_schemes", "shortcuts")
        for item in self.items():
            for scheme in self.scheme.schemes():
                item.save(scheme)
            item.clearSettings()
            item.switchScheme(self.scheme.currentScheme())
        
    def loadSettings(self):
        self.scheme.loadSettings("shortcut_scheme", "shortcut_schemes")
        # clear the settings in all the items
        for item in self.items():
            item.clearSettings()
            item.switchScheme(self.scheme.currentScheme())
        
    def slotSchemeChanged(self):
        """Called when the Scheme combobox is changed by the user."""
        for item in self.items():
            item.switchScheme(self.scheme.currentScheme())
        
    def slotCurrentItemChanged(self, item):
        if isinstance(item, ShortcutItem):
            self.edit.setText(
                _("&Edit Shortcut for \"{name}\"").format(name=item.text(0)))
            self.edit.setEnabled(True)
            global _lastaction
            _lastaction = item.name
        else:
            self.edit.setText(_("(no shortcut)"))
            self.edit.setEnabled(False)
        
    def editCurrentItem(self):
        item = self.tree.currentItem()
        if not isinstance(item, ShortcutItem):
            return
        try:
            dlg = self._editdialog
        except AttributeError:
            dlg = self._editdialog = ShortcutEditDialog(self)
        scheme = self.scheme.currentScheme()
        action = item.action(scheme)
        default = item.defaultShortcuts()
        if dlg.editAction(action, default):
            shortcuts = action.shortcuts()
            # check for conflicts
            conflicting = []
            for i in self.items():
                if i is not item:
                    for s1, s2 in itertools.product(i.shortcuts(scheme), shortcuts):
                        if s1.matches(s2) or s2.matches(s1):
                            conflicting.append(i)
            if conflicting:
                # show a question dialog
                msg = [_("This shortcut conflicts with the following command:",
                        "This shortcut conflicts with the following commands:", len(conflicting))]
                msg.append('<br/>'.join(i.text(0) for i in conflicting))
                msg.append(_("Remove the shortcut from that command?",
                             "Remove the shortcut from those commands?", len(conflicting)))
                msg = '<p>{0}</p>'.format('</p><p>'.join(msg))
                res = QMessageBox.warning(self, _("Shortcut Conflict"), msg,
                        QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel)
                if res == QMessageBox.Yes:
                    # remove from conflicting
                    for i in conflicting:
                        l = i.shortcuts(scheme)
                        for s1 in list(l): # copy
                            for s2 in shortcuts:
                                if s1.matches(s2) or s2.matches(s1):
                                    l.remove(s1)
                        i.setShortcuts(l, scheme)
                elif res == QMessageBox.No:
                    # remove from ourselves
                    for i in conflicting:
                        for s1 in list(shortcuts): # copy
                            for s2 in i.shortcuts(scheme):
                                if s1.matches(s2) or s2.matches(s1):
                                    shortcuts.remove(s1)
                else:
                    return # cancelled
            # store the shortcut
            item.setShortcuts(shortcuts, scheme)
            self.changed.emit()
Ejemplo n.º 2
0
class Shortcuts(preferences.Page):
    def __init__(self, dialog):
        super(Shortcuts, self).__init__(dialog)

        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        self.setLayout(layout)

        self.scheme = SchemeSelector(self)
        layout.addWidget(self.scheme)
        self.tree = QTreeWidget(self)
        self.tree.setHeaderLabels([_("Command"), _("Shortcut")])
        self.tree.setRootIsDecorated(False)
        self.tree.setColumnCount(2)
        self.tree.setAllColumnsShowFocus(True)
        self.tree.setAnimated(True)
        layout.addWidget(self.tree)

        self.edit = QPushButton(
            icons.get("preferences-desktop-keyboard-shortcuts"), '')
        layout.addWidget(self.edit)

        # signals
        self.scheme.currentChanged.connect(self.slotSchemeChanged)
        self.scheme.changed.connect(self.changed)
        self.tree.currentItemChanged.connect(self.slotCurrentItemChanged)
        self.tree.itemDoubleClicked.connect(self.editCurrentItem)
        self.edit.clicked.connect(self.editCurrentItem)

        # make a dict of all actions with the actions as key and the names as
        # value, with the collection prepended (for loading/saving)
        win = dialog.parent()
        allactions = {}
        for collection in actioncollectionmanager.manager(
                win).actionCollections():
            for name, action in collection.actions().items():
                allactions[action] = (collection, name)

        # keep a list of actions not in the menu structure
        left = list(allactions.keys())

        def add_actions(menuitem, actions):
            """Add actions to a QTreeWidgetItem."""
            for a in actions:
                if a.menu():
                    item = build_menu_item(a)
                    if item.childCount():
                        menuitem.addChild(item)
                elif a in left:
                    left.remove(a)
                    menuitem.addChild(ShortcutItem(a, *allactions[a]))
            menuitem.setFlags(Qt.ItemIsEnabled)  # disable selection

        def build_menu_item(action):
            """Return a QTreeWidgetItem with children for all the actions in the submenu."""
            menuitem = QTreeWidgetItem()
            text = qutil.removeAccelerator(action.text())
            menuitem.setText(0, _("Menu {name}").format(name=text))
            add_actions(menuitem, action.menu().actions())
            return menuitem

        # present the actions nicely ordered as in the menus
        for a in win.menuBar().actions():
            menuitem = build_menu_item(a)
            if menuitem.childCount():
                self.tree.addTopLevelItem(menuitem)

        # sort leftover actions
        left.sort(key=lambda i: i.text())

        # show actions that are left, grouped by collection
        titlegroups = {}
        for a in left[:]:  # copy
            collection, name = allactions[a]
            if collection.title():
                titlegroups.setdefault(collection.title(), []).append(a)
                left.remove(a)
        for title in sorted(titlegroups):
            item = QTreeWidgetItem(["{0}:".format(title)])
            for a in titlegroups[title]:
                item.addChild(ShortcutItem(a, *allactions[a]))
            self.tree.addTopLevelItem(item)
            item.setFlags(Qt.ItemIsEnabled)  # disable selection

        # show other actions that were not in the menus
        item = QTreeWidgetItem([_("Other commands:")])
        for a in left:
            if a.text() and not a.menu():
                item.addChild(ShortcutItem(a, *allactions[a]))
        if item.childCount():
            self.tree.addTopLevelItem(item)
            item.setFlags(Qt.ItemIsEnabled)  # disable selection

        self.tree.expandAll()

        item = self.tree.topLevelItem(0).child(0)
        if _lastaction:
            # find the previously selected item
            for i in self.items():
                if i.name == _lastaction:
                    item = i
                    break
        self.tree.setCurrentItem(item)
        self.tree.resizeColumnToContents(0)

    def items(self):
        """Yield all the items in the actions tree."""
        def children(item):
            for i in range(item.childCount()):
                c = item.child(i)
                if c.childCount():
                    for c1 in children(c):
                        yield c1
                else:
                    yield c

        for c in children(self.tree.invisibleRootItem()):
            yield c

    def item(self, collection, name):
        for item in self.items():
            if item.collection.name == collection and item.name == name:
                return item

    def saveSettings(self):
        self.scheme.saveSettings("shortcut_scheme", "shortcut_schemes",
                                 "shortcuts")
        for item in self.items():
            for scheme in self.scheme.schemes():
                item.save(scheme)
            item.clearSettings()
            item.switchScheme(self.scheme.currentScheme())

    def loadSettings(self):
        self.scheme.loadSettings("shortcut_scheme", "shortcut_schemes")
        # clear the settings in all the items
        for item in self.items():
            item.clearSettings()
            item.switchScheme(self.scheme.currentScheme())

    def slotSchemeChanged(self):
        """Called when the Scheme combobox is changed by the user."""
        for item in self.items():
            item.switchScheme(self.scheme.currentScheme())

    def slotCurrentItemChanged(self, item):
        if isinstance(item, ShortcutItem):
            self.edit.setText(
                _("&Edit Shortcut for \"{name}\"").format(name=item.text(0)))
            self.edit.setEnabled(True)
            global _lastaction
            _lastaction = item.name
        else:
            self.edit.setText(_("(no shortcut)"))
            self.edit.setEnabled(False)

    def import_(self, filename):
        from . import import_export
        import_export.importShortcut(filename, self, self.scheme)

    def export(self, name, filename):
        from . import import_export
        try:
            import_export.exportShortcut(self, self.scheme.currentScheme(),
                                         name, filename)
        except (IOError, OSError) as e:
            QMessageBox.critical(
                self, _("Error"),
                _("Can't write to destination:\n\n{url}\n\n{error}").format(
                    url=filename, error=e.strerror))

    def findShortcutConflict(self, shortcut):
        """Find the possible shortcut conflict and return the conflict name."""
        if shortcut:
            item = self.tree.currentItem()
            if not isinstance(item, ShortcutItem):
                return None
            scheme = self.scheme.currentScheme()
            for i in self.items():
                a = i.action(scheme)
                if i != item and a.shortcuts():
                    for s1 in a.shortcuts():
                        if s1.matches(shortcut) or shortcut.matches(s1):
                            return qutil.removeAccelerator(a.text())
        return None

    def editCurrentItem(self):
        item = self.tree.currentItem()
        if not isinstance(item, ShortcutItem):
            return

        dlg = ShortcutEditDialog(self, self.findShortcutConflict)
        scheme = self.scheme.currentScheme()
        action = item.action(scheme)
        default = item.defaultShortcuts() or None
        if dlg.editAction(action, default):
            shortcuts = action.shortcuts()
            # check for conflicts
            conflicting = []
            for i in self.items():
                if i is not item:
                    for s1, s2 in itertools.product(i.shortcuts(scheme),
                                                    shortcuts):
                        if s1.matches(s2) or s2.matches(s1):
                            conflicting.append(i)
            if conflicting:
                for i in conflicting:
                    l = i.shortcuts(scheme)
                    for s1 in list(l):  # copy
                        for s2 in shortcuts:
                            if s1.matches(s2) or s2.matches(s1):
                                l.remove(s1)
                    i.setShortcuts(l, scheme)

            # store the shortcut
            item.setShortcuts(shortcuts, scheme)
            self.changed.emit()
Ejemplo n.º 3
0
class FontsColors(preferences.Page):
    def __init__(self, dialog):
        super(FontsColors, self).__init__(dialog)

        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        self.setLayout(layout)

        self.scheme = SchemeSelector(self)
        layout.addWidget(self.scheme)

        self.printScheme = QCheckBox()
        layout.addWidget(self.printScheme)

        hbox = QHBoxLayout()
        self.tree = QTreeWidget(self)
        self.tree.setHeaderHidden(True)
        self.tree.setAnimated(True)
        self.stack = QStackedWidget(self)
        hbox.addWidget(self.tree)
        hbox.addWidget(self.stack)
        layout.addLayout(hbox)

        hbox = QHBoxLayout()
        self.fontLabel = QLabel()
        self.fontChooser = QFontComboBox()
        self.fontSize = QDoubleSpinBox()
        self.fontSize.setRange(6.0, 32.0)
        self.fontSize.setSingleStep(0.5)
        self.fontSize.setDecimals(1)
        hbox.addWidget(self.fontLabel)
        hbox.addWidget(self.fontChooser, 1)
        hbox.addWidget(self.fontSize)
        layout.addLayout(hbox)

        # add the items to our list
        self.baseColorsItem = i = QTreeWidgetItem()
        self.tree.addTopLevelItem(i)
        self.defaultStylesItem = i = QTreeWidgetItem()
        self.tree.addTopLevelItem(i)

        self.defaultStyles = {}
        for name in textformats.defaultStyles:
            self.defaultStyles[name] = i = QTreeWidgetItem()
            self.defaultStylesItem.addChild(i)
            i.name = name
        self.defaultStylesItem.setExpanded(True)

        self.allStyles = {}
        for group, styles in ly.colorize.default_mapping():
            i = QTreeWidgetItem()
            children = {}
            self.allStyles[group] = (i, children)
            self.tree.addTopLevelItem(i)
            i.group = group
            for name, base, clss in styles:
                j = QTreeWidgetItem()
                j.name = name
                j.base = base
                i.addChild(j)
                children[name] = j

        self.baseColorsWidget = BaseColors(self)
        self.customAttributesWidget = CustomAttributes(self)
        self.emptyWidget = QWidget(self)
        self.stack.addWidget(self.baseColorsWidget)
        self.stack.addWidget(self.customAttributesWidget)
        self.stack.addWidget(self.emptyWidget)

        self.tree.currentItemChanged.connect(self.currentItemChanged)
        self.tree.setCurrentItem(self.baseColorsItem)
        self.scheme.currentChanged.connect(self.currentSchemeChanged)
        self.scheme.changed.connect(self.changed)
        self.baseColorsWidget.changed.connect(self.baseColorsChanged)
        self.customAttributesWidget.changed.connect(
            self.customAttributesChanged)
        self.fontChooser.currentFontChanged.connect(self.fontChanged)
        self.fontSize.valueChanged.connect(self.fontChanged)
        self.printScheme.clicked.connect(self.printSchemeChanged)

        app.translateUI(self)

    def translateUI(self):
        self.printScheme.setText(_("Use this scheme for printing"))
        self.fontLabel.setText(_("Font:"))
        self.baseColorsItem.setText(0, _("Base Colors"))
        self.defaultStylesItem.setText(0, _("Default Styles"))

        self.defaultStyleNames = defaultStyleNames()
        self.allStyleNames = allStyleNames()

        for name in textformats.defaultStyles:
            self.defaultStyles[name].setText(0, self.defaultStyleNames[name])
        for group, styles in ly.colorize.default_mapping():
            self.allStyles[group][0].setText(0, self.allStyleNames[group][0])
            for name, base, clss in styles:
                self.allStyles[group][1][name].setText(
                    0, self.allStyleNames[group][1][name])

    def currentItemChanged(self, item, previous):
        if item is self.baseColorsItem:
            self.stack.setCurrentWidget(self.baseColorsWidget)
        elif not item.parent():
            self.stack.setCurrentWidget(self.emptyWidget)
        else:
            data = self.data[self.scheme.currentScheme()]
            w = self.customAttributesWidget
            self.stack.setCurrentWidget(w)
            toptext = None
            if item.parent() is self.defaultStylesItem:
                # default style
                w.setTitle(item.text(0))
                w.setTristate(False)
                w.setTextFormat(data.defaultStyles[item.name])
            else:
                # specific style of specific group
                group, name = item.parent().group, item.name
                w.setTitle("{0}: {1}".format(item.parent().text(0),
                                             item.text(0)))
                inherit = item.base
                if inherit:
                    toptext = _("(Inherits: {name})").format(
                        name=self.defaultStyleNames[inherit])
                w.setTristate(bool(inherit))
                w.setTextFormat(data.allStyles[group][name])
            w.setTopText(toptext)

    def currentSchemeChanged(self):
        scheme = self.scheme.currentScheme()
        if scheme not in self.data:
            self.data[scheme] = textformats.TextFormatData(scheme)
        self.updateDisplay()
        if self.tree.currentItem():
            self.currentItemChanged(self.tree.currentItem(), None)
        with qutil.signalsBlocked(self.printScheme):
            self.printScheme.setChecked(scheme == self._printScheme)

    def fontChanged(self):
        data = self.data[self.scheme.currentScheme()]
        data.font = self.fontChooser.currentFont()
        data.font.setPointSizeF(self.fontSize.value())
        self.updateDisplay()
        self.changed.emit()

    def printSchemeChanged(self):
        if self.printScheme.isChecked():
            self._printScheme = self.scheme.currentScheme()
        else:
            self._printScheme = None
        self.changed.emit()

    def addSchemeData(self, scheme, tfd):
        self.data[scheme] = tfd

    def currentSchemeData(self):
        return self.data[self.scheme.currentScheme()]

    def updateDisplay(self):
        data = self.data[self.scheme.currentScheme()]

        with qutil.signalsBlocked(self.fontChooser, self.fontSize):
            self.fontChooser.setCurrentFont(data.font)
            self.fontSize.setValue(data.font.pointSizeF())

        with qutil.signalsBlocked(self):
            # update base colors
            for name in textformats.baseColors:
                self.baseColorsWidget.color[name].setColor(
                    data.baseColors[name])

        # update base colors for whole treewidget
        p = QApplication.palette()
        p.setColor(QPalette.Base, data.baseColors['background'])
        p.setColor(QPalette.Text, data.baseColors['text'])
        p.setColor(QPalette.Highlight, data.baseColors['selectionbackground'])
        p.setColor(QPalette.HighlightedText, data.baseColors['selectiontext'])
        self.tree.setPalette(p)

        def setItemTextFormat(item, f):
            font = QFont(data.font)
            if f.hasProperty(QTextFormat.ForegroundBrush):
                item.setForeground(0, f.foreground().color())
            else:
                item.setForeground(0, data.baseColors['text'])
            if f.hasProperty(QTextFormat.BackgroundBrush):
                item.setBackground(0, f.background().color())
            else:
                item.setBackground(0, QBrush())
            font.setWeight(f.fontWeight())
            font.setItalic(f.fontItalic())
            font.setUnderline(f.fontUnderline())
            item.setFont(0, font)

        # update looks of default styles
        for name in textformats.defaultStyles:
            setItemTextFormat(self.defaultStyles[name],
                              data.defaultStyles[name])

        # update looks of all the specific styles
        for group, styles in ly.colorize.default_mapping():
            children = self.allStyles[group][1]
            for name, inherit, clss in styles:
                f = QTextCharFormat(data.defaultStyles[inherit]
                                    ) if inherit else QTextCharFormat()
                f.merge(data.allStyles[group][name])
                setItemTextFormat(children[name], f)

    def baseColorsChanged(self, name):
        # keep data up to date with base colors
        data = self.data[self.scheme.currentScheme()]
        data.baseColors[name] = self.baseColorsWidget.color[name].color()
        self.updateDisplay()
        self.changed.emit()

    def customAttributesChanged(self):
        item = self.tree.currentItem()
        if not item or not item.parent():
            return
        data = self.data[self.scheme.currentScheme()]
        if item.parent() is self.defaultStylesItem:
            # a default style has been changed
            data.defaultStyles[
                item.name] = self.customAttributesWidget.textFormat()
        else:
            # a specific style has been changed
            group, name = item.parent().group, item.name
            data.allStyles[group][
                name] = self.customAttributesWidget.textFormat()
        self.updateDisplay()
        self.changed.emit()

    def import_(self, filename):
        from . import import_export
        import_export.importTheme(filename, self, self.scheme)

    def export(self, name, filename):
        from . import import_export
        try:
            import_export.exportTheme(self, name, filename)
        except (IOError, OSError) as e:
            QMessageBox.critical(
                self, _("Error"),
                _("Can't write to destination:\n\n{url}\n\n{error}").format(
                    url=filename, error=e.strerror))

    def loadSettings(self):
        self.data = {}  # holds all data with scheme as key
        self._printScheme = QSettings().value("printer_scheme", "default", str)
        self.scheme.loadSettings("editor_scheme", "editor_schemes")

    def saveSettings(self):
        self.scheme.saveSettings("editor_scheme", "editor_schemes",
                                 "fontscolors")
        for scheme in self.scheme.schemes():
            if scheme in self.data:
                self.data[scheme].save(scheme)
        if self._printScheme:
            QSettings().setValue("printer_scheme", self._printScheme)
        else:
            QSettings().remove("printer_scheme")
Ejemplo n.º 4
0
class FontsColors(preferences.Page):
    def __init__(self, dialog):
        super(FontsColors, self).__init__(dialog)

        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        self.setLayout(layout)

        self.scheme = SchemeSelector(self)
        layout.addWidget(self.scheme)

        self.printScheme = QCheckBox()
        layout.addWidget(self.printScheme)

        hbox = QHBoxLayout()
        self.tree = QTreeWidget(self)
        self.tree.setHeaderHidden(True)
        self.tree.setAnimated(True)
        self.stack = QStackedWidget(self)
        hbox.addWidget(self.tree)
        hbox.addWidget(self.stack)
        layout.addLayout(hbox)

        hbox = QHBoxLayout()
        self.fontLabel = QLabel()
        self.fontChooser = QFontComboBox()
        self.fontSize = QDoubleSpinBox()
        self.fontSize.setRange(6.0, 32.0)
        self.fontSize.setSingleStep(0.5)
        self.fontSize.setDecimals(1)
        hbox.addWidget(self.fontLabel)
        hbox.addWidget(self.fontChooser, 1)
        hbox.addWidget(self.fontSize)
        layout.addLayout(hbox)

        # add the items to our list
        self.baseColorsItem = i = QTreeWidgetItem()
        self.tree.addTopLevelItem(i)
        self.defaultStylesItem = i = QTreeWidgetItem()
        self.tree.addTopLevelItem(i)

        self.defaultStyles = {}
        for name in textformats.defaultStyles:
            self.defaultStyles[name] = i = QTreeWidgetItem()
            self.defaultStylesItem.addChild(i)
            i.name = name
        self.defaultStylesItem.setExpanded(True)

        self.allStyles = {}
        for group, styles in textformats.allStyles:
            i = QTreeWidgetItem()
            children = {}
            self.allStyles[group] = (i, children)
            self.tree.addTopLevelItem(i)
            i.group = group
            for name in styles:
                j = QTreeWidgetItem()
                j.name = name
                i.addChild(j)
                children[name] = j

        self.baseColorsWidget = BaseColors(self)
        self.customAttributesWidget = CustomAttributes(self)
        self.emptyWidget = QWidget(self)
        self.stack.addWidget(self.baseColorsWidget)
        self.stack.addWidget(self.customAttributesWidget)
        self.stack.addWidget(self.emptyWidget)

        self.tree.currentItemChanged.connect(self.currentItemChanged)
        self.tree.setCurrentItem(self.baseColorsItem)
        self.scheme.currentChanged.connect(self.currentSchemeChanged)
        self.scheme.changed.connect(self.changed)
        self.baseColorsWidget.changed.connect(self.baseColorsChanged)
        self.customAttributesWidget.changed.connect(self.customAttributesChanged)
        self.fontChooser.currentFontChanged.connect(self.fontChanged)
        self.fontSize.valueChanged.connect(self.fontChanged)
        self.printScheme.clicked.connect(self.printSchemeChanged)

        app.translateUI(self)

    def translateUI(self):
        self.printScheme.setText(_("Use this scheme for printing"))
        self.fontLabel.setText(_("Font:"))
        self.baseColorsItem.setText(0, _("Base Colors"))
        self.defaultStylesItem.setText(0, _("Default Styles"))

        self.defaultStyleNames = defaultStyleNames()
        self.allStyleNames = allStyleNames()

        for name in textformats.defaultStyles:
            self.defaultStyles[name].setText(0, self.defaultStyleNames[name])
        for group, styles in textformats.allStyles:
            self.allStyles[group][0].setText(0, self.allStyleNames[group][0])
            for name in styles:
                self.allStyles[group][1][name].setText(0, self.allStyleNames[group][1][name])

    def currentItemChanged(self, item, previous):
        if item is self.baseColorsItem:
            self.stack.setCurrentWidget(self.baseColorsWidget)
        elif not item.parent():
            self.stack.setCurrentWidget(self.emptyWidget)
        else:
            data = self.data[self.scheme.currentScheme()]
            w = self.customAttributesWidget
            self.stack.setCurrentWidget(w)
            toptext = None
            if item.parent() is self.defaultStylesItem:
                # default style
                w.setTitle(item.text(0))
                w.setTristate(False)
                w.setTextFormat(data.defaultStyles[item.name])
            else:
                # specific style of specific group
                group, name = item.parent().group, item.name
                w.setTitle("{0}: {1}".format(item.parent().text(0), item.text(0)))
                inherit = textformats.inherits[group].get(name)
                if inherit:
                    toptext = _("(Inherits: {name})").format(name=self.defaultStyleNames[inherit])
                w.setTristate(bool(inherit))
                w.setTextFormat(data.allStyles[group][name])
            w.setTopText(toptext)

    def currentSchemeChanged(self):
        scheme = self.scheme.currentScheme()
        if scheme not in self.data:
            self.data[scheme] = textformats.TextFormatData(scheme)
        self.updateDisplay()
        if self.tree.currentItem():
            self.currentItemChanged(self.tree.currentItem(), None)
        with util.signalsBlocked(self.printScheme):
            self.printScheme.setChecked(scheme == self._printScheme)

    def fontChanged(self):
        data = self.data[self.scheme.currentScheme()]
        data.font = self.fontChooser.currentFont()
        data.font.setPointSizeF(self.fontSize.value())
        self.updateDisplay()
        self.changed.emit()

    def printSchemeChanged(self):
        if self.printScheme.isChecked():
            self._printScheme = self.scheme.currentScheme()
        else:
            self._printScheme = None
        self.changed.emit()

    def updateDisplay(self):
        data = self.data[self.scheme.currentScheme()]

        with util.signalsBlocked(self.fontChooser, self.fontSize):
            self.fontChooser.setCurrentFont(data.font)
            self.fontSize.setValue(data.font.pointSizeF())

        with util.signalsBlocked(self):
            # update base colors
            for name in textformats.baseColors:
                self.baseColorsWidget.color[name].setColor(data.baseColors[name])

        # update base colors for whole treewidget
        p = QApplication.palette()
        p.setColor(QPalette.Base, data.baseColors["background"])
        p.setColor(QPalette.Text, data.baseColors["text"])
        p.setColor(QPalette.Highlight, data.baseColors["selectionbackground"])
        p.setColor(QPalette.HighlightedText, data.baseColors["selectiontext"])
        self.tree.setPalette(p)

        def setItemTextFormat(item, f):
            font = QFont(data.font)
            if f.hasProperty(QTextFormat.ForegroundBrush):
                item.setForeground(0, f.foreground().color())
            else:
                item.setForeground(0, data.baseColors["text"])
            if f.hasProperty(QTextFormat.BackgroundBrush):
                item.setBackground(0, f.background().color())
            else:
                item.setBackground(0, QBrush())
            font.setWeight(f.fontWeight())
            font.setItalic(f.fontItalic())
            font.setUnderline(f.fontUnderline())
            item.setFont(0, font)

        # update looks of default styles
        for name in textformats.defaultStyles:
            setItemTextFormat(self.defaultStyles[name], data.defaultStyles[name])

        # update looks of all the specific styles
        for group, styles in textformats.allStyles:
            children = self.allStyles[group][1]
            for name in styles:
                inherit = textformats.inherits[group].get(name)
                f = QTextCharFormat(data.defaultStyles[inherit]) if inherit else QTextCharFormat()
                f.merge(data.allStyles[group][name])
                setItemTextFormat(children[name], f)

    def baseColorsChanged(self, name):
        # keep data up to date with base colors
        data = self.data[self.scheme.currentScheme()]
        data.baseColors[name] = self.baseColorsWidget.color[name].color()
        self.updateDisplay()
        self.changed.emit()

    def customAttributesChanged(self):
        item = self.tree.currentItem()
        if not item or not item.parent():
            return
        data = self.data[self.scheme.currentScheme()]
        if item.parent() is self.defaultStylesItem:
            # a default style has been changed
            data.defaultStyles[item.name] = self.customAttributesWidget.textFormat()
        else:
            # a specific style has been changed
            group, name = item.parent().group, item.name
            data.allStyles[group][name] = self.customAttributesWidget.textFormat()
        self.updateDisplay()
        self.changed.emit()

    def loadSettings(self):
        self.data = {}  # holds all data with scheme as key
        self._printScheme = QSettings().value("printer_scheme", "default")
        self.scheme.loadSettings("editor_scheme", "editor_schemes")

    def saveSettings(self):
        self.scheme.saveSettings("editor_scheme", "editor_schemes", "fontscolors")
        for scheme in self.scheme.schemes():
            if scheme in self.data:
                self.data[scheme].save(scheme)
        if self._printScheme:
            QSettings().setValue("printer_scheme", self._printScheme)
        else:
            QSettings().remove("printer_scheme")
Ejemplo n.º 5
0
class Shortcuts(preferences.Page):
    def __init__(self, dialog):
        super(Shortcuts, self).__init__(dialog)

        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        self.setLayout(layout)

        self.scheme = SchemeSelector(self)
        layout.addWidget(self.scheme)
        self.searchEntry = LineEdit()
        self.searchEntry.setPlaceholderText(_("Search..."))
        layout.addWidget(self.searchEntry)
        self.tree = QTreeWidget(self)
        self.tree.setHeaderLabels([_("Command"), _("Shortcut")])
        self.tree.setRootIsDecorated(False)
        self.tree.setColumnCount(2)
        self.tree.setAllColumnsShowFocus(True)
        self.tree.setAnimated(True)
        layout.addWidget(self.tree)

        self.edit = QPushButton(icons.get("preferences-desktop-keyboard-shortcuts"), '')
        layout.addWidget(self.edit)

        # signals
        self.searchEntry.textChanged.connect(self.updateFilter)
        self.scheme.currentChanged.connect(self.slotSchemeChanged)
        self.scheme.changed.connect(self.changed)
        self.tree.currentItemChanged.connect(self.slotCurrentItemChanged)
        self.tree.itemDoubleClicked.connect(self.editCurrentItem)
        self.edit.clicked.connect(self.editCurrentItem)

        # make a dict of all actions with the actions as key and the names as
        # value, with the collection prepended (for loading/saving)
        win = dialog.parent()
        allactions = {}
        for collection in actioncollectionmanager.manager(win).actionCollections():
            for name, action in collection.actions().items():
                allactions[action] = (collection, name)

        # keep a list of actions not in the menu structure
        left = list(allactions.keys())

        def add_actions(menuitem, actions):
            """Add actions to a QTreeWidgetItem."""
            for a in actions:
                if a.menu():
                    item = build_menu_item(a)
                    if item.childCount():
                        menuitem.addChild(item)
                elif a in left:
                    left.remove(a)
                    menuitem.addChild(ShortcutItem(a, *allactions[a]))
            menuitem.setFlags(Qt.ItemIsEnabled) # disable selection

        def build_menu_item(action):
            """Return a QTreeWidgetItem with children for all the actions in the submenu."""
            menuitem = QTreeWidgetItem()
            text = qutil.removeAccelerator(action.text())
            menuitem.setText(0, _("Menu {name}").format(name=text))
            add_actions(menuitem, action.menu().actions())
            return menuitem

        # present the actions nicely ordered as in the menus
        for a in win.menuBar().actions():
            menuitem = build_menu_item(a)
            if menuitem.childCount():
                self.tree.addTopLevelItem(menuitem)

        # sort leftover actions
        left.sort(key=lambda i: i.text())

        # show actions that are left, grouped by collection
        titlegroups = {}
        for a in left[:]: # copy
            collection, name = allactions[a]
            if collection.title():
                titlegroups.setdefault(collection.title(), []).append(a)
                left.remove(a)
        for title in sorted(titlegroups):
            item = QTreeWidgetItem(["{0}:".format(title)])
            for a in titlegroups[title]:
                item.addChild(ShortcutItem(a, *allactions[a]))
            self.tree.addTopLevelItem(item)
            item.setFlags(Qt.ItemIsEnabled) # disable selection

        # show other actions that were not in the menus
        item = QTreeWidgetItem([_("Other commands:")])
        for a in left:
            if a.text() and not a.menu():
                item.addChild(ShortcutItem(a, *allactions[a]))
        if item.childCount():
            self.tree.addTopLevelItem(item)
            item.setFlags(Qt.ItemIsEnabled) # disable selection

        self.tree.expandAll()

        item = self.tree.topLevelItem(0).child(0)
        if _lastaction:
            # find the previously selected item
            for i in self.items():
                if i.name == _lastaction:
                    item = i
                    break
        self.tree.setCurrentItem(item)
        self.tree.resizeColumnToContents(0)

    def items(self):
        """Yield all the items in the actions tree."""
        def children(item):
            for i in range(item.childCount()):
                c = item.child(i)
                if c.childCount():
                    for c1 in children(c):
                        yield c1
                else:
                    yield c
        for c in children(self.tree.invisibleRootItem()):
            yield c

    def item(self, collection, name):
        for item in self.items():
            if item.collection.name == collection and item.name == name:
                return item

    def saveSettings(self):
        self.scheme.saveSettings("shortcut_scheme", "shortcut_schemes", "shortcuts")
        for item in self.items():
            for scheme in self.scheme.schemes():
                item.save(scheme)
            item.clearSettings()
            item.switchScheme(self.scheme.currentScheme())

    def loadSettings(self):
        self.scheme.loadSettings("shortcut_scheme", "shortcut_schemes")
        # clear the settings in all the items
        for item in self.items():
            item.clearSettings()
            item.switchScheme(self.scheme.currentScheme())

    def slotSchemeChanged(self):
        """Called when the Scheme combobox is changed by the user."""
        for item in self.items():
            item.switchScheme(self.scheme.currentScheme())

    def slotCurrentItemChanged(self, item):
        if isinstance(item, ShortcutItem):
            self.edit.setText(
                _("&Edit Shortcut for \"{name}\"").format(name=item.text(0)))
            self.edit.setEnabled(True)
            global _lastaction
            _lastaction = item.name
        else:
            self.edit.setText(_("(no shortcut)"))
            self.edit.setEnabled(False)

    def import_(self, filename):
        from . import import_export
        import_export.importShortcut(filename, self, self.scheme)

    def export(self, name, filename):
        from . import import_export
        try:
            import_export.exportShortcut(self, self.scheme.currentScheme(), name, filename)
        except (IOError, OSError) as e:
            QMessageBox.critical(self, _("Error"), _(
                "Can't write to destination:\n\n{url}\n\n{error}").format(
                url=filename, error=e.strerror))

    def findShortcutConflict(self, shortcut):
        """Find the possible shortcut conflict and return the conflict name."""
        if shortcut:
            item = self.tree.currentItem()
            if not isinstance(item, ShortcutItem):
                return None
            scheme = self.scheme.currentScheme()
            for i in self.items():
                a = i.action(scheme)
                if i != item and a.shortcuts():
                    for s1 in a.shortcuts():
                        if s1.matches(shortcut) or shortcut.matches(s1):
                            return qutil.removeAccelerator(a.text())
        return None

    def editCurrentItem(self):
        item = self.tree.currentItem()
        if not isinstance(item, ShortcutItem):
            return

        dlg = ShortcutEditDialog(self, self.findShortcutConflict)
        scheme = self.scheme.currentScheme()
        action = item.action(scheme)
        default = item.defaultShortcuts() or None
        if dlg.editAction(action, default):
            shortcuts = action.shortcuts()
            # check for conflicts
            conflicting = []
            for i in self.items():
                if i is not item:
                    for s1, s2 in itertools.product(i.shortcuts(scheme), shortcuts):
                        if s1.matches(s2) or s2.matches(s1):
                            conflicting.append(i)
            if conflicting:
                for i in conflicting:
                    l = i.shortcuts(scheme)
                    for s1 in list(l): # copy
                        for s2 in shortcuts:
                            if s1.matches(s2) or s2.matches(s1):
                                l.remove(s1)
                    i.setShortcuts(l, scheme)

            # store the shortcut
            item.setShortcuts(shortcuts, scheme)
            self.changed.emit()

    def updateFilter(self):
        """Called when the search text changes."""
        search = self.searchEntry.text()
        scheme = self.scheme.currentScheme()
        def hidechildren(item):
            hideparent = True
            for n in range(item.childCount()):
                c = item.child(n)
                if c.childCount():
                    # submenu item
                    if hidechildren(c):
                        c.setHidden(True)
                    else:
                        c.setHidden(False)
                        c.setExpanded(True)
                        hideparent = False
                elif isinstance(c, ShortcutItem):
                    # shortcut item, should be the case
                    if c.matches(scheme, search):
                        c.setHidden(False)
                        hideparent = False
                    else:
                        c.setHidden(True)
            return hideparent
        hidechildren(self.tree.invisibleRootItem())