summaryrefslogtreecommitdiff
path: root/kberylsettings/contentframe.py
diff options
context:
space:
mode:
Diffstat (limited to 'kberylsettings/contentframe.py')
-rw-r--r--kberylsettings/contentframe.py493
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()