From bbef55326b5fea1afdc1f48b3098deae91e1c17a Mon Sep 17 00:00:00 2001 From: Jonathan Corwin Date: Wed, 30 Sep 2009 20:26:51 +0100 Subject: [PATCH 1/3] Individual presentation controllers are now completely self-contained. (left out dll to see if breaks the merge, so powerpoint viewer won't work anymore. If this succeeds and is approved I'll then try pushing the dll separately) --- openlp/plugins/presentations/lib/__init__.py | 3 - .../presentations/lib/impresscontroller.py | 29 +++++---- openlp/plugins/presentations/lib/mediaitem.py | 3 +- .../presentations/lib/powerpointcontroller.py | 25 +++++--- .../presentations/lib/pptviewcontroller.py | 23 +++++-- .../presentations/lib/pptviewlib/README.TXT | 3 + .../lib/pptviewlib/pptviewlib.cpp | 8 +++ .../presentations/lib/pptviewlib/pptviewlib.h | 1 + .../lib/presentationcontroller.py | 19 +++++- .../presentations/lib/presentationtab.py | 64 ++++++++----------- .../presentations/presentationplugin.py | 53 ++++++++------- 11 files changed, 138 insertions(+), 93 deletions(-) diff --git a/openlp/plugins/presentations/lib/__init__.py b/openlp/plugins/presentations/lib/__init__.py index 61aa34228..d042a10e4 100644 --- a/openlp/plugins/presentations/lib/__init__.py +++ b/openlp/plugins/presentations/lib/__init__.py @@ -23,9 +23,6 @@ ############################################################################### from presentationcontroller import PresentationController -from impresscontroller import ImpressController -from powerpointcontroller import PowerpointController -from pptviewcontroller import PptviewController from messagelistener import MessageListener from mediaitem import PresentationMediaItem from presentationtab import PresentationTab diff --git a/openlp/plugins/presentations/lib/impresscontroller.py b/openlp/plugins/presentations/lib/impresscontroller.py index 869fd5fb2..ff8d13729 100644 --- a/openlp/plugins/presentations/lib/impresscontroller.py +++ b/openlp/plugins/presentations/lib/impresscontroller.py @@ -34,11 +34,8 @@ if os.name == u'nt': else: import uno -from PyQt4 import QtCore - from presentationcontroller import PresentationController - class ImpressController(PresentationController): """ Class to control interactions with Impress presentations. @@ -47,6 +44,7 @@ class ImpressController(PresentationController): """ global log log = logging.getLogger(u'ImpressController') + log.info(u'loaded') def __init__(self, plugin): """ @@ -59,16 +57,17 @@ class ImpressController(PresentationController): self.presentation = None self.controller = None - def is_available(self): + def check_available(self): """ - PPT Viewer is able to run on this machine + Impress is able to run on this machine """ - log.debug(u'is_available') - try: - self.start_process() + log.debug(u'check_available') + if os.name == u'nt': + return self.get_com_servicemanager() is not None + else: + # If not windows, and we've got this far then probably + # installed else the import uno would likely have failed return True - except: - return False def start_process(self): """ @@ -148,13 +147,21 @@ class ImpressController(PresentationController): def get_com_desktop(self): log.debug(u'getCOMDesktop') try: - smgr = Dispatch("com.sun.star.ServiceManager") + smgr = self.get_com_servicemanager() desktop = smgr.createInstance( "com.sun.star.frame.Desktop") return desktop except: log.exception(u'Failed to get COM desktop') return None + def get_com_servicemanager(self): + log.debug(u'get_com_servicemanager') + try: + return Dispatch("com.sun.star.ServiceManager") + except: + log.exception(u'Failed to get COM service manager') + return None + def close_presentation(self): """ Close presentation and clean up objects diff --git a/openlp/plugins/presentations/lib/mediaitem.py b/openlp/plugins/presentations/lib/mediaitem.py index be308f1d9..356cf3acc 100644 --- a/openlp/plugins/presentations/lib/mediaitem.py +++ b/openlp/plugins/presentations/lib/mediaitem.py @@ -94,7 +94,8 @@ class PresentationMediaItem(MediaManagerItem): self.loadList(list) for item in self.controllers: #load the drop down selection - self.DisplayTypeComboBox.addItem(item) + if self.controllers[item].enabled: + self.DisplayTypeComboBox.addItem(item) def loadList(self, list): for file in list: diff --git a/openlp/plugins/presentations/lib/powerpointcontroller.py b/openlp/plugins/presentations/lib/powerpointcontroller.py index cac1b7008..0f13493e5 100644 --- a/openlp/plugins/presentations/lib/powerpointcontroller.py +++ b/openlp/plugins/presentations/lib/powerpointcontroller.py @@ -27,6 +27,7 @@ import logging if os.name == u'nt': from win32com.client import Dispatch + import _winreg from presentationcontroller import PresentationController @@ -41,7 +42,8 @@ class PowerpointController(PresentationController): """ global log log = logging.getLogger(u'PowerpointController') - + log.info(u'loaded') + def __init__(self, plugin): """ Initialise the class @@ -51,18 +53,18 @@ class PowerpointController(PresentationController): self.process = None self.presentation = None - def is_available(self): + def check_available(self): """ PowerPoint is able to run on this machine """ - log.debug(u'is_available') - if os.name != u'nt': - return False - try: - self.start_process() - return True - except: - return False + log.debug(u'check_available') + if os.name == u'nt': + try: + _winreg.OpenKey(_winreg.HKEY_CLASSES_ROOT, u'PowerPoint.Application').Close() + return True + except: + pass + return False if os.name == u'nt': def start_process(self): @@ -83,6 +85,9 @@ class PowerpointController(PresentationController): return False def kill(self): + """ + Called at system exit to clean up any running presentations + """ self.process.Quit() self.process = None diff --git a/openlp/plugins/presentations/lib/pptviewcontroller.py b/openlp/plugins/presentations/lib/pptviewcontroller.py index 97c0ceb5d..3622085cd 100644 --- a/openlp/plugins/presentations/lib/pptviewcontroller.py +++ b/openlp/plugins/presentations/lib/pptviewcontroller.py @@ -39,37 +39,50 @@ class PptviewController(PresentationController): """ global log log = logging.getLogger(u'PptviewController') + log.info(u'loaded') def __init__(self, plugin): """ Initialise the class """ log.debug(u'Initialising') - PresentationController.__init__(self, plugin, u'Powerpoint Viewer') self.process = None + PresentationController.__init__(self, plugin, u'Powerpoint Viewer') self.pptid = None self.thumbnailpath = os.path.join(plugin.config.get_data_path(), u'pptview', u'thumbnails') self.thumbprefix = u'slide' - def is_available(self): + def check_available(self): """ PPT Viewer is able to run on this machine """ - log.debug(u'is_available') + log.debug(u'check_available') if os.name != u'nt': return False try: - self.start_process() - return True + return self.check_installed() except: return False if os.name == u'nt': + def check_installed(self): + """ + Check the viewer is installed + """ + log.debug(u'Check installed') + try: + self.start_process() + return self.process.CheckInstalled() + except: + return False + def start_process(self): """ Loads the PPTVIEWLIB library """ + if self.process is not None: + return log.debug(u'start PPTView') self.process = cdll.LoadLibrary(r'openlp\plugins\presentations\lib\pptviewlib\pptviewlib.dll') diff --git a/openlp/plugins/presentations/lib/pptviewlib/README.TXT b/openlp/plugins/presentations/lib/pptviewlib/README.TXT index 0ffcde1bf..5afcfd3f4 100644 --- a/openlp/plugins/presentations/lib/pptviewlib/README.TXT +++ b/openlp/plugins/presentations/lib/pptviewlib/README.TXT @@ -25,6 +25,9 @@ This library has a limit of 50 PowerPoints which can be opened simultaneously. USAGE ----- +BOOL CheckInstalled(void); + Returns TRUE if PowerPointViewer is installed. FALSE if not. + int OpenPPT(char *filename, HWND hParentWnd, RECT rect, char *previewpath); Opens the PowerPoint file, counts the number of slides, sizes and positions accordingly diff --git a/openlp/plugins/presentations/lib/pptviewlib/pptviewlib.cpp b/openlp/plugins/presentations/lib/pptviewlib/pptviewlib.cpp index 8e4cc7e82..abba3088b 100644 --- a/openlp/plugins/presentations/lib/pptviewlib/pptviewlib.cpp +++ b/openlp/plugins/presentations/lib/pptviewlib/pptviewlib.cpp @@ -82,6 +82,14 @@ DllExport void SetDebug(BOOL onoff) DEBUG("enabled\n"); } +DllExport BOOL CheckInstalled() +{ + DEBUG("CheckInstalled\n"); + char cmdline[MAX_PATH * 2]; + + return GetPPTViewerPath(cmdline, sizeof(cmdline)); +} + // Open the PointPoint, count the slides and take a snapshot of each slide // for use in previews // previewpath is a prefix for the location to put preview images of each slide. diff --git a/openlp/plugins/presentations/lib/pptviewlib/pptviewlib.h b/openlp/plugins/presentations/lib/pptviewlib/pptviewlib.h index 3e365215b..6012b0467 100644 --- a/openlp/plugins/presentations/lib/pptviewlib/pptviewlib.h +++ b/openlp/plugins/presentations/lib/pptviewlib/pptviewlib.h @@ -4,6 +4,7 @@ enum PPTVIEWSTATE { PPT_CLOSED, PPT_STARTED, PPT_OPENED, PPT_LOADED, PPT_CLOSING}; DllExport int OpenPPT(char *filename, HWND hParentWnd, RECT rect, char *previewpath); +DllExport BOOL CheckInstalled(); DllExport void ClosePPT(int id); DllExport int GetCurrentSlide(int id); DllExport int GetSlideCount(int id); diff --git a/openlp/plugins/presentations/lib/presentationcontroller.py b/openlp/plugins/presentations/lib/presentationcontroller.py index 8edbd5f25..6f501a092 100644 --- a/openlp/plugins/presentations/lib/presentationcontroller.py +++ b/openlp/plugins/presentations/lib/presentationcontroller.py @@ -20,6 +20,8 @@ Place, Suite 330, Boston, MA 02111-1307 USA import logging +from PyQt4 import QtCore + class PresentationController(object): """ Base class for presentation controllers to inherit from @@ -32,6 +34,13 @@ class PresentationController(object): ``name`` The name that appears in the options and the media manager + ``enabled`` + The controller is enabled + + ``available`` + The controller is available on this machine. Set by init via + call to check_available + ``plugin`` The presentationplugin object @@ -40,7 +49,7 @@ class PresentationController(object): ``kill()`` Called at system exit to clean up any running presentations - ``is_available()`` + ``check_available()`` Returns True if presentation application is installed/can run on this machine ``load_presentation(presentation)`` @@ -108,8 +117,14 @@ class PresentationController(object): """ self.plugin = plugin self.name = name + self.available = self.check_available() + if self.available: + self.enabled = int(plugin.config.get_config( + name, QtCore.Qt.Unchecked)) == QtCore.Qt.Checked + else: + self.enabled = False - def is_available(self): + def check_available(self): """ Presentation app is able to run on this machine """ diff --git a/openlp/plugins/presentations/lib/presentationtab.py b/openlp/plugins/presentations/lib/presentationtab.py index 0cbeb4b9f..8eb04d44d 100644 --- a/openlp/plugins/presentations/lib/presentationtab.py +++ b/openlp/plugins/presentations/lib/presentationtab.py @@ -60,24 +60,17 @@ class PresentationTab(SettingsTab): self.VerseTypeLayout.setSpacing(8) self.VerseTypeLayout.setMargin(0) self.VerseTypeLayout.setObjectName(u'VerseTypeLayout') - self.PowerpointCheckBox = QtGui.QCheckBox(self.VerseDisplayGroupBox) - self.PowerpointCheckBox.setTristate(False) - if os.name != u'nt': - self.PowerpointCheckBox.setEnabled(False) - self.PowerpointCheckBox.setObjectName(u'PowerpointCheckBox') - self.VerseDisplayLayout.addWidget(self.PowerpointCheckBox, 0, 0, 1, 1) - self.PowerpointViewerCheckBox = QtGui.QCheckBox( - self.VerseDisplayGroupBox) - self.PowerpointViewerCheckBox.setTristate(False) - if os.name != u'nt': - self.PowerpointViewerCheckBox.setEnabled(False) - self.PowerpointViewerCheckBox.setObjectName(u'PowerpointViewerCheckBox') - self.VerseDisplayLayout.addWidget( - self.PowerpointViewerCheckBox, 1, 0, 1, 1) - self.ImpressCheckBox = QtGui.QCheckBox(self.VerseDisplayGroupBox) - self.ImpressCheckBox.setTristate(False) - self.ImpressCheckBox.setObjectName(u'ImpressCheckBox') - self.VerseDisplayLayout.addWidget(self.ImpressCheckBox, 2, 0, 1, 1) + self.PresenterCheckboxes = {} + index = 0 + for key in self.controllers: + controller = self.controllers[key] + checkbox = QtGui.QCheckBox(self.VerseDisplayGroupBox) + checkbox.setTristate(False) + checkbox.setEnabled(controller.available) + checkbox.setObjectName(controller.name + u'CheckBox') + self.PresenterCheckboxes[controller.name] = checkbox + index = index + 1 + self.VerseDisplayLayout.addWidget(checkbox, index, 0, 1, 1) self.PresentationThemeWidget = QtGui.QWidget(self.VerseDisplayGroupBox) self.PresentationThemeWidget.setObjectName(u'PresentationThemeWidget') self.PresentationThemeLayout = QtGui.QHBoxLayout( @@ -103,26 +96,23 @@ class PresentationTab(SettingsTab): self.PresentationLayout.addWidget(self.PresentationRightWidget) def retranslateUi(self): - self.PowerpointCheckBox.setText( - translate(u'PresentationTab', 'Powerpoint available:')) - self.PowerpointViewerCheckBox.setText( - translate(u'PresentationTab', 'PowerpointViewer available:')) - self.ImpressCheckBox.setText( - translate(u'PresentationTab', 'Impress available:')) + for key in self.controllers: + controller = self.controllers[key] + checkbox = self.PresenterCheckboxes[controller.name] + checkbox.setText(translate(u'PresentationTab', + controller.name + u' available:')) def load(self): - self.PowerpointCheckBox.setChecked( - int(self.config.get_config(u'Powerpoint', 0))) - self.PowerpointViewerCheckBox.setChecked( - int(self.config.get_config(u'Powerpoint Viewer', 0))) - self.ImpressCheckBox.setChecked( - int(self.config.get_config(u'Impress', 0))) + for key in self.controllers: + controller = self.controllers[key] + if controller.available: + checkbox = self.PresenterCheckboxes[controller.name] + checkbox.setChecked( + int(self.config.get_config(controller.name, 0))) def save(self): - self.config.set_config( - u'Powerpoint', unicode(self.PowerpointCheckBox.checkState())) - self.config.set_config( - u'Powerpoint Viewer', - unicode(self.PowerpointViewerCheckBox.checkState())) - self.config.set_config( - u'Impress', unicode(self.ImpressCheckBox.checkState())) + for key in self.controllers: + controller = self.controllers[key] + checkbox = self.PresenterCheckboxes[controller.name] + self.config.set_config( + controller.name, unicode(checkbox.checkState())) diff --git a/openlp/plugins/presentations/presentationplugin.py b/openlp/plugins/presentations/presentationplugin.py index f44617e7d..1827bbd4a 100644 --- a/openlp/plugins/presentations/presentationplugin.py +++ b/openlp/plugins/presentations/presentationplugin.py @@ -22,11 +22,12 @@ # Temple Place, Suite 330, Boston, MA 02111-1307 USA # ############################################################################### +import os import logging -from PyQt4 import QtCore +from PyQt4 import QtGui -from openlp.core.lib import Plugin, buildIcon +from openlp.core.lib import Plugin from openlp.plugins.presentations.lib import * class PresentationPlugin(Plugin): @@ -41,7 +42,9 @@ class PresentationPlugin(Plugin): Plugin.__init__(self, u'Presentations', u'1.9.0', plugin_helpers) self.weight = -8 # Create the plugin icon - self.icon = buildIcon(u':/media/media_presentation.png') + self.icon = QtGui.QIcon() + self.icon.addPixmap(QtGui.QPixmap(u':/media/media_presentation.png'), + QtGui.QIcon.Normal, QtGui.QIcon.Off) def get_settings_tab(self): """ @@ -67,25 +70,25 @@ class PresentationPlugin(Plugin): If Not do not install the plugin. """ log.debug(u'check_pre_conditions') - #Lets see if Powerpoint is required (Default is Not wanted) - controller = PowerpointController(self) - if int(self.config.get_config( - controller.name, QtCore.Qt.Unchecked)) == QtCore.Qt.Checked: - if controller.is_available(): - self.registerControllers(controller) - #Lets see if Impress is required (Default is Not wanted) - controller = ImpressController(self) - if int(self.config.get_config( - controller.name, QtCore.Qt.Unchecked)) == QtCore.Qt.Checked: - if controller.is_available(): - self.registerControllers(controller) - #Lets see if Powerpoint Viewer is required (Default is Not wanted) - controller = PptviewController(self) - if int(self.config.get_config( - controller.name, QtCore.Qt.Unchecked)) == QtCore.Qt.Checked: - if controller.is_available(): - self.registerControllers(controller) - #If we have no available controllers disable plugin + dir = os.path.join(os.path.dirname(__file__), u'lib') + for filename in os.listdir(dir): + if filename.endswith(u'controller.py') and \ + not filename == 'presentationcontroller.py': + path = os.path.join(dir, filename) + if os.path.isfile(path): + modulename = u'openlp.plugins.presentations.lib.' + \ + os.path.splitext(filename)[0] + log.debug(u'Importing controller %s', modulename) + try: + __import__(modulename, globals(), locals(), []) + except ImportError, e: + log.error(u'Failed to import %s on path %s for reason %s', modulename, path, e.args[0]) + controller_classes = PresentationController.__subclasses__() + for controller_class in controller_classes: + controller = controller_class(self) + self.registerControllers(controller) + if controller.enabled: + controller.start_process() if len(self.controllers) > 0: return True else: @@ -94,5 +97,7 @@ class PresentationPlugin(Plugin): def finalise(self): log.debug(u'Finalise') #Ask each controller to tidy up - for controller in self.controllers: - self.controllers[controller].kill() + for key in self.controllers: + controller = self.controllers[key] + if controller.enabled: + controller.kill() From e639b319360e8174cba5a2cb4e41dad88bce935e Mon Sep 17 00:00:00 2001 From: Jonathan Corwin Date: Wed, 30 Sep 2009 20:37:45 +0100 Subject: [PATCH 2/3] *sigh* and again --- openlp/plugins/presentations/lib/impresscontroller.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openlp/plugins/presentations/lib/impresscontroller.py b/openlp/plugins/presentations/lib/impresscontroller.py index ff8d13729..a202926e5 100644 --- a/openlp/plugins/presentations/lib/impresscontroller.py +++ b/openlp/plugins/presentations/lib/impresscontroller.py @@ -34,6 +34,8 @@ if os.name == u'nt': else: import uno +from PyQt4 import QtCore + from presentationcontroller import PresentationController class ImpressController(PresentationController): From 1f9eb94682c21627fb646c1e0f5cd3ebdeacf8f2 Mon Sep 17 00:00:00 2001 From: Jonathan Corwin Date: Wed, 30 Sep 2009 21:24:37 +0100 Subject: [PATCH 3/3] New checkinstalled method. Lets see if I can break launchpad again --- .../lib/pptviewlib/pptviewlib.dll | Bin 46080 -> 47104 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/openlp/plugins/presentations/lib/pptviewlib/pptviewlib.dll b/openlp/plugins/presentations/lib/pptviewlib/pptviewlib.dll index 52a6504eefeac8e4c6866ea8d733197b5942eb42..bb8e9d9a47aaf572decc48c3521dc0f61b2df51c 100644 GIT binary patch delta 8038 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