forked from openlp/openlp
For worshipassistant add a default verse-id for lyrics to use if none is given. Fixes bug 1458056.
Don't import setting keys that does not exists. Fixes bug 1458672. When going from a theme-blanked item to item which doesn't support theme-blanking, switch to black-blank. Only use transitions if we are changing slide. Fixes bug 1449064. Make translation of 'Advanced' specific to the bible plugin. Many PowerPoint fixes/improvements: * Make screenshots for main webview, even on single screen setup. Fixes bug 1449041. * Implement workaround for unblanking bug in PowerPoint 2010. * Open PowerPoint hidden so the main application window isn't visible. * Added support for odp for PowerPoint 2007 and newer. * Added support for Powerpoint events, which is used to update the slidecontroller if OpenLP is not in focus. * Minimized the flashing of the PowerPoint presentation window in the taskbar. bzr-revno: 2539
This commit is contained in:
commit
623a0974a5
@ -1 +1 @@
|
|||||||
2.1.3
|
2.1.4
|
||||||
|
@ -900,7 +900,10 @@ class MainWindow(QtGui.QMainWindow, Ui_MainWindow, RegistryProperties):
|
|||||||
for section_key in import_keys:
|
for section_key in import_keys:
|
||||||
if 'eneral' in section_key:
|
if 'eneral' in section_key:
|
||||||
section_key = section_key.lower()
|
section_key = section_key.lower()
|
||||||
value = import_settings.value(section_key)
|
try:
|
||||||
|
value = import_settings.value(section_key)
|
||||||
|
except KeyError:
|
||||||
|
log.warning('The key "%s" does not exist (anymore), so it will be skipped.' % section_key)
|
||||||
if value is not None:
|
if value is not None:
|
||||||
settings.setValue('%s' % (section_key), value)
|
settings.setValue('%s' % (section_key), value)
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
|
@ -828,8 +828,10 @@ class SlideController(DisplayController, RegistryProperties):
|
|||||||
self.selected_row = 0
|
self.selected_row = 0
|
||||||
# take a copy not a link to the servicemanager copy.
|
# take a copy not a link to the servicemanager copy.
|
||||||
self.service_item = copy.copy(service_item)
|
self.service_item = copy.copy(service_item)
|
||||||
if old_item and self.is_live and old_item.is_capable(ItemCapabilities.ProvidesOwnDisplay):
|
# Reset blanking if needed
|
||||||
self._reset_blank()
|
if old_item and self.is_live and (old_item.is_capable(ItemCapabilities.ProvidesOwnDisplay) or
|
||||||
|
self.service_item.is_capable(ItemCapabilities.ProvidesOwnDisplay)):
|
||||||
|
self._reset_blank(self.service_item.is_capable(ItemCapabilities.ProvidesOwnDisplay))
|
||||||
if service_item.is_command():
|
if service_item.is_command():
|
||||||
Registry().execute(
|
Registry().execute(
|
||||||
'%s_start' % service_item.name.lower(), [self.service_item, self.is_live, self.hide_mode(), slide_no])
|
'%s_start' % service_item.name.lower(), [self.service_item, self.is_live, self.hide_mode(), slide_no])
|
||||||
@ -1080,6 +1082,7 @@ class SlideController(DisplayController, RegistryProperties):
|
|||||||
% timeout)
|
% timeout)
|
||||||
return
|
return
|
||||||
row = self.preview_widget.current_slide_number()
|
row = self.preview_widget.current_slide_number()
|
||||||
|
old_selected_row = self.selected_row
|
||||||
self.selected_row = 0
|
self.selected_row = 0
|
||||||
if -1 < row < self.preview_widget.slide_count():
|
if -1 < row < self.preview_widget.slide_count():
|
||||||
if self.service_item.is_command():
|
if self.service_item.is_command():
|
||||||
@ -1089,7 +1092,7 @@ class SlideController(DisplayController, RegistryProperties):
|
|||||||
else:
|
else:
|
||||||
to_display = self.service_item.get_rendered_frame(row)
|
to_display = self.service_item.get_rendered_frame(row)
|
||||||
if self.service_item.is_text():
|
if self.service_item.is_text():
|
||||||
self.display.text(to_display)
|
self.display.text(to_display, row != old_selected_row)
|
||||||
else:
|
else:
|
||||||
if start:
|
if start:
|
||||||
self.display.build_html(self.service_item, to_display)
|
self.display.build_html(self.service_item, to_display)
|
||||||
@ -1119,8 +1122,7 @@ class SlideController(DisplayController, RegistryProperties):
|
|||||||
This updates the preview frame, for example after changing a slide or using *Blank to Theme*.
|
This updates the preview frame, for example after changing a slide or using *Blank to Theme*.
|
||||||
"""
|
"""
|
||||||
self.log_debug('update_preview %s ' % self.screens.current['primary'])
|
self.log_debug('update_preview %s ' % self.screens.current['primary'])
|
||||||
if not self.screens.current['primary'] and self.service_item and \
|
if self.service_item and self.service_item.is_capable(ItemCapabilities.ProvidesOwnDisplay):
|
||||||
self.service_item.is_capable(ItemCapabilities.ProvidesOwnDisplay):
|
|
||||||
# Grab now, but try again in a couple of seconds if slide change is slow
|
# Grab now, but try again in a couple of seconds if slide change is slow
|
||||||
QtCore.QTimer.singleShot(0.5, self.grab_maindisplay)
|
QtCore.QTimer.singleShot(0.5, self.grab_maindisplay)
|
||||||
QtCore.QTimer.singleShot(2.5, self.grab_maindisplay)
|
QtCore.QTimer.singleShot(2.5, self.grab_maindisplay)
|
||||||
@ -1349,7 +1351,11 @@ class SlideController(DisplayController, RegistryProperties):
|
|||||||
|
|
||||||
:param item: The service item to be processed
|
:param item: The service item to be processed
|
||||||
"""
|
"""
|
||||||
self.media_controller.video(self.controller_type, item, self.hide_mode())
|
if self.is_live and self.hide_mode() == HideMode.Theme:
|
||||||
|
self.media_controller.video(self.controller_type, item, HideMode.Blank)
|
||||||
|
self.on_blank_display(True)
|
||||||
|
else:
|
||||||
|
self.media_controller.video(self.controller_type, item, self.hide_mode())
|
||||||
if not self.is_live:
|
if not self.is_live:
|
||||||
self.preview_display.show()
|
self.preview_display.show()
|
||||||
self.slide_preview.hide()
|
self.slide_preview.hide()
|
||||||
@ -1362,16 +1368,22 @@ class SlideController(DisplayController, RegistryProperties):
|
|||||||
self.preview_display.hide()
|
self.preview_display.hide()
|
||||||
self.slide_preview.show()
|
self.slide_preview.show()
|
||||||
|
|
||||||
def _reset_blank(self):
|
def _reset_blank(self, no_theme):
|
||||||
"""
|
"""
|
||||||
Used by command items which provide their own displays to reset the
|
Used by command items which provide their own displays to reset the
|
||||||
screen hide attributes
|
screen hide attributes
|
||||||
|
|
||||||
|
:param no_theme: Does the new item support theme-blanking.
|
||||||
"""
|
"""
|
||||||
hide_mode = self.hide_mode()
|
hide_mode = self.hide_mode()
|
||||||
if hide_mode == HideMode.Blank:
|
if hide_mode == HideMode.Blank:
|
||||||
self.on_blank_display(True)
|
self.on_blank_display(True)
|
||||||
elif hide_mode == HideMode.Theme:
|
elif hide_mode == HideMode.Theme:
|
||||||
self.on_theme_display(True)
|
# The new item-type doesn't support theme-blanking, so 'switch' to normal blanking.
|
||||||
|
if no_theme:
|
||||||
|
self.on_blank_display(True)
|
||||||
|
else:
|
||||||
|
self.on_theme_display(True)
|
||||||
elif hide_mode == HideMode.Screen:
|
elif hide_mode == HideMode.Screen:
|
||||||
self.on_hide_display(True)
|
self.on_hide_display(True)
|
||||||
else:
|
else:
|
||||||
|
@ -193,7 +193,7 @@ class BibleMediaItem(MediaManagerItem):
|
|||||||
self.add_search_fields('quick', translate('BiblesPlugin.MediaItem', 'Quick'))
|
self.add_search_fields('quick', translate('BiblesPlugin.MediaItem', 'Quick'))
|
||||||
self.quickTab.setVisible(True)
|
self.quickTab.setVisible(True)
|
||||||
# Add the Advanced Search tab.
|
# Add the Advanced Search tab.
|
||||||
self.add_search_tab('advanced', UiStrings().Advanced)
|
self.add_search_tab('advanced', translate('BiblesPlugin.MediaItem', 'Advanced'))
|
||||||
self.advanced_book_label = QtGui.QLabel(self.advancedTab)
|
self.advanced_book_label = QtGui.QLabel(self.advancedTab)
|
||||||
self.advanced_book_label.setObjectName('advanced_book_label')
|
self.advanced_book_label.setObjectName('advanced_book_label')
|
||||||
self.advancedLayout.addWidget(self.advanced_book_label, 0, 0, QtCore.Qt.AlignRight)
|
self.advancedLayout.addWidget(self.advanced_book_label, 0, 0, QtCore.Qt.AlignRight)
|
||||||
|
@ -243,6 +243,9 @@ class Controller(object):
|
|||||||
Instruct the controller to stop and hide the presentation.
|
Instruct the controller to stop and hide the presentation.
|
||||||
"""
|
"""
|
||||||
log.debug('Live = %s, stop' % self.is_live)
|
log.debug('Live = %s, stop' % self.is_live)
|
||||||
|
# Save the current slide number to be able to return to this slide if the presentation is activated again.
|
||||||
|
if self.doc.is_active():
|
||||||
|
self.doc.slidenumber = self.doc.get_slide_number()
|
||||||
self.hide_mode = HideMode.Screen
|
self.hide_mode = HideMode.Screen
|
||||||
if not self.doc:
|
if not self.doc:
|
||||||
return
|
return
|
||||||
@ -266,8 +269,6 @@ class Controller(object):
|
|||||||
return
|
return
|
||||||
if not self.activate():
|
if not self.activate():
|
||||||
return
|
return
|
||||||
if self.doc.slidenumber and self.doc.slidenumber != self.doc.get_slide_number():
|
|
||||||
self.doc.goto_slide(self.doc.slidenumber)
|
|
||||||
self.doc.unblank_screen()
|
self.doc.unblank_screen()
|
||||||
Registry().execute('live_display_hide', HideMode.Screen)
|
Registry().execute('live_display_hide', HideMode.Screen)
|
||||||
|
|
||||||
|
@ -32,12 +32,15 @@ import time
|
|||||||
from openlp.core.common import is_win, Settings
|
from openlp.core.common import is_win, Settings
|
||||||
|
|
||||||
if is_win():
|
if is_win():
|
||||||
from win32com.client import Dispatch
|
from win32com.client import DispatchWithEvents
|
||||||
import win32com
|
import win32com
|
||||||
|
import win32con
|
||||||
import winreg
|
import winreg
|
||||||
import win32ui
|
import win32ui
|
||||||
|
import win32gui
|
||||||
import pywintypes
|
import pywintypes
|
||||||
|
|
||||||
|
|
||||||
from openlp.core.lib import ScreenList
|
from openlp.core.lib import ScreenList
|
||||||
from openlp.core.lib.ui import UiStrings, critical_error_message_box, translate
|
from openlp.core.lib.ui import UiStrings, critical_error_message_box, translate
|
||||||
from openlp.core.common import trace_error_handler, Registry
|
from openlp.core.common import trace_error_handler, Registry
|
||||||
@ -70,8 +73,18 @@ class PowerpointController(PresentationController):
|
|||||||
if is_win():
|
if is_win():
|
||||||
try:
|
try:
|
||||||
winreg.OpenKey(winreg.HKEY_CLASSES_ROOT, 'PowerPoint.Application').Close()
|
winreg.OpenKey(winreg.HKEY_CLASSES_ROOT, 'PowerPoint.Application').Close()
|
||||||
|
try:
|
||||||
|
# Try to detect if the version is 12 (2007) or above, and if so add 'odp' as a support filetype
|
||||||
|
version_key = winreg.OpenKey(winreg.HKEY_CLASSES_ROOT, 'PowerPoint.Application\\CurVer')
|
||||||
|
tmp1, app_version_string, tmp2 = winreg.EnumValue(version_key, 0)
|
||||||
|
version_key.Close()
|
||||||
|
app_version = int(app_version_string[-2:])
|
||||||
|
if app_version >= 12:
|
||||||
|
self.also_supports = ['odp']
|
||||||
|
except (OSError, ValueError):
|
||||||
|
log.warning('Detection of powerpoint version using registry failed.')
|
||||||
return True
|
return True
|
||||||
except WindowsError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -80,12 +93,22 @@ class PowerpointController(PresentationController):
|
|||||||
"""
|
"""
|
||||||
Loads PowerPoint process.
|
Loads PowerPoint process.
|
||||||
"""
|
"""
|
||||||
|
class PowerPointEvents:
|
||||||
|
"""
|
||||||
|
Class to catch events from PowerPoint.
|
||||||
|
"""
|
||||||
|
def OnSlideShowNextClick(self, slideshow_window, effect):
|
||||||
|
"""
|
||||||
|
Occurs on the next click of the slide.
|
||||||
|
If the main OpenLP window is not in focus force update of the slidecontroller.
|
||||||
|
"""
|
||||||
|
if not Registry().get('main_window').isActiveWindow():
|
||||||
|
log.debug('main window is not in focus - should update slidecontroller')
|
||||||
|
Registry().execute('slidecontroller_live_change', slideshow_window.View.CurrentShowPosition)
|
||||||
|
|
||||||
log.debug('start_process')
|
log.debug('start_process')
|
||||||
if not self.process:
|
if not self.process:
|
||||||
self.process = Dispatch('PowerPoint.Application')
|
self.process = DispatchWithEvents('PowerPoint.Application', PowerPointEvents)
|
||||||
self.process.Visible = True
|
|
||||||
# ppWindowMinimized = 2
|
|
||||||
self.process.WindowState = 2
|
|
||||||
|
|
||||||
def kill(self):
|
def kill(self):
|
||||||
"""
|
"""
|
||||||
@ -124,6 +147,9 @@ class PowerpointDocument(PresentationDocument):
|
|||||||
self.presentation = None
|
self.presentation = None
|
||||||
self.index_map = {}
|
self.index_map = {}
|
||||||
self.slide_count = 0
|
self.slide_count = 0
|
||||||
|
self.blank_slide = 1
|
||||||
|
self.blank_click = None
|
||||||
|
self.presentation_hwnd = None
|
||||||
|
|
||||||
def load_presentation(self):
|
def load_presentation(self):
|
||||||
"""
|
"""
|
||||||
@ -132,23 +158,15 @@ class PowerpointDocument(PresentationDocument):
|
|||||||
"""
|
"""
|
||||||
log.debug('load_presentation')
|
log.debug('load_presentation')
|
||||||
try:
|
try:
|
||||||
if not self.controller.process or not self.controller.process.Visible:
|
if not self.controller.process:
|
||||||
self.controller.start_process()
|
self.controller.start_process()
|
||||||
self.controller.process.Presentations.Open(self.file_path, False, False, True)
|
self.controller.process.Presentations.Open(os.path.normpath(self.file_path), False, False, False)
|
||||||
self.presentation = self.controller.process.Presentations(self.controller.process.Presentations.Count)
|
self.presentation = self.controller.process.Presentations(self.controller.process.Presentations.Count)
|
||||||
self.create_thumbnails()
|
self.create_thumbnails()
|
||||||
self.create_titles_and_notes()
|
self.create_titles_and_notes()
|
||||||
# Powerpoint 2010 and 2013 pops up when loading a file, so we minimize it again
|
# Make sure powerpoint doesn't steal focus, unless we're on a single screen setup
|
||||||
if float(self.presentation.Application.Version) >= 14.0:
|
if len(ScreenList().screen_list) > 1:
|
||||||
try:
|
Registry().get('main_window').activateWindow()
|
||||||
# ppWindowMinimized = 2
|
|
||||||
self.presentation.Application.WindowState = 2
|
|
||||||
except (AttributeError, pywintypes.com_error) as e:
|
|
||||||
log.exception('Failed to minimize main powerpoint window')
|
|
||||||
log.exception(e)
|
|
||||||
trace_error_handler(log)
|
|
||||||
# Make sure powerpoint doesn't steal focus
|
|
||||||
Registry().get('main_window').activateWindow()
|
|
||||||
return True
|
return True
|
||||||
except (AttributeError, pywintypes.com_error) as e:
|
except (AttributeError, pywintypes.com_error) as e:
|
||||||
log.exception('Exception caught while loading Powerpoint presentation')
|
log.exception('Exception caught while loading Powerpoint presentation')
|
||||||
@ -194,8 +212,9 @@ class PowerpointDocument(PresentationDocument):
|
|||||||
trace_error_handler(log)
|
trace_error_handler(log)
|
||||||
self.presentation = None
|
self.presentation = None
|
||||||
self.controller.remove_doc(self)
|
self.controller.remove_doc(self)
|
||||||
# Make sure powerpoint doesn't steal focus
|
# Make sure powerpoint doesn't steal focus, unless we're on a single screen setup
|
||||||
Registry().get('main_window').activateWindow()
|
if len(ScreenList().screen_list) > 1:
|
||||||
|
Registry().get('main_window').activateWindow()
|
||||||
|
|
||||||
def is_loaded(self):
|
def is_loaded(self):
|
||||||
"""
|
"""
|
||||||
@ -203,10 +222,6 @@ class PowerpointDocument(PresentationDocument):
|
|||||||
"""
|
"""
|
||||||
log.debug('is_loaded')
|
log.debug('is_loaded')
|
||||||
try:
|
try:
|
||||||
if not self.controller.process.Visible:
|
|
||||||
return False
|
|
||||||
if self.controller.process.Windows.Count == 0:
|
|
||||||
return False
|
|
||||||
if self.controller.process.Presentations.Count == 0:
|
if self.controller.process.Presentations.Count == 0:
|
||||||
return False
|
return False
|
||||||
except (AttributeError, pywintypes.com_error) as e:
|
except (AttributeError, pywintypes.com_error) as e:
|
||||||
@ -241,13 +256,11 @@ class PowerpointDocument(PresentationDocument):
|
|||||||
"""
|
"""
|
||||||
log.debug('unblank_screen')
|
log.debug('unblank_screen')
|
||||||
try:
|
try:
|
||||||
self.presentation.SlideShowSettings.Run()
|
|
||||||
# ppSlideShowRunning = 1
|
|
||||||
self.presentation.SlideShowWindow.View.State = 1
|
|
||||||
self.presentation.SlideShowWindow.Activate()
|
self.presentation.SlideShowWindow.Activate()
|
||||||
# Unblanking is broken in PowerPoint 2010 and 2013, need to redisplay
|
self.presentation.SlideShowWindow.View.State = 1
|
||||||
if float(self.presentation.Application.Version) >= 14.0:
|
# Unblanking is broken in PowerPoint 2010 (14.0), need to redisplay
|
||||||
self.presentation.SlideShowWindow.View.GotoSlide(self.blank_slide, False)
|
if 15.0 > float(self.presentation.Application.Version) >= 14.0:
|
||||||
|
self.presentation.SlideShowWindow.View.GotoSlide(self.index_map[self.blank_slide], False)
|
||||||
if self.blank_click:
|
if self.blank_click:
|
||||||
self.presentation.SlideShowWindow.View.GotoClick(self.blank_click)
|
self.presentation.SlideShowWindow.View.GotoClick(self.blank_click)
|
||||||
except (AttributeError, pywintypes.com_error) as e:
|
except (AttributeError, pywintypes.com_error) as e:
|
||||||
@ -255,8 +268,12 @@ class PowerpointDocument(PresentationDocument):
|
|||||||
log.exception(e)
|
log.exception(e)
|
||||||
trace_error_handler(log)
|
trace_error_handler(log)
|
||||||
self.show_error_msg()
|
self.show_error_msg()
|
||||||
# Make sure powerpoint doesn't steal focus
|
# Stop powerpoint from flashing in the taskbar
|
||||||
Registry().get('main_window').activateWindow()
|
if self.presentation_hwnd:
|
||||||
|
win32gui.FlashWindowEx(self.presentation_hwnd, win32con.FLASHW_STOP, 0, 0)
|
||||||
|
# Make sure powerpoint doesn't steal focus, unless we're on a single screen setup
|
||||||
|
if len(ScreenList().screen_list) > 1:
|
||||||
|
Registry().get('main_window').activateWindow()
|
||||||
|
|
||||||
def blank_screen(self):
|
def blank_screen(self):
|
||||||
"""
|
"""
|
||||||
@ -264,8 +281,8 @@ class PowerpointDocument(PresentationDocument):
|
|||||||
"""
|
"""
|
||||||
log.debug('blank_screen')
|
log.debug('blank_screen')
|
||||||
try:
|
try:
|
||||||
# Unblanking is broken in PowerPoint 2010 and 2013, need to save info for later
|
# Unblanking is broken in PowerPoint 2010 (14.0), need to save info for later
|
||||||
if float(self.presentation.Application.Version) >= 14.0:
|
if 15.0 > float(self.presentation.Application.Version) >= 14.0:
|
||||||
self.blank_slide = self.get_slide_number()
|
self.blank_slide = self.get_slide_number()
|
||||||
self.blank_click = self.presentation.SlideShowWindow.View.GetClickIndex()
|
self.blank_click = self.presentation.SlideShowWindow.View.GetClickIndex()
|
||||||
# ppSlideShowBlackScreen = 3
|
# ppSlideShowBlackScreen = 3
|
||||||
@ -295,7 +312,7 @@ class PowerpointDocument(PresentationDocument):
|
|||||||
|
|
||||||
def stop_presentation(self):
|
def stop_presentation(self):
|
||||||
"""
|
"""
|
||||||
Stops the current presentation and hides the output.
|
Stops the current presentation and hides the output. Used when blanking to desktop.
|
||||||
"""
|
"""
|
||||||
log.debug('stop_presentation')
|
log.debug('stop_presentation')
|
||||||
try:
|
try:
|
||||||
@ -321,28 +338,52 @@ class PowerpointDocument(PresentationDocument):
|
|||||||
except win32ui.error:
|
except win32ui.error:
|
||||||
dpi = 96
|
dpi = 96
|
||||||
size = ScreenList().current['size']
|
size = ScreenList().current['size']
|
||||||
ppt_window = self.presentation.SlideShowSettings.Run()
|
ppt_window = None
|
||||||
if not ppt_window:
|
|
||||||
return
|
|
||||||
try:
|
try:
|
||||||
ppt_window.Top = size.y() * 72 / dpi
|
ppt_window = self.presentation.SlideShowSettings.Run()
|
||||||
ppt_window.Height = size.height() * 72 / dpi
|
except (AttributeError, pywintypes.com_error) as e:
|
||||||
ppt_window.Left = size.x() * 72 / dpi
|
log.exception('Caught exception while in start_presentation')
|
||||||
ppt_window.Width = size.width() * 72 / dpi
|
|
||||||
except AttributeError as e:
|
|
||||||
log.exception('AttributeError while in start_presentation')
|
|
||||||
log.exception(e)
|
log.exception(e)
|
||||||
# Powerpoint 2010 and 2013 pops up when starting a file, so we minimize it again
|
trace_error_handler(log)
|
||||||
if float(self.presentation.Application.Version) >= 14.0:
|
self.show_error_msg()
|
||||||
|
if ppt_window and not Settings().value('presentations/powerpoint control window'):
|
||||||
try:
|
try:
|
||||||
# ppWindowMinimized = 2
|
ppt_window.Top = size.y() * 72 / dpi
|
||||||
self.presentation.Application.WindowState = 2
|
ppt_window.Height = size.height() * 72 / dpi
|
||||||
except (AttributeError, pywintypes.com_error) as e:
|
ppt_window.Left = size.x() * 72 / dpi
|
||||||
log.exception('Failed to minimize main powerpoint window')
|
ppt_window.Width = size.width() * 72 / dpi
|
||||||
|
except AttributeError as e:
|
||||||
|
log.exception('AttributeError while in start_presentation')
|
||||||
log.exception(e)
|
log.exception(e)
|
||||||
trace_error_handler(log)
|
# Find the presentation window and save the handle for later
|
||||||
# Make sure powerpoint doesn't steal focus
|
self.presentation_hwnd = None
|
||||||
Registry().get('main_window').activateWindow()
|
if ppt_window:
|
||||||
|
log.debug('main display size: y=%d, height=%d, x=%d, width=%d'
|
||||||
|
% (size.y(), size.height(), size.x(), size.width()))
|
||||||
|
win32gui.EnumWindows(self._window_enum_callback, size)
|
||||||
|
# Make sure powerpoint doesn't steal focus, unless we're on a single screen setup
|
||||||
|
if len(ScreenList().screen_list) > 1:
|
||||||
|
Registry().get('main_window').activateWindow()
|
||||||
|
|
||||||
|
def _window_enum_callback(self, hwnd, size):
|
||||||
|
"""
|
||||||
|
Method for callback from win32gui.EnumWindows.
|
||||||
|
Used to find the powerpoint presentation window and stop it flashing in the taskbar.
|
||||||
|
"""
|
||||||
|
# Get the size of the current window and if it matches the size of our main display we assume
|
||||||
|
# it is the powerpoint presentation window.
|
||||||
|
(left, top, right, bottom) = win32gui.GetWindowRect(hwnd)
|
||||||
|
window_title = win32gui.GetWindowText(hwnd)
|
||||||
|
log.debug('window size: left=%d, top=%d, right=%d, width=%d' % (left, top, right, bottom))
|
||||||
|
log.debug('compare size: %d and %d, %d and %d, %d and %d, %d and %d'
|
||||||
|
% (size.y(), top, size.height(), (bottom - top), size.x(), left, size.width(), (right - left)))
|
||||||
|
log.debug('window title: %s' % window_title)
|
||||||
|
if size.y() == top and size.height() == (bottom - top) and size.x() == left and \
|
||||||
|
size.width() == (right - left) and os.path.basename(self.file_path) in window_title:
|
||||||
|
log.debug('Found a match and will save the handle')
|
||||||
|
self.presentation_hwnd = hwnd
|
||||||
|
# Stop powerpoint from flashing in the taskbar
|
||||||
|
win32gui.FlashWindowEx(self.presentation_hwnd, win32con.FLASHW_STOP, 0, 0)
|
||||||
|
|
||||||
def get_slide_number(self):
|
def get_slide_number(self):
|
||||||
"""
|
"""
|
||||||
@ -384,7 +425,7 @@ class PowerpointDocument(PresentationDocument):
|
|||||||
log.debug('goto_slide')
|
log.debug('goto_slide')
|
||||||
try:
|
try:
|
||||||
if Settings().value('presentations/powerpoint slide click advance') \
|
if Settings().value('presentations/powerpoint slide click advance') \
|
||||||
and self.get_slide_number() == self.index_map[slide_no]:
|
and self.get_slide_number() == slide_no:
|
||||||
click_index = self.presentation.SlideShowWindow.View.GetClickIndex()
|
click_index = self.presentation.SlideShowWindow.View.GetClickIndex()
|
||||||
click_count = self.presentation.SlideShowWindow.View.GetClickCount()
|
click_count = self.presentation.SlideShowWindow.View.GetClickCount()
|
||||||
log.debug('We are already on this slide - go to next effect if any left, idx: %d, count: %d'
|
log.debug('We are already on this slide - go to next effect if any left, idx: %d, count: %d'
|
||||||
@ -405,6 +446,7 @@ class PowerpointDocument(PresentationDocument):
|
|||||||
"""
|
"""
|
||||||
log.debug('next_step')
|
log.debug('next_step')
|
||||||
try:
|
try:
|
||||||
|
self.presentation.SlideShowWindow.Activate()
|
||||||
self.presentation.SlideShowWindow.View.Next()
|
self.presentation.SlideShowWindow.View.Next()
|
||||||
except (AttributeError, pywintypes.com_error) as e:
|
except (AttributeError, pywintypes.com_error) as e:
|
||||||
log.exception('Caught exception while in next_step')
|
log.exception('Caught exception while in next_step')
|
||||||
@ -415,6 +457,12 @@ class PowerpointDocument(PresentationDocument):
|
|||||||
if self.get_slide_number() > self.get_slide_count():
|
if self.get_slide_number() > self.get_slide_count():
|
||||||
log.debug('past end, stepping back to previous')
|
log.debug('past end, stepping back to previous')
|
||||||
self.previous_step()
|
self.previous_step()
|
||||||
|
# Stop powerpoint from flashing in the taskbar
|
||||||
|
if self.presentation_hwnd:
|
||||||
|
win32gui.FlashWindowEx(self.presentation_hwnd, win32con.FLASHW_STOP, 0, 0)
|
||||||
|
# Make sure powerpoint doesn't steal focus, unless we're on a single screen setup
|
||||||
|
if len(ScreenList().screen_list) > 1:
|
||||||
|
Registry().get('main_window').activateWindow()
|
||||||
|
|
||||||
def previous_step(self):
|
def previous_step(self):
|
||||||
"""
|
"""
|
||||||
@ -490,8 +538,12 @@ def _get_text_from_shapes(shapes):
|
|||||||
:param shapes: A set of shapes to search for text.
|
:param shapes: A set of shapes to search for text.
|
||||||
"""
|
"""
|
||||||
text = ''
|
text = ''
|
||||||
for shape in shapes:
|
try:
|
||||||
if shape.PlaceholderFormat.Type == 2: # 2 from is enum PpPlaceholderType.ppPlaceholderBody
|
for shape in shapes:
|
||||||
if shape.HasTextFrame and shape.TextFrame.HasText:
|
if shape.PlaceholderFormat.Type == 2: # 2 from is enum PpPlaceholderType.ppPlaceholderBody
|
||||||
text += shape.TextFrame.TextRange.Text + '\n'
|
if shape.HasTextFrame and shape.TextFrame.HasText:
|
||||||
|
text += shape.TextFrame.TextRange.Text + '\n'
|
||||||
|
except pywintypes.com_error as e:
|
||||||
|
log.warning('Failed to extract text from powerpoint slide')
|
||||||
|
log.warning(e)
|
||||||
return text
|
return text
|
||||||
|
@ -74,8 +74,11 @@ class PresentationTab(SettingsTab):
|
|||||||
self.powerpoint_layout = QtGui.QVBoxLayout(self.powerpoint_group_box)
|
self.powerpoint_layout = QtGui.QVBoxLayout(self.powerpoint_group_box)
|
||||||
self.powerpoint_layout.setObjectName('powerpoint_layout')
|
self.powerpoint_layout.setObjectName('powerpoint_layout')
|
||||||
self.ppt_slide_click_check_box = QtGui.QCheckBox(self.powerpoint_group_box)
|
self.ppt_slide_click_check_box = QtGui.QCheckBox(self.powerpoint_group_box)
|
||||||
self.powerpoint_group_box.setObjectName('ppt_slide_click_check_box')
|
self.ppt_slide_click_check_box.setObjectName('ppt_slide_click_check_box')
|
||||||
self.powerpoint_layout.addWidget(self.ppt_slide_click_check_box)
|
self.powerpoint_layout.addWidget(self.ppt_slide_click_check_box)
|
||||||
|
self.ppt_window_check_box = QtGui.QCheckBox(self.powerpoint_group_box)
|
||||||
|
self.ppt_window_check_box.setObjectName('ppt_window_check_box')
|
||||||
|
self.powerpoint_layout.addWidget(self.ppt_window_check_box)
|
||||||
self.left_layout.addWidget(self.powerpoint_group_box)
|
self.left_layout.addWidget(self.powerpoint_group_box)
|
||||||
# Pdf options
|
# Pdf options
|
||||||
self.pdf_group_box = QtGui.QGroupBox(self.left_column)
|
self.pdf_group_box = QtGui.QGroupBox(self.left_column)
|
||||||
@ -123,6 +126,9 @@ class PresentationTab(SettingsTab):
|
|||||||
self.ppt_slide_click_check_box.setText(
|
self.ppt_slide_click_check_box.setText(
|
||||||
translate('PresentationPlugin.PresentationTab',
|
translate('PresentationPlugin.PresentationTab',
|
||||||
'Clicking on a selected slide in the slidecontroller advances to next effect.'))
|
'Clicking on a selected slide in the slidecontroller advances to next effect.'))
|
||||||
|
self.ppt_window_check_box.setText(
|
||||||
|
translate('PresentationPlugin.PresentationTab',
|
||||||
|
'Let PowerPoint control the size and position of the presentation window.'))
|
||||||
self.pdf_program_check_box.setText(
|
self.pdf_program_check_box.setText(
|
||||||
translate('PresentationPlugin.PresentationTab', 'Use given full path for mudraw or ghostscript binary:'))
|
translate('PresentationPlugin.PresentationTab', 'Use given full path for mudraw or ghostscript binary:'))
|
||||||
|
|
||||||
@ -148,6 +154,8 @@ class PresentationTab(SettingsTab):
|
|||||||
self.ppt_slide_click_check_box.setChecked(Settings().value(self.settings_section +
|
self.ppt_slide_click_check_box.setChecked(Settings().value(self.settings_section +
|
||||||
'/powerpoint slide click advance'))
|
'/powerpoint slide click advance'))
|
||||||
self.ppt_slide_click_check_box.setEnabled(powerpoint_available)
|
self.ppt_slide_click_check_box.setEnabled(powerpoint_available)
|
||||||
|
self.ppt_window_check_box.setChecked(Settings().value(self.settings_section + '/powerpoint control window'))
|
||||||
|
self.ppt_window_check_box.setEnabled(powerpoint_available)
|
||||||
# load pdf-program settings
|
# load pdf-program settings
|
||||||
enable_pdf_program = Settings().value(self.settings_section + '/enable_pdf_program')
|
enable_pdf_program = Settings().value(self.settings_section + '/enable_pdf_program')
|
||||||
self.pdf_program_check_box.setChecked(enable_pdf_program)
|
self.pdf_program_check_box.setChecked(enable_pdf_program)
|
||||||
@ -186,6 +194,10 @@ class PresentationTab(SettingsTab):
|
|||||||
if Settings().value(setting_key) != self.ppt_slide_click_check_box.checkState():
|
if Settings().value(setting_key) != self.ppt_slide_click_check_box.checkState():
|
||||||
Settings().setValue(setting_key, self.ppt_slide_click_check_box.checkState())
|
Settings().setValue(setting_key, self.ppt_slide_click_check_box.checkState())
|
||||||
changed = True
|
changed = True
|
||||||
|
setting_key = self.settings_section + '/powerpoint control window'
|
||||||
|
if Settings().value(setting_key) != self.ppt_window_check_box.checkState():
|
||||||
|
Settings().setValue(setting_key, self.ppt_window_check_box.checkState())
|
||||||
|
changed = True
|
||||||
# Save pdf-settings
|
# Save pdf-settings
|
||||||
pdf_program = self.pdf_program_path.text()
|
pdf_program = self.pdf_program_path.text()
|
||||||
enable_pdf_program = self.pdf_program_check_box.checkState()
|
enable_pdf_program = self.pdf_program_check_box.checkState()
|
||||||
|
@ -45,7 +45,8 @@ __default_settings__ = {'presentations/override app': QtCore.Qt.Unchecked,
|
|||||||
'presentations/Pdf': QtCore.Qt.Checked,
|
'presentations/Pdf': QtCore.Qt.Checked,
|
||||||
'presentations/presentations files': [],
|
'presentations/presentations files': [],
|
||||||
'presentations/thumbnail_scheme': '',
|
'presentations/thumbnail_scheme': '',
|
||||||
'presentations/powerpoint slide click advance': QtCore.Qt.Unchecked
|
'presentations/powerpoint slide click advance': QtCore.Qt.Unchecked,
|
||||||
|
'presentations/powerpoint control window': QtCore.Qt.Unchecked
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -131,6 +131,7 @@ class WorshipAssistantImport(SongImport):
|
|||||||
return
|
return
|
||||||
verse = ''
|
verse = ''
|
||||||
used_verses = []
|
used_verses = []
|
||||||
|
verse_id = VerseType.tags[VerseType.Verse] + '1'
|
||||||
for line in lyrics.splitlines():
|
for line in lyrics.splitlines():
|
||||||
if line.startswith('['): # verse marker
|
if line.startswith('['): # verse marker
|
||||||
# Add previous verse
|
# Add previous verse
|
||||||
|
@ -164,45 +164,42 @@ class TestPowerpointDocument(TestCase, TestMixin):
|
|||||||
"""
|
"""
|
||||||
Test creating the titles from PowerPoint
|
Test creating the titles from PowerPoint
|
||||||
"""
|
"""
|
||||||
if is_win() and self.real_controller.check_available():
|
# GIVEN: mocked save_titles_and_notes, _get_text_from_shapes and two mocked slides
|
||||||
# GIVEN: mocked save_titles_and_notes, _get_text_from_shapes and two mocked slides
|
self.doc = PowerpointDocument(self.mock_controller, self.file_name)
|
||||||
self.doc = PowerpointDocument(self.real_controller, self.file_name)
|
self.doc.get_slide_count = MagicMock()
|
||||||
self.doc.save_titles_and_notes = MagicMock()
|
self.doc.get_slide_count.return_value = 2
|
||||||
self.doc._PowerpointDocument__get_text_from_shapes = MagicMock()
|
self.doc.index_map = {1: 1, 2: 2}
|
||||||
slide = MagicMock()
|
self.doc.save_titles_and_notes = MagicMock()
|
||||||
slide.Shapes.Title.TextFrame.TextRange.Text = 'SlideText'
|
self.doc._PowerpointDocument__get_text_from_shapes = MagicMock()
|
||||||
pres = MagicMock()
|
slide = MagicMock()
|
||||||
pres.Slides = [slide, slide]
|
slide.Shapes.Title.TextFrame.TextRange.Text = 'SlideText'
|
||||||
self.doc.presentation = pres
|
pres = MagicMock()
|
||||||
|
pres.Slides = MagicMock(side_effect=[slide, slide])
|
||||||
|
self.doc.presentation = pres
|
||||||
|
|
||||||
# WHEN reading the titles and notes
|
# WHEN reading the titles and notes
|
||||||
self.doc.create_titles_and_notes()
|
self.doc.create_titles_and_notes()
|
||||||
|
|
||||||
# THEN the save should have been called exactly once with 2 titles and 2 notes
|
# THEN the save should have been called exactly once with 2 titles and 2 notes
|
||||||
self.doc.save_titles_and_notes.assert_called_once_with(['SlideText\n', 'SlideText\n'], [' ', ' '])
|
self.doc.save_titles_and_notes.assert_called_once_with(['SlideText\n', 'SlideText\n'], [' ', ' '])
|
||||||
else:
|
|
||||||
self.skipTest('Powerpoint not available, skipping test.')
|
|
||||||
|
|
||||||
def create_titles_and_notes_with_no_slides_test(self):
|
def create_titles_and_notes_with_no_slides_test(self):
|
||||||
"""
|
"""
|
||||||
Test creating the titles from PowerPoint when it returns no slides
|
Test creating the titles from PowerPoint when it returns no slides
|
||||||
"""
|
"""
|
||||||
if is_win() and self.real_controller.check_available():
|
# GIVEN: mocked save_titles_and_notes, _get_text_from_shapes and two mocked slides
|
||||||
# GIVEN: mocked save_titles_and_notes, _get_text_from_shapes and two mocked slides
|
doc = PowerpointDocument(self.mock_controller, self.file_name)
|
||||||
doc = PowerpointDocument(self.real_controller, self.file_name)
|
doc.save_titles_and_notes = MagicMock()
|
||||||
doc.save_titles_and_notes = MagicMock()
|
doc._PowerpointDocument__get_text_from_shapes = MagicMock()
|
||||||
doc._PowerpointDocument__get_text_from_shapes = MagicMock()
|
pres = MagicMock()
|
||||||
pres = MagicMock()
|
pres.Slides = []
|
||||||
pres.Slides = []
|
doc.presentation = pres
|
||||||
doc.presentation = pres
|
|
||||||
|
|
||||||
# WHEN reading the titles and notes
|
# WHEN reading the titles and notes
|
||||||
doc.create_titles_and_notes()
|
doc.create_titles_and_notes()
|
||||||
|
|
||||||
# THEN the save should have been called exactly once with empty titles and notes
|
# THEN the save should have been called exactly once with empty titles and notes
|
||||||
doc.save_titles_and_notes.assert_called_once_with([], [])
|
doc.save_titles_and_notes.assert_called_once_with([], [])
|
||||||
else:
|
|
||||||
self.skipTest('Powerpoint not available, skipping test.')
|
|
||||||
|
|
||||||
def get_text_from_shapes_test(self):
|
def get_text_from_shapes_test(self):
|
||||||
"""
|
"""
|
||||||
@ -253,3 +250,54 @@ class TestPowerpointDocument(TestCase, TestMixin):
|
|||||||
|
|
||||||
# THEN: next_step() should be call to try to advance to the next effect.
|
# THEN: next_step() should be call to try to advance to the next effect.
|
||||||
self.assertTrue(doc.next_step.called, 'next_step() should have been called!')
|
self.assertTrue(doc.next_step.called, 'next_step() should have been called!')
|
||||||
|
|
||||||
|
def blank_screen_test(self):
|
||||||
|
"""
|
||||||
|
Test that blank_screen works as expected
|
||||||
|
"""
|
||||||
|
# GIVEN: A Document with mocked controller, presentation, and mocked function get_slide_number
|
||||||
|
doc = PowerpointDocument(self.mock_controller, self.mock_presentation)
|
||||||
|
doc.presentation = MagicMock()
|
||||||
|
doc.presentation.SlideShowWindow.View.GetClickIndex.return_value = 3
|
||||||
|
doc.presentation.Application.Version = 14.0
|
||||||
|
doc.get_slide_number = MagicMock()
|
||||||
|
doc.get_slide_number.return_value = 2
|
||||||
|
|
||||||
|
# WHEN: Calling goto_slide
|
||||||
|
doc.blank_screen()
|
||||||
|
|
||||||
|
# THEN: The view state, doc.blank_slide and doc.blank_click should have new values
|
||||||
|
self.assertEquals(doc.presentation.SlideShowWindow.View.State, 3, 'The View State should be 3')
|
||||||
|
self.assertEquals(doc.blank_slide, 2, 'doc.blank_slide should be 2 because of the PowerPoint version')
|
||||||
|
self.assertEquals(doc.blank_click, 3, 'doc.blank_click should be 3 because of the PowerPoint version')
|
||||||
|
|
||||||
|
def unblank_screen_test(self):
|
||||||
|
"""
|
||||||
|
Test that unblank_screen works as expected
|
||||||
|
"""
|
||||||
|
# GIVEN: A Document with mocked controller, presentation, ScreenList, and mocked function get_slide_number
|
||||||
|
with patch('openlp.plugins.presentations.lib.powerpointcontroller.ScreenList') as mocked_screen_list:
|
||||||
|
mocked_screen_list_ret = MagicMock()
|
||||||
|
mocked_screen_list_ret.screen_list = [1]
|
||||||
|
mocked_screen_list.return_value = mocked_screen_list_ret
|
||||||
|
doc = PowerpointDocument(self.mock_controller, self.mock_presentation)
|
||||||
|
doc.presentation = MagicMock()
|
||||||
|
doc.presentation.SlideShowWindow.View.GetClickIndex.return_value = 3
|
||||||
|
doc.presentation.Application.Version = 14.0
|
||||||
|
doc.get_slide_number = MagicMock()
|
||||||
|
doc.get_slide_number.return_value = 2
|
||||||
|
doc.index_map[1] = 1
|
||||||
|
doc.blank_slide = 1
|
||||||
|
doc.blank_click = 1
|
||||||
|
|
||||||
|
# WHEN: Calling goto_slide
|
||||||
|
doc.unblank_screen()
|
||||||
|
|
||||||
|
# THEN: The view state have new value, and several function should have been called
|
||||||
|
self.assertEquals(doc.presentation.SlideShowWindow.View.State, 1, 'The View State should be 1')
|
||||||
|
self.assertEquals(doc.presentation.SlideShowWindow.Activate.called, True,
|
||||||
|
'SlideShowWindow.Activate should have been called')
|
||||||
|
self.assertEquals(doc.presentation.SlideShowWindow.View.GotoSlide.called, True,
|
||||||
|
'View.GotoSlide should have been called because of the PowerPoint version')
|
||||||
|
self.assertEquals(doc.presentation.SlideShowWindow.View.GotoClick.called, True,
|
||||||
|
'View.GotoClick should have been called because of the PowerPoint version')
|
||||||
|
@ -49,3 +49,5 @@ class TestWorshipAssistantFileImport(SongImportTestHelper):
|
|||||||
self.load_external_result_data(os.path.join(TEST_PATH, 'would_you_be_free.json')))
|
self.load_external_result_data(os.path.join(TEST_PATH, 'would_you_be_free.json')))
|
||||||
self.file_import(os.path.join(TEST_PATH, 'would_you_be_free2.csv'),
|
self.file_import(os.path.join(TEST_PATH, 'would_you_be_free2.csv'),
|
||||||
self.load_external_result_data(os.path.join(TEST_PATH, 'would_you_be_free.json')))
|
self.load_external_result_data(os.path.join(TEST_PATH, 'would_you_be_free.json')))
|
||||||
|
self.file_import(os.path.join(TEST_PATH, 'lift_up_your_heads.csv'),
|
||||||
|
self.load_external_result_data(os.path.join(TEST_PATH, 'lift_up_your_heads.json')))
|
||||||
|
40
tests/resources/worshipassistantsongs/lift_up_your_heads.csv
Normal file
40
tests/resources/worshipassistantsongs/lift_up_your_heads.csv
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
"SongID","SongNr","Title","Author","Copyright","FirstLine","PriKey","AltKey","Tempo","Focus","Theme","Scripture","Active","Songbook","TimeSig","Introduced","LastUsed","TimesUsed","CCLINr","User1","User2","User3","User4","User5","Roadmap","Overmap","FileLink1","FileLink2","Updated","Lyrics","Info","Lyrics2","Background"
|
||||||
|
"000013ab-0000-0000-0000-000000000000","0","Lift Up Your Heads"," Bryan Mierau","Public Domain","Lift up your heads and the doors","Em","NULL","NULL","NULL","NULL","NULL","1","1","NULL","NULL","NULL","0","NULL","NULL","NULL","NULL","NULL","NULL","NULL","NULL","NULL","NULL","2004-04-07 06:36:18.952",".Em D C D
|
||||||
|
Lift up your heads and the doors of your heart
|
||||||
|
. Am B7 Em
|
||||||
|
And the King of glory will come in
|
||||||
|
(Repeat)
|
||||||
|
|
||||||
|
.G Am D
|
||||||
|
Who is this King of Glory?
|
||||||
|
. B7 Em
|
||||||
|
The Lord strong and mighty!
|
||||||
|
.G Am D
|
||||||
|
Who is this King of Glory?
|
||||||
|
. B7
|
||||||
|
The Lord, mighty in battle!
|
||||||
|
|
||||||
|
.G Am D
|
||||||
|
Who is this King of Glory?
|
||||||
|
.B7 Em
|
||||||
|
Jesus our Messiah!
|
||||||
|
.G Am D
|
||||||
|
Who is this King of Glory?
|
||||||
|
.B7 Em
|
||||||
|
Jesus, Lord of Lords!
|
||||||
|
|
||||||
|
","NULL","Lift up your heads and the doors of your heart
|
||||||
|
And the King of glory will come in
|
||||||
|
(Repeat)
|
||||||
|
|
||||||
|
Who is this King of Glory?
|
||||||
|
The Lord strong and mighty!
|
||||||
|
Who is this King of Glory?
|
||||||
|
The Lord, mighty in battle!
|
||||||
|
|
||||||
|
Who is this King of Glory?
|
||||||
|
Jesus our Messiah!
|
||||||
|
Who is this King of Glory?
|
||||||
|
Jesus, Lord of Lords!
|
||||||
|
|
||||||
|
","NULL"
|
|
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"authors": [
|
||||||
|
"Bryan Mierau"
|
||||||
|
],
|
||||||
|
"title": "Lift Up Your Heads",
|
||||||
|
"verse_order_list": [],
|
||||||
|
"verses": [
|
||||||
|
[
|
||||||
|
"Lift up your heads and the doors of your heart\nAnd the King of glory will come in\n(Repeat)\n\nWho is this King of Glory?\nThe Lord strong and mighty!\nWho is this King of Glory?\nThe Lord, mighty in battle!\n\nWho is this King of Glory?\nJesus our Messiah!\nWho is this King of Glory?\nJesus, Lord of Lords!\n",
|
||||||
|
"v1"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user