diff options
Diffstat (limited to 'kberylsettings/contentframe.py')
-rw-r--r-- | kberylsettings/contentframe.py | 493 |
1 files changed, 347 insertions, 146 deletions
diff --git a/kberylsettings/contentframe.py b/kberylsettings/contentframe.py index 68e8582..c4085fe 100644 --- a/kberylsettings/contentframe.py +++ b/kberylsettings/contentframe.py @@ -4,98 +4,89 @@ """ from kdecore import KIcon, i18n -from kdeui import KMessageBox, KPassivePopup, KPushButton, \ - KSeparator, KStdGuiItem +from kdeui import KDialog, KMessageBox, KPassivePopup, \ + KSeparator, KStdGuiItem, KTabWidget from qt import Qt, QFrame, QHBoxLayout, QLabel, QScrollView, QSize, \ QSizePolicy, QStringList, QToolTip, QVBoxLayout +from kberylsettings.beryl import Setting from kberylsettings.lib import App, Signals, buildPart, icon, iconLoader, iconSet -from kberylsettings.widget import Frame, SmallPushButton, WidgetStack +from kberylsettings.widget import Frame, SmallPushButton, WidgetStack, guiButton from kberylsettings.settingwidget import settingWidget +marginHint = KDialog.marginHint() +spacingHint = KDialog.spacingHint() + + class ContentFrame(WidgetStack): - """ ContentFrame -> stack with About page, Settings Page. + """ ContentFrame -> multiple displays of plugin information + + This widget stack builds and manages three types of pages: + 1. an 'about' page for displaying plugin information. + + 2. a 'single' page for displaying a setting or a + sequence of settings. + + 3. a 'multiple' page for displaying settings in groups + via a tabbed interface. + """ - aboutPageId = 0 - settingsPageId = 1 - + aboutPageId, singlePageId, tabPageId = range(3) + def __init__(self, parent): WidgetStack.__init__(self, parent) - self.loader = iconLoader() - self.buildAboutPage() - self.buildSettingsPage() - self.buildConnections() - - def buildAboutPage(self): - """ builds the About Plugin page + self.aboutPage = aboutPage = AboutPage(self) + self.singlePage = singlePage = SinglePage(self) + self.tabPage = tabPage = TabPage(self) + + addWidget = self.addWidget + addWidget(aboutPage, self.aboutPageId) + addWidget(singlePage, self.singlePageId) + addWidget(tabPage, self.tabPageId) + + root = self.topLevelWidget() + connect = self.connect + connect(root, Signals.showAbout, self.showAboutPage) + connect(root, Signals.showSettings, self.showSinglePage) + connect(root, Signals.showGroups, self.showTabPage) + + def showAboutPage(self, plugin): + """ displays the about page + @param plugin berylsettings Plugin instance @return None """ - self.aboutPage = Frame(self, margin=6, spacing=10) - self.addWidget(self.aboutPage, self.aboutPageId) - self.infoHtml = buildPart(self.aboutPage, 'text/html', - "Type == 'Service'", True) - + self.aboutPage.showAbout(plugin) + self.raiseWidget(self.aboutPageId) - def buildSettingsPage(self): - """ builds page for displaying one or more settings + def showSinglePage(self, plugin, arg): + """ displays the page for a single type of setting + @param plugin berylsettings Plugin instance @return None """ - self.settingsPage = page = Frame(self, 6, 10) - self.addWidget(page, self.settingsPageId) - - self.pluginHeader = header = QFrame(page) - self.pluginIconLabel = QLabel(header) - self.pluginNameLabel = QLabel(header) - - headerLayout = QHBoxLayout(header) - headerLayout.addWidget(self.pluginIconLabel, 1) - headerLayout.addWidget(self.pluginNameLabel, 10) - - self.settingsMain = main = SettingsContainer(page) - self.scrollerWrap = wrap = Frame(page) - self.settingsScroller = scroller = ContentScroll(wrap, main) - - KSeparator(KSeparator.Horizontal, page) - self.settingsFooter = footer = QFrame(page) - footerLayout = QHBoxLayout(footer) - - def contentButton(name): - gui = getattr(KStdGuiItem, name)() - button = KPushButton(gui.iconSet(), gui.text(), footer) - footerLayout.addWidget(button) - return button - - self.helpButton = contentButton('help') - self.defaultsButton = contentButton('defaults') - footerLayout.addStretch(2) - self.applyButton = applyButton = contentButton('apply') - applyButton.setEnabled(False) - self.resetButton = resetButton = contentButton('reset') - resetButton.setEnabled(False) - - def buildConnections(self): - """ build the connections for this instance + self.singlePage.showSettings(plugin, arg) + self.raiseWidget(self.singlePageId) + def showTabPage(self, plugin): + """ displays the page for groups of settings + + @param plugin berylsettings Plugin instance @return None """ - connect = self.connect - connect(self.helpButton, Signals.clicked, self.settingHelp) - connect(self.defaultsButton, Signals.clicked, self.settingDefaults) - connect(self.resetButton, Signals.clicked, self.settingsReset) - connect(self.applyButton, Signals.clicked, self.settingApply) - root = self.topLevelWidget() - connect(self, Signals.berylSettingChanged, root.onContextChanged) - connect(self, Signals.statusMessage, root.showMessage) - connect(self.settingsMain, Signals.someChange, - self.onSomethingChanged) + self.tabPage.setGroups(plugin) + self.raiseWidget(self.tabPageId) - def onSomethingChanged(self): - self.applyButton.setEnabled(True) - self.resetButton.setEnabled(True) + +class AboutPage(Frame): + """ AboutPage -> frame to display a bit of info about a plugin + + """ + def __init__(self, parent): + Frame.__init__(self, parent) + self.infoHtml = buildPart(self, 'text/html', "Type == 'Service'", True) def showAbout(self, plugin): """ displays the About Plugin page with information from the plugin @@ -109,90 +100,135 @@ class ContentFrame(WidgetStack): pluginname = 'Beryl Settings' plugindesc = 'Beryl Settings - Get Your Effects On!' else: - #logofile = 'file://%s' % App.basedir + '/pixmaps/beryl-settings-section-%s.png' % plugin.Name pluginname = plugin.ShortDesc plugindesc = plugin.LongDesc self.infoHtml.begin() self.infoHtml.write(htmlsrc % locals()) self.infoHtml.end() - self.raiseWidget(self.aboutPageId) - unsavedText = """There are unsaved changes in the active settings. - Do you want to apply the changes before changing views or discard the changes? +class SettingPage: + """ SettingPage -> mixin with methods common to both the + SinglePage and the TabPage types. + """ - - def showSettings(self, plugin, arg): - """ displays the settings page with individual setting frames + unsavedText = ('There are unsaved changes in the active settings.\n' + 'Do you want to apply the changes before changing views ' + 'or discard the changes?') + + def buildConnections(self): + """ build the connections for this instance + + @return None + """ + connect = self.connect + connect(self.footer.helpButton, Signals.clicked, self.settingHelp) + connect(self.footer.defaultsButton, Signals.clicked, self.settingDefaults) + connect(self.footer.resetButton, Signals.clicked, self.settingsReset) + connect(self.footer.applyButton, Signals.clicked, self.settingApply) + root = self.topLevelWidget() + connect(self, Signals.berylSettingChanged, root.onContextChanged) + connect(self, Signals.statusMessage, root.showMessage) + connect(self, Signals.selectPrevious, root, Signals.selectPrevious) + + def onSomethingChanged(self): + """ enable apply and reset buttons on setting value change + + @return None + """ + self.enableApplyReset(True, True) + + def enableApplyReset(self, enableApply, enableReset): + """ convenience for setting enabled state of apply and reset buttons + + @return None + """ + self.footer.applyButton.setEnabled(enableApply) + self.footer.resetButton.setEnabled(enableReset) + + def unsaved(self): + """ checks for unsaved items (defers to apply button enabled state) + + @return True if unsaved settings + """ + return self.footer.applyButton.isEnabled() + + def unsavedDialog(self): + """ display a dialog warning of unsaved changes + + @return KMessageBox.Yes, .No, or .Cancel + """ + msg = KMessageBox.warningYesNoCancel + return msg(self, self.unsavedText, + i18n('Unsaved Changes'), + KStdGuiItem.apply(), + KStdGuiItem.discard()) + + def unsavedCheck(self, plugin, arg): + """ checks for unsaved items and prompts for action if any @param plugin berylsettings Plugin instance @param arg setting name or setting section name - @return None + @return True if no save needed, changes discarded or applied; + False otherwise """ - if self.applyButton.isEnabled(): - msg = KMessageBox.warningYesNoCancel - res = msg(self, self.unsavedText, i18n('Unsaved Changes'), - KStdGuiItem.apply(), KStdGuiItem.discard()) + ret = True + if self.unsaved(): + res = self.unsavedDialog() if res == KMessageBox.Cancel: - return + self.emit(Signals.selectPrevious, (plugin, arg)) + ret = False elif res == KMessageBox.Yes: self.settingApply() - #else it's no/discard so fall thru - ico = plugin.icon(KIcon.SizeLarge, self.loader) - self.pluginIconLabel.setPixmap(ico) - if arg in plugin.settings: - settings = plugin.settings[arg] - extra = ' - %s' % (arg, ) - else: - settings = [arg, ] - extra = '' - self.pluginNameLabel.setText('<b>%s%s</b>' % (plugin.ShortDesc, extra)) - self.settingsMain.addSettings(plugin, settings) - self.applyButton.setEnabled(False) - self.resetButton.setEnabled(False) - self.raiseWidget(self.settingsPageId) + else: + self.settingDiscard() + return ret def settingHelp(self): """ not implemented """ + def settingDiscard(self): + """ unsaved changes discarded; disable apply and reset buttons + + @return None + """ + self.enableApplyReset(False, False) + + def settingDefaults(self): """ set each setting to its default value @return None """ - frame = self.settingsPage - widgets = frame.queryList('SettingFrame') + widgets = self.queryList('SettingFrame') for widget in widgets: try: widget.setDefault() except (Exception, ), exc: print 'reset exception:', exc - self.applyButton.setEnabled(True) - self.resetButton.setEnabled(True) + self.enableApplyReset(True, True) def settingsReset(self): """ set each setting to its previous value @return None """ - frame = self.settingsPage - widgets = frame.queryList('SettingWidget') + widgets = self.queryList('SettingWidget') for widget in widgets: try: widget.reset() except (Exception, ), exc: print 'reset exception:', exc - self.applyButton.setEnabled(True) - self.resetButton.setEnabled(False) + self.enableApplyReset(False, False) def settingApply(self): """ not final """ excs = [] - for w in self.settingsPage.queryList('SettingWidget'): + for w in self.queryList('SettingWidget'): try: v = w.value() w.setting.set(v) @@ -205,48 +241,154 @@ class ContentFrame(WidgetStack): excstrs.append('Plugin:%s Setting:%s Exception:%s' % exc) KMessageBox.errorList(None, 'Exceptions Saving Settings', excstrs) - self.applyButton.setEnabled(False) - self.resetButton.setEnabled(False) + self.enableApplyReset(False, False) self.emit(Signals.statusMessage, ('Saving settings...', )) self.emit(Signals.berylSettingChanged, ()) -class SettingsContainer(QFrame): +class SinglePage(Frame, SettingPage): + """ SinglePage -> frame to display a single group of settings + + """ def __init__(self, parent): - QFrame.__init__(self, parent) - self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) - self.settingFrames = [] - layout = QVBoxLayout(self, 5) + Frame.__init__(self, parent, marginHint, spacingHint) + self.previous = (None, None) + self.header = ContentHeader(self) + self.main = MultiSettingFrame(self) + self.footer = ContentFooter(self) + self.buildConnections() + + def buildConnections(self): + """ build the connections for this instance + + @return None + """ + SettingPage.buildConnections(self) + self.connect(self.main.container, Signals.someChange, + self.onSomethingChanged) + + def showSettings(self, plugin, arg): + """ displays the settings page with individual setting frames + + @param plugin berylsettings Plugin instance + @param arg setting name or setting section name + @return None + """ + if not self.unsavedCheck(*self.previous): + return + self.previous = (plugin, arg) + self.header.setFromPlugin(plugin, arg) + self.main.setPlugin(plugin, arg) + + +class TabPage(Frame, SettingPage): + """ TabPage -> tab widget to display multiple groups of settings + + """ + def __init__(self, parent): + Frame.__init__(self, parent, marginHint, spacingHint) + self.header = ContentHeader(self) + self.tabs = KTabWidget(self) + sep = KSeparator(KSeparator.Horizontal, self) + self.footer = ContentFooter(self) + self.buildConnections() + + def setGroups(self, plugin): + """ builds new page for each plugin setting group + + @param plugin berylsettings Plugin instance + @return None + """ + if not self.unsavedCheck(plugin, None): + return + self.header.setFromPlugin(plugin) + self.clearPages() + tabs = self.tabs + changed = Signals.someChange + for group in plugin.groups: + page = MultiSettingFrame(tabs) + page.setPlugin(plugin, group.settings(plugin)) + tabs.addTab(page, group.label) + self.connect(page.container, changed, self.onSomethingChanged) + tabs.setCurrentPage(0) + + def clearPages(self): + """ deletes all pages from the tab widget child + + @return None + """ + tabs = self.tabs + page = tabs.currentPage() + while page: + tabs.removePage(page) + page.deleteLater() + page = tabs.currentPage() + + +class MultiSettingFrame(Frame): + """ MultiSettingFrame -> a scrollable view of multiple settings + + """ + def __init__(self, parent, *args, **kwds): + Frame.__init__(self, parent) + self.frames = [] + self.previous = (None, None) + self.scroll = ContentScroll(self) + viewport = self.scroll.viewport() + self.container = Frame(viewport, marginHint, spacingHint, False) + self.scroll.addChild(self.container) + + def setPlugin(self, plugin, arg): + """ displays the settings page with individual setting frames - def addSettings(self, plugin, settings): - self.clearFrames() - layout = self.layout() + @param plugin berylsettings Plugin instance + @param arg setting name, setting type name, or sequence of settings + @return None + """ + if isinstance(arg, basestring): + settings = plugin.settings[arg] + elif isinstance(arg, Setting): + settings = [arg, ] + elif isinstance(arg, (list, tuple)): + settings = arg + else: + print 'unknown setting type', arg + settings = () + if self.previous == (plugin, settings): + return + self.previous = (plugin, settings) + self.clearFrames() + container = self.container + layout = container.layout() changed = Signals.someChange + frames = self.frames for setting in settings: - frame = SettingFrame(self, plugin, setting) - self.settingFrames.append(frame) - layout.addWidget(frame, 0, Qt.AlignTop) - self.connect(frame, changed, self, changed) - layout.addStretch(5) - for frame in self.settingFrames: + frame = SettingFrame(container, plugin, setting) frame.show() - self.updateGeometry() + frames.append(frame) + layout.addWidget(frame, 0, Qt.AlignTop) + self.connect(frame, changed, container, changed) + layout.addStretch(1) def clearFrames(self): - for widget in self.settingFrames: - widget.close(True) - self.layout().deleteAllItems() - self.settingFrames = [] + """ removes child frames and clears the container frame + + @return None + """ + for widget in self.frames: + widget.close(True) + self.container.layout().deleteAllItems() + self.frames = [] class SettingFrame(QFrame): - """ SettingFrame -> displays editing widgets for a single Setting instance. + """ SettingFrame -> display and editing widgets for a single Setting. """ def __init__(self, parent, plugin, setting): QFrame.__init__(self, parent) mainLayout = QVBoxLayout(self) - innerLayout = QHBoxLayout(mainLayout, 3) + innerLayout = QHBoxLayout(mainLayout) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum) self.infoCaption = setting.ShortDesc self.infoText = setting.LongDesc @@ -261,33 +403,40 @@ class SettingFrame(QFrame): align = Qt.AlignLeft | Qt.AlignTop innerLayout.addWidget(info, 0, align) innerLayout.addWidget(reset, 0, align) - innerLayout.addWidget(widget, 10) - mainLayout.addSpacing(10) + innerLayout.addWidget(widget, 1) + mainLayout.addSpacing(spacingHint) connect = self.connect - connect(info, Signals.clicked, self.showInfo) + connect(info, Signals.clicked, self.showTip) connect(reset, Signals.clicked, self.setDefault) - connect(widget, Signals.someChange, - self, Signals.someChange) + connect(widget, Signals.someChange, self, Signals.someChange) def setDefault(self): - w = self.settingWidget - curv = w.value() - newv = w.initial = w.setting.ResetToDefault() - w.setting.set(curv) # because it's not applied yet - w.reset() + """ set the setting value to its default - def showInfo(self): + The berylsettings extension module performs the reset directly + on the setting object. In this method, we save the current + value as defined by the widget and set that value again after + the reset. + """ + widget = self.settingWidget + current = widget.value() + widget.initial = widget.setting.ResetToDefault() + widget.setting.set(current) + widget.reset() + widget.initial = current + + def showTip(self): """ show a balloon tip with the setting description @return None """ parent = self.resetButton - ico = icon('help', size=KIcon.SizeLarge) - txt = self.infoText - cap = self.infoCaption - msg = KPassivePopup.message - pop = msg(KPassivePopup.Balloon, cap, txt, ico, parent) + pop = KPassivePopup.message(KPassivePopup.Balloon, + self.infoCaption, + self.infoText, + icon('help', size=KIcon.SizeLarge), + parent) pos = pop.pos() pos.setY(pos.y() + (parent.height() / 2)) pos.setX(pos.x() + (parent.width() * 2)) @@ -295,12 +444,64 @@ class SettingFrame(QFrame): pop.move(pos) +class ContentHeader(QFrame): + """ ContentHeader -> header frame with a pixmap and label for plugin + + """ + def __init__(self, parent): + QFrame.__init__(self, parent) + layout = QHBoxLayout(self) + self.pluginIconLabel = iconLabel = QLabel(self) + self.pluginNameLabel = nameLabel = QLabel(self) + layout.addWidget(iconLabel) + layout.addWidget(nameLabel, 1) + + def setFromPlugin(self, plugin, extra=''): + """ sets this header icon and label to plugin values + + @param plugin berylsettings Plugin instance + @param extra any object; if string or unicode, added to end of label + @return None + """ + ico = plugin.icon(KIcon.SizeLarge, iconLoader()) + self.pluginIconLabel.setPixmap(ico) + if isinstance(extra, basestring) and extra: + extra = ' - %s' % (extra, ) + else: + extra = '' + values = (plugin.ShortDesc, extra) + self.pluginNameLabel.setText('<b>%s%s</b>' % values) + + +class ContentFooter(QFrame): + """ ContentFooter -> footer frame with common buttons + + """ + def __init__(self, parent): + QFrame.__init__(self, parent) + layout = QHBoxLayout(self) + self.helpButton = guiButton(self, 'help', layout) + self.defaultsButton = guiButton(self, 'defaults', layout) + layout.addStretch(1) + self.applyButton = guiButton(self, 'apply', layout, False) + self.resetButton = guiButton(self, 'reset', layout, False) + + class ContentScroll(QScrollView): """ ContentScroll -> a scroll view fitted to a single child + The redefined sizeHint method is important -- without it, the + widget size isn't picked up correctly by the parent. Found in + kcontrol sources. """ - def __init__(self, parent, child): + def __init__(self, parent): QScrollView.__init__(self, parent) self.setResizePolicy(QScrollView.AutoOneFit) self.setFrameShape(self.NoFrame) - self.addChild(child) + + def sizeHint(self): + """ recommended size for this widget + + @return QSize instance + """ + return self.minimumSizeHint() |