This commit is contained in:
Raoul Snyman 2010-07-14 17:59:13 +02:00
commit 38afbbdd5b
21 changed files with 782 additions and 260 deletions

View File

@ -174,6 +174,8 @@ def resize_image(image, width, height):
"""
preview = QtGui.QImage(image)
if not preview.isNull():
if preview.width() == width and preview.height == height:
return preview
preview = preview.scaled(width, height, QtCore.Qt.KeepAspectRatio,
QtCore.Qt.SmoothTransformation)
realw = preview.width()

View File

@ -323,9 +323,15 @@ class MediaManagerItem(QtGui.QWidget):
translate('MediaManagerItem',
'&Add to selected Service Item'),
self.onAddEditClick))
QtCore.QObject.connect(
self.listView, QtCore.SIGNAL(u'doubleClicked(QModelIndex)'),
self.onPreviewClick)
if QtCore.QSettings().value(u'advanced/double click live',
QtCore.QVariant(False)).toBool():
QtCore.QObject.connect(self.listView,
QtCore.SIGNAL(u'doubleClicked(QModelIndex)'),
self.onLiveClick)
else:
QtCore.QObject.connect(self.listView,
QtCore.SIGNAL(u'doubleClicked(QModelIndex)'),
self.onPreviewClick)
def initialise(self):
"""
@ -379,14 +385,17 @@ class MediaManagerItem(QtGui.QWidget):
"""
Validates to see if the file still exists or thumbnail is up to date
"""
if os.path.exists(file):
if not os.path.exists(file):
return False
if os.path.exists(thumb):
filedate = os.stat(file).st_mtime
thumbdate = os.stat(thumb).st_mtime
#if file updated rebuild icon
if filedate > thumbdate:
self.iconFromFile(file, thumb)
return True
return False
else:
self.iconFromFile(file, thumb)
return True
def iconFromFile(self, file, thumb):
"""

View File

@ -269,8 +269,22 @@ class Plugin(QtCore.QObject):
if self.settings_tab:
self.settingsForm.insertTab(self.settings_tab, self.weight)
def canDeleteTheme(self, theme):
def usesTheme(self, theme):
"""
Called to ask the plugin if a theme can be deleted
Called to find out if a plugin is currently using a theme.
Returns True if the theme is being used, otherwise returns False.
"""
return True
return False
def renameTheme(self, oldTheme, newTheme):
"""
Renames a theme a plugin is using making the plugin use the new name.
``oldTheme``
The name of the theme the plugin should stop using.
``newTheme``
The new name the plugin should now use.
"""
pass

View File

@ -53,22 +53,34 @@ class AdvancedTab(SettingsTab):
self.leftLayout = QtGui.QVBoxLayout(self.leftWidget)
self.leftLayout.setSpacing(8)
self.leftLayout.setMargin(0)
self.recentGroupBox = QtGui.QGroupBox(self.leftWidget)
self.recentGroupBox.setObjectName(u'recentGroupBox')
self.recentGroupBox.setGeometry(QtCore.QRect(0, 0, 220, 57))
self.recentGroupBox.setMaximumSize(QtCore.QSize(220, 57))
self.recentLayout = QtGui.QHBoxLayout(self.recentGroupBox)
self.uiGroupBox = QtGui.QGroupBox(self.leftWidget)
self.uiGroupBox.setObjectName(u'uiGroupBox')
self.uiLayout = QtGui.QVBoxLayout(self.uiGroupBox)
self.uiLayout.setSpacing(8)
self.uiLayout.setMargin(6)
self.uiLayout.setObjectName(u'uiLayout')
self.recentLayout = QtGui.QHBoxLayout()
self.recentLayout.setSpacing(8)
self.recentLayout.setMargin(0)
self.recentLayout.setObjectName(u'recentLayout')
self.recentLabel = QtGui.QLabel(self.recentGroupBox)
self.recentLabel = QtGui.QLabel(self.uiGroupBox)
self.recentLabel.setObjectName(u'recentLabel')
self.recentLayout.addWidget(self.recentLabel)
self.recentSpinBox = QtGui.QSpinBox(self.recentGroupBox)
self.recentSpinBox.setMinimum(0)
self.recentSpinBox = QtGui.QSpinBox(self.uiGroupBox)
self.recentSpinBox.setObjectName(u'recentSpinBox')
self.recentSpinBox.setMinimum(0)
self.recentLayout.addWidget(self.recentSpinBox)
self.leftLayout.addWidget(self.recentGroupBox)
self.recentSpacer = QtGui.QSpacerItem(50, 20,
QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
self.recentLayout.addItem(self.recentSpacer)
self.uiLayout.addLayout(self.recentLayout)
self.mediaPluginCheckBox = QtGui.QCheckBox(self.uiGroupBox)
self.mediaPluginCheckBox.setObjectName(u'mediaPluginCheckBox')
self.uiLayout.addWidget(self.mediaPluginCheckBox)
self.doubleClickLiveCheckBox = QtGui.QCheckBox(self.uiGroupBox)
self.doubleClickLiveCheckBox.setObjectName(u'doubleClickLiveCheckBox')
self.uiLayout.addWidget(self.doubleClickLiveCheckBox)
self.leftLayout.addWidget(self.uiGroupBox)
# self.sharedDirGroupBox = QtGui.QGroupBox(self.leftWidget)
# self.sharedDirGroupBox.setObjectName(u'sharedDirGroupBox')
# self.sharedDirGroupBox.setGeometry(QtCore.QRect(0, 65, 500, 85))
@ -97,10 +109,10 @@ class AdvancedTab(SettingsTab):
QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
self.leftLayout.addItem(self.leftSpacer)
self.advancedTabLayout.addWidget(self.leftWidget)
# self.rightWidget = QtGui.QWidget(self)
# self.rightLayout = QtGui.QVBoxLayout(self.rightWidget)
# self.rightLayout.setSpacing(8)
# self.rightLayout.setMargin(0)
self.rightWidget = QtGui.QWidget(self)
self.rightLayout = QtGui.QVBoxLayout(self.rightWidget)
self.rightLayout.setSpacing(8)
self.rightLayout.setMargin(0)
# self.databaseGroupBox = QtGui.QGroupBox(self.rightWidget)
# self.databaseGroupBox.setObjectName(u'databaseGroupBox')
# self.databaseGroupBox.setEnabled(False)
@ -108,7 +120,10 @@ class AdvancedTab(SettingsTab):
# self.databaseLayout.setSpacing(8)
# self.databaseLayout.setMargin(8)
# self.rightLayout.addWidget(self.databaseGroupBox)
# self.advancedTabLayout.addWidget(self.rightWidget)
self.rightSpacer = QtGui.QSpacerItem(20, 40,
QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)
self.rightLayout.addItem(self.rightSpacer)
self.advancedTabLayout.addWidget(self.rightWidget)
# QtCore.QObject.connect(self.sharedCheckBox,
# QtCore.SIGNAL(u'stateChanged(int)'), self.onSharedCheckBoxChanged)
@ -116,9 +131,13 @@ class AdvancedTab(SettingsTab):
"""
Setup the interface translation strings.
"""
self.recentGroupBox.setTitle(translate('AdvancedTab', 'Recent Files'))
self.uiGroupBox.setTitle(translate('AdvancedTab', 'UI Settings'))
self.recentLabel.setText(
translate('AdvancedTab', 'Number of recent files to list:'))
translate('AdvancedTab', 'Number of recent files to display:'))
self.mediaPluginCheckBox.setText(translate('AdvancedTab',
'Save currently selected media manager plugin'))
self.doubleClickLiveCheckBox.setText(translate('AdvancedTab',
'Double-click to send items straight to live (requires restart)'))
# self.sharedDirGroupBox.setTitle(
# translate('AdvancedTab', 'Central Data Store'))
# self.sharedCheckBox.setText(
@ -140,6 +159,12 @@ class AdvancedTab(SettingsTab):
u'max recent files', QtCore.QVariant(20)).toInt()[0])
self.recentSpinBox.setValue(settings.value(u'recent file count',
QtCore.QVariant(4)).toInt()[0])
self.mediaPluginCheckBox.setChecked(
settings.value(u'save current plugin',
QtCore.QVariant(False)).toBool())
self.doubleClickLiveCheckBox.setChecked(
settings.value(u'double click live',
QtCore.QVariant(False)).toBool())
settings.endGroup()
def save(self):
@ -150,6 +175,10 @@ class AdvancedTab(SettingsTab):
settings.beginGroup(self.settingsSection)
settings.setValue(u'recent file count',
QtCore.QVariant(self.recentSpinBox.value()))
settings.setValue(u'save current plugin',
QtCore.QVariant(self.mediaPluginCheckBox.isChecked()))
settings.setValue(u'double click live',
QtCore.QVariant(self.doubleClickLiveCheckBox.isChecked()))
settings.endGroup()
def onSharedCheckBoxChanged(self, checked):
@ -159,4 +188,3 @@ class AdvancedTab(SettingsTab):
self.sharedLabel.setEnabled(checked)
self.sharedTextEdit.setEnabled(checked)
self.sharedPushButton.setEnabled(checked)

View File

@ -130,9 +130,9 @@ class Ui_AmendThemeDialog(object):
self.ImageLineEdit.setObjectName(u'ImageLineEdit')
self.horizontalLayout_2.addWidget(self.ImageLineEdit)
self.ImageToolButton = QtGui.QToolButton(self.ImageFilenameWidget)
icon1 = build_icon(u':/general/general_open.png')
self.ImageToolButton.setIcon(icon1)
self.ImageToolButton.setIcon(build_icon(u':/general/general_open.png'))
self.ImageToolButton.setObjectName(u'ImageToolButton')
self.ImageToolButton.setAutoRaise(True)
self.horizontalLayout_2.addWidget(self.ImageToolButton)
self.BackgroundLayout.setWidget(4, QtGui.QFormLayout.FieldRole,
self.ImageFilenameWidget)

View File

@ -36,15 +36,21 @@ from amendthemedialog import Ui_AmendThemeDialog
log = logging.getLogger(u'AmendThemeForm')
class AmendThemeForm(QtGui.QDialog, Ui_AmendThemeDialog):
"""
The :class:`AmendThemeForm` class provides the user interface to set up
new and edit existing themes.
"""
def __init__(self, parent):
"""
Initialise the theme editor user interface
"""
QtGui.QDialog.__init__(self, parent)
self.thememanager = parent
self.path = None
self.theme = ThemeXML()
self.setupUi(self)
#define signals
#Buttons
# define signals
# Buttons
QtCore.QObject.connect(self.Color1PushButton,
QtCore.SIGNAL(u'pressed()'), self.onColor1PushButtonClicked)
QtCore.QObject.connect(self.Color2PushButton,
@ -59,8 +65,8 @@ class AmendThemeForm(QtGui.QDialog, Ui_AmendThemeDialog):
QtCore.QObject.connect(self.ShadowColorPushButton,
QtCore.SIGNAL(u'pressed()'), self.onShadowColorPushButtonClicked)
QtCore.QObject.connect(self.ImageToolButton,
QtCore.SIGNAL(u'pressed()'), self.onImageToolButtonClicked)
#Combo boxes
QtCore.SIGNAL(u'clicked()'), self.onImageToolButtonClicked)
# Combo boxes
QtCore.QObject.connect(self.BackgroundComboBox,
QtCore.SIGNAL(u'activated(int)'), self.onBackgroundComboBoxSelected)
QtCore.QObject.connect(self.BackgroundTypeComboBox,
@ -82,16 +88,13 @@ class AmendThemeForm(QtGui.QDialog, Ui_AmendThemeDialog):
QtCore.SIGNAL(u'activated(int)'), self.onHorizontalComboBoxSelected)
QtCore.QObject.connect(self.VerticalComboBox,
QtCore.SIGNAL(u'activated(int)'), self.onVerticalComboBoxSelected)
#Spin boxes
# Spin boxes
QtCore.QObject.connect(self.FontMainSizeSpinBox,
QtCore.SIGNAL(u'editingFinished()'),
self.onFontMainSizeSpinBoxChanged)
QtCore.QObject.connect(self.FontFooterSizeSpinBox,
QtCore.SIGNAL(u'editingFinished()'),
self.onFontFooterSizeSpinBoxChanged)
QtCore.QObject.connect(self.FontMainDefaultCheckBox,
QtCore.SIGNAL(u'stateChanged(int)'),
self.onFontMainDefaultCheckBoxChanged)
QtCore.QObject.connect(self.FontMainXSpinBox,
QtCore.SIGNAL(u'editingFinished()'), self.onFontMainXSpinBoxChanged)
QtCore.QObject.connect(self.FontMainYSpinBox,
@ -108,9 +111,6 @@ class AmendThemeForm(QtGui.QDialog, Ui_AmendThemeDialog):
QtCore.QObject.connect(self.FontMainLineSpacingSpinBox,
QtCore.SIGNAL(u'editingFinished()'),
self.onFontMainLineSpacingSpinBoxChanged)
QtCore.QObject.connect(self.FontFooterDefaultCheckBox,
QtCore.SIGNAL(u'stateChanged(int)'),
self.onFontFooterDefaultCheckBoxChanged)
QtCore.QObject.connect(self.FontFooterXSpinBox,
QtCore.SIGNAL(u'editingFinished()'),
self.onFontFooterXSpinBoxChanged)
@ -123,16 +123,23 @@ class AmendThemeForm(QtGui.QDialog, Ui_AmendThemeDialog):
QtCore.QObject.connect(self.FontFooterHeightSpinBox,
QtCore.SIGNAL(u'editingFinished()'),
self.onFontFooterHeightSpinBoxChanged)
QtCore.QObject.connect(self.OutlineCheckBox,
QtCore.SIGNAL(u'stateChanged(int)'), self.onOutlineCheckBoxChanged)
QtCore.QObject.connect(self.ShadowSpinBox,
QtCore.SIGNAL(u'editingFinished()'),
self.onShadowSpinBoxChanged)
QtCore.QObject.connect(self.ShadowCheckBox,
QtCore.SIGNAL(u'stateChanged(int)'), self.onShadowCheckBoxChanged)
QtCore.QObject.connect(self.OutlineSpinBox,
QtCore.SIGNAL(u'editingFinished()'),
self.onOutlineSpinBoxChanged)
# CheckBoxes
QtCore.QObject.connect(self.FontMainDefaultCheckBox,
QtCore.SIGNAL(u'stateChanged(int)'),
self.onFontMainDefaultCheckBoxChanged)
QtCore.QObject.connect(self.FontFooterDefaultCheckBox,
QtCore.SIGNAL(u'stateChanged(int)'),
self.onFontFooterDefaultCheckBoxChanged)
QtCore.QObject.connect(self.OutlineCheckBox,
QtCore.SIGNAL(u'stateChanged(int)'), self.onOutlineCheckBoxChanged)
QtCore.QObject.connect(self.ShadowCheckBox,
QtCore.SIGNAL(u'stateChanged(int)'), self.onShadowCheckBoxChanged)
QtCore.QObject.connect(self.SlideTransitionCheckBox,
QtCore.SIGNAL(u'stateChanged(int)'),
self.onSlideTransitionCheckBoxChanged)

View File

@ -658,6 +658,12 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
log.info(u'Load Themes')
self.ThemeManagerContents.loadThemes()
log.info(u'Load data from Settings')
if QtCore.QSettings().value(u'advanced/save current plugin',
QtCore.QVariant(False)).toBool():
savedPlugin = QtCore.QSettings().value(
u'advanced/current media plugin', QtCore.QVariant()).toInt()[0]
if savedPlugin != -1:
self.MediaToolBox.setCurrentIndex(savedPlugin)
self.settingsForm.postSetUp()
def setAutoLanguage(self, value):
@ -820,6 +826,10 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
"""
# Clean temporary files used by services
self.ServiceManagerContents.cleanUp()
if QtCore.QSettings().value(u'advanced/save current plugin',
QtCore.QVariant(False)).toBool():
QtCore.QSettings().setValue(u'advanced/current media plugin',
QtCore.QVariant(self.MediaToolBox.currentIndex()))
# Call the cleanup method to shutdown plugins.
log.info(u'cleanup plugins')
self.plugin_manager.finalise_plugins()

View File

@ -202,15 +202,21 @@ class SlideController(QtGui.QWidget):
self.Toolbar.addToolbarWidget(u'Hide Menu', self.HideMenu)
self.HideMenu.setMenu(QtGui.QMenu(
translate('SlideController', 'Hide'), self.Toolbar))
self.BlankScreen = QtGui.QAction(QtGui.QIcon( u':/slides/slide_blank.png'), u'Blank Screen', self.HideMenu)
self.BlankScreen = QtGui.QAction(QtGui.QIcon(
u':/slides/slide_blank.png'), u'Blank Screen', self.HideMenu)
self.BlankScreen.setCheckable(True)
QtCore.QObject.connect(self.BlankScreen, QtCore.SIGNAL("triggered(bool)"), self.onBlankDisplay)
self.ThemeScreen = QtGui.QAction(QtGui.QIcon(u':/slides/slide_theme.png'), u'Blank to Theme', self.HideMenu)
QtCore.QObject.connect(self.BlankScreen,
QtCore.SIGNAL("triggered(bool)"), self.onBlankDisplay)
self.ThemeScreen = QtGui.QAction(QtGui.QIcon(
u':/slides/slide_theme.png'), u'Blank to Theme', self.HideMenu)
self.ThemeScreen.setCheckable(True)
QtCore.QObject.connect(self.ThemeScreen, QtCore.SIGNAL("triggered(bool)"), self.onThemeDisplay)
self.DesktopScreen = QtGui.QAction(QtGui.QIcon(u':/slides/slide_desktop.png'), u'Show Desktop', self.HideMenu)
QtCore.QObject.connect(self.ThemeScreen,
QtCore.SIGNAL("triggered(bool)"), self.onThemeDisplay)
self.DesktopScreen = QtGui.QAction(QtGui.QIcon(
u':/slides/slide_desktop.png'), u'Show Desktop', self.HideMenu)
self.DesktopScreen.setCheckable(True)
QtCore.QObject.connect(self.DesktopScreen, QtCore.SIGNAL("triggered(bool)"), self.onHideDisplay)
QtCore.QObject.connect(self.DesktopScreen,
QtCore.SIGNAL("triggered(bool)"), self.onHideDisplay)
self.HideMenu.setDefaultAction(self.BlankScreen)
self.HideMenu.menu().addAction(self.BlankScreen)
self.HideMenu.menu().addAction(self.ThemeScreen)
@ -241,9 +247,8 @@ class SlideController(QtGui.QWidget):
self.Toolbar.addToolbarWidget(
u'Image SpinBox', self.DelaySpinBox)
self.DelaySpinBox.setSuffix(translate('SlideController', 's'))
self.DelaySpinBox.setToolTip(
translate('SlideController',
'Delay between slides in seconds'))
self.DelaySpinBox.setToolTip(translate('SlideController',
'Delay between slides in seconds'))
self.ControllerLayout.addWidget(self.Toolbar)
#Build a Media ToolBar
self.Mediabar = OpenLPToolbar(self)
@ -268,8 +273,7 @@ class SlideController(QtGui.QWidget):
self.volumeSlider = Phonon.VolumeSlider()
self.volumeSlider.setGeometry(QtCore.QRect(90, 260, 221, 24))
self.volumeSlider.setObjectName(u'volumeSlider')
self.Mediabar.addToolbarWidget(
u'Audio Volume', self.volumeSlider)
self.Mediabar.addToolbarWidget(u'Audio Volume', self.volumeSlider)
self.ControllerLayout.addWidget(self.Mediabar)
# Build the Song Toolbar
if isLive:
@ -328,6 +332,11 @@ class SlideController(QtGui.QWidget):
# Signals
QtCore.QObject.connect(self.PreviewListWidget,
QtCore.SIGNAL(u'clicked(QModelIndex)'), self.onSlideSelected)
if not self.isLive:
if QtCore.QSettings().value(u'advanced/double click live',
QtCore.QVariant(False)).toBool():
QtCore.QObject.connect(self.PreviewListWidget,
QtCore.SIGNAL(u'doubleClicked(QModelIndex)'), self.onGoLive)
if isLive:
QtCore.QObject.connect(Receiver.get_receiver(),
QtCore.SIGNAL(u'slidecontroller_live_spin_delay'),

View File

@ -48,6 +48,7 @@ class ThemeManager(QtGui.QWidget):
QtGui.QWidget.__init__(self, parent)
self.parent = parent
self.settingsSection = u'themes'
self.serviceComboBox = self.parent.ServiceManagerContents.ThemeComboBox
self.Layout = QtGui.QVBoxLayout(self)
self.Layout.setSpacing(0)
self.Layout.setMargin(0)
@ -116,6 +117,7 @@ class ThemeManager(QtGui.QWidget):
self.thumbPath = os.path.join(self.path, u'thumbnails')
self.checkThemesExists(self.thumbPath)
self.amendThemeForm.path = self.path
self.oldBackgroundImage = None
# Last little bits of setting up
self.global_theme = unicode(QtCore.QSettings().value(
self.settingsSection + u'/global theme',
@ -182,11 +184,17 @@ class ThemeManager(QtGui.QWidget):
Loads the settings for the theme that is to be edited and launches the
theme editing form so the user can make their changes.
"""
self.editingDefault = False
if check_item_selected(self.ThemeListWidget, translate('ThemeManager',
'You must select a theme to edit.')):
item = self.ThemeListWidget.currentItem()
themeName = unicode(item.text())
if themeName != unicode(item.data(QtCore.Qt.UserRole).toString()):
self.editingDefault = True
theme = self.getThemeData(
unicode(item.data(QtCore.Qt.UserRole).toString()))
if theme.background_type == u'image':
self.oldBackgroundImage = theme.background_filename
self.amendThemeForm.loadTheme(theme)
self.saveThemeName = unicode(
item.data(QtCore.Qt.UserRole).toString())
@ -212,37 +220,44 @@ class ThemeManager(QtGui.QWidget):
QtGui.QMessageBox.StandardButtons(QtGui.QMessageBox.Ok))
else:
for plugin in self.parent.plugin_manager.plugins:
if not plugin.canDeleteTheme(theme):
if plugin.usesTheme(theme):
QtGui.QMessageBox.critical(self,
translate('ThemeManager', 'Error'),
unicode(translate('ThemeManager',
'Theme %s is use in %s plugin.')) % \
(theme, plugin.name))
return
if unicode(self.parent.ServiceManagerContents.ThemeComboBox \
.currentText()) == theme:
if unicode(self.serviceComboBox.currentText()) == theme:
QtGui.QMessageBox.critical(self,
translate('ThemeManager', 'Error'),
unicode(translate('ThemeManager',
'Theme %s is use by the service manager.')) % theme)
return
self.themelist.remove(theme)
th = theme + u'.png'
row = self.ThemeListWidget.row(item)
self.ThemeListWidget.takeItem(row)
try:
os.remove(os.path.join(self.path, th))
os.remove(os.path.join(self.thumbPath, th))
encoding = get_filesystem_encoding()
shutil.rmtree(
os.path.join(self.path, theme).encode(encoding))
except OSError:
#if not present do not worry
pass
# As we do not reload the themes push out the change
# Reaload the list as the internal lists and events need
# to be triggered
self.pushThemes()
self.deleteTheme(theme)
def deleteTheme(self, theme):
"""
Delete a theme.
``theme``
The theme to delete.
"""
self.themelist.remove(theme)
th = theme + u'.png'
try:
os.remove(os.path.join(self.path, th))
os.remove(os.path.join(self.thumbPath, th))
encoding = get_filesystem_encoding()
shutil.rmtree(os.path.join(self.path, theme).encode(encoding))
except OSError:
#if not present do not worry
pass
# As we do not reload the themes push out the change
# Reaload the list as the internal lists and events need
# to be triggered
self.pushThemes()
def onExportTheme(self):
"""
@ -532,18 +547,31 @@ class ThemeManager(QtGui.QWidget):
os.mkdir(os.path.join(self.path, name))
theme_file = os.path.join(theme_dir, name + u'.xml')
log.debug(theme_file)
editedServiceTheme = False
result = QtGui.QMessageBox.Yes
if self.saveThemeName != name:
if os.path.exists(theme_file):
result = QtGui.QMessageBox.question(self,
translate('ThemeManager', 'Theme Exists'),
translate('ThemeManager',
'A theme with this name already exists. '
'Would you like to overwrite it?'),
translate('ThemeManager', 'A theme with this name already '
'exists. Would you like to overwrite it?'),
(QtGui.QMessageBox.Yes | QtGui.QMessageBox.No),
QtGui.QMessageBox.No)
if self.saveThemeName != u'':
for plugin in self.parent.plugin_manager.plugins:
if plugin.usesTheme(self.saveThemeName):
plugin.renameTheme(self.saveThemeName, name)
if unicode(self.serviceComboBox.currentText()) == name:
editedServiceTheme = True
self.deleteTheme(self.saveThemeName)
if result == QtGui.QMessageBox.Yes:
# Save the theme, overwriting the existing theme if necessary.
if image_to and self.oldBackgroundImage and \
image_to != self.oldBackgroundImage:
try:
os.remove(self.oldBackgroundImage)
except OSError:
log.exception(u'Unable to remove old theme background')
outfile = None
try:
outfile = open(theme_file, u'w')
@ -563,6 +591,26 @@ class ThemeManager(QtGui.QWidget):
log.exception(u'Failed to save theme image')
self.generateAndSaveImage(self.path, name, theme_xml)
self.loadThemes()
# Check if we need to set a new service theme
if editedServiceTheme:
newThemeIndex = self.serviceComboBox.findText(name)
if newThemeIndex != -1:
self.serviceComboBox.setCurrentIndex(newThemeIndex)
if self.editingDefault:
newThemeItem = self.ThemeListWidget.findItems(name,
QtCore.Qt.MatchExactly)[0]
newThemeIndex = self.ThemeListWidget.indexFromItem(
newThemeItem).row()
self.global_theme = unicode(
self.ThemeListWidget.item(newThemeIndex).text())
newName = unicode(translate('ThemeManager', '%s (default)')) % \
self.global_theme
self.ThemeListWidget.item(newThemeIndex).setText(newName)
QtCore.QSettings().setValue(
self.settingsSection + u'/global theme',
QtCore.QVariant(self.global_theme))
Receiver.send_message(u'theme_update_global', self.global_theme)
self.pushThemes()
else:
# Don't close the dialog - allow the user to change the name of
# the theme or to cancel the theme dialog completely.

View File

@ -95,7 +95,26 @@ class BiblePlugin(Plugin):
'displayed on the screen during the service.')
return about_text
def canDeleteTheme(self, theme):
def usesTheme(self, theme):
"""
Called to find out if the bible plugin is currently using a theme.
Returns True if the theme is being used, otherwise returns False.
"""
if self.settings_tab.bible_theme == theme:
return False
return True
return True
return False
def renameTheme(self, oldTheme, newTheme):
"""
Rename the theme the bible plugin is using making the plugin use the
new name.
``oldTheme``
The name of the theme the plugin should stop using. Unused for
this particular plugin.
``newTheme``
The new name the plugin should now use.
"""
self.settings_tab.bible_theme = newTheme

View File

@ -69,8 +69,30 @@ class CustomPlugin(Plugin):
'songs plugin.<br>')
return about_text
def canDeleteTheme(self, theme):
if not self.custommanager.get_all_objects_filtered(CustomSlide,
def usesTheme(self, theme):
"""
Called to find out if the custom plugin is currently using a theme.
Returns True if the theme is being used, otherwise returns False.
"""
if self.custommanager.get_all_objects_filtered(CustomSlide,
CustomSlide.theme_name == theme):
return True
return False
return False
def renameTheme(self, oldTheme, newTheme):
"""
Renames a theme the custom plugin is using making the plugin use the
new name.
``oldTheme``
The name of the theme the plugin should stop using.
``newTheme``
The new name the plugin should now use.
"""
customsUsingTheme = self.custommanager.get_all_objects_filtered(
CustomSlide, CustomSlide.theme_name == oldTheme)
for custom in customsUsingTheme:
custom.theme_name = newTheme
self.custommanager.save_object(custom)

View File

@ -37,8 +37,6 @@ import logging
import os
import time
from openlp.core.lib import resize_image
if os.name == u'nt':
from win32com.client import Dispatch
import pywintypes
@ -74,6 +72,7 @@ class ImpressController(PresentationController):
self.alsosupports = [u'.ppt', u'.pps', u'.pptx', u'.ppsx']
self.process = None
self.desktop = None
self.manager = None
def check_available(self):
"""
@ -104,6 +103,10 @@ class ImpressController(PresentationController):
self.process.waitForStarted()
def get_uno_desktop(self):
"""
On non-Windows platforms, use Uno. Get the OpenOffice desktop
which will be used to manage impress
"""
log.debug(u'get UNO Desktop Openoffice')
ctx = None
loop = 0
@ -134,10 +137,19 @@ class ImpressController(PresentationController):
return None
def get_com_desktop(self):
"""
On Windows platforms, use COM. Return the desktop object which
will be used to manage Impress
"""
log.debug(u'get COM Desktop OpenOffice')
if not self.manager:
return None
return self.manager.createInstance(u'com.sun.star.frame.Desktop')
def get_com_servicemanager(self):
"""
Return the OOo service manager for windows
"""
log.debug(u'get_com_servicemanager openoffice')
try:
return Dispatch(u'com.sun.star.ServiceManager')
@ -171,13 +183,23 @@ class ImpressController(PresentationController):
log.exception(u'Failed to terminate OpenOffice')
def add_doc(self, name):
"""
Called when a new Impress document is opened
"""
log.debug(u'Add Doc OpenOffice')
doc = ImpressDocument(self, name)
self.docs.append(doc)
return doc
class ImpressDocument(PresentationDocument):
"""
Class which holds information and controls a single presentation
"""
def __init__(self, controller, presentation):
"""
Constructor, store information about the file and initialise
"""
log.debug(u'Init Presentation OpenOffice')
PresentationDocument.__init__(self, controller, presentation)
self.document = None
@ -208,9 +230,8 @@ class ImpressDocument(PresentationDocument):
desktop = self.controller.get_uno_desktop()
url = uno.systemPathToFileUrl(self.filepath)
if desktop is None:
return
return False
self.desktop = desktop
#print "s.dsk2 ", self.desktop
properties = []
properties.append(self.create_property(u'Minimized', True))
properties = tuple(properties)
@ -219,12 +240,13 @@ class ImpressDocument(PresentationDocument):
0, properties)
except:
log.exception(u'Failed to load presentation')
return
return False
self.presentation = self.document.getPresentation()
self.presentation.Display = \
self.controller.plugin.renderManager.screens.current_display + 1
self.control = None
self.create_thumbnails()
return True
def create_thumbnails(self):
"""
@ -234,30 +256,36 @@ class ImpressDocument(PresentationDocument):
if self.check_thumbnails():
return
if os.name == u'nt':
thumbdir = u'file:///' + self.thumbnailpath.replace(
thumbdirurl = u'file:///' + self.get_temp_folder().replace(
u'\\', u'/').replace(u':', u'|').replace(u' ', u'%20')
else:
thumbdir = uno.systemPathToFileUrl(self.thumbnailpath)
thumbdirurl = uno.systemPathToFileUrl(self.get_temp_folder())
props = []
props.append(self.create_property(u'FilterName', u'impress_png_Export'))
props = tuple(props)
doc = self.document
pages = doc.getDrawPages()
if not os.path.isdir(self.get_temp_folder()):
os.makedirs(self.get_temp_folder())
for idx in range(pages.getCount()):
page = pages.getByIndex(idx)
doc.getCurrentController().setCurrentPage(page)
path = u'%s/%s%s.png' % (thumbdir, self.controller.thumbnailprefix,
unicode(idx + 1))
urlpath = u'%s/%s.png' % (thumbdirurl, unicode(idx + 1))
path = os.path.join(self.get_temp_folder(),
unicode(idx + 1) + u'.png')
try:
doc.storeToURL(path , props)
preview = resize_image(path, 640, 480)
doc.storeToURL(urlpath, props)
self.convert_thumbnail(path, idx + 1)
if os.path.exists(path):
os.remove(path)
preview.save(path, u'png')
except:
log.exception(u'%s - Unable to store openoffice preview' % path)
def create_property(self, name, value):
"""
Create an OOo style property object which are passed into some
Uno methods
"""
log.debug(u'create property OpenOffice')
if os.name == u'nt':
prop = self.controller.manager.\
@ -288,6 +316,9 @@ class ImpressDocument(PresentationDocument):
self.controller.remove_doc(self)
def is_loaded(self):
"""
Returns true if a presentation is loaded
"""
log.debug(u'is loaded OpenOffice')
#print "is_loaded "
if self.presentation is None or self.document is None:
@ -302,6 +333,9 @@ class ImpressDocument(PresentationDocument):
return True
def is_active(self):
"""
Returns true if a presentation is active and running
"""
log.debug(u'is active OpenOffice')
#print "is_active "
if not self.is_loaded():
@ -313,10 +347,16 @@ class ImpressDocument(PresentationDocument):
return True
def unblank_screen(self):
"""
Unblanks the screen
"""
log.debug(u'unblank screen OpenOffice')
return self.control.resume()
def blank_screen(self):
"""
Blanks the screen
"""
log.debug(u'blank screen OpenOffice')
self.control.blankScreen(0)
@ -331,6 +371,9 @@ class ImpressDocument(PresentationDocument):
return False
def stop_presentation(self):
"""
Stop the presentation, remove from screen
"""
log.debug(u'stop presentation OpenOffice')
# deactivate should hide the screen according to docs, but doesn't
#self.control.deactivate()
@ -338,6 +381,9 @@ class ImpressDocument(PresentationDocument):
self.control = None
def start_presentation(self):
"""
Start the presentation from the beginning
"""
log.debug(u'start presentation OpenOffice')
if self.control is None or not self.control.isRunning():
self.presentation.start()
@ -354,12 +400,21 @@ class ImpressDocument(PresentationDocument):
self.goto_slide(1)
def get_slide_number(self):
"""
Return the current slide number on the screen, from 1
"""
return self.control.getCurrentSlideIndex() + 1
def get_slide_count(self):
"""
Return the total number of slides
"""
return self.document.getDrawPages().getCount()
def goto_slide(self, slideno):
"""
Go to a specific slide (from 1)
"""
self.control.gotoSlideIndex(slideno-1)
def next_step(self):

View File

@ -30,14 +30,17 @@ from PyQt4 import QtCore, QtGui
from openlp.core.lib import MediaManagerItem, BaseListWithDnD, build_icon, \
SettingsManager, translate, check_item_selected
from openlp.core.utils import AppLocation
from openlp.plugins.presentations.lib import MessageListener
log = logging.getLogger(__name__)
# We have to explicitly create separate classes for each plugin
# in order for DnD to the Service manager to work correctly.
class PresentationListView(BaseListWithDnD):
"""
Class for the list of Presentations
We have to explicitly create separate classes for each plugin
in order for DnD to the Service manager to work correctly.
"""
def __init__(self, parent=None):
self.PluginName = u'Presentations'
BaseListWithDnD.__init__(self, parent)
@ -45,11 +48,14 @@ class PresentationListView(BaseListWithDnD):
class PresentationMediaItem(MediaManagerItem):
"""
This is the Presentation media manager item for Presentation Items.
It can present files using Openoffice
It can present files using Openoffice and Powerpoint
"""
log.info(u'Presentations Media Item loaded')
def __init__(self, parent, icon, title, controllers):
"""
Constructor. Setup defaults
"""
self.controllers = controllers
self.PluginNameShort = u'Presentation'
self.pluginNameVisible = translate('PresentationPlugin.MediaItem',
@ -63,6 +69,9 @@ class PresentationMediaItem(MediaManagerItem):
self.message_listener = MessageListener(self)
def retranslateUi(self):
"""
The name of the plugin media displayed in UI
"""
self.OnNewPrompt = translate('PresentationPlugin.MediaItem',
'Select Presentation(s)')
self.Automatic = translate('PresentationPlugin.MediaItem',
@ -80,12 +89,18 @@ class PresentationMediaItem(MediaManagerItem):
'Presentations (%s)' % fileType)
def requiredIcons(self):
"""
Set which icons the media manager tab should show
"""
MediaManagerItem.requiredIcons(self)
self.hasFileIcon = True
self.hasNewIcon = False
self.hasEditIcon = False
def addEndHeaderBar(self):
"""
Display custom media manager items for presentations
"""
self.PresentationWidget = QtGui.QWidget(self)
sizePolicy = QtGui.QSizePolicy(
QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
@ -109,15 +124,13 @@ class PresentationMediaItem(MediaManagerItem):
self.pageLayout.addWidget(self.PresentationWidget)
def initialise(self):
self.servicePath = os.path.join(
AppLocation.get_section_data_path(self.settingsSection),
u'thumbnails')
"""
Populate the media manager tab
"""
self.listView.setIconSize(QtCore.QSize(88, 50))
if not os.path.exists(self.servicePath):
os.mkdir(self.servicePath)
list = SettingsManager.load_list(
self.settingsSection, u'presentations')
self.loadList(list)
self.loadList(list, True)
for item in self.controllers:
#load the drop down selection
if self.controllers[item].enabled:
@ -126,7 +139,12 @@ class PresentationMediaItem(MediaManagerItem):
self.DisplayTypeComboBox.insertItem(0, self.Automatic)
self.DisplayTypeComboBox.setCurrentIndex(0)
def loadList(self, list):
def loadList(self, list, initialLoad=False):
"""
Add presentations into the media manager
This is called both on initial load of the plugin to populate with
existing files, and when the user adds new files via the media manager
"""
currlist = self.getFileList()
titles = []
for file in currlist:
@ -136,40 +154,43 @@ class PresentationMediaItem(MediaManagerItem):
continue
filename = os.path.split(unicode(file))[1]
if titles.count(filename) > 0:
QtGui.QMessageBox.critical(
self, translate('PresentationPlugin.MediaItem',
'File exists'),
if not initialLoad:
QtGui.QMessageBox.critical(
self, translate('PresentationPlugin.MediaItem',
'File exists'),
translate('PresentationPlugin.MediaItem',
'A presentation with that filename already exists.'),
QtGui.QMessageBox.Ok)
else:
icon = None
for controller in self.controllers:
thumbPath = os.path.join(
AppLocation.get_section_data_path(
self.settingsSection),
u'thumbnails', controller, filename)
thumb = os.path.join(thumbPath, u'slide1.png')
preview = os.path.join(
AppLocation.get_section_data_path(
self.settingsSection),
controller, u'thumbnails', filename, u'slide1.png')
if os.path.exists(preview):
if os.path.exists(thumb):
if self.validate(preview, thumb):
icon = build_icon(thumb)
else:
icon = build_icon(
u':/general/general_delete.png')
else:
os.makedirs(thumbPath)
icon = self.iconFromFile(preview, thumb)
if not icon:
QtGui.QMessageBox.Ok)
continue
controller_name = self.findControllerByType(filename)
if controller_name:
controller = self.controllers[controller_name]
doc = controller.add_doc(unicode(file))
thumb = os.path.join(doc.get_thumbnail_folder(), u'icon.png')
preview = doc.get_thumbnail_path(1, True)
if not preview and not initialLoad:
doc.load_presentation()
preview = doc.get_thumbnail_path(1, True)
doc.close_presentation()
if preview and self.validate(preview, thumb):
icon = build_icon(thumb)
else:
icon = build_icon(u':/general/general_delete.png')
item_name = QtGui.QListWidgetItem(filename)
item_name.setData(QtCore.Qt.UserRole, QtCore.QVariant(file))
item_name.setIcon(icon)
self.listView.addItem(item_name)
else:
if initialLoad:
icon = build_icon(u':/general/general_delete.png')
else:
QtGui.QMessageBox.critical(
self, translate('PresentationPlugin.MediaItem',
'Unsupported file'),
translate('PresentationPlugin.MediaItem',
'This type of presentation is not supported'),
QtGui.QMessageBox.Ok)
continue
item_name = QtGui.QListWidgetItem(filename)
item_name.setData(QtCore.Qt.UserRole, QtCore.QVariant(file))
item_name.setIcon(icon)
self.listView.addItem(item_name)
def onDeleteClick(self):
"""
@ -184,8 +205,6 @@ class PresentationMediaItem(MediaManagerItem):
for item in items:
filepath = unicode(item.data(
QtCore.Qt.UserRole).toString())
#not sure of this has errors
#John please can you look at .
for cidx in self.controllers:
doc = self.controllers[cidx].add_doc(filepath)
doc.presentation_deleted()
@ -196,6 +215,11 @@ class PresentationMediaItem(MediaManagerItem):
self.settingsSection, self.getFileList())
def generateSlideData(self, service_item, item=None):
"""
Load the relevant information for displaying the presentation
in the slidecontroller. In the case of powerpoints, an image
for each slide
"""
items = self.listView.selectedIndexes()
if len(items) > 1:
return False
@ -213,20 +237,27 @@ class PresentationMediaItem(MediaManagerItem):
controller = self.controllers[service_item.shortname]
(path, name) = os.path.split(filename)
doc = controller.add_doc(filename)
if doc.get_slide_preview_file(1) is None:
if doc.get_thumbnail_path(1, True) is None:
doc.load_presentation()
i = 1
img = doc.get_slide_preview_file(i)
img = doc.get_thumbnail_path(i, True)
while img:
service_item.add_from_command(path, name, img)
i = i + 1
img = doc.get_slide_preview_file(i)
img = doc.get_thumbnail_path(i, True)
doc.close_presentation()
return True
else:
return False
def findControllerByType(self, filename):
"""
Determine the default application controller to use for the selected
file type. This is used if "Automatic" is set as the preferred
controller. Find the first (alphabetic) enabled controller which
"supports" the extension. If none found, then look for a controller
which "alsosupports" it instead.
"""
filetype = os.path.splitext(filename)[1]
if not filetype:
return None

View File

@ -41,17 +41,28 @@ class Controller(object):
log.info(u'Controller loaded')
def __init__(self, live):
"""
Constructor
"""
self.is_live = live
self.doc = None
log.info(u'%s controller loaded' % live)
def add_handler(self, controller, file, is_blank):
"""
Add a handler, which is an instance of a presentation and
slidecontroller combination. If the slidecontroller has a display
then load the presentation.
"""
log.debug(u'Live = %s, add_handler %s' % (self.is_live, file))
self.controller = controller
if self.doc is not None:
self.shutdown()
self.doc = self.controller.add_doc(file)
self.doc.load_presentation()
if not self.doc.load_presentation():
# Display error message to user
# Inform slidecontroller that the action failed?
return
if self.is_live:
self.doc.start_presentation()
if is_blank:
@ -60,6 +71,10 @@ class Controller(object):
self.doc.slidenumber = 0
def activate(self):
"""
Active the presentation, and show it on the screen.
Use the last slide number.
"""
log.debug(u'Live = %s, activate' % self.is_live)
if self.doc.is_active():
return
@ -71,6 +86,9 @@ class Controller(object):
self.doc.goto_slide(self.doc.slidenumber)
def slide(self, slide):
"""
Go to a specific slide
"""
log.debug(u'Live = %s, slide' % self.is_live)
if not self.is_live:
return
@ -152,6 +170,9 @@ class Controller(object):
#self.timer.stop()
def blank(self):
"""
Instruct the controller to blank the presentation
"""
log.debug(u'Live = %s, blank' % self.is_live)
if not self.is_live:
return
@ -162,6 +183,9 @@ class Controller(object):
self.doc.blank_screen()
def stop(self):
"""
Instruct the controller to stop and hide the presentation
"""
log.debug(u'Live = %s, stop' % self.is_live)
if not self.is_live:
return
@ -172,6 +196,9 @@ class Controller(object):
self.doc.stop_presentation()
def unblank(self):
"""
Instruct the controller to unblank the presentation
"""
log.debug(u'Live = %s, unblank' % self.is_live)
if not self.is_live:
return
@ -246,6 +273,9 @@ class MessageListener(object):
controller.add_handler(self.controllers[self.handler], file, is_blank)
def slide(self, message):
"""
React to the message to move to a specific slide
"""
is_live = message[1]
slide = message[2]
if is_live:
@ -254,6 +284,9 @@ class MessageListener(object):
self.preview_handler.slide(slide)
def first(self, message):
"""
React to the message to move to the first slide
"""
is_live = message[1]
if is_live:
self.live_handler.first()
@ -261,6 +294,9 @@ class MessageListener(object):
self.preview_handler.first()
def last(self, message):
"""
React to the message to move to the last slide
"""
is_live = message[1]
if is_live:
self.live_handler.last()
@ -268,6 +304,9 @@ class MessageListener(object):
self.preview_handler.last()
def next(self, message):
"""
React to the message to move to the next animation/slide
"""
is_live = message[1]
if is_live:
self.live_handler.next()
@ -275,6 +314,9 @@ class MessageListener(object):
self.preview_handler.next()
def previous(self, message):
"""
React to the message to move to the previous animation/slide
"""
is_live = message[1]
if is_live:
self.live_handler.previous()
@ -282,6 +324,10 @@ class MessageListener(object):
self.preview_handler.previous()
def shutdown(self, message):
"""
React to message to shutdown the presentation. I.e. end the show
and close the file
"""
is_live = message[1]
if is_live:
Receiver.send_message(u'maindisplay_show')
@ -290,19 +336,34 @@ class MessageListener(object):
self.preview_handler.shutdown()
def hide(self, message):
"""
React to the message to show the desktop
"""
is_live = message[1]
if is_live:
self.live_handler.stop()
def blank(self, message):
"""
React to the message to blank the display
"""
is_live = message[1]
if is_live:
self.live_handler.blank()
def unblank(self, message):
"""
React to the message to unblank the display
"""
is_live = message[1]
if is_live:
self.live_handler.unblank()
def timeout(self):
"""
The presentation may be timed or might be controlled by the
application directly, rather than through OpenLP. Poll occassionally
to check which slide is currently displayed so the slidecontroller
view can be updated
"""
self.live_handler.poll()

View File

@ -97,13 +97,23 @@ class PowerpointController(PresentationController):
self.process = None
def add_doc(self, name):
"""
Called when a new powerpoint document is opened
"""
log.debug(u'Add Doc PowerPoint')
doc = PowerpointDocument(self, name)
self.docs.append(doc)
return doc
class PowerpointDocument(PresentationDocument):
"""
Class which holds information and controls a single presentation
"""
def __init__(self, controller, presentation):
"""
Constructor, store information about the file and initialise
"""
log.debug(u'Init Presentation Powerpoint')
PresentationDocument.__init__(self, controller, presentation)
self.presentation = None
@ -111,22 +121,23 @@ class PowerpointDocument(PresentationDocument):
def load_presentation(self):
"""
Called when a presentation is added to the SlideController.
It builds the environment, starts communcations with the background
OpenOffice task started earlier. If OpenOffice is not present is is
started. Once the environment is available the presentation is loaded
and started.
Opens the PowerPoint file using the process created earlier
``presentation``
The file name of the presentations to run.
"""
log.debug(u'LoadPresentation')
if not self.controller.process.Visible:
if not self.controller.process or not self.controller.process.Visible:
self.controller.start_process()
self.controller.process.Presentations.Open(self.filepath, False, False,
True)
try:
self.controller.process.Presentations.Open(self.filepath, False,
False, True)
except pywintypes.com_error:
return False
self.presentation = self.controller.process.Presentations(
self.controller.process.Presentations.Count)
self.create_thumbnails()
return True
def create_thumbnails(self):
"""
@ -139,8 +150,8 @@ class PowerpointDocument(PresentationDocument):
"""
if self.check_thumbnails():
return
self.presentation.Export(os.path.join(self.thumbnailpath, ''), 'png',
320, 240)
self.presentation.Export(os.path.join(self.get_thumbnail_folder(), ''),
'png', 320, 240)
def close_presentation(self):
"""
@ -298,4 +309,4 @@ class PowerpointDocument(PresentationDocument):
shape = shapes(idx + 1)
if shape.HasTextFrame:
text += shape.TextFrame.TextRange.Text + '\n'
return text
return text

View File

@ -93,13 +93,22 @@ class PptviewController(PresentationController):
self.docs[0].close_presentation()
def add_doc(self, name):
"""
Called when a new powerpoint document is opened
"""
log.debug(u'Add Doc PPTView')
doc = PptviewDocument(self, name)
self.docs.append(doc)
return doc
class PptviewDocument(PresentationDocument):
"""
Class which holds information and controls a single presentation
"""
def __init__(self, controller, presentation):
"""
Constructor, store information about the file and initialise
"""
log.debug(u'Init Presentation PowerPoint')
PresentationDocument.__init__(self, controller, presentation)
self.presentation = None
@ -117,17 +126,31 @@ class PptviewDocument(PresentationDocument):
The file name of the presentations to run.
"""
log.debug(u'LoadPresentation')
#if self.pptid >= 0:
# self.close_presentation()
rendermanager = self.controller.plugin.renderManager
rect = rendermanager.screens.current[u'size']
rect = RECT(rect.x(), rect.y(), rect.right(), rect.bottom())
filepath = str(self.filepath.replace(u'/', u'\\'))
if not os.path.isdir(self.get_temp_folder()):
os.makedirs(self.get_temp_folder())
self.pptid = self.controller.process.OpenPPT(filepath, None, rect,
str(os.path.join(self.thumbnailpath,
self.controller.thumbnailprefix)))
if self.pptid:
str(self.get_temp_folder()) + '\\slide')
if self.pptid >= 0:
self.create_thumbnails()
self.stop_presentation()
return True
else:
return False
def create_thumbnails(self):
"""
PPTviewLib creates large BMP's, but we want small PNG's for consistency.
Convert them here.
"""
if self.check_thumbnails():
return
for idx in range(self.get_slide_count()):
path = u'%s\\slide%s.bmp' % (self.get_temp_folder(), unicode(idx + 1))
self.convert_thumbnail(path, idx + 1)
def close_presentation(self):
"""
@ -224,17 +247,3 @@ class PptviewDocument(PresentationDocument):
"""
self.controller.process.PrevStep(self.pptid)
def get_slide_preview_file(self, slide_no):
"""
Returns an image path containing a preview for the requested slide
``slide_no``
The slide an image is required for, starting at 1
"""
path = os.path.join(self.thumbnailpath,
self.controller.thumbnailprefix + unicode(slide_no) + u'.bmp')
if os.path.isfile(path):
return path
else:
return None

View File

@ -29,7 +29,7 @@ import shutil
from PyQt4 import QtCore
from openlp.core.lib import Receiver
from openlp.core.lib import Receiver, resize_image
from openlp.core.utils import AppLocation
log = logging.getLogger(__name__)
@ -63,6 +63,13 @@ class PresentationController(object):
``plugin``
The presentationplugin object
``supports``
The primary native file types this application supports
``alsosupports``
Other file types the application can import, although not necessarily
the first choice due to potential incompatibilities
**Hook Functions**
``kill()``
@ -109,12 +116,16 @@ class PresentationController(object):
QtCore.Qt.Checked
else:
self.enabled = False
self.thumbnailroot = os.path.join(
self.temp_folder = os.path.join(
AppLocation.get_section_data_path(self.settings_section), name)
self.thumbnail_folder = os.path.join(
AppLocation.get_section_data_path(self.settings_section),
name, u'thumbnails')
self.thumbnailprefix = u'slide'
if not os.path.isdir(self.thumbnailroot):
os.makedirs(self.thumbnailroot)
u'thumbnails')
self.thumbnail_prefix = u'slide'
if not os.path.isdir(self.thumbnail_folder):
os.makedirs(self.thumbnail_folder)
if not os.path.isdir(self.temp_folder):
os.makedirs(self.temp_folder)
def check_available(self):
"""
@ -208,14 +219,19 @@ class PresentationDocument(object):
``previous_step()``
Triggers the previous slide on the running presentation
``get_slide_preview_file(slide_no)``
``get_thumbnail_path(slide_no, check_exists)``
Returns a path to an image containing a preview for the requested slide
"""
def __init__(self, controller, name):
"""
Constructor for the PresentationController class
"""
self.slidenumber = 0
self.controller = controller
self.store_filename(name)
self.filepath = name
if not os.path.isdir(self.get_thumbnail_folder()):
os.mkdir(self.get_thumbnail_folder())
def load_presentation(self):
"""
@ -224,9 +240,10 @@ class PresentationDocument(object):
``presentation``
The file name of the presentations to the run.
Returns False if the file could not be opened
"""
pass
return False
def presentation_deleted(self):
"""
@ -234,33 +251,37 @@ class PresentationDocument(object):
a file, e.g. thumbnails
"""
try:
shutil.rmtree(self.thumbnailpath)
shutil.rmtree(self.get_thumbnail_folder())
shutil.rmtree(self.get_temp_folder())
except OSError:
log.exception(u'Failed to delete presentation controller files')
def store_filename(self, presentation):
def get_file_name(self):
"""
Set properties for the filename and thumbnail paths
Return just the filename of the presention, without the directory
"""
self.filepath = presentation
self.filename = self.get_file_name(presentation)
self.thumbnailpath = self.get_thumbnail_path(presentation)
if not os.path.isdir(self.thumbnailpath):
os.mkdir(self.thumbnailpath)
return os.path.split(self.filepath)[1]
def get_file_name(self, presentation):
return os.path.split(presentation)[1]
def get_thumbnail_path(self, presentation):
def get_thumbnail_folder(self):
"""
The location where thumbnail images will be stored
"""
return os.path.join(
self.controller.thumbnailroot, self.get_file_name(presentation))
self.controller.thumbnail_folder, self.get_file_name())
def get_temp_folder(self):
"""
The location where thumbnail images will be stored
"""
return os.path.join(
self.controller.temp_folder, self.get_file_name())
def check_thumbnails(self):
"""
Returns true if the thumbnail images look to exist and are more
recent than the powerpoint
"""
lastimage = self.get_slide_preview_file(self.get_slide_count())
lastimage = self.get_thumbnail_path(self.get_slide_count(), True)
if not (lastimage and os.path.isfile(lastimage)):
return False
imgdate = os.stat(lastimage).st_mtime
@ -350,16 +371,27 @@ class PresentationDocument(object):
"""
pass
def get_slide_preview_file(self, slide_no):
def convert_thumbnail(self, file, idx):
"""
Convert the slide image the application made to a standard 320x240
.png image.
"""
if self.check_thumbnails():
return
if os.path.isfile(file):
img = resize_image(file, 320, 240)
img.save(self.get_thumbnail_path(idx, False))
def get_thumbnail_path(self, slide_no, check_exists):
"""
Returns an image path containing a preview for the requested slide
``slide_no``
The slide an image is required for, starting at 1
"""
path = os.path.join(self.thumbnailpath,
self.controller.thumbnailprefix + unicode(slide_no) + u'.png')
if os.path.isfile(path):
path = os.path.join(self.get_thumbnail_folder(),
self.controller.thumbnail_prefix + unicode(slide_no) + u'.png')
if os.path.isfile(path) or not check_exists:
return path
else:
return None

View File

@ -32,10 +32,16 @@ class PresentationTab(SettingsTab):
PresentationsTab is the Presentations settings tab in the settings dialog.
"""
def __init__(self, title, controllers):
"""
Constructor
"""
self.controllers = controllers
SettingsTab.__init__(self, title)
def setupUi(self):
"""
Create the controls for the settings tab
"""
self.setObjectName(u'PresentationTab')
self.tabTitleVisible = translate('PresentationPlugin.PresentationTab',
'Presentations')
@ -89,6 +95,9 @@ class PresentationTab(SettingsTab):
self.PresentationLayout.addWidget(self.PresentationRightWidget)
def retranslateUi(self):
"""
Make any translation changes
"""
self.VerseDisplayGroupBox.setTitle(
translate('PresentationPlugin.PresentationTab',
'Available Controllers'))
@ -100,6 +109,9 @@ class PresentationTab(SettingsTab):
translate('PresentationPlugin.PresentationTab', 'available')))
def load(self):
"""
Load the settings.
"""
for key in self.controllers:
controller = self.controllers[key]
if controller.available:
@ -109,6 +121,9 @@ class PresentationTab(SettingsTab):
QtCore.QVariant(0)).toInt()[0])
def save(self):
"""
Save the settings.
"""
for key in self.controllers:
controller = self.controllers[key]
checkbox = self.PresenterCheckboxes[controller.name]

View File

@ -33,9 +33,17 @@ from openlp.plugins.presentations.lib import *
log = logging.getLogger(__name__)
class PresentationPlugin(Plugin):
"""
This plugin allowed a Presentation to be opened, controlled and displayed
on the output display. The plugin controls third party applications such
as OpenOffice.org Impress, Microsoft PowerPoint and the PowerPoint viewer
"""
log = logging.getLogger(u'PresentationPlugin')
def __init__(self, plugin_helpers):
"""
PluginPresentation constructor.
"""
log.debug(u'Initialised')
self.controllers = {}
Plugin.__init__(self, u'Presentations', u'1.9.2', plugin_helpers)
@ -51,6 +59,10 @@ class PresentationPlugin(Plugin):
return PresentationTab(self.name, self.controllers)
def initialise(self):
"""
Initialise the plugin. Determine which controllers are enabled
are start their processes.
"""
log.info(u'Presentations Initialising')
Plugin.initialise(self)
self.insertToolboxItem()
@ -59,6 +71,10 @@ class PresentationPlugin(Plugin):
self.controllers[controller].start_process()
def finalise(self):
"""
Finalise the plugin. Ask all the enabled presentation applications
to close down their applications and release resources.
"""
log.info(u'Plugin Finalise')
#Ask each controller to tidy up
for key in self.controllers:
@ -75,6 +91,10 @@ class PresentationPlugin(Plugin):
self, self.icon, self.name, self.controllers)
def registerControllers(self, controller):
"""
Register each presentation controller (Impress, PPT etc) and
store for later use
"""
self.controllers[controller.name] = controller
def checkPreConditions(self):
@ -109,9 +129,13 @@ class PresentationPlugin(Plugin):
return False
def about(self):
"""
Return information about this plugin
"""
about_text = translate('PresentationPlugin',
'<b>Presentation Plugin</b> <br> Delivers '
'the ability to show presentations using a number of different '
'programs. The choice of available presentation programs is '
'available to the user in a drop down box.')
return about_text
return about_text

View File

@ -24,6 +24,7 @@
###############################################################################
from PyQt4 import QtGui, QtCore
from sqlalchemy.sql import and_
from openlp.core.lib import translate
from openlp.plugins.songs.forms import AuthorsForm, TopicsForm, SongBookForm
@ -97,6 +98,9 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog):
QtGui.QMessageBox.critical(self, dlg_title, sel_text)
def resetAuthors(self):
"""
Reloads the Authors list.
"""
self.AuthorsListWidget.clear()
authors = self.songmanager.get_all_objects(Author, Author.display_name)
for author in authors:
@ -109,6 +113,9 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog):
self.AuthorsListWidget.addItem(author_name)
def resetTopics(self):
"""
Reloads the Topics list.
"""
self.TopicsListWidget.clear()
topics = self.songmanager.get_all_objects(Topic, Topic.name)
for topic in topics:
@ -117,13 +124,88 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog):
self.TopicsListWidget.addItem(topic_name)
def resetBooks(self):
"""
Reloads the Books list.
"""
self.BooksListWidget.clear()
books = self.songmanager.get_all_objects(Book, Book.name)
for book in books:
book_name = QtGui.QListWidgetItem(book.name)
book_name = QtGui.QListWidgetItem(u'%s (%s)' % (book.name,
book.publisher))
book_name.setData(QtCore.Qt.UserRole, QtCore.QVariant(book.id))
self.BooksListWidget.addItem(book_name)
def checkAuthor(self, new_author, edit=False):
"""
Returns False when the given Author is already in the list elsewise
True.
"""
authors = self.songmanager.get_all_objects_filtered(Author,
and_(
Author.first_name == new_author.first_name,
Author.last_name == new_author.last_name,
Author.display_name == new_author.display_name
)
)
if len(authors) > 0:
# If we edit an existing Author, we need to make sure that we do
# not return False when nothing has changed (because this would
# cause an error message later on).
if edit:
if authors[0].id == new_author.id:
return True
else:
return False
else:
return False
else:
return True
def checkTopic(self, new_topic, edit=False):
"""
Returns False when the given Topic is already in the list elsewise True.
"""
topics = self.songmanager.get_all_objects_filtered(Topic,
Topic.name == new_topic.name
)
if len(topics) > 0:
# If we edit an existing Topic, we need to make sure that we do
# not return False when nothing has changed (because this would
# cause an error message later on).
if edit:
if topics[0].id == new_topic.id:
return True
else:
return False
else:
return False
else:
return True
def checkBook(self, new_book, edit=False):
"""
Returns False when the given Book is already in the list elsewise True.
"""
books = self.songmanager.get_all_objects_filtered(Book,
and_(
Book.name == new_book.name,
Book.publisher == new_book.publisher
)
)
if len(books) > 0:
# If we edit an existing Book, we need to make sure that we do
# not return False when nothing has changed (because this would
# cause an error message later on).
if edit:
if books[0].id == new_book.id:
return True
else:
return False
else:
return False
else:
return True
def onAuthorAddButtonClick(self):
self.authorform.setAutoDisplayName(True)
if self.authorform.exec_():
@ -131,86 +213,93 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog):
first_name=unicode(self.authorform.FirstNameEdit.text()),
last_name=unicode(self.authorform.LastNameEdit.text()),
display_name=unicode(self.authorform.DisplayEdit.text()))
if self.songmanager.save_object(author):
self.resetAuthors()
if self.checkAuthor(author):
if self.songmanager.save_object(author):
self.resetAuthors()
else:
QtGui.QMessageBox.critical(
self, translate('SongsPlugin.SongMaintenanceForm',
'Error'),
QtGui.QMessageBox.critical(self,
translate('SongsPlugin.SongMaintenanceForm', 'Error'),
translate('SongsPlugin.SongMaintenanceForm',
'Couldn\'t add your author.'))
'Could not add your author.'))
def onTopicAddButtonClick(self):
if self.topicform.exec_():
topic = Topic.populate(name=unicode(self.topicform.NameEdit.text()))
if self.songmanager.save_object(topic):
self.resetTopics()
if self.checkTopic(topic):
if self.songmanager.save_object(topic):
self.resetTopics()
else:
QtGui.QMessageBox.critical(
self, translate('SongsPlugin.SongMaintenanceForm',
'Error'),
QtGui.QMessageBox.critical(self,
translate('SongsPlugin.SongMaintenanceForm', 'Error'),
translate('SongsPlugin.SongMaintenanceForm',
'Couldn\'t add your topic.'))
'Could not add your topic.'))
def onBookAddButtonClick(self):
if self.bookform.exec_():
book = Book.populate(
name=unicode(self.bookform.NameEdit.text()),
publisher=unicode(self.bookform.PublisherEdit.text()))
if self.songmanager.save_object(book):
self.resetBooks()
if self.checkBook(book):
if self.songmanager.save_object(book):
self.resetBooks()
else:
QtGui.QMessageBox.critical(
self, translate('SongsPlugin.SongMaintenanceForm',
'Error'),
QtGui.QMessageBox.critical(self,
translate('SongsPlugin.SongMaintenanceForm', 'Error'),
translate('SongsPlugin.SongMaintenanceForm',
'Couldn\'t add your book.'))
'Could not add your book.'))
def onAuthorEditButtonClick(self):
author_id = self._getCurrentItemId(self.AuthorsListWidget)
if author_id != -1:
author = self.songmanager.get_object(Author, author_id)
# Just make sure none of the fields is None
if author.first_name is None:
author.first_name = u''
if author.last_name is None:
author.last_name = u''
if author.display_name is None:
author.display_name = u''
self.authorform.setAutoDisplayName(False)
self.authorform.FirstNameEdit.setText(author.first_name)
self.authorform.LastNameEdit.setText(author.last_name)
self.authorform.DisplayEdit.setText(author.display_name)
# Save the author's first and last name as well as the display name
# for the case that they have to be restored.
temp_first_name = author.first_name
temp_last_name = author.last_name
temp_display_name = author.display_name
if self.authorform.exec_(False):
author.first_name = unicode(
self.authorform.FirstNameEdit.text())
author.last_name = unicode(self.authorform.LastNameEdit.text())
author.display_name = unicode(
self.authorform.DisplayEdit.text())
if self.songmanager.save_object(author):
self.resetAuthors()
if self.checkAuthor(author, True):
if self.songmanager.save_object(author):
self.resetAuthors()
else:
QtGui.QMessageBox.critical(
self, translate('SongsPlugin.SongMaintenanceForm',
'Error'),
# We restore the author's old first and last name as well as
# his display name.
author.first_name = temp_first_name
author.last_name = temp_last_name
author.display_name = temp_display_name
QtGui.QMessageBox.critical(self,
translate('SongsPlugin.SongMaintenanceForm', 'Error'),
translate('SongsPlugin.SongMaintenanceForm',
'Couldn\'t save your author.'))
'Could not save your author.'))
def onTopicEditButtonClick(self):
topic_id = self._getCurrentItemId(self.TopicsListWidget)
if topic_id != -1:
topic = self.songmanager.get_object(Topic, topic_id)
self.topicform.NameEdit.setText(topic.name)
# Save the topic's name for the case that he has to be restored.
temp_name = topic.name
if self.topicform.exec_(False):
topic.name = unicode(self.topicform.NameEdit.text())
if self.songmanager.save_object(topic):
self.resetTopics()
if self.checkTopic(topic, True):
if self.songmanager.save_object(topic):
self.resetTopics()
else:
QtGui.QMessageBox.critical(
self, translate('SongsPlugin.SongMaintenanceForm',
'Error'),
# We restore the topics's old name.
topic.name = temp_name
QtGui.QMessageBox.critical(self,
translate('SongsPlugin.SongMaintenanceForm', 'Error'),
translate('SongsPlugin.SongMaintenanceForm',
'Couldn\'t save your topic.'))
'Could not save your topic.'))
def onBookEditButtonClick(self):
book_id = self._getCurrentItemId(self.BooksListWidget)
@ -218,17 +307,24 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog):
book = self.songmanager.get_object(Book, book_id)
self.bookform.NameEdit.setText(book.name)
self.bookform.PublisherEdit.setText(book.publisher)
# Save the book's name and publisher for the case that they have to
# be restored.
temp_name = book.name
temp_publisher = book.publisher
if self.bookform.exec_(False):
book.name = unicode(self.bookform.NameEdit.text())
book.publisher = unicode(self.bookform.PublisherEdit.text())
if self.songmanager.save_object(book):
self.resetBooks()
if self.checkBook(book, True):
if self.songmanager.save_object(book):
self.resetBooks()
else:
QtGui.QMessageBox.critical(
self, translate('SongsPlugin.SongMaintenanceForm',
'Error'),
# We restore the book's old name and publisher.
book.name = temp_name
book.publisher = temp_publisher
QtGui.QMessageBox.critical(self,
translate('SongsPlugin.SongMaintenanceForm', 'Error'),
translate('SongsPlugin.SongMaintenanceForm',
'Couldn\'t save your book.'))
'Could not save your book.'))
def onAuthorDeleteButtonClick(self):
"""
@ -236,13 +332,12 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog):
"""
self._deleteItem(Author, self.AuthorsListWidget, self.resetAuthors,
translate('SongsPlugin.SongMaintenanceForm', 'Delete Author'),
translate('SongsPlugin.SongMaintenanceForm',
translate('SongsPlugin.SongMaintenanceForm',
'Are you sure you want to delete the selected author?'),
translate('SongsPlugin.SongMaintenanceForm',
'This author can\'t be deleted, they are currently '
'This author cannot be deleted, they are currently '
'assigned to at least one song.'),
translate('SongsPlugin.SongMaintenanceForm',
'No author selected!'))
translate('SongsPlugin.SongMaintenanceForm', 'No author selected!'))
def onTopicDeleteButtonClick(self):
"""
@ -250,13 +345,12 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog):
"""
self._deleteItem(Topic, self.TopicsListWidget, self.resetTopics,
translate('SongsPlugin.SongMaintenanceForm', 'Delete Topic'),
translate('SongsPlugin.SongMaintenanceForm',
'Are you sure you want to delete the selected topic?'),
translate('SongsPlugin.SongMaintenanceForm',
'This topic can\'t be deleted, it is currently '
'assigned to at least one song.'),
translate('SongsPlugin.SongMaintenanceForm',
'No topic selected!'))
'Are you sure you want to delete the selected topic?'),
translate('SongsPlugin.SongMaintenanceForm',
'This topic cannot be deleted, it is currently '
'assigned to at least one song.'),
translate('SongsPlugin.SongMaintenanceForm', 'No topic selected!'))
def onBookDeleteButtonClick(self):
"""
@ -266,7 +360,7 @@ class SongMaintenanceForm(QtGui.QDialog, Ui_SongMaintenanceDialog):
translate('SongsPlugin.SongMaintenanceForm', 'Delete Book'),
translate('SongsPlugin.SongMaintenanceForm',
'Are you sure you want to delete the selected book?'),
translate('SongsPlugin.SongMaintenanceForm',
'This book can\'t be deleted, it is currently '
translate('SongsPlugin.SongMaintenanceForm',
'This book cannot be deleted, it is currently '
'assigned to at least one song.'),
translate('SongsPlugin.SongMaintenanceForm', u'No book selected!'))
translate('SongsPlugin.SongMaintenanceForm', 'No book selected!'))

View File

@ -192,8 +192,30 @@ class SongsPlugin(Plugin):
'This plugin allows songs to be managed and displayed.')
return about_text
def canDeleteTheme(self, theme):
if not self.manager.get_all_objects_filtered(Song,
def usesTheme(self, theme):
"""
Called to find out if the song plugin is currently using a theme.
Returns True if the theme is being used, otherwise returns False.
"""
if self.manager.get_all_objects_filtered(Song,
Song.theme_name == theme):
return True
return False
return False
def renameTheme(self, oldTheme, newTheme):
"""
Renames a theme the song plugin is using making the plugin use the new
name.
``oldTheme``
The name of the theme the plugin should stop using.
``newTheme``
The new name the plugin should now use.
"""
songsUsingTheme = self.manager.get_all_objects_filtered(Song,
Song.theme_name == oldTheme)
for song in songsUsingTheme:
song.theme_name = newTheme
self.custommanager.save_object(song)