diff --git a/openlp/core/api/http/wsgiapp.py b/openlp/core/api/http/wsgiapp.py index 3c262e365..48364f340 100644 --- a/openlp/core/api/http/wsgiapp.py +++ b/openlp/core/api/http/wsgiapp.py @@ -69,6 +69,7 @@ def _make_response(view_result): """ Create a Response object from response """ + log.debug("in Make response") if isinstance(view_result, Response): return view_result elif isinstance(view_result, tuple): @@ -88,6 +89,9 @@ def _make_response(view_result): elif isinstance(view_result, str): return Response(body=view_result, status=200, content_type='text/html', charset='utf8') + else: + return Response(body=view_result, status=200, + content_type='text/plain', charset='utf8') def _handle_exception(error): diff --git a/openlp/core/app.py b/openlp/core/app.py index 2a6cf8494..eb15c69b7 100644 --- a/openlp/core/app.py +++ b/openlp/core/app.py @@ -120,7 +120,8 @@ class OpenLP(QtWidgets.QApplication, LogMixin): self.main_window.show() if can_show_splash: # now kill the splashscreen - self.splash.finish(self.main_window) + log.debug('Splashscreen closing') + self.splash.close() log.debug('Splashscreen closed') # make sure Qt really display the splash screen self.processEvents() diff --git a/openlp/core/common/mixins.py b/openlp/core/common/mixins.py index 28c478b93..4ff8f798d 100644 --- a/openlp/core/common/mixins.py +++ b/openlp/core/common/mixins.py @@ -56,7 +56,10 @@ class LogMixin(object): def wrapped(*args, **kwargs): parent.logger.debug("Entering {function}".format(function=func.__name__)) try: - return func(*args, **kwargs) + if len(inspect.signature(func).parameters.values()): + return func(*args, **kwargs) + else: + return func(*args) except Exception as e: if parent.logger.getEffectiveLevel() <= logging.ERROR: parent.logger.error('Exception in {function} : {error}'.format(function=func.__name__, @@ -89,6 +92,13 @@ class LogMixin(object): trace_error_handler(self.logger) self.logger.error(message) + def log_critical(self, message): + """ + Common log critical handler which prints the calling path + """ + trace_error_handler(self.logger) + self.logger.critical(message) + def log_exception(self, message): """ Common log exception handler which prints the calling path diff --git a/openlp/core/common/settings.py b/openlp/core/common/settings.py index 1c1186feb..947a9ce6c 100644 --- a/openlp/core/common/settings.py +++ b/openlp/core/common/settings.py @@ -259,7 +259,8 @@ class Settings(QtCore.QSettings): ('media/last directory', 'media/last directory', [(str_to_path, None)]), ('songuasge/db password', 'songusage/db password', []), ('songuasge/db hostname', 'songusage/db hostname', []), - ('songuasge/db database', 'songusage/db database', []) + ('songuasge/db database', 'songusage/db database', []), + ('presentations / Powerpoint Viewer', '', []) ] @staticmethod diff --git a/openlp/core/ui/aboutdialog.py b/openlp/core/ui/aboutdialog.py index d60f0ee0c..b162d0586 100644 --- a/openlp/core/ui/aboutdialog.py +++ b/openlp/core/ui/aboutdialog.py @@ -98,7 +98,7 @@ class UiAboutDialog(object): 'OpenLP is free church presentation software, or lyrics ' 'projection software, used to display slides of songs, Bible ' 'verses, videos, images, and even presentations (if ' - 'Impress, PowerPoint or PowerPoint Viewer is installed) ' + 'Impress or PowerPoint is installed) ' 'for church worship using a computer and a data projector.\n' '\n' 'Find out more about OpenLP: http://openlp.org/\n' diff --git a/openlp/core/ui/mainwindow.py b/openlp/core/ui/mainwindow.py index a48fb3dfe..0c16d24b2 100644 --- a/openlp/core/ui/mainwindow.py +++ b/openlp/core/ui/mainwindow.py @@ -37,7 +37,7 @@ from openlp.core.common import is_win, is_macosx, add_actions from openlp.core.common.actions import ActionList, CategoryOrder from openlp.core.common.applocation import AppLocation from openlp.core.common.i18n import LanguageManager, UiStrings, translate -from openlp.core.common.mixins import RegistryProperties +from openlp.core.common.mixins import LogMixin, RegistryProperties from openlp.core.common.path import Path, copyfile, create_paths from openlp.core.common.registry import Registry from openlp.core.common.settings import Settings @@ -56,8 +56,6 @@ from openlp.core.version import get_version from openlp.core.widgets.dialogs import FileDialog from openlp.core.widgets.docks import OpenLPDockWidget, MediaDockManager -log = logging.getLogger(__name__) - class Ui_MainWindow(object): """ @@ -465,12 +463,10 @@ class Ui_MainWindow(object): self.mode_live_item.setStatusTip(translate('OpenLP.MainWindow', 'Use layout that focuses on Live.')) -class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties): +class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, LogMixin, RegistryProperties): """ The main window. """ - log.info('MainWindow loaded') - def __init__(self): """ This constructor sets up the interface, the various managers, and the plugins. @@ -557,7 +553,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties): wait_dialog.setCancelButton(None) wait_dialog.show() for thread_name in self.application.worker_threads.keys(): - log.debug('Waiting for thread %s', thread_name) + self.log_debug('Waiting for thread %s' % thread_name) self.application.processEvents() thread = self.application.worker_threads[thread_name]['thread'] worker = self.application.worker_threads[thread_name]['worker'] @@ -595,7 +591,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties): """ Called on start up to restore the last active media plugin. """ - log.info('Load data from Settings') + self.log_info('Load data from Settings') if Settings().value('advanced/save current plugin'): saved_plugin_id = Settings().value('advanced/current media plugin') if saved_plugin_id != -1: @@ -627,7 +623,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties): :param version: The Version to be displayed. """ - log.debug('version_notice') version_text = translate('OpenLP.MainWindow', 'Version {new} of OpenLP is now available for download (you are ' 'currently running version {current}). \n\nYou can download the latest version from ' 'http://openlp.org/.').format(new=version, current=get_version()[u'full']) @@ -774,7 +769,8 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties): self.application.splash.close() QtWidgets.QMessageBox.information(self, title, message) - def on_help_web_site_clicked(self): + @staticmethod + def on_help_web_site_clicked(): """ Load the OpenLP website """ @@ -891,7 +887,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties): settings = Settings() import_settings = Settings(str(temp_config_path), Settings.IniFormat) - log.info('hook upgrade_plugin_settings') + self.log_info('hook upgrade_plugin_settings') self.plugin_manager.hook_upgrade_plugin_settings(import_settings) # Upgrade settings to prepare the import. if import_settings.can_upgrade(): @@ -929,7 +925,8 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties): value = import_settings.value(section_key) except KeyError: value = None - log.warning('The key "{key}" does not exist (anymore), so it will be skipped.'.format(key=section_key)) + self.log_warning('The key "{key}" does not exist (anymore), so it will be skipped.'. + format(key=section_key)) if value is not None: settings.setValue('{key}'.format(key=section_key), value) now = datetime.now() @@ -1013,7 +1010,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties): """ The screen has changed so we have to update components such as the renderer. """ - log.debug('screen_changed') self.application.set_busy_cursor() self.image_manager.update_display() self.renderer.update_display() @@ -1076,7 +1072,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties): if Settings().value('advanced/save current plugin'): Settings().setValue('advanced/current media plugin', self.media_tool_box.currentIndex()) # Call the cleanup method to shutdown plugins. - log.info('cleanup plugins') + self.log_info('cleanup plugins') self.plugin_manager.finalise_plugins() if save_settings: # Save settings @@ -1216,7 +1212,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties): """ Load the main window settings. """ - log.debug('Loading QSettings') settings = Settings() # Remove obsolete entries. settings.remove('custom slide') @@ -1243,7 +1238,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties): # Exit if we just did a settings import. if self.settings_imported: return - log.debug('Saving QSettings') settings = Settings() settings.beginGroup(self.general_settings_section) settings.setValue('recent files', self.recent_files) @@ -1268,7 +1262,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties): if not recent_path.is_file(): continue count += 1 - log.debug('Recent file name: {name}'.format(name=recent_path)) + self.log_debug('Recent file name: {name}'.format(name=recent_path)) action = create_action(self, '', text='&{n} {name}'.format(n=count, name=recent_path.name), data=recent_path, triggers=self.service_manager_contents.on_recent_service_clicked) @@ -1350,21 +1344,21 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties): """ Change the data directory. """ - log.info('Changing data path to {newpath}'.format(newpath=self.new_data_path)) + self.log_info('Changing data path to {newpath}'.format(newpath=self.new_data_path)) old_data_path = AppLocation.get_data_path() # Copy OpenLP data to new location if requested. self.application.set_busy_cursor() if self.copy_data: - log.info('Copying data to new path') + self.log_info('Copying data to new path') try: self.show_status_message( translate('OpenLP.MainWindow', 'Copying OpenLP data to new data directory location - {path} ' '- Please wait for copy to finish').format(path=self.new_data_path)) dir_util.copy_tree(str(old_data_path), str(self.new_data_path)) - log.info('Copy successful') + self.log_info('Copy successful') except (OSError, DistutilsFileError) as why: self.application.set_normal_cursor() - log.exception('Data copy failed {err}'.format(err=str(why))) + self.log_exception('Data copy failed {err}'.format(err=str(why))) err_text = translate('OpenLP.MainWindow', 'OpenLP Data directory copy failed\n\n{err}').format(err=str(why)), QtWidgets.QMessageBox.critical(self, translate('OpenLP.MainWindow', 'New Data Directory Error'), @@ -1372,7 +1366,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow, RegistryProperties): QtWidgets.QMessageBox.StandardButtons(QtWidgets.QMessageBox.Ok)) return False else: - log.info('No data copy requested') + self.log_info('No data copy requested') # Change the location of data directory in config file. settings = QtCore.QSettings() settings.setValue('advanced/data path', self.new_data_path) diff --git a/openlp/core/ui/servicemanager.py b/openlp/core/ui/servicemanager.py index d2453480b..5969f7bda 100644 --- a/openlp/core/ui/servicemanager.py +++ b/openlp/core/ui/servicemanager.py @@ -302,7 +302,7 @@ class Ui_ServiceManager(object): class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixin, RegistryProperties): """ Manages the services. This involves taking text strings from plugins and adding them to the service. This service - can then be zipped up with all the resources used into one OSZ or oszl file for use on any OpenLP v2 installation. + can then be zipped up with all the resources used into one OSZ or OSZL file for use on any OpenLP installation. Also handles the UI tasks of moving things up and down etc. """ servicemanager_set_item = QtCore.pyqtSignal(int) @@ -415,10 +415,9 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi if suffix not in self.suffixes: self.suffixes.append(suffix) - def on_new_service_clicked(self, field=None): + def on_new_service_clicked(self): """ Create a new service. - :param field: """ if self.is_modified(): result = self.save_modified_service() @@ -466,10 +465,9 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi QtWidgets.QMessageBox.Save | QtWidgets.QMessageBox.Discard | QtWidgets.QMessageBox.Cancel, QtWidgets.QMessageBox.Save) - def on_recent_service_clicked(self, field=None): + def on_recent_service_clicked(self): """ Load a recent file as the service triggered by mainwindow recent service list. - :param field: """ if self.is_modified(): result = self.save_modified_service() @@ -608,7 +606,7 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi self.set_modified(False) return True - def save_file_as(self, field=None): + def save_file_as(self): """ Get a file name and then call :func:`ServiceManager.save_file` to save the file. """ @@ -660,10 +658,9 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi self.set_file_name(file_path) self.decide_save_method() - def decide_save_method(self, field=None): + def decide_save_method(self): """ Determine which type of save method to use. - :param field: """ if not self.file_name(): return self.save_file_as() @@ -824,10 +821,9 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi theme_action.setChecked(True) self.menu.exec(self.service_manager_list.mapToGlobal(point)) - def on_service_item_note_form(self, field=None): + def on_service_item_note_form(self): """ Allow the service note to be edited - :param field: """ item = self.find_service_item()[0] self.service_note_form.text_edit.setPlainText(self.service_items[item]['service_item'].notes) @@ -836,21 +832,18 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi self.repaint_service_list(item, -1) self.set_modified() - def on_start_time_form(self, field=None): + def on_start_time_form(self): """ Opens a dialog to type in service item notes. - :param field: """ item = self.find_service_item()[0] self.start_time_form.item = self.service_items[item] if self.start_time_form.exec(): self.repaint_service_list(item, -1) - def toggle_auto_play_slides_once(self, field=None): + def toggle_auto_play_slides_once(self): """ Toggle Auto play slide once. Inverts auto play once option for the item - - :param field: """ item = self.find_service_item()[0] service_item = self.service_items[item]['service_item'] @@ -863,11 +856,9 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi self.main_window.general_settings_section + '/loop delay') self.set_modified() - def toggle_auto_play_slides_loop(self, field=None): + def toggle_auto_play_slides_loop(self): """ Toggle Auto play slide loop. - - :param field: """ item = self.find_service_item()[0] service_item = self.service_items[item]['service_item'] @@ -880,10 +871,9 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi self.main_window.general_settings_section + '/loop delay') self.set_modified() - def on_timed_slide_interval(self, field=None): + def on_timed_slide_interval(self): """ Shows input dialog for enter interval in seconds for delay - :param field: """ item = self.find_service_item()[0] service_item = self.service_items[item]['service_item'] @@ -906,7 +896,7 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi service_item.auto_play_slides_once = False self.set_modified() - def on_auto_start(self, field=None): + def on_auto_start(self): """ Toggles to Auto Start Setting. """ @@ -914,10 +904,9 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi self.service_items[item]['service_item'].will_auto_start = \ not self.service_items[item]['service_item'].will_auto_start - def on_service_item_edit_form(self, field=None): + def on_service_item_edit_form(self): """ Opens a dialog to edit the service item and update the service display if changes are saved. - :param field: """ item = self.find_service_item()[0] self.service_item_edit_form.set_service_item(self.service_items[item]['service_item']) @@ -990,11 +979,9 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi prev_item_last_slide = service_iterator.value() service_iterator += 1 - def on_set_item(self, message, field=None): + def on_set_item(self, message): """ Called by a signal to select a specific item and make it live usually from remote. - - :param field: :param message: The data passed in from a remove message """ self.log_debug(message) @@ -1060,10 +1047,9 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi self.service_manager.collapsed(item.parent()) self.service_manager_list.setCurrentItem(item.parent()) - def on_collapse_all(self, field=None): + def on_collapse_all(self): """ Collapse all the service items. - :param field: """ for item in self.service_items: item['expanded'] = False @@ -1080,10 +1066,9 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi if item.childCount(): self.service_items[pos - 1]['expanded'] = False - def on_expand_all(self, field=None): + def on_expand_all(self): """ Collapse all the service items. - :param field: """ for item in self.service_items: item['expanded'] = True @@ -1100,10 +1085,9 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi if item.childCount(): self.service_items[pos - 1]['expanded'] = True - def on_service_top(self, field=None): + def on_service_top(self): """ Move the current ServiceItem to the top of the list. - :param field: """ item, child = self.find_service_item() if item < len(self.service_items) and item != -1: @@ -1113,10 +1097,9 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi self.repaint_service_list(0, child) self.set_modified() - def on_service_up(self, field=None): + def on_service_up(self): """ Move the current ServiceItem one position up in the list. - :param field: """ item, child = self.find_service_item() if item > 0: @@ -1126,10 +1109,9 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi self.repaint_service_list(item - 1, child) self.set_modified() - def on_service_down(self, field=None): + def on_service_down(self): """ Move the current ServiceItem one position down in the list. - :param field: """ item, child = self.find_service_item() if item < len(self.service_items) and item != -1: @@ -1139,10 +1121,9 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi self.repaint_service_list(item + 1, child) self.set_modified() - def on_service_end(self, field=None): + def on_service_end(self): """ Move the current ServiceItem to the bottom of the list. - :param field: """ item, child = self.find_service_item() if item < len(self.service_items) and item != -1: @@ -1152,7 +1133,7 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi self.repaint_service_list(len(self.service_items) - 1, child) self.set_modified() - def on_delete_from_service(self, field=None): + def on_delete_from_service(self): """ Remove the current ServiceItem from the list. :param field: @@ -1378,7 +1359,7 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi self.drop_position = -1 self.set_modified() - def make_preview(self, field=None): + def make_preview(self): """ Send the current item to the Preview slide controller :param field: @@ -1403,7 +1384,7 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi else: return self.service_items[item]['service_item'] - def on_double_click_live(self, field=None): + def on_double_click_live(self): """ Send the current item to the Live slide controller but triggered by a tablewidget click event. :param field: @@ -1411,7 +1392,7 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi self.list_double_clicked = True self.make_live() - def on_single_click_preview(self, field=None): + def on_single_click_preview(self): """ If single click previewing is enabled, and triggered by a tablewidget click event, start a timeout to verify a double-click hasn't triggered. @@ -1463,7 +1444,7 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi 'is missing or inactive')) self.application.set_normal_cursor() - def remote_edit(self, field=None): + def remote_edit(self): """ Triggers a remote edit to a plugin to allow item to be edited. :param field: @@ -1475,7 +1456,7 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi if new_item: self.add_service_item(new_item, replace=True) - def on_service_item_rename(self, field=None): + def on_service_item_rename(self): """ Opens a dialog to rename the service item. @@ -1493,7 +1474,7 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi self.repaint_service_list(item, -1) self.set_modified() - def create_custom(self, field=None): + def create_custom(self): """ Saves the current text item as a custom slide :param field: @@ -1613,7 +1594,7 @@ class ServiceManager(QtWidgets.QWidget, RegistryBase, Ui_ServiceManager, LogMixi self.renderer.set_service_theme(self.service_theme) self.regenerate_service_items() - def on_theme_change_action(self, field=None): + def on_theme_change_action(self): """ Handles theme change events diff --git a/openlp/core/ui/slidecontroller.py b/openlp/core/ui/slidecontroller.py index 66b5ac51e..f9128923c 100644 --- a/openlp/core/ui/slidecontroller.py +++ b/openlp/core/ui/slidecontroller.py @@ -545,14 +545,14 @@ class SlideController(DisplayController, LogMixin, RegistryProperties): self.on_theme_display(False) self.on_hide_display(False) - def service_previous(self, field=None): + def service_previous(self): """ Live event to select the previous service item from the service manager. """ self.keypress_queue.append(ServiceItemAction.Previous) self._process_queue() - def service_next(self, field=None): + def service_next(self): """ Live event to select the next service item from the service manager. """ @@ -1103,7 +1103,7 @@ class SlideController(DisplayController, LogMixin, RegistryProperties): else: Registry().execute('live_display_show') - def on_slide_selected(self, field=None): + def on_slide_selected(self): """ Slide selected in controller Note for some reason a dummy field is required. Nothing is passed! @@ -1252,7 +1252,7 @@ class SlideController(DisplayController, LogMixin, RegistryProperties): self.preview_widget.change_slide(row) self.slide_selected() - def on_slide_selected_previous(self, field=None): + def on_slide_selected_previous(self): """ Go to the previous slide. """ @@ -1380,7 +1380,7 @@ class SlideController(DisplayController, LogMixin, RegistryProperties): if event.timerId() == self.timer_id: self.on_slide_selected_next(self.play_slides_loop.isChecked()) - def on_edit_song(self, field=None): + def on_edit_song(self): """ From the preview display requires the service Item to be editied """ @@ -1389,16 +1389,16 @@ class SlideController(DisplayController, LogMixin, RegistryProperties): if new_item: self.add_service_item(new_item) - def on_preview_add_to_service(self, field=None): + def on_preview_add_to_service(self): """ From the preview display request the Item to be added to service """ if self.service_item: self.service_manager.add_service_item(self.service_item) - def on_preview_double_click(self, field=None): + def on_preview_double_click(self): """ - Triggered when a preview slide item is doubleclicked + Triggered when a preview slide item is double clicked """ if self.service_item: if Settings().value('advanced/double click live') and Settings().value('core/auto unblank'): diff --git a/openlp/plugins/bibles/lib/mediaitem.py b/openlp/plugins/bibles/lib/mediaitem.py index dc5792dac..c2cbec7ae 100755 --- a/openlp/plugins/bibles/lib/mediaitem.py +++ b/openlp/plugins/bibles/lib/mediaitem.py @@ -384,7 +384,7 @@ class BibleMediaItem(MediaManagerItem): This initialises the given bible, which means that its book names and their chapter numbers is added to the combo boxes on the 'Select' Tab. This is not of any importance of the 'Search' Tab. - :param last_book_id: The "book reference id" of the book which is chosen at the moment. (int) + :param last_book: The "book reference id" of the book which is chosen at the moment. (int) :return: None """ log.debug('initialise_advanced_bible {bible}, {ref}'.format(bible=self.bible, ref=last_book)) @@ -574,6 +574,7 @@ class BibleMediaItem(MediaManagerItem): Update the second bible. If changing from single to dual bible modes as if the user wants to clear the search results, if not revert to the previously selected bible + :param: selection not required by part of the signature :return: None """ new_selection = self.second_combo_box.currentData() @@ -1005,14 +1006,17 @@ class BibleMediaItem(MediaManagerItem): }[self.settings.display_style] return '{{su}}{bracket[0]}{verse_text}{bracket[1]}{{/su}} '.format(verse_text=verse_text, bracket=bracket) - def search(self, string, showError): + def search(self, string, show_error=True): """ Search for some Bible verses (by reference). + :param string: search string + :param show_error: do we show the error + :return: the results of the search """ if self.bible is None: return [] reference = self.plugin.manager.parse_ref(self.bible.name, string) - search_results = self.plugin.manager.get_verses(self.bible.name, reference, showError) + search_results = self.plugin.manager.get_verses(self.bible.name, reference, show_error) if search_results: verse_text = ' '.join([verse.text for verse in search_results]) return [[string, verse_text]] diff --git a/openlp/plugins/presentations/lib/pptviewcontroller.py b/openlp/plugins/presentations/lib/pptviewcontroller.py deleted file mode 100644 index 4a9c9f267..000000000 --- a/openlp/plugins/presentations/lib/pptviewcontroller.py +++ /dev/null @@ -1,310 +0,0 @@ -# -*- coding: utf-8 -*- -# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 - -############################################################################### -# OpenLP - Open Source Lyrics Projection # -# --------------------------------------------------------------------------- # -# Copyright (c) 2008-2018 OpenLP Developers # -# --------------------------------------------------------------------------- # -# This program is free software; you can redistribute it and/or modify it # -# under the terms of the GNU General Public License as published by the Free # -# Software Foundation; version 2 of the License. # -# # -# This program is distributed in the hope that it will be useful, but WITHOUT # -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # -# more details. # -# # -# You should have received a copy of the GNU General Public License along # -# with this program; if not, write to the Free Software Foundation, Inc., 59 # -# Temple Place, Suite 330, Boston, MA 02111-1307 USA # -############################################################################### -import logging -import re -import zipfile -from xml.etree import ElementTree - -from openlp.core.common import is_win -from openlp.core.common.applocation import AppLocation -from openlp.core.display.screens import ScreenList -from openlp.plugins.presentations.lib.presentationcontroller import PresentationController, PresentationDocument - -if is_win(): - from ctypes import cdll - from ctypes.wintypes import RECT - -log = logging.getLogger(__name__) - - -class PptviewController(PresentationController): - """ - Class to control interactions with PowerPoint Viewer Presentations. It creates the runtime Environment , Loads the - and Closes the Presentation. As well as triggering the correct activities based on the users input - """ - log.info('PPTViewController loaded') - - def __init__(self, plugin): - """ - Initialise the class - """ - log.debug('Initialising') - self.process = None - super(PptviewController, self).__init__(plugin, 'Powerpoint Viewer', PptviewDocument) - self.supports = ['ppt', 'pps', 'pptx', 'ppsx', 'pptm'] - - def check_available(self): - """ - PPT Viewer is able to run on this machine. - """ - log.debug('check_available') - if not is_win(): - return False - return self.check_installed() - - if is_win(): - def check_installed(self): - """ - Check the viewer is installed. - """ - log.debug('Check installed') - try: - self.start_process() - return self.process.CheckInstalled() - except OSError: - return False - - def start_process(self): - """ - Loads the PPTVIEWLIB library. - """ - if self.process: - return - log.debug('start PPTView') - dll_path = AppLocation.get_directory(AppLocation.AppDir) \ - / 'plugins' / 'presentations' / 'lib' / 'pptviewlib' / 'pptviewlib.dll' - self.process = cdll.LoadLibrary(str(dll_path)) - if log.isEnabledFor(logging.DEBUG): - self.process.SetDebug(1) - - def kill(self): - """ - Called at system exit to clean up any running presentations - """ - log.debug('Kill pptviewer') - while self.docs: - self.docs[0].close_presentation() - - -class PptviewDocument(PresentationDocument): - """ - Class which holds information and controls a single presentation. - """ - def __init__(self, controller, document_path): - """ - Constructor, store information about the file and initialise. - - :param openlp.core.common.path.Path document_path: File path to the document to load - :rtype: None - """ - log.debug('Init Presentation PowerPoint') - super().__init__(controller, document_path) - self.presentation = None - self.ppt_id = None - self.blanked = False - self.hidden = False - - def load_presentation(self): - """ - Called when a presentation is added to the SlideController. It builds the environment, starts communication with - the background PptView task started earlier. - """ - log.debug('LoadPresentation') - temp_path = self.get_temp_folder() - size = ScreenList().current['size'] - rect = RECT(size.x(), size.y(), size.right(), size.bottom()) - preview_path = temp_path / 'slide' - # Ensure that the paths are null terminated - file_path_utf16 = str(self.file_path).encode('utf-16-le') + b'\0' - preview_path_utf16 = str(preview_path).encode('utf-16-le') + b'\0' - if not temp_path.is_dir(): - temp_path.mkdir(parents=True) - self.ppt_id = self.controller.process.OpenPPT(file_path_utf16, None, rect, preview_path_utf16) - if self.ppt_id >= 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. - """ - log.debug('create_thumbnails') - if self.check_thumbnails(): - return - log.debug('create_thumbnails proceeding') - for idx in range(self.get_slide_count()): - path = self.get_temp_folder() / 'slide{index:d}.bmp'.format(index=idx + 1) - self.convert_thumbnail(path, idx + 1) - - def create_titles_and_notes(self): - """ - Extracts the titles and notes from the zipped file - and writes the list of titles (one per slide) - to 'titles.txt' - and the notes to 'slideNotes[x].txt' - in the thumbnails directory - """ - titles = None - notes = None - # let's make sure we have a valid zipped presentation - if self.file_path.exists() and zipfile.is_zipfile(str(self.file_path)): - namespaces = {"p": "http://schemas.openxmlformats.org/presentationml/2006/main", - "a": "http://schemas.openxmlformats.org/drawingml/2006/main"} - # open the file - with zipfile.ZipFile(str(self.file_path)) as zip_file: - # find the presentation.xml to get the slide count - with zip_file.open('ppt/presentation.xml') as pres: - tree = ElementTree.parse(pres) - nodes = tree.getroot().findall(".//p:sldIdLst/p:sldId", namespaces=namespaces) - # initialize the lists - titles = ['' for i in range(len(nodes))] - notes = ['' for i in range(len(nodes))] - # loop thru the file list to find slides and notes - for zip_info in zip_file.infolist(): - node_type = '' - index = -1 - list_to_add = None - # check if it is a slide - match = re.search(r'slides/slide(.+)\.xml', zip_info.filename) - if match: - index = int(match.group(1)) - 1 - node_type = 'ctrTitle' - list_to_add = titles - # or a note - match = re.search(r'notesSlides/notesSlide(.+)\.xml', zip_info.filename) - if match: - index = int(match.group(1)) - 1 - node_type = 'body' - list_to_add = notes - # if it is one of our files, index shouldn't be -1 - if index >= 0: - with zip_file.open(zip_info) as zipped_file: - tree = ElementTree.parse(zipped_file) - text = '' - nodes = tree.getroot().findall(".//p:ph[@type='" + node_type + "']../../..//p:txBody//a:t", - namespaces=namespaces) - # if we found any content - if nodes and len(nodes) > 0: - for node in nodes: - if len(text) > 0: - text += '\n' - text += node.text - # Let's remove the \n from the titles and - # just add one at the end - if node_type == 'ctrTitle': - text = text.replace('\n', ' ').replace('\x0b', ' ') + '\n' - list_to_add[index] = text - # now let's write the files - self.save_titles_and_notes(titles, notes) - - def close_presentation(self): - """ - Close presentation and clean up objects. Triggered by new object being added to SlideController or OpenLP being - shut down. - """ - log.debug('ClosePresentation') - if self.controller.process: - self.controller.process.ClosePPT(self.ppt_id) - self.ppt_id = -1 - self.controller.remove_doc(self) - - def is_loaded(self): - """ - Returns true if a presentation is loaded. - """ - if self.ppt_id < 0: - return False - if self.get_slide_count() < 0: - return False - return True - - def is_active(self): - """ - Returns true if a presentation is currently active. - """ - return self.is_loaded() and not self.hidden - - def blank_screen(self): - """ - Blanks the screen. - """ - self.controller.process.Blank(self.ppt_id) - self.blanked = True - - def unblank_screen(self): - """ - Unblanks (restores) the presentation. - """ - self.controller.process.Unblank(self.ppt_id) - self.blanked = False - - def is_blank(self): - """ - Returns true if screen is blank. - """ - log.debug('is blank OpenOffice') - return self.blanked - - def stop_presentation(self): - """ - Stops the current presentation and hides the output. - """ - self.hidden = True - self.controller.process.Stop(self.ppt_id) - - def start_presentation(self): - """ - Starts a presentation from the beginning. - """ - if self.hidden: - self.hidden = False - self.controller.process.Resume(self.ppt_id) - else: - self.controller.process.RestartShow(self.ppt_id) - - def get_slide_number(self): - """ - Returns the current slide number. - """ - return self.controller.process.GetCurrentSlide(self.ppt_id) - - def get_slide_count(self): - """ - Returns total number of slides. - """ - return self.controller.process.GetSlideCount(self.ppt_id) - - def goto_slide(self, slide_no): - """ - Moves to a specific slide in the presentation. - - :param slide_no: The slide the text is required for, starting at 1 - """ - self.controller.process.GotoSlide(self.ppt_id, slide_no) - - def next_step(self): - """ - Triggers the next effect of slide on the running presentation. - """ - if self.controller.process.NextStep(self.ppt_id) == 0: - return False - else: - return True - - def previous_step(self): - """ - Triggers the previous slide on the running presentation. - """ - self.controller.process.PrevStep(self.ppt_id) diff --git a/openlp/plugins/presentations/lib/pptviewlib/README.TXT b/openlp/plugins/presentations/lib/pptviewlib/README.TXT deleted file mode 100644 index 85bbf7954..000000000 --- a/openlp/plugins/presentations/lib/pptviewlib/README.TXT +++ /dev/null @@ -1,121 +0,0 @@ - -PPTVIEWLIB - Control PowerPoint Viewer 2003/2007 (for openlp.org) -Copyright (C) 2008-2018 Jonathan Corwin (j@corwin.co.uk) - -This library wrappers the free Microsoft PowerPoint Viewer (2003/2007) program, -allowing it to be more easily controlled from another program. - -The PowerPoint Viewer must already be installed on the destination machine, and is -freely available at microsoft.com. - -The full Microsoft Office PowerPoint and PowerPoint Viewer 97 have a COM interface allowing -automation. This ability was removed from the 2003+ viewer offerings. - -To developers: I am not a C/C++ or Win32 API programmer as you can probably tell. -The code and API of this DLL could certainly do with some tidying up, and the -error trapping, where it exists, is very basic. I'll happily accept patches! - -This library is covered by the GPL (http://www.gnu.org/licenses/) -It is NOT covered by the LGPL, so can only be used in GPL compatable programs. -(http://www.gnu.org/licenses/why-not-lgpl.html) - -This README.TXT must be distributed with the pptviewlib.dll - -This library has a limit of 50 PowerPoints which can be opened simultaneously. - -This project can be built with the free Microsoft Visual C++ 2008 Express Edition. - -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 - and creates preview images of each slide. Note PowerPoint Viewer only allows the - slideshow to be resized whilst it is being loaded. It can be moved at any time however. - - The only way to count the number of slides is to step through the entire show. Therefore - there will be a delay whilst opening large presentations for the first time. - For pre XP/2003 systems, the slideshow will flicker as the screen snapshots are taken. - - filename: The PowerPoint file to be opened. Full path - hParentWnd: The window which will become the parent of the slideshow window. - Can be NULL. - rect: The location/dimensions of the slideshow window. - If all properties of this structure are zero, the dimensions of the hParentWnd - are used. - previewpath If specified, the prefix to use for snapshot images of each slide, in the - form: previewpath + n + ".bmp", where n is the slide number. - A file called previewpath + "info.txt" will also be created containing information - about the PPT file, to speed up future openings of the unmodified file. - Note it is up the calling program to directly access these images if they - are required. - - RETURNS: An unique identifier to pass to other methods in this library. - If < 0, then the PPT failed to open. - If >=0, ClosePPT must be called when the PPT is no longer being used - or when the calling program is closed to release resources/hooks. - -void ClosePPT(int id); - Closes the presentation, releasing any resources and hooks. - - id: The value returned from OpenPPT. - -int GetCurrentSlide(int id); - Returns the current slide number (from 1) - - id: The value returned from OpenPPT. - -int GetSlideCount(int id); - Returns the total number of slides. - - id: The value returned from OpenPPT. - -void NextStep(int id); - Advances one step (animation) through the slideshow. - - id: The value returned from OpenPPT. - -void PrevStep(int id); - Goes backwards one step (animation) through the slideshow. - - id: The value returned from OpenPPT. - -void GotoSlide(int id, int slideno); - Goes directly to a specific slide in the slideshow - - id: The value returned from OpenPPT. - slideno: The number of the slide (from 1) to go directly to. - - If the slide has already been displayed, then the completed slide with animations performed - will be shown. This is how the PowerPoint Viewer works so have no control over this. - -void RestartShow(int id); - Restarts the show from the beginning. To reset animations, behind the scenes it - has to travel to the end and step backwards though the entire show. Therefore - for large presentations there might be a delay. - - id: The value returned from OpenPPT. - -void Blank(int id); - Blanks the screen, colour black. - - id: The value returned from OpenPPT. - -void Unblank(int id) - Unblanks the screen, restoring it to it's pre-blank state. - - id: The value returned from OpenPPT. - -void Stop(int id) - Moves the slideshow off the screen. (There is no concept of stop show in the PowerPoint Viewer) - - id: The value returned from OpenPPT. - -void Resume(int id) - Moves the slideshow display back onto the screen following a Stop() - - id: The value returned from OpenPPT. - diff --git a/openlp/plugins/presentations/lib/pptviewlib/ppttest.py b/openlp/plugins/presentations/lib/pptviewlib/ppttest.py deleted file mode 100644 index 008e66164..000000000 --- a/openlp/plugins/presentations/lib/pptviewlib/ppttest.py +++ /dev/null @@ -1,210 +0,0 @@ -# -*- coding: utf-8 -*- -# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 - -############################################################################### -# OpenLP - Open Source Lyrics Projection # -# --------------------------------------------------------------------------- # -# Copyright (c) 2008-2018 OpenLP Developers # -# --------------------------------------------------------------------------- # -# This program is free software; you can redistribute it and/or modify it # -# under the terms of the GNU General Public License as published by the Free # -# Software Foundation; version 2 of the License. # -# # -# This program is distributed in the hope that it will be useful, but WITHOUT # -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # -# more details. # -# # -# You should have received a copy of the GNU General Public License along # -# with this program; if not, write to the Free Software Foundation, Inc., 59 # -# Temple Place, Suite 330, Boston, MA 02111-1307 USA # -############################################################################### - -import sys -from ctypes import * -from ctypes.wintypes import RECT - -from PyQt5 import QtWidgets - - -class PPTViewer(QtWidgets.QWidget): - """ - Standalone Test Harness for the pptviewlib library - """ - def __init__(self, parent=None): - super(PPTViewer, self).__init__(parent) - self.pptid = -1 - self.setWindowTitle('PowerPoint Viewer Test') - - ppt_label = QtWidgets.QLabel('Open PowerPoint file') - slide_label = QtWidgets.QLabel('Go to slide #') - self.pptEdit = QtWidgets.QLineEdit() - self.slideEdit = QtWidgets.QLineEdit() - x_label = QtWidgets.QLabel('X pos') - y_label = QtWidgets.QLabel('Y pos') - width_label = QtWidgets.QLabel('Width') - height_label = QtWidgets.QLabel('Height') - self.xEdit = QtWidgets.QLineEdit('100') - self.yEdit = QtWidgets.QLineEdit('100') - self.widthEdit = QtWidgets.QLineEdit('900') - self.heightEdit = QtWidgets.QLineEdit('700') - self.total = QtWidgets.QLabel() - ppt_btn = QtWidgets.QPushButton('Open') - ppt_dlg_btn = QtWidgets.QPushButton('...') - folder_label = QtWidgets.QLabel('Slide .bmp path') - self.folderEdit = QtWidgets.QLineEdit('slide') - slide_btn = QtWidgets.QPushButton('Go') - prev = QtWidgets.QPushButton('Prev') - next = QtWidgets.QPushButton('Next') - blank = QtWidgets.QPushButton('Blank') - unblank = QtWidgets.QPushButton('Unblank') - restart = QtWidgets.QPushButton('Restart') - close = QtWidgets.QPushButton('Close') - resume = QtWidgets.QPushButton('Resume') - stop = QtWidgets.QPushButton('Stop') - grid = QtWidgets.QGridLayout() - row = 0 - grid.addWidget(folder_label, 0, 0) - grid.addWidget(self.folderEdit, 0, 1) - row += 1 - grid.addWidget(x_label, row, 0) - grid.addWidget(self.xEdit, row, 1) - grid.addWidget(y_label, row, 2) - grid.addWidget(self.yEdit, row, 3) - row += 1 - grid.addWidget(width_label, row, 0) - grid.addWidget(self.widthEdit, row, 1) - grid.addWidget(height_label, row, 2) - grid.addWidget(self.heightEdit, row, 3) - row += 1 - grid.addWidget(ppt_label, row, 0) - grid.addWidget(self.pptEdit, row, 1) - grid.addWidget(ppt_dlg_btn, row, 2) - grid.addWidget(ppt_btn, row, 3) - row += 1 - grid.addWidget(slide_label, row, 0) - grid.addWidget(self.slideEdit, row, 1) - grid.addWidget(slide_btn, row, 2) - row += 1 - grid.addWidget(prev, row, 0) - grid.addWidget(next, row, 1) - row += 1 - grid.addWidget(blank, row, 0) - grid.addWidget(unblank, row, 1) - row += 1 - grid.addWidget(restart, row, 0) - grid.addWidget(close, row, 1) - row += 1 - grid.addWidget(stop, row, 0) - grid.addWidget(resume, row, 1) - ppt_btn.clicked.connect(self.openClick) - ppt_dlg_btn.clicked.connect(self.openDialog) - slide_btn.clicked.connect(self.gotoClick) - prev.clicked.connect(self.prevClick) - next.clicked.connect(self.nextClick) - blank.clicked.connect(self.blankClick) - unblank.clicked.connect(self.unblankClick) - restart.clicked.connect(self.restartClick) - close.clicked.connect(self.closeClick) - stop.clicked.connect(self.stopClick) - resume.clicked.connect(self.resumeClick) - self.setLayout(grid) - self.resize(300, 150) - - def prevClick(self): - if self.pptid < 0: - return - self.pptdll.PrevStep(self.pptid) - self.updateCurrSlide() - app.processEvents() - - def nextClick(self): - if self.pptid < 0: - return - self.pptdll.NextStep(self.pptid) - self.updateCurrSlide() - app.processEvents() - - def blankClick(self): - if self.pptid < 0: - return - self.pptdll.Blank(self.pptid) - app.processEvents() - - def unblankClick(self): - if self.pptid < 0: - return - self.pptdll.Unblank(self.pptid) - app.processEvents() - - def restartClick(self): - if self.pptid < 0: - return - self.pptdll.RestartShow(self.pptid) - self.updateCurrSlide() - app.processEvents() - - def stopClick(self): - if self.pptid < 0: - return - self.pptdll.Stop(self.pptid) - app.processEvents() - - def resumeClick(self): - if self.pptid < 0: - return - self.pptdll.Resume(self.pptid) - app.processEvents() - - def closeClick(self): - if self.pptid < 0: - return - self.pptdll.ClosePPT(self.pptid) - self.pptid = -1 - app.processEvents() - - def openClick(self): - oldid = self.pptid - rect = RECT(int(self.xEdit.text()), int(self.yEdit.text()), - int(self.widthEdit.text()), int(self.heightEdit.text())) - filename = str(self.pptEdit.text().replace('/', '\\')) - folder = str(self.folderEdit.text().replace('/', '\\')) - print(filename, folder) - self.pptid = self.pptdll.OpenPPT(filename, None, rect, folder) - print('id: ' + str(self.pptid)) - if oldid >= 0: - self.pptdll.ClosePPT(oldid) - slides = self.pptdll.GetSlideCount(self.pptid) - print('slidecount: ' + str(slides)) - self.total.setNum(self.pptdll.GetSlideCount(self.pptid)) - self.updateCurrSlide() - - def updateCurrSlide(self): - if self.pptid < 0: - return - slide = str(self.pptdll.GetCurrentSlide(self.pptid)) - print('currslide: ' + slide) - self.slideEdit.setText(slide) - app.processEvents() - - def gotoClick(self): - if self.pptid < 0: - return - print(self.slideEdit.text()) - self.pptdll.GotoSlide(self.pptid, int(self.slideEdit.text())) - self.updateCurrSlide() - app.processEvents() - - def openDialog(self): - self.pptEdit.setText(QtWidgets.QFileDialog.getOpenFileName(self, 'Open file')[0]) - - -if __name__ == '__main__': - pptdll = cdll.LoadLibrary(r'pptviewlib.dll') - pptdll.SetDebug(1) - print('Begin...') - app = QtWidgets.QApplication(sys.argv) - window = PPTViewer() - window.pptdll = pptdll - window.show() - sys.exit(app.exec()) diff --git a/openlp/plugins/presentations/lib/pptviewlib/pptviewlib.cpp b/openlp/plugins/presentations/lib/pptviewlib/pptviewlib.cpp deleted file mode 100644 index 152451749..000000000 --- a/openlp/plugins/presentations/lib/pptviewlib/pptviewlib.cpp +++ /dev/null @@ -1,922 +0,0 @@ -/****************************************************************************** -* OpenLP - Open Source Lyrics Projection * -* --------------------------------------------------------------------------- * -* Copyright (c) 2008-2018 OpenLP Developers * -* --------------------------------------------------------------------------- * -* This program is free software; you can redistribute it and/or modify it * -* under the terms of the GNU General Public License as published by the Free * -* Software Foundation; version 2 of the License. * -* * -* This program is distributed in the hope that it will be useful, but WITHOUT * -* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * -* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * -* more details. * -* * -* You should have received a copy of the GNU General Public License along * -* with this program; if not, write to the Free Software Foundation, Inc., 59 * -* Temple Place, Suite 330, Boston, MA 02111-1307 USA * -******************************************************************************/ - -#define WIN32_LEAN_AND_MEAN -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "pptviewlib.h" - -// Because of the callbacks used by SetWindowsHookEx, the memory used needs to -// be sharable across processes (the callbacks are done from a different -// process) Therefore use data_seg with RWS memory. -// -// See http://msdn.microsoft.com/en-us/library/aa366551(VS.85).aspx for -// alternative method of holding memory, removing fixed limits which would allow -// dynamic number of items, rather than a fixed number. Use a Local\ mapping, -// since global has UAC issues in Vista. - -#pragma data_seg(".PPTVIEWLIB") -PPTVIEW pptView[MAX_PPTS] = {NULL}; -HHOOK globalHook = NULL; -BOOL debug = FALSE; -#pragma data_seg() -#pragma comment(linker, "/SECTION:.PPTVIEWLIB,RWS") - -HINSTANCE hInstance = NULL; - -BOOL APIENTRY DllMain(HMODULE hModule, DWORD ulReasonForCall, - LPVOID lpReserved) -{ - hInstance = (HINSTANCE)hModule; - switch(ulReasonForCall) - { - case DLL_PROCESS_ATTACH: - DEBUG(L"PROCESS_ATTACH\n"); - break; - case DLL_THREAD_ATTACH: - //DEBUG(L"THREAD_ATTACH\n"); - break; - case DLL_THREAD_DETACH: - //DEBUG(L"THREAD_DETACH\n"); - break; - case DLL_PROCESS_DETACH: - // Clean up... hopefully there is only the one process attached? - // We'll find out soon enough during tests! - DEBUG(L"PROCESS_DETACH\n"); - for (int i = 0; i < MAX_PPTS; i++) - ClosePPT(i); - break; - } - return TRUE; -} - -DllExport void SetDebug(BOOL onOff) -{ - printf("SetDebug\n"); - debug = onOff; - DEBUG(L"enabled\n"); -} - -DllExport BOOL CheckInstalled() -{ - wchar_t cmdLine[MAX_PATH * 2]; - - DEBUG(L"CheckInstalled\n"); - BOOL found = GetPPTViewerPath(cmdLine, sizeof(cmdLine)); - if(found) - { - DEBUG(L"Exe: %s\n", cmdLine); - } - return found; -} - -// 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. -// ".bmp" will be appended to complete the path. E.g. "c:\temp\slide" would -// create "c:\temp\slide1.bmp" slide2.bmp, slide3.bmp etc. -// It will also create a *info.txt containing information about the ppt -DllExport int OpenPPT(wchar_t *filename, HWND hParentWnd, RECT rect, - wchar_t *previewPath) -{ - STARTUPINFO si; - PROCESS_INFORMATION pi; - wchar_t cmdLine[MAX_PATH * 2]; - int id; - - DEBUG(L"OpenPPT start: %s; %s\n", filename, previewPath); - DEBUG(L"OpenPPT start: %u; %i, %i, %i, %i\n", hParentWnd, rect.top, - rect.left, rect.bottom, rect.right); - if (GetPPTViewerPath(cmdLine, sizeof(cmdLine)) == FALSE) - { - DEBUG(L"OpenPPT: GetPPTViewerPath failed\n"); - return -1; - } - id = -1; - for (int i = 0; i < MAX_PPTS; i++) - { - if (pptView[i].state == PPT_CLOSED) - { - id = i; - break; - } - } - if (id < 0) - { - DEBUG(L"OpenPPT: Too many PPTs\n"); - return -1; - } - memset(&pptView[id], 0, sizeof(PPTVIEW)); - wcscpy_s(pptView[id].filename, MAX_PATH, filename); - wcscpy_s(pptView[id].previewPath, MAX_PATH, previewPath); - pptView[id].state = PPT_CLOSED; - pptView[id].slideCount = 0; - pptView[id].currentSlide = 0; - pptView[id].firstSlideSteps = 0; - pptView[id].lastSlideSteps = 0; - pptView[id].guess = 0; - pptView[id].hParentWnd = hParentWnd; - pptView[id].hWnd = NULL; - pptView[id].hWnd2 = NULL; - for (int i = 0; i < MAX_SLIDES; i++) - { - pptView[id].slideNos[i] = 0; - } - if (hParentWnd != NULL && rect.top == 0 && rect.bottom == 0 - && rect.left == 0 && rect.right == 0) - { - LPRECT windowRect = NULL; - GetWindowRect(hParentWnd, windowRect); - pptView[id].rect.top = 0; - pptView[id].rect.left = 0; - pptView[id].rect.bottom = windowRect->bottom - windowRect->top; - pptView[id].rect.right = windowRect->right - windowRect->left; - } - else - { - pptView[id].rect.top = rect.top; - pptView[id].rect.left = rect.left; - pptView[id].rect.bottom = rect.bottom; - pptView[id].rect.right = rect.right; - } - wcscat_s(cmdLine, MAX_PATH * 2, L" /F /S \""); - wcscat_s(cmdLine, MAX_PATH * 2, filename); - wcscat_s(cmdLine, MAX_PATH * 2, L"\""); - memset(&si, 0, sizeof(si)); - memset(&pi, 0, sizeof(pi)); - BOOL gotInfo = GetPPTInfo(id); - /* - * I'd really like to just hook on the new threadid. However this always - * gives error 87. Perhaps I'm hooking to soon? No idea... however can't - * wait since I need to ensure I pick up the WM_CREATE as this is the only - * time the window can be resized in such away the content scales correctly - * - * hook = SetWindowsHookEx(WH_CBT,CbtProc,hInstance,pi.dwThreadId); - */ - if (globalHook != NULL) - { - UnhookWindowsHookEx(globalHook); - } - globalHook = SetWindowsHookEx(WH_CBT, CbtProc, hInstance, NULL); - if (globalHook == 0) - { - DEBUG(L"OpenPPT: SetWindowsHookEx failed\n"); - ClosePPT(id); - return -1; - } - pptView[id].state = PPT_STARTED; - Sleep(10); - if (!CreateProcess(NULL, cmdLine, NULL, NULL, FALSE, 0, 0, NULL, &si, &pi)) - { - DEBUG(L"OpenPPT: CreateProcess failed: %s\n", cmdLine); - ClosePPT(id); - return -1; - } - pptView[id].dwProcessId = pi.dwProcessId; - pptView[id].dwThreadId = pi.dwThreadId; - pptView[id].hThread = pi.hThread; - pptView[id].hProcess = pi.hProcess; - while (pptView[id].state == PPT_STARTED) - Sleep(10); - if (gotInfo) - { - DEBUG(L"OpenPPT: Info loaded, no refresh\n"); - pptView[id].state = PPT_LOADED; - Resume(id); - } - else - { - DEBUG(L"OpenPPT: Get info\n"); - pptView[id].steps = 0; - int steps = 0; - while (pptView[id].state == PPT_OPENED) - { - if (steps <= pptView[id].steps) - { - Sleep(100); - DEBUG(L"OpenPPT: Step %d/%d\n", steps, pptView[id].steps); - steps++; - NextStep(id); - } - Sleep(10); - } - DEBUG(L"OpenPPT: Slides %d, Steps %d, first slide steps %d\n", - pptView[id].slideCount, pptView[id].steps, - pptView[id].firstSlideSteps); - for(int i = 1; i <= pptView[id].slideCount; i++) - { - DEBUG(L"OpenPPT: Slide %d = %d\n", i, pptView[id].slideNos[i]); - } - SavePPTInfo(id); - if (pptView[id].state == PPT_CLOSING - || pptView[id].slideCount <= 0) - { - ClosePPT(id); - id=-1; - } - else - { - RestartShow(id); - } - } - if (id >= 0) - { - if (pptView[id].msgHook != NULL) - { - UnhookWindowsHookEx(pptView[id].msgHook); - } - pptView[id].msgHook = NULL; - } - DEBUG(L"OpenPPT: Exit: id=%i\n", id); - return id; -} -// Load information about the ppt from an info.txt file. -// Format: -// version -// filedate -// filesize -// slidecount -// first slide steps -BOOL GetPPTInfo(int id) -{ - struct _stat fileStats; - wchar_t info[MAX_PATH]; - FILE* pFile; - wchar_t buf[100]; - - DEBUG(L"GetPPTInfo: start\n"); - if (_wstat(pptView[id].filename, &fileStats) != 0) - { - return FALSE; - } - swprintf_s(info, MAX_PATH, L"%sinfo.txt", pptView[id].previewPath); - int err = _wfopen_s(&pFile, info, L"r"); - if (err != 0) - { - DEBUG(L"GetPPTInfo: file open failed - %d\n", err); - return FALSE; - } - fgetws(buf, 100, pFile); // version == 1 - fgetws(buf, 100, pFile); - if (fileStats.st_mtime != _wtoi(buf)) - { - DEBUG(L"GetPPTInfo: date changed\n"); - fclose (pFile); - return FALSE; - } - fgetws(buf, 100, pFile); - if (fileStats.st_size != _wtoi(buf)) - { - DEBUG(L"GetPPTInfo: size changed\n"); - fclose (pFile); - return FALSE; - } - fgetws(buf, 100, pFile); // slidecount - int slideCount = _wtoi(buf); - fgetws(buf, 100, pFile); // first slide steps - int firstSlideSteps = _wtoi(buf); - // check all the preview images still exist - for (int i = 1; i <= slideCount; i++) - { - swprintf_s(info, MAX_PATH, L"%s%i.bmp", pptView[id].previewPath, i); - if (GetFileAttributes(info) == INVALID_FILE_ATTRIBUTES) - { - DEBUG(L"GetPPTInfo: bmp not found\n"); - return FALSE; - } - } - fclose(pFile); - pptView[id].slideCount = slideCount; - pptView[id].firstSlideSteps = firstSlideSteps; - DEBUG(L"GetPPTInfo: exit ok\n"); - return TRUE; -} - -BOOL SavePPTInfo(int id) -{ - struct _stat fileStats; - wchar_t info[MAX_PATH]; - FILE* pFile; - - DEBUG(L"SavePPTInfo: start\n"); - if (_wstat(pptView[id].filename, &fileStats) != 0) - { - DEBUG(L"SavePPTInfo: stat of %s failed\n", pptView[id].filename); - return FALSE; - } - swprintf_s(info, MAX_PATH, L"%sinfo.txt", pptView[id].previewPath); - int err = _wfopen_s(&pFile, info, L"w"); - if (err != 0) - { - DEBUG(L"SavePPTInfo: fopen of %s failed%i\n", info, err); - return FALSE; - } - fprintf(pFile, "1\n"); - fprintf(pFile, "%u\n", fileStats.st_mtime); - fprintf(pFile, "%u\n", fileStats.st_size); - fprintf(pFile, "%u\n", pptView[id].slideCount); - fprintf(pFile, "%u\n", pptView[id].firstSlideSteps); - fclose(pFile); - DEBUG(L"SavePPTInfo: exit ok\n"); - return TRUE; -} - -// Get the path of the PowerPoint viewer from the registry -BOOL GetPPTViewerPath(wchar_t *pptViewerPath, int stringSize) -{ - wchar_t cwd[MAX_PATH]; - - DEBUG(L"GetPPTViewerPath: start\n"); - if(GetPPTViewerPathFromReg(pptViewerPath, stringSize)) - { - if(_waccess(pptViewerPath, 0) != -1) - { - DEBUG(L"GetPPTViewerPath: exit registry\n"); - return TRUE; - } - } - // This is where it gets ugly. PPT2007 it seems no longer stores its - // location in the registry. So we have to use the defaults which will - // upset those who like to put things somewhere else - - // Viewer 2007 in 64bit Windows: - if(_waccess(L"C:\\Program Files (x86)\\Microsoft Office\\Office12\\PPTVIEW.EXE", - 0) != -1) - { - wcscpy_s( - L"C:\\Program Files (x86)\\Microsoft Office\\Office12\\PPTVIEW.EXE", - stringSize, pptViewerPath); - DEBUG(L"GetPPTViewerPath: exit 64bit 2007\n"); - return TRUE; - } - // Viewer 2007 in 32bit Windows: - if(_waccess(L"C:\\Program Files\\Microsoft Office\\Office12\\PPTVIEW.EXE", 0) - != -1) - { - wcscpy_s(L"C:\\Program Files\\Microsoft Office\\Office12\\PPTVIEW.EXE", - stringSize, pptViewerPath); - DEBUG(L"GetPPTViewerPath: exit 32bit 2007\n"); - return TRUE; - } - // Give them the opportunity to place it in the same folder as the app - _wgetcwd(cwd, MAX_PATH); - wcscat_s(cwd, MAX_PATH, L"\\PPTVIEW.EXE"); - if(_waccess(cwd, 0) != -1) - { - wcscpy_s(pptViewerPath, stringSize, cwd); - DEBUG(L"GetPPTViewerPath: exit local\n"); - return TRUE; - } - DEBUG(L"GetPPTViewerPath: exit fail\n"); - return FALSE; -} -BOOL GetPPTViewerPathFromReg(wchar_t *pptViewerPath, int stringSize) -{ - HKEY hKey; - DWORD dwType, dwSize; - LRESULT lResult; - - // The following registry settings are for, respectively, (I think) - // PPT Viewer 2007 (older versions. Latest not in registry) & PPT Viewer 2010 - // PPT Viewer 2003 (recent versions) - // PPT Viewer 2003 (older versions) - // PPT Viewer 97 - if ((RegOpenKeyExW(HKEY_CLASSES_ROOT, - L"PowerPointViewer.Show.12\\shell\\Show\\command", 0, KEY_READ, &hKey) - != ERROR_SUCCESS) - && (RegOpenKeyExW(HKEY_CLASSES_ROOT, - L"PowerPointViewer.Show.11\\shell\\Show\\command", 0, KEY_READ, &hKey) - != ERROR_SUCCESS) - && (RegOpenKeyExW(HKEY_CLASSES_ROOT, - L"Applications\\PPTVIEW.EXE\\shell\\open\\command", 0, KEY_READ, &hKey) - != ERROR_SUCCESS) - && (RegOpenKeyExW(HKEY_CLASSES_ROOT, - L"Applications\\PPTVIEW.EXE\\shell\\Show\\command", 0, KEY_READ, &hKey) - != ERROR_SUCCESS)) - { - return FALSE; - } - dwType = REG_SZ; - dwSize = (DWORD)stringSize; - lResult = RegQueryValueEx(hKey, NULL, NULL, &dwType, (LPBYTE)pptViewerPath, - &dwSize); - RegCloseKey(hKey); - if (lResult != ERROR_SUCCESS) - { - return FALSE; - } - // remove "%1" from end of key value - pptViewerPath[wcslen(pptViewerPath) - 4] = '\0'; - return TRUE; -} - -// Unhook the Windows hook -void Unhook(int id) -{ - DEBUG(L"Unhook: start %d\n", id); - if (pptView[id].hook != NULL) - { - UnhookWindowsHookEx(pptView[id].hook); - } - if (pptView[id].msgHook != NULL) - { - UnhookWindowsHookEx(pptView[id].msgHook); - } - pptView[id].hook = NULL; - pptView[id].msgHook = NULL; - DEBUG(L"Unhook: exit ok\n"); -} - -// Close the PowerPoint viewer, release resources -DllExport void ClosePPT(int id) -{ - DEBUG(L"ClosePPT: start%d\n", id); - pptView[id].state = PPT_CLOSED; - Unhook(id); - if (pptView[id].hWnd == 0) - { - TerminateThread(pptView[id].hThread, 0); - } - else - { - PostMessage(pptView[id].hWnd, WM_CLOSE, 0, 0); - } - CloseHandle(pptView[id].hThread); - CloseHandle(pptView[id].hProcess); - memset(&pptView[id], 0, sizeof(PPTVIEW)); - DEBUG(L"ClosePPT: exit ok\n"); - return; -} -// Moves the show back onto the display -DllExport void Resume(int id) -{ - DEBUG(L"Resume: %d\n", id); - MoveWindow(pptView[id].hWnd, pptView[id].rect.left, - pptView[id].rect.top, - pptView[id].rect.right - pptView[id].rect.left, - pptView[id].rect.bottom - pptView[id].rect.top, TRUE); - Unblank(id); -} -// Moves the show off the screen so it can't be seen -DllExport void Stop(int id) -{ - DEBUG(L"Stop:%d\n", id); - MoveWindow(pptView[id].hWnd, -32000, -32000, - pptView[id].rect.right - pptView[id].rect.left, - pptView[id].rect.bottom - pptView[id].rect.top, TRUE); -} - -// Return the total number of slides -DllExport int GetSlideCount(int id) -{ - DEBUG(L"GetSlideCount:%d\n", id); - if (pptView[id].state == 0) - { - return -1; - } - else - { - return pptView[id].slideCount; - } -} - -// Return the number of the slide currently viewing -DllExport int GetCurrentSlide(int id) -{ - DEBUG(L"GetCurrentSlide:%d\n", id); - if (pptView[id].state == 0) - { - return -1; - } - else - { - return pptView[id].currentSlide; - } -} - -// Take a step forwards through the show -DllExport int NextStep(int id) -{ - DEBUG(L"NextStep:%d (%d)\n", id, pptView[id].currentSlide); - // Return 1 to signal that the slideshow has gone past the end - if (pptView[id].currentSlide > pptView[id].slideCount) return 1; - if (pptView[id].currentSlide < pptView[id].slideCount) - { - pptView[id].guess = pptView[id].currentSlide + 1; - } - PostMessage(pptView[id].hWnd2, WM_MOUSEWHEEL, MAKEWPARAM(0, -WHEEL_DELTA), - 0); - return 0; -} - -// Take a step backwards through the show -DllExport void PrevStep(int id) -{ - DEBUG(L"PrevStep:%d (%d)\n", id, pptView[id].currentSlide); - if (pptView[id].currentSlide > 1) - { - pptView[id].guess = pptView[id].currentSlide - 1; - } - PostMessage(pptView[id].hWnd2, WM_MOUSEWHEEL, MAKEWPARAM(0, WHEEL_DELTA), - 0); -} - -// Blank the show (black screen) -DllExport void Blank(int id) -{ - // B just toggles blank on/off. However pressing any key unblanks. - // So send random unmapped letter first (say 'A'), then we can - // better guarantee B will blank instead of trying to guess - // whether it was already blank or not. - DEBUG(L"Blank:%d\n", id); - HWND h1 = GetForegroundWindow(); - HWND h2 = GetFocus(); - SetForegroundWindow(pptView[id].hWnd); - SetFocus(pptView[id].hWnd); - // slight pause, otherwise event triggering this call may grab focus back! - Sleep(50); - keybd_event((int)'A', 0, 0, 0); - keybd_event((int)'A', 0, KEYEVENTF_KEYUP, 0); - keybd_event((int)'B', 0, 0, 0); - keybd_event((int)'B', 0, KEYEVENTF_KEYUP, 0); - SetForegroundWindow(h1); - SetFocus(h2); -} -// Unblank the show -DllExport void Unblank(int id) -{ - DEBUG(L"Unblank:%d\n", id); - // Pressing any key resumes. - // For some reason SendMessage works for unblanking, but not blanking. - SendMessage(pptView[id].hWnd2, WM_CHAR, 'A', 0); -} - -// Go directly to a slide -DllExport void GotoSlide(int id, int slideNo) -{ - DEBUG(L"GotoSlide %i %i:\n", id, slideNo); - // Did try WM_KEYDOWN/WM_CHAR/WM_KEYUP with SendMessage but didn't work - // perhaps I was sending to the wrong window? No idea. - // Anyway fall back to keybd_event, which is OK as long we makesure - // the slideshow has focus first - char ch[10]; - - if (slideNo < 0) return; - pptView[id].guess = slideNo; - _itoa_s(slideNo, ch, 10, 10); - HWND h1 = GetForegroundWindow(); - HWND h2 = GetFocus(); - SetForegroundWindow(pptView[id].hWnd); - SetFocus(pptView[id].hWnd); - // slight pause, otherwise event triggering this call may grab focus back! - Sleep(50); - for (int i=0; i<10; i++) - { - if (ch[i] == '\0') break; - keybd_event((BYTE)ch[i], 0, 0, 0); - keybd_event((BYTE)ch[i], 0, KEYEVENTF_KEYUP, 0); - } - keybd_event(VK_RETURN, 0, 0, 0); - keybd_event(VK_RETURN, 0, KEYEVENTF_KEYUP, 0); - SetForegroundWindow(h1); - SetFocus(h2); -} - -// Restart the show from the beginning -DllExport void RestartShow(int id) -{ - // If we just go direct to slide one, then it remembers that all other - // slides have been animated, so ends up just showing the completed slides - // of those slides that have been animated next time we advance. - // Only way I've found to get around this is to step backwards all the way - // through. Lets move the window out of the way first so the audience - // doesn't see this. - DEBUG(L"RestartShow:%d\n", id); - Stop(id); - GotoSlide(id, pptView[id].slideCount); - for (int i=0; i <= pptView[id].steps - pptView[id].lastSlideSteps; i++) - { - PrevStep(id); - Sleep(10); - } - int i = 0; - while ((pptView[id].currentSlide > 1) && (i++ < 30000)) - { - Sleep(10); - } - Resume(id); -} - -// This hook is started with the PPTVIEW.EXE process and waits for the -// WM_CREATEWND message. At this point (and only this point) can the -// window be resized to the correct size. -// Release the hook as soon as we're complete to free up resources -LRESULT CALLBACK CbtProc(int nCode, WPARAM wParam, LPARAM lParam) -{ - HHOOK hook = globalHook; - if (nCode == HCBT_CREATEWND) - { - wchar_t csClassName[32]; - HWND hCurrWnd = (HWND)wParam; - DWORD retProcId = NULL; - GetClassName(hCurrWnd, csClassName, sizeof(csClassName)); - if ((wcscmp(csClassName, L"paneClassDC") == 0) - ||(wcscmp(csClassName, L"screenClass") == 0)) - { - int id = -1; - DWORD windowThread = GetWindowThreadProcessId(hCurrWnd, NULL); - for (int i=0; i < MAX_PPTS; i++) - { - if (pptView[i].dwThreadId == windowThread) - { - id = i; - break; - } - } - if (id >= 0) - { - if (wcscmp(csClassName, L"paneClassDC") == 0) - { - pptView[id].hWnd2 = hCurrWnd; - } - else - { - pptView[id].hWnd = hCurrWnd; - CBT_CREATEWND* cw = (CBT_CREATEWND*)lParam; - if (pptView[id].hParentWnd != NULL) - { - cw->lpcs->hwndParent = pptView[id].hParentWnd; - } - cw->lpcs->cy = pptView[id].rect.bottom - - pptView[id].rect.top; - cw->lpcs->cx = pptView[id].rect.right - - pptView[id].rect.left; - cw->lpcs->y = -32000; - cw->lpcs->x = -32000; - } - if ((pptView[id].hWnd != NULL) && (pptView[id].hWnd2 != NULL)) - { - UnhookWindowsHookEx(globalHook); - globalHook = NULL; - pptView[id].hook = SetWindowsHookEx(WH_CALLWNDPROC, - CwpProc, hInstance, pptView[id].dwThreadId); - pptView[id].msgHook = SetWindowsHookEx(WH_GETMESSAGE, - GetMsgProc, hInstance, pptView[id].dwThreadId); - Sleep(10); - pptView[id].state = PPT_OPENED; - } - } - } - } - return CallNextHookEx(hook, nCode, wParam, lParam); -} - -// This hook exists whilst the slideshow is loading but only listens on the -// slideshows thread. It listens out for mousewheel events -LRESULT CALLBACK GetMsgProc(int nCode, WPARAM wParam, LPARAM lParam) -{ - HHOOK hook = NULL; - MSG *pMSG = (MSG *)lParam; - DWORD windowThread = GetWindowThreadProcessId(pMSG->hwnd, NULL); - int id = -1; - for (int i = 0; i < MAX_PPTS; i++) - { - if (pptView[i].dwThreadId == windowThread) - { - id = i; - hook = pptView[id].msgHook; - break; - } - } - if (id >= 0 && nCode == HC_ACTION && wParam == PM_REMOVE - && pMSG->message == WM_MOUSEWHEEL) - { - if (pptView[id].state != PPT_LOADED) - { - if (pptView[id].currentSlide == 1) - { - pptView[id].firstSlideSteps++; - } - pptView[id].steps++; - pptView[id].lastSlideSteps++; - } - } - return CallNextHookEx(hook, nCode, wParam, lParam); -} -// This hook exists whilst the slideshow is running but only listens on the -// slideshows thread. It listens out for slide changes, message WM_USER+22. -LRESULT CALLBACK CwpProc(int nCode, WPARAM wParam, LPARAM lParam){ - CWPSTRUCT *cwp; - cwp = (CWPSTRUCT *)lParam; - HHOOK hook = NULL; - wchar_t filename[MAX_PATH]; - - DWORD windowThread = GetWindowThreadProcessId(cwp->hwnd, NULL); - int id = -1; - for (int i = 0; i < MAX_PPTS; i++) - { - if (pptView[i].dwThreadId == windowThread) - { - id = i; - hook = pptView[id].hook; - break; - } - } - if ((id >= 0) && (nCode == HC_ACTION)) - { - if (cwp->message == WM_USER + 22) - { - if (pptView[id].state != PPT_LOADED) - { - if ((pptView[id].currentSlide > 0) - && (pptView[id].previewPath != NULL - && wcslen(pptView[id].previewPath) > 0)) - { - swprintf_s(filename, MAX_PATH, L"%s%i.bmp", - pptView[id].previewPath, - pptView[id].currentSlide); - CaptureAndSaveWindow(cwp->hwnd, filename); - } - if (((cwp->wParam == 0) - || (pptView[id].slideNos[1] == cwp->wParam)) - && (pptView[id].currentSlide > 0)) - { - pptView[id].state = PPT_LOADED; - pptView[id].currentSlide = pptView[id].slideCount + 1; - } - else - { - if (cwp->wParam > 0) - { - pptView[id].currentSlide = pptView[id].currentSlide + 1; - pptView[id].slideNos[pptView[id].currentSlide] - = cwp->wParam; - pptView[id].slideCount = pptView[id].currentSlide; - pptView[id].lastSlideSteps = 0; - } - } - } - else - { - if (cwp->wParam > 0) - { - if(pptView[id].guess > 0 - && pptView[id].slideNos[pptView[id].guess] == 0) - { - pptView[id].currentSlide = 0; - } - for(int i = 1; i <= pptView[id].slideCount; i++) - { - if(pptView[id].slideNos[i] == cwp->wParam) - { - pptView[id].currentSlide = i; - break; - } - } - if(pptView[id].currentSlide == 0) - { - pptView[id].slideNos[pptView[id].guess] = cwp->wParam; - pptView[id].currentSlide = pptView[id].guess; - } - pptView[id].guess = 0; - } - } - } - if ((pptView[id].state != PPT_CLOSED) - - &&(cwp->message == WM_CLOSE || cwp->message == WM_QUIT)) - { - pptView[id].state = PPT_CLOSING; - } - } - return CallNextHookEx(hook, nCode, wParam, lParam); -} - -VOID CaptureAndSaveWindow(HWND hWnd, wchar_t* filename) -{ - HBITMAP hBmp; - if ((hBmp = CaptureWindow(hWnd)) == NULL) - { - return; - } - RECT client; - GetClientRect(hWnd, &client); - UINT uiBytesPerRow = 3 * client.right; // RGB takes 24 bits - UINT uiRemainderForPadding; - - if ((uiRemainderForPadding = uiBytesPerRow % sizeof(DWORD)) > 0) - uiBytesPerRow += (sizeof(DWORD) - uiRemainderForPadding); - - UINT uiBytesPerAllRows = uiBytesPerRow * client.bottom; - PBYTE pDataBits; - - if ((pDataBits = new BYTE[uiBytesPerAllRows]) != NULL) - { - BITMAPINFOHEADER bmi = {0}; - BITMAPFILEHEADER bmf = {0}; - - // Prepare to get the data out of HBITMAP: - bmi.biSize = sizeof(bmi); - bmi.biPlanes = 1; - bmi.biBitCount = 24; - bmi.biHeight = client.bottom; - bmi.biWidth = client.right; - - // Get it: - HDC hDC = GetDC(hWnd); - GetDIBits(hDC, hBmp, 0, client.bottom, pDataBits, (BITMAPINFO*) &bmi, - DIB_RGB_COLORS); - ReleaseDC(hWnd, hDC); - - // Fill the file header: - bmf.bfOffBits = sizeof(bmf) + sizeof(bmi); - bmf.bfSize = bmf.bfOffBits + uiBytesPerAllRows; - bmf.bfType = 0x4D42; - - // Writing: - FILE* pFile; - int err = _wfopen_s(&pFile, filename, L"wb"); - if (err == 0) - { - fwrite(&bmf, sizeof(bmf), 1, pFile); - fwrite(&bmi, sizeof(bmi), 1, pFile); - fwrite(pDataBits, sizeof(BYTE), uiBytesPerAllRows, pFile); - fclose(pFile); - } - delete [] pDataBits; - } - DeleteObject(hBmp); -} -HBITMAP CaptureWindow(HWND hWnd) -{ - HDC hDC; - BOOL bOk = FALSE; - HBITMAP hImage = NULL; - - hDC = GetDC(hWnd); - RECT rcClient; - GetClientRect(hWnd, &rcClient); - if ((hImage = CreateCompatibleBitmap(hDC, rcClient.right, rcClient.bottom)) - != NULL) - { - HDC hMemDC; - HBITMAP hDCBmp; - - if ((hMemDC = CreateCompatibleDC(hDC)) != NULL) - { - hDCBmp = (HBITMAP)SelectObject(hMemDC, hImage); - HMODULE hLib = LoadLibrary(L"User32"); - // PrintWindow works for windows outside displayable area - // but was only introduced in WinXP. BitBlt requires the window to - // be topmost and within the viewable area of the display - if (GetProcAddress(hLib, "PrintWindow") == NULL) - { - SetWindowPos(hWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE); - BitBlt(hMemDC, 0, 0, rcClient.right, rcClient.bottom, hDC, 0, - 0, SRCCOPY); - SetWindowPos(hWnd, HWND_NOTOPMOST, -32000, -32000, 0, 0, - SWP_NOSIZE); - } - else - { - PrintWindow(hWnd, hMemDC, 0); - } - SelectObject(hMemDC, hDCBmp); - DeleteDC(hMemDC); - bOk = TRUE; - } - } - ReleaseDC(hWnd, hDC); - if (!bOk) - { - if (hImage) - { - DeleteObject(hImage); - hImage = NULL; - } - } - return hImage; -} diff --git a/openlp/plugins/presentations/lib/pptviewlib/pptviewlib.h b/openlp/plugins/presentations/lib/pptviewlib/pptviewlib.h deleted file mode 100644 index 0cd4ea36e..000000000 --- a/openlp/plugins/presentations/lib/pptviewlib/pptviewlib.h +++ /dev/null @@ -1,80 +0,0 @@ -/****************************************************************************** -* OpenLP - Open Source Lyrics Projection * -* --------------------------------------------------------------------------- * -* Copyright (c) 2008-2018 OpenLP Developers * -* --------------------------------------------------------------------------- * -* This program is free software; you can redistribute it and/or modify it * -* under the terms of the GNU General Public License as published by the Free * -* Software Foundation; version 2 of the License. * -* * -* This program is distributed in the hope that it will be useful, but WITHOUT * -* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * -* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * -* more details. * -* * -* You should have received a copy of the GNU General Public License along * -* with this program; if not, write to the Free Software Foundation, Inc., 59 * -* Temple Place, Suite 330, Boston, MA 02111-1307 USA * -******************************************************************************/ - -#define DllExport extern "C" __declspec( dllexport ) - -#define DEBUG(...) if (debug) wprintf(__VA_ARGS__) - -enum PPTVIEWSTATE {PPT_CLOSED, PPT_STARTED, PPT_OPENED, PPT_LOADED, - PPT_CLOSING}; - -DllExport int OpenPPT(wchar_t *filename, HWND hParentWnd, RECT rect, - wchar_t *previewPath); -DllExport BOOL CheckInstalled(); -DllExport void ClosePPT(int id); -DllExport int GetCurrentSlide(int id); -DllExport int GetSlideCount(int id); -DllExport int NextStep(int id); -DllExport void PrevStep(int id); -DllExport void GotoSlide(int id, int slide_no); -DllExport void RestartShow(int id); -DllExport void Blank(int id); -DllExport void Unblank(int id); -DllExport void Stop(int id); -DllExport void Resume(int id); -DllExport void SetDebug(BOOL onOff); - -LRESULT CALLBACK CbtProc(int nCode, WPARAM wParam, LPARAM lParam); -LRESULT CALLBACK CwpProc(int nCode, WPARAM wParam, LPARAM lParam); -LRESULT CALLBACK GetMsgProc(int nCode, WPARAM wParam, LPARAM lParam); -BOOL GetPPTViewerPath(wchar_t *pptViewerPath, int stringSize); -BOOL GetPPTViewerPathFromReg(wchar_t *pptViewerPath, int stringSize); -HBITMAP CaptureWindow(HWND hWnd); -VOID SaveBitmap(wchar_t* filename, HBITMAP hBmp) ; -VOID CaptureAndSaveWindow(HWND hWnd, wchar_t* filename); -BOOL GetPPTInfo(int id); -BOOL SavePPTInfo(int id); -void Unhook(int id); - -#define MAX_PPTS 16 -#define MAX_SLIDES 256 - -struct PPTVIEW -{ - HHOOK hook; - HHOOK msgHook; - HWND hWnd; - HWND hWnd2; - HWND hParentWnd; - HANDLE hProcess; - HANDLE hThread; - DWORD dwProcessId; - DWORD dwThreadId; - RECT rect; - int slideCount; - int currentSlide; - int firstSlideSteps; - int lastSlideSteps; - int steps; - int guess; - wchar_t filename[MAX_PATH]; - wchar_t previewPath[MAX_PATH]; - int slideNos[MAX_SLIDES]; - PPTVIEWSTATE state; -}; diff --git a/openlp/plugins/presentations/lib/pptviewlib/pptviewlib.vcproj b/openlp/plugins/presentations/lib/pptviewlib/pptviewlib.vcproj deleted file mode 100644 index d2843e510..000000000 --- a/openlp/plugins/presentations/lib/pptviewlib/pptviewlib.vcproj +++ /dev/null @@ -1,202 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/openlp/plugins/presentations/lib/pptviewlib/test.ppt b/openlp/plugins/presentations/lib/pptviewlib/test.ppt deleted file mode 100644 index 1d90168b1..000000000 Binary files a/openlp/plugins/presentations/lib/pptviewlib/test.ppt and /dev/null differ diff --git a/openlp/plugins/presentations/lib/pptviewlib/test.pptx b/openlp/plugins/presentations/lib/pptviewlib/test.pptx deleted file mode 100644 index c8beab172..000000000 Binary files a/openlp/plugins/presentations/lib/pptviewlib/test.pptx and /dev/null differ diff --git a/openlp/plugins/presentations/presentationplugin.py b/openlp/plugins/presentations/presentationplugin.py index b0fd8d7cf..fb0cbee94 100644 --- a/openlp/plugins/presentations/presentationplugin.py +++ b/openlp/plugins/presentations/presentationplugin.py @@ -44,7 +44,6 @@ __default_settings__ = {'presentations/override app': QtCore.Qt.Unchecked, 'presentations/pdf_program': None, 'presentations/Impress': QtCore.Qt.Checked, 'presentations/Powerpoint': QtCore.Qt.Checked, - 'presentations/Powerpoint Viewer': QtCore.Qt.Checked, 'presentations/Pdf': QtCore.Qt.Checked, 'presentations/presentations files': [], 'presentations/thumbnail_scheme': '', @@ -57,7 +56,7 @@ __default_settings__ = {'presentations/override app': QtCore.Qt.Unchecked, 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. + third party applications such as OpenOffice.org Impress, and Microsoft PowerPoint. """ log = logging.getLogger('PresentationPlugin') diff --git a/openlp/plugins/songs/lib/mediaitem.py b/openlp/plugins/songs/lib/mediaitem.py index 3d46311df..a99dd0a13 100644 --- a/openlp/plugins/songs/lib/mediaitem.py +++ b/openlp/plugins/songs/lib/mediaitem.py @@ -321,9 +321,12 @@ class SongMediaItem(MediaManagerItem): :param search_results: A tuple containing (songbook entry, book name, song title, song id) :return: None """ - def get_songbook_key(result): - """Get the key to sort by""" - return (get_natural_key(result[1]), get_natural_key(result[0]), get_natural_key(result[2])) + def get_songbook_key(text_array): + """ + Get the key to sort by + :param text_array: the result text to be processed. + """ + return get_natural_key(text_array[1]), get_natural_key(text_array[0]), get_natural_key(text_array[2]) log.debug('display results Book') self.list_view.clear() @@ -373,7 +376,7 @@ class SongMediaItem(MediaManagerItem): """ def get_theme_key(song): """Get the key to sort by""" - return (get_natural_key(song.theme_name), song.sort_key) + return get_natural_key(song.theme_name), song.sort_key log.debug('display results Themes') self.list_view.clear() @@ -396,7 +399,7 @@ class SongMediaItem(MediaManagerItem): """ def get_cclinumber_key(song): """Get the key to sort by""" - return (get_natural_key(song.ccli_number), song.sort_key) + return get_natural_key(song.ccli_number), song.sort_key log.debug('display results CCLI number') self.list_view.clear() @@ -460,6 +463,8 @@ class SongMediaItem(MediaManagerItem): """ Called by ServiceManager or SlideController by event passing the Song Id in the payload along with an indicator to say which type of display is required. + :param song_id: the id of the song + :param preview: show we preview after the update """ log.debug('on_remote_edit for song {song}'.format(song=song_id)) song_id = int(song_id) @@ -721,7 +726,8 @@ class SongMediaItem(MediaManagerItem): self.generate_footer(item, song) return item - def _authors_match(self, song, authors): + @staticmethod + def _authors_match(song, authors): """ Checks whether authors from a song in the database match the authors of the song to be imported. @@ -738,11 +744,12 @@ class SongMediaItem(MediaManagerItem): # List must be empty at the end return not author_list - def search(self, string, show_error): + def search(self, string, show_error=True): """ Search for some songs :param string: The string to show :param show_error: Is this an error? + :return: the results of the search """ search_results = self.search_entire(string) return [[song.id, song.title, song.alternate_title] for song in search_results] diff --git a/tests/functional/openlp_core/api/endpoint/__init__.py b/tests/functional/openlp_core/api/endpoint/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/functional/openlp_core/api/endpoint/test_controller.py b/tests/functional/openlp_core/api/endpoint/test_controller.py new file mode 100644 index 000000000..08b1f5d69 --- /dev/null +++ b/tests/functional/openlp_core/api/endpoint/test_controller.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2018 OpenLP Developers # +# --------------------------------------------------------------------------- # +# This program is free software; you can redistribute it and/or modify it # +# under the terms of the GNU General Public License as published by the Free # +# Software Foundation; version 2 of the License. # +# # +# This program is distributed in the hope that it will be useful, but WITHOUT # +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # +# more details. # +# # +# You should have received a copy of the GNU General Public License along # +# with this program; if not, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### +from unittest import TestCase +from unittest.mock import MagicMock, patch + +from openlp.core.common.registry import Registry +from openlp.core.api.endpoint.controller import controller_text + + +class TestController(TestCase): + """ + Test the Remote plugin deploy functions + """ + + def setUp(self): + """ + Setup for tests + """ + Registry.create() + self.registry = Registry() + self.mocked_live_controller = MagicMock() + Registry().register('live_controller', self.mocked_live_controller) + + def test_controller_text(self): + """ + Remote Deploy tests - test the dummy zip file is processed correctly + """ + # GIVEN: A mocked service with a dummy service item + self.mocked_live_controller.service_item = MagicMock() + # WHEN: I trigger the method + ret = controller_text("SomeText") + # THEN: I get a basic set of results + results = ret['results'] + assert isinstance(results['item'], MagicMock) + assert len(results['slides']) == 0 diff --git a/tests/functional/openlp_plugins/bibles/test_mediaitem.py b/tests/functional/openlp_plugins/bibles/test_mediaitem.py index 1d5425b2a..549cc4b57 100755 --- a/tests/functional/openlp_plugins/bibles/test_mediaitem.py +++ b/tests/functional/openlp_plugins/bibles/test_mediaitem.py @@ -167,7 +167,7 @@ class TestMediaItem(TestCase, TestMixin): # THEN: It should be a subclass of :class:`MediaManagerItem` assert isinstance(self.media_item, MediaManagerItem) - def test_steup_item(self): + def test_setup_item(self): """ Test the setup_item method """ diff --git a/tests/functional/openlp_plugins/presentations/test_pptviewcontroller.py b/tests/functional/openlp_plugins/presentations/test_pptviewcontroller.py deleted file mode 100644 index 5e6a7abdd..000000000 --- a/tests/functional/openlp_plugins/presentations/test_pptviewcontroller.py +++ /dev/null @@ -1,226 +0,0 @@ -# -*- coding: utf-8 -*- -# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 - -############################################################################### -# OpenLP - Open Source Lyrics Projection # -# --------------------------------------------------------------------------- # -# Copyright (c) 2008-2018 OpenLP Developers # -# --------------------------------------------------------------------------- # -# This program is free software; you can redistribute it and/or modify it # -# under the terms of the GNU General Public License as published by the Free # -# Software Foundation; version 2 of the License. # -# # -# This program is distributed in the hope that it will be useful, but WITHOUT # -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # -# more details. # -# # -# You should have received a copy of the GNU General Public License along # -# with this program; if not, write to the Free Software Foundation, Inc., 59 # -# Temple Place, Suite 330, Boston, MA 02111-1307 USA # -############################################################################### -""" -This module contains tests for the pptviewcontroller module of the Presentations plugin. -""" -import shutil -from tempfile import mkdtemp -from unittest import TestCase, skipIf -from unittest.mock import MagicMock, patch - -from openlp.core.common import is_win -from openlp.core.common.path import Path -from openlp.plugins.presentations.lib.pptviewcontroller import PptviewDocument, PptviewController -from tests.helpers.testmixin import TestMixin -from tests.utils.constants import RESOURCE_PATH - - -class TestPptviewController(TestCase, TestMixin): - """ - Test the PptviewController Class - """ - def setUp(self): - """ - Set up the patches and mocks need for all tests. - """ - self.setup_application() - self.build_settings() - self.mock_plugin = MagicMock() - self.temp_folder = mkdtemp() - self.mock_plugin.settings_section = self.temp_folder - - def tearDown(self): - """ - Stop the patches - """ - self.destroy_settings() - shutil.rmtree(self.temp_folder) - - def test_constructor(self): - """ - Test the Constructor from the PptViewController - """ - # GIVEN: No presentation controller - controller = None - - # WHEN: The presentation controller object is created - controller = PptviewController(plugin=self.mock_plugin) - - # THEN: The name of the presentation controller should be correct - assert 'Powerpoint Viewer' == controller.name, 'The name of the presentation controller should be correct' - - def test_check_available(self): - """ - Test check_available / check_installed - """ - # GIVEN: A mocked dll loader and a controller - with patch('ctypes.cdll.LoadLibrary') as mocked_load_library: - mocked_process = MagicMock() - mocked_process.CheckInstalled.return_value = True - mocked_load_library.return_value = mocked_process - controller = PptviewController(plugin=self.mock_plugin) - - # WHEN: check_available is called - available = controller.check_available() - - # THEN: On windows it should return True, on other platforms False - if is_win(): - assert available is True, 'check_available should return True on windows.' - else: - assert available is False, 'check_available should return False when not on windows.' - - -class TestPptviewDocument(TestCase): - """ - Test the PptviewDocument Class - """ - def setUp(self): - """ - Set up the patches and mocks need for all tests. - """ - self.pptview_document_create_thumbnails_patcher = patch( - 'openlp.plugins.presentations.lib.pptviewcontroller.PptviewDocument.create_thumbnails') - self.pptview_document_stop_presentation_patcher = patch( - 'openlp.plugins.presentations.lib.pptviewcontroller.PptviewDocument.stop_presentation') - self.presentation_document_get_temp_folder_patcher = patch( - 'openlp.plugins.presentations.lib.pptviewcontroller.PresentationDocument.get_temp_folder') - self.presentation_document_setup_patcher = patch( - 'openlp.plugins.presentations.lib.pptviewcontroller.PresentationDocument._setup') - self.screen_list_patcher = patch('openlp.plugins.presentations.lib.pptviewcontroller.ScreenList') - self.rect_patcher = MagicMock() - self.mock_pptview_document_create_thumbnails = self.pptview_document_create_thumbnails_patcher.start() - self.mock_pptview_document_stop_presentation = self.pptview_document_stop_presentation_patcher.start() - self.mock_presentation_document_get_temp_folder = self.presentation_document_get_temp_folder_patcher.start() - self.mock_presentation_document_setup = self.presentation_document_setup_patcher.start() - self.mock_rect = self.rect_patcher.start() - self.mock_screen_list = self.screen_list_patcher.start() - self.mock_controller = MagicMock() - self.mock_presentation = MagicMock() - self.temp_folder = mkdtemp() - self.mock_presentation_document_get_temp_folder.return_value = self.temp_folder - - def tearDown(self): - """ - Stop the patches - """ - self.pptview_document_create_thumbnails_patcher.stop() - self.pptview_document_stop_presentation_patcher.stop() - self.presentation_document_get_temp_folder_patcher.stop() - self.presentation_document_setup_patcher.stop() - self.rect_patcher.stop() - self.screen_list_patcher.stop() - shutil.rmtree(self.temp_folder) - - @skipIf(not is_win(), 'Not Windows') - def test_load_presentation_succesful(self): - """ - Test the PptviewDocument.load_presentation() method when the PPT is successfully opened - """ - # GIVEN: A reset mocked_os - self.mock_controller.process.OpenPPT.return_value = 0 - instance = PptviewDocument(self.mock_controller, self.mock_presentation) - instance.file_path = 'test\path.ppt' - - # WHEN: The temporary directory exists and OpenPPT returns successfully (not -1) - result = instance.load_presentation() - - # THEN: PptviewDocument.load_presentation should return True - assert result is True - - @skipIf(not is_win(), 'Not Windows') - def test_load_presentation_un_succesful(self): - """ - Test the PptviewDocument.load_presentation() method when the temporary directory does not exist and the PPT is - not successfully opened - """ - # GIVEN: A reset mock_os_isdir - self.mock_controller.process.OpenPPT.return_value = -1 - instance = PptviewDocument(self.mock_controller, self.mock_presentation) - instance.file_path = 'test\path.ppt' - - # WHEN: The temporary directory does not exist and OpenPPT returns unsuccessfully (-1) - with patch.object(instance, 'get_temp_folder') as mocked_get_folder: - mocked_get_folder.return_value = MagicMock(spec=Path) - result = instance.load_presentation() - - # THEN: The temp folder should be created and PptviewDocument.load_presentation should return False - assert result is False - - def test_create_titles_and_notes(self): - """ - Test PowerpointController.create_titles_and_notes - """ - # GIVEN: mocked PresentationController.save_titles_and_notes and a pptx file - doc = PptviewDocument(self.mock_controller, self.mock_presentation) - doc.file_path = RESOURCE_PATH / 'presentations' / 'test.pptx' - doc.save_titles_and_notes = MagicMock() - - # WHEN reading the titles and notes - doc.create_titles_and_notes() - - # THEN save_titles_and_notes should have been called once with empty arrays - doc.save_titles_and_notes.assert_called_once_with(['Test 1\n', '\n', 'Test 2\n', 'Test 4\n', 'Test 3\n'], - ['Notes for slide 1', 'Inserted', 'Notes for slide 2', - 'Notes \nfor slide 4', 'Notes for slide 3']) - - def test_create_titles_and_notes_nonexistent_file(self): - """ - Test PowerpointController.create_titles_and_notes with nonexistent file - """ - # GIVEN: mocked PresentationController.save_titles_and_notes and an nonexistent file - with patch('builtins.open') as mocked_open, \ - patch.object(Path, 'exists') as mocked_path_exists, \ - patch('openlp.plugins.presentations.lib.presentationcontroller.create_paths') as \ - mocked_dir_exists: - mocked_path_exists.return_value = False - mocked_dir_exists.return_value = False - doc = PptviewDocument(self.mock_controller, self.mock_presentation) - doc.file_path = Path('Idontexist.pptx') - doc.save_titles_and_notes = MagicMock() - - # WHEN: Reading the titles and notes - doc.create_titles_and_notes() - - # THEN: File existens should have been checked, and not have been opened. - doc.save_titles_and_notes.assert_called_once_with(None, None) - mocked_path_exists.assert_called_with() - assert mocked_open.call_count == 0, 'There should be no calls to open a file.' - - def test_create_titles_and_notes_invalid_file(self): - """ - Test PowerpointController.create_titles_and_notes with invalid file - """ - # GIVEN: mocked PresentationController.save_titles_and_notes and an invalid file - with patch('builtins.open') as mocked_open, \ - patch('openlp.plugins.presentations.lib.pptviewcontroller.zipfile.is_zipfile') as mocked_is_zf: - mocked_is_zf.return_value = False - mocked_open.filesize = 10 - doc = PptviewDocument(self.mock_controller, self.mock_presentation) - doc.file_path = RESOURCE_PATH / 'presentations' / 'test.ppt' - doc.save_titles_and_notes = MagicMock() - - # WHEN: reading the titles and notes - doc.create_titles_and_notes() - - # THEN: - doc.save_titles_and_notes.assert_called_once_with(None, None) - assert mocked_is_zf.call_count == 1, 'is_zipfile should have been called once'